2021年2月19日星期五

Java String类型

主要属性和方法

public final class String
   implments java.io.Serializable, Comparable<String>, CharSequence {
   /** The value is used for character storage. */
   // 用来存储字符串的值
   private final char value[];

   /** Cache the hash code for the string */
   // 用来缓存hash code 调用hashCode方法时会首先对hash的值进行判断,如果已经存在值, 由于String是不可变的,直接返回hash即可,不用重新对该String对象的hash code的进行计算
   // 默认值为0  
   private int hash; // Default to 0
// 其他属性和方法...
}

从源码String源码可以看出,Stringfinal修饰符修饰的类,表示String不能被继承。String 底层使用final修饰的char[]数组来储存字符串的值。

构造方法

 

 

String有多种构造构造,可以传入char[]StringStringBufferStringBuilder等属性构造String对象。

由于String为不可变类型,当调用String中方法对String修改操作时,都会调用相应的构造方法生成一个新的对象并返回,原String对象并不会产生改变。下面以replace()方法为例:

public String replace(char oldChar, char newChar) {
   // 只有old != new才进行处理 否则直接返回
   if (oldChar != newChar) {
       int len = value.length;
       int i = -1;
       char[] val = value; /* avoid getfield opcode */
       // 通过while循环找到第一个oldChar的位置
       while (++i < len) {
           if (val[i] == oldChar) {
               break;
          }
      }

       if (i < len) {
           // 创建一个新的char数组 用来存新的String对象的值
           char buf[] = new char[len];
           // 将第一个oldChar前的所有char都存入buf数组中
           for (int j = 0; j < i; j++) {
               buf[j] = val[j];
          }
           // 将剩余val数组中等于oldChar的值修改为newChar后存入buf中
           while (i < len) {
               char c = val[i];
               buf[i] = (c == oldChar) ? newChar : c;
               i++;
          }
           // 根据buf数组生成新的String对象
           return new String(buf, true);
      }
  }
   return this;
}

equals方法

//Object中的equals方法
public boolean equals(Object obj) {
   return (this == obj);
}
//String重写Object的equals方法
public boolean equals(Object anObject) {
   if (this == anObject) {
       return true;
  }
   if (anObject instanceof String) {
       String anotherString = (String)anObject;
       int n = value.length;
       if (n == anotherString.value.length) {
           char v1[] = value;
           char v2[] = anotherString.value;
           int i = 0;
           while (n-- != 0) {
               if (v1[i] != v2[i])
                   return false;
               i++;
          }
           return true;
      }
  }
   return false;
}

可以看出Object中的equals方法直接使用"=="判断两个对象是否相等,"=="对基本类型进行判断是,比较的是基本类型的值,而对引用类型的对象进行判断时,比较的是两个对象的引用的值是否相等,也就是说"=="判断两个对象与对象的内容无关,只与对象的地址有关,当且仅当两个对象的地址一致时(为同一个对象)才会返回true。如:

String s1 = new String("string");
String s2 = new String("string");
s1 == s2; // false "=="只判断地址 s1 s2是单独的两个对象

String重写了equals方法,从源码可以看出,equals方法传入的参数类型为Object,调用equals时,首先会使用"==''比较this和待比较对象anObject的地址是否相等,若相等则表示这个两个对象为同一个对象,直接返回true即可。如果不相等,使用instanceof判断anObject是否为String类型,若不是则返回false,否则进行下一步操作,先判断两个String对象的value数组的长度是否相等,再循环比较数组中的每个元素是否相等。Stringequals比较的是两个String对象的内容——也就是value数组是否相等。

String s1 = new String("string");
String s2 = new String("string");
s1.equals(s2); // true s1 s2是单独的两个对象 但是他们的内容都是 "string"

equals方法外,由于String实现了comparable接口,也可以通过comparablecompareTo方法判断两个字符串是否相等

compareTo

public int compareTo(String anotherString) {
   int len1 = value.length;
   int len2 = anotherString.value.length;
   int lim = Math.min(len1, len2);
   char v1[] = value;
   char v2[] = anotherString.value;

   int k = 0;
   while (k < lim) {
       char c1 = v1[k];
       char c2 = v2[k];
       if (c1 != c2) {
           return c1 - c2;
      }
       k++;
  }
   return len1 - len2;
}

从源码可以看出,compareTo方法的参数类型为StringequalsObject不同,返回值为intequalsboolean也不一样,compareTo方法首先挨个比较两个字符串中较短字符串的所有元素与较长字符串中对应位置的元素的大小,当对应元素不相等时返回差值,当较短字符串都比较完毕后返回两个字符串的长度的差值。当返回值为0时表明这两个元素相等。返回值<0时,表示按字典顺序thisanotherString的前面,反之亦然。

由于String实现Comparable接口,String列表或数组可以通过Collections.sort或者Arrays.sort方法进行自动排序;同时String对象也可以作为有序映射(TreeMap)中的键或者有序集合(TreeSet)中的元素,无需指定比较器。

其他

equalscompareTo都用对应的xxxIgnoreCase方法,equalsIngoreCaseequals类似,先比较地址再比较长度最后通过regionMatches比较两个字符串忽略小写之后的内容是否相等

public boolean equalsIgnoreCase(String anotherString) {
   return (this == anotherString) ? true
      : (anotherString != null)
   && (anotherString.value.length == value.length)
       && regionMatches(true, 0, anotherString, 0, value.length);
}

