1 String

在 JDK8 中,String 类使用 char[] 数组保存值。这个 char 数组使用 final 修饰符修饰,意味着一旦确定就引用即不可修改;此外,String 类中也没有提供修改数组、修改值的方法。上述两种方式保证了 String 值的不可变性。此外 String 类只用 final 修饰,表明其不可继承。

2 String不可变性带来的好处

  • 可作为 Hash 的 key,减少计算量。
    • 例如String作为HashMap的key,不可变的特性使得hash值也不可变,所以只需要进行一次hash计算。
  • 可以用于String Pool。
    • 字符串常量池是方法区域中的一个特殊存储区域。当创建字符串时,如果字符串已经存在于池中,则将返回现有字符串的引用,而不是创建新对象。字符串常量池实现的前提条件是Java中String对象是不可变的,字符串的值存放在字符串常量池中。
  • 作为参数具有安全性。
    • String作为参数,保证了参数一旦确定就不可变。
  • 天生的线程安全性。

    3 String中equals方法

    1. public boolean equals(Object anObject) {
    2. // 首先使用==判断二者内存是否指向同一个地方
    3. if (this == anObject) {
    4. return true;
    5. }
    6. // 再判断类型是否都为String
    7. if (anObject instanceof String) {
    8. String anotherString = (String)anObject;
    9. int n = value.length;
    10. // 如果类型相等再根据长度逐一对比
    11. if (n == anotherString.value.length) {
    12. char v1[] = value;
    13. char v2[] = anotherString.value;
    14. int i = 0;
    15. // 最后逐一对比字符是否相等
    16. while (n-- != 0) {
    17. if (v1[i] != v2[i])
    18. return false;
    19. i++;
    20. }
    21. return true;
    22. }
    23. }
    24. return false;
    25. }

    4 String、StringBuilder与StringBuffer的异同

    StringBuilder 和 StringBuffer 均继承自 AbstractStringBuilder ,这个抽象父类里实现了除 toString 以外的所有方法。StringBuilder 在重写方法的基础上没做其他扩展。StringBuffer 在重写方法的同时几乎为所有方法添加了synchronized 关键字用于同步。下面对这三者进行对比:
String StringBuilder StringBuffer
可变性 String使用final修饰的char数组,因此不可变 使用普通的char数组,可变 使用普通的char数组,可变
线程安全性 String由于不可变性天生线程安全 StringBuilder则不保证线程安全 StringBuffer由于使用了synchronized关键字同样线程安全
性能 最差 最好 中等
是否可继承 不可 不可 不可
equals方法 重写了 未重写 未重写
valueOf方法 有该方法 没有该方法 没有该方法
substring方法 有该方法 没有该方法 有该方法
toString方法 直接返回自身 返回一个新的String 用synchronized修饰,返回一个新的String
  1. package org.example;
  2. public class App {
  3. public static void main( String[] args ) {
  4. String s1 = "s1";
  5. StringBuilder s2 = new StringBuilder("s2");
  6. StringBuffer s3 = new StringBuffer("s3");
  7. System.out.println(s1 == s1.toString()); // true
  8. System.out.println(s1.equals(s1.toString())); // true
  9. System.out.println(s2.equals(s2.toString())); // false
  10. System.out.println(s3.equals(s3.toString())); // false
  11. String s11 = "s1";
  12. StringBuilder s22 = new StringBuilder("s2");
  13. StringBuffer s33 = new StringBuffer("s3");
  14. System.out.println(s1.equals(s11)); // true
  15. System.out.println(s2.equals(s22)); // false
  16. System.out.println(s3.equals(s33)); // false
  17. String substring = s11.substring(1);
  18. String substring1 = s22.substring(1);
  19. String substring2 = s33.substring(1);
  20. System.out.println(substring); // 1
  21. System.out.println(substring1); // 2 虽然输出了结果,实际上调用的是父类AbstractStringBuilder中的substring方法
  22. System.out.println(substring2); // 3
  23. }
  24. }

