上一篇博客:Java String详解(一)
写在前面:大家好!我是
晴空๓。如果博客中有不足或者的错误的地方欢迎在评论区或者私信我指正,感谢大家的不吝赐教。我的唯一博客更新地址是:https://ac-fun.blog.csdn.net/。非常感谢大家的支持。一起加油,冲鸭!
用知识改变命运,用知识成就未来!加油 (ง •̀o•́)ง (ง •̀o•́)ง
文章目录
- 前言
 - substring()截取字符串的子串
 - 连接字符串
 - 使用 + 进行连接
 
前言
在上一篇博客Java String详解(一)中主要讲了String类的声明、字符串存储的位置以及String的不可变性。本篇主要写一下关于String的常用方法以及一些注意点。
substring()截取字符串的子串
String类的 substring 方法可以从一个较大的字符串中提取出一个子串。通过查看String.java类可以发现一共有两种截取子串的方法:
public String substring(int beginIndex)
public String substring(int beginIndex, int endIndex)
 
对于这个方法主要注意的是 substring() 是从 0 开始的;截取的子字符串包含 beginIndex 坐标的字符,但是不包含 endIndex 的字符(前闭后开式[))。通过substring()的源码就可以看到 substring() 会返回一个新的字符串对象,而不是修改了原有的字符串。这也是String不可变的一个体现:
public String substring(int beginIndex, int endIndex) {
        if (beginIndex < 0) {
            throw new StringIndexOutOfBoundsException(beginIndex);
        }
        if (endIndex > value.length) {
            throw new StringIndexOutOfBoundsException(endIndex);
        }
        int subLen = endIndex - beginIndex;
        if (subLen < 0) {
            throw new StringIndexOutOfBoundsException(subLen);
        }
        return ((beginIndex == 0) && (endIndex == value.length)) ? this
                : new String(value, beginIndex, subLen);
    }
 
连接字符串
使用 + 进行连接
Java 语言允许使用 + 号拼接两个字符串,应用于 String 的 + 和 += 是 Java 中仅有的被重载的操作符,Java 不允许程序员重载其他操作符。我们可以使用 JDK 自带的 javap 工具反编译一下以下使用 + 号进行连接字符串时发生了什么:
public class StringTest {
    public static void main(String[] args) {
        String str1 = "hello";
        String str2 = "world";
        String str = str1 + str2;
        System.out.println(str);
    }
}
 
使用 javac .\StringTest.java 编译代码之后再使用 javap -c .\StringTest.class 反编译.class文件,使用 -c 选项对代码进行反汇编,显示 Java 字节码的指令。可以看到如下字节码及相应的作用:
Compiled from "StringTest.java"
public class StringTest {
  public StringTest();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return
  public static void main(java.lang.String[]);
    Code:
       0: ldc           #2                  // String hello
       2: astore_1
       3: ldc           #3                  // String world
       5: astore_2
       6: new           #4                  // class java/lang/StringBuilder
       9: dup
      10: invokespecial #5                  // Method java/lang/StringBuilder."<init>":()V
      13: aload_1
      14: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      17: aload_2
      18: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      21: invokevirtual #7                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      24: astore_3
      25: getstatic     #8                  // Field java/lang/System.out:Ljava/io/PrintStream;
      28: aload_3
      29: invokevirtual #9                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      32: return
}
 
ldc 命令是从常量池中加载各种类型的常量,包括字符串常量、整数常量、浮点数常量,甚至类引用等。对于字符串常量,ldc 指令的行为如下:
1. 从常量池加载字符串:ldc 首先检查字符串常量池中是否已经有内容相同的字符串对象。
2. 复用已有字符串对象:如果字符串常量池中已经存在内容相同的字符串对象,ldc 会将该对象的引用加载到操作数栈上。
3. 没有则创建新对象并加入常量池:如果字符串常量池中没有相同内容的字符串对象,JVM 会在常量池中创建一个新的字符串对象,并将其引用加载到操作数栈中。
new 命令是创建一个新的对象,指令后面紧跟着指向常量池中的索引值,该索引值指向一个类符号引用,即类的全限定名。
invokevirtual 命令是 JVM 字节码用于调用对象实例方法的指令
从以上字节码我们可以看出 java 中使用 + 拼接字符串本质上是通过 StringBuilder 调用 append() 方法实现的,拼接完成之后调用 toString() 得到一个 String 对象 。这里需要注意的一个点就是当我们在循环内使用 “+” 对字符串进行拼接的话会有一个明显的缺陷,那就是会造成编译器创建多个 StringBuilder 对象。每进行一次循环就会创建一个 StringBuilder 对象。如下:
public class StringTest {
    public static void main(String[] args) {
        String[] arr = {"he", "llo", "wor" + "ld"};
        String str = "";
        for (int i = 0; i < arr.length; i++) {
            str += arr[i];
        }
        System.out.println(str);
    }
}
 
使用 javac 反编译之后可以看到字节码如下:
Compiled from "StringTest.java"
public class StringTest {
  public StringTest();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return
  public static void main(java.lang.String[]);
    Code:
       0: iconst_3
       1: anewarray     #2                  // class java/lang/String
       4: dup
       5: iconst_0
       6: ldc           #3                  // String he
       8: aastore
       9: dup
      10: iconst_1
      11: ldc           #4                  // String llo
      13: aastore
      14: dup
      15: iconst_2
      16: ldc           #5                  // String world
      18: aastore
      19: astore_1
      20: ldc           #6                  // String
      22: astore_2
      23: iconst_0
      24: istore_3
      25: iload_3
      26: aload_1
      27: arraylength
      28: if_icmpge     58
      31: new           #7                  // class java/lang/StringBuilder
      34: dup
      48: invokevirtual #10                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      51: astore_2
      52: iinc          3, 1
      55: goto          25
      58: getstatic     #11                 // Field java/lang/System.out:Ljava/io/PrintStream;
      61: aload_2
      62: invokevirtual #12                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      65: return
}
 
我们只需要关注 20~55 即可,相关的解释如下:
20: ldc #6 // String:加载空字符串。
22: astore_2:将其存储在局部变量 StringBuilder 引用。
23: iconst_0:将 0 压入栈(循环索引)。
24: istore_3:存储循环索引。
25: iload_3:加载循环索引。
26: aload_1:加载字符串数组引用。
27: arraylength:获取数组长度。
28: if_icmpge 58:如果循环索引大于等于数组长度,跳转到标签 58(结束循环)。
31: new #7 // class java/lang/StringBuilder:创建一个新的 StringBuilder 对象。
34: dup:复制 StringBuilder 对象引用。
48: invokevirtual #10 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;:调用 StringBuilder 的 toString 方法。
51: astore_2:将结果字符串存储在局部变量 StringBuilder 引用。
52: iinc 3, 1:循环索引加 1。
55: goto 25:跳转回循环开始。
 
 所以当我们需要在循环内进行字符串拼接时最好直接使用 StringBuilder 对象进行字符串拼接,这样就不会造成以上的问题。如果你使用的是 IDEA 的话,IDEA 自带的代码检查也会提示你修改代码
 
public class StringTest {
    public static void main(String[] args) {
        String[] arr = {"he", "llo", "wor" + "ld"};
        StringBuilder str = new StringBuilder();
        for (int i = 0; i < arr.length; i++) {
            str.append(arr[i]);
        }
        System.out.println(str);
    }
}
 
未完待续……
- 《大话Java性能优化》
 - 字符串拼接用 + 还是 stringbuilder
 - 还在无脑用 StringBuilder?来重温一下字符串拼接吧
 



