compareToIgnoreCase 通过比较器静态内部类CaseInsensitiveComparatorcompare方法来实现,比较逻辑与compareTo基本相同只是多了一些大小写转换后判断的操作

public int compareToIgnoreCase(String str) {
   return CASE_INSENSITIVE_ORDER.compare(this, str);
}

public static final Comparator<String> CASE_INSENSITIVE_ORDER
   = new CaseInsensitiveComparator();

private static class CaseInsensitiveComparator
   implements Comparator<String>, java.io.Serializable {
   // use serialVersionUID from JDK 1.2.2 for interoperability
   private static final long serialVersionUID = 8575799808933029326L;

   public int compare(String s1, String s2) {
       int n1 = s1.length();
       int n2 = s2.length();
       int min = Math.min(n1, n2);
       for (int i = 0; i < min; i++) {
           char c1 = s1.charAt(i);
           char c2 = s2.charAt(i);
           if (c1 != c2) {
               c1 = Character.toUpperCase(c1);
               c2 = Character.toUpperCase(c2);
               if (c1 != c2) {
                   c1 = Character.toLowerCase(c1);
                   c2 = Character.toLowerCase(c2);
                   if (c1 != c2) {
                       // No overflow because of numeric promotion
                       return c1 - c2;
                  }
              }
          }
      }
       return n1 - n2;
  }
   /** Replaces the de-serialized object. */
   private Object readResolve() { return CASE_INSENSITIVE_ORDER; }
}

其他方法:

  • indexOf()

  • lastIndexOf()

  • contains()

  • trim()

  • split()

  • ...



String不可变类型

final修饰String类,String类不可继承。

final修饰char类型的value数组,初始化过后,value指向的数组不能修改,并且每次对对象进行修改时都会通过构造器创建一个新的对象,保证了String的不可变。

设计成不可变的原因

  1. 安全

  2. 高效

    使用JVM字符串常量池来缓存字符串,只有当字符串为不可变时,才能实现字符串常量池,由于String类型使用很频繁,字符串常量池的存在能有效提高程序的运行效率。



字符串常量池

(JDK1.7之后永久代换成了元空间,将字符串常量池从方法区移到了堆上)

String的常见的创建方式有两种:

  1. 通过字面量的方式创建 编译时决定

    String s1 = "string";
    String s2 = "string";
    s1 == s2 // true 字面量创建的String s1 s2都指向字符串常量池中的"string"
  1. 通过new String的方式创建 运行时决定

    String s3 = new String("string");
    String s4 = new String("string");
    s3 == s4 // false s3 s4 指向的是 堆上的内容为"string"的对象,此时有两个这种对象

字面量方式创建首先会查找字符串常量池中是否已经存在该字符串,有则直接指向该字符串,否则先在常量池中创建该字符串,然后将引用指向创建的字符串;而通过new创建String,一定会在堆上创建一个字符串对象,然后判断常量池是否已经存在该字符串的值,如果不存在则会在常量池中创建该字符串,然后将引用的值指向该字符串(s3s4指向堆中对象的地址 而堆中保存字符串常量池中"string"的地址)。

弄清字符串是在编译时 还是 运行时 进入常量池

String s1 = "Hello World";
String s2 = "Hello ";
String s3 = "World";
s1 == "Hello " + "World"; //true 字面量相加 直接在编译期完全确定 并且放入字符串常量池中
s1 == s2 + s3; // false 引用相加 不能在编译期确定 s2 + s3的值
final String s4 = "Hello ";
final String s5 = "World";
s1 == s4 + s5; // true 在编译器能够确定final修饰的s4和s5指向的值

intern

public native String intern();

intern是一个native方法,当使用intern方法时,首先会检查字符串常量池中,是否已经存在该字符串,如果已存在,直接返回该字符串,否则创建之后再返回。

String s1 = "String";
String s2 = new String("String");
s1 == s2; // false
s1 == s2.intern() // true


StringStringBuffer StringBuilder

由于String为不可变类型,每次对String进行修改时都会产生新的String对象,在拼接字符串的时候可能会出现很多无用的String对象,性能会很低,此时就需要StringBuffer来对字符串进行拼接。StringBufferStringBuilder继承自AbstractStringBuilder,提供了append、insert等方法对字符串进行拼接和修改。

StringBuffer为线程安全,使用sychronized对方法加锁实现线程安全,相应的效率也会变低,在非并发的条件下可以使用StringBuilder提高效率。

 

 

 

以上为个人总结的Java String相关知识,如有不对之处,敬请批评指正!









原文转载:http://www.shaoqun.com/a/568612.html

跨境电商:https://www.ikjzd.com/

急速:https://www.ikjzd.com/w/1861

beien:https://www.ikjzd.com/w/1336


主要属性和方法publicfinalclassStringimplmentsjava.io.Serializable,Comparable<String>,CharSequence{/**Thevalueisusedforcharacterstorage.*///用来存储字符串的值privatefinalcharvalue[];​/**Cachethehashcodeforthestr
亚马逊礼品卡:https://www.ikjzd.com/w/1090.html
myyearbook:https://www.ikjzd.com/w/726
败欧洲运费:https://www.ikjzd.com/w/1555
CPC和关键词的各模式下的匹配:https://www.kjyunke.com/courses/577
罢工Prime Day?德国亚马逊工人:"工资不打折"!:https://www.ikjzd.com/home/101218
​2021年亚马逊新卖家怎么选品?:https://www.ikjzd.com/home/141578

没有评论:

发表评论