5 字符串常量池

字符串常量池(String Pool)保存着所有字符串字面量(literal strings),这些字面量在编译时期就确定。
不仅如此,还可以使用 String 的 intern() 方法在运行过程中将字符串添加到 String Pool 中。当一个字符串调用 intern() 方法时,如果 StringPool 中已经存在一个字符串和该字符串值相等(使用 equals() 方法进行确定),那么就会返回 StringPool 中字符串的引用;否则,就会在 String Pool 中添加一个新的字符串,并返回这个新字符串的引用。

  1. StringBuilder sb = new StringBuilder("sdkfgusdkugjsdkljgb");
  2. sb.append("abc");
  3. String s = sb.toString();
  4. s.intern();
  5. String str = "sdkfgusdkugjsdkljgbabc";
  6. System.out.println(s == str); // true
  7. StringBuilder sb2 = new StringBuilder("sdkfgusaaaaaaaaaaa");
  8. sb2.append("bc");
  9. String s2 = sb2.toString();
  10. //s2.intern();
  11. String str2 = "sdkfgusaaaaaaaaaaabc";
  12. System.out.println(s2 == str2); // false

6 字符串拼接优化

字符串拼接从 JDK5 开始就已经完成优化,并且没有进行新的优化。

  • 循环内 String + 常量 会每次 new 一个 StringBuilder ,再调用 append 方法。
  • 循环外字符串拼接可以直接使用 String 的 + 操作,没有必要通过 StringBuilder 进行 append。
  • 有循环体的话,最好在循环外声明 StringBuilder 对象,在循环内进行手动 append。这样不论循环多少层都只有一个 StringBuilder 对象。

StringBuffer 和 StringBuilder 的扩容策略:当字符串缓冲区容量不足时,原有容量将会加倍,以新的容量来申请内存空间,建立新的char数组,然后将原数组中的内容复制到这个新的数组当中。
因此,对于大对象的扩容会涉及大量的内存复制操作。所以,如果能够预先评估 StringBuilder 或 StringBuffer 的大小,将能够有效的节省这些操作,从而提高系统的性能。

  1. String s = "a";
  2. long begin1 = System.currentTimeMillis();
  3. for (int i = 0; i < 10000; i++) {
  4. s = s + i;
  5. }
  6. long end1 = System.currentTimeMillis();
  7. System.out.println(end1 - begin1); // 289
  8. StringBuilder s2 = new StringBuilder("a");
  9. long begin2 = System.currentTimeMillis();
  10. for (int i = 0; i < 10000; i++) {
  11. s2 = s2.append(i);
  12. }
  13. long end2 = System.currentTimeMillis();
  14. System.out.println(end2 - begin2); // 0,是的,没看出,确实是 0

7 字符串和整型的转换

其实 String 和 byte、short、int、long、float、double的转换方法都差不多,这里以 int 为例。

  • 把 String 转换成 int
    • Integer.ParseInt(str):直接使用静态方法,只产生一个对象,但会抛出 NumberFormatException 异常。
    • Integer.valueOf(str).intValue():Integer.valueOf(s) 相当于 new Integer(Integer.parseInt(s)),也会抛 NumberFormatException 异常,但会多产生一个对象。
  • 把 int 转换成 String
    • String.valueOf( i ):直接使用静态方法,只产生一个对象
    • Integer.toString( i ):直接使用静态方法,只产生一个对象
    • “” + i:会产生两个对象 ```java // String 转 int String str = “1”; int i = Integer.parseInt(str); int i1 = Integer.valueOf(str).intValue();

// int 转 String int j = 99; String s1 = String.valueOf(j); String s2 = Integer.toString(j); String s3 = “” + j;

System.out.println(s1.getClass()); //class java.lang.String System.out.println(s2.getClass()); //class java.lang.String System.out.println(s3.getClass()); //class java.lang.String ```

参考