Java 字符串学习笔记

316 阅读7分钟

概念

  • Java字符串就是Unicode字符序列
  • 每个用双引号括起来的字符串都是String类型

子串

  • substring方法

  • String s1 = "hello";
    // 返回位置0到位置3的子串,不包括位置3,3-0即子串的长度
    String s2 = s1.substring(0,3);
    // 返回从位置1到字符串尾部的子串
    String s3 = s1.substring(1);
    

拼接

  • +号连接字符串

    • 当其中一个操作数是非字符串类型时,会自动转换成字符串类型

    • int x=1,y=2,z=3;
          System.out.println("x+y+z="+x+y+z);
      System.out.println("x+y+z="+(x+y+z));
      
      //Output
      x+y+z=123
      x+y+z=6
      
    • 使用+拼接字符串时,底层会创建StringBuilder对象,调用append方法拼接字符串,最后调用toString方法将StringBuilder对象转换为字符串对象

      • 两个字符串拼接时

        • public static void test3(){
              String res = "a"+"b";
          }
          
          // 等价于
          public static void test4(){
              StringBuilder res = new StringBuilder();
              res.append("a");
              res.append("b");
              String s = res.toString();
          }
          
          • 此时,StringBuilder对象内部的byte数组为默认容量16,因此,在拼接两个总长度大于16的字符串时,需要扩容(创建新的byte数组,将原数组元素复制到新数组中)。
          • 如果直接创建StringBuilder对象连接字符串,可以在调用构造方法时指定byte数组初始容量,减少后续的扩容次数,提高效率。
      • 多个字符串拼接时

        • public  static void test1(){
              String res = "";
              for(int i=0;i<10;i++)
                  res += "a";
          }
          
          // 等价于
          public static void test2(){
              String res = "";
              for(int i=0;i<10;i++)
                  res = new StringBuilder().append(res).append("a").toString();
          }
          
          • 可见,在循环中拼接多个字符串时,会创建多个StringBuilder对象,浪费内存空间并且降低效率。
        • 如果直接创建StringBuilder对象

          • public static void test5(){
                StringBuilder res = new StringBuilder(10);
                for(int i=0;i<10;i++)
                    res.append("a");
                String s = res.toString();
            }
            
            • 只需创建一个StringBuilder对象,并且可以通过指定初始容量,避免调用append方法时由于byte数据容量不够导致的扩容操作,从而提高效率
      • 总结,Java字符串拼接使用StringBuilder类的append方法的效率要高于"+"

    • concat方法

      • "a".concat("b")
        
        • 连接两个字符串
        • 底层是创建一个byte数组,分别将两个字符串复制进该数组中,之后根据该byte数组重新创建一个String对象并返回。
        • 在拼接两个字符串时
          • concat方法是创建一个byte数组,即分配一次内存空间,将两个字符串复制进该数组,然后根据该byte数组创建String对象
          • StringBuilder的append方法也是创建一个byte数组,即分配一次内存空间,然后将两个字符串复制进该数组中,最后需要调用toString方法返回字符串对象,该方法会把两个字符串再复制一次
          • 综上,拼接两个字符串时,concat方法效率更高,但是拼接多个字符串时,concat每次都需要分配内存空间,而StringBuilder可以指定容量减少扩容次数,此时,StringBuilder的append方法效率高
  • join方法

    • // 第一个参数为界定符
      String s3 = String.join(" ","hello","world","Java");
      
      // output
      hello world Java
      
  • repeat方法

    • // 指定字符串重复多次
      String s3 = "Java".repeat(2);
      
      // output
      JavaJava
      

不可变字符串

  • Java中,String对象不可修改,但是可以让String类型变量引用指向另外一个String对象

    • String s1 = "hello";
      s1 = "world";
      
      String s = "x";
      s += "y"; // s = "xy",s由字符串"x"指向了字符串"xy"
      
  • 优点

    • 原始字符串存在堆中,编译器可以让字符串共享

检测字符串是否相等

  • equals方法

    • String s1 = "hello";
      s1.equals("hello"); // true
      
  • 注意

    • 不要通过"=="判断字符串是否相等

    • "==",比较的是两个字符串变量指向的内存地址是否相等

      • "hello"=="hello"; // true,因为指向同一块内存
        "hello".substring(0,3)=="hel"; // false,因为不指向同一块内存
        "hello".substring(0,3).equals("hel") // true,字面量相等
        

空串与null串

  • 空串与null串不同
    • 空串是Java对象,串长度为0,内容为空
    • null表示目前没有任何对象与该变量关联

码点与代码单元

  • 码点是指与一个编码表中的某个字符对应的代码值。Unicode标准中,码点采用十六进制书写,并加上前缀"U+",如"U+0041"表示A

  • 每个字符用16位表示,称为代码单元

  • length方法返回给定字符串所需要的代码单元数量,codePointCount返回自己码点数量

    • String s1 = "\uD835\uDD46";
      System.out.println(s1); // 𝕆
      System.out.println(s1.length()); // 2
      System.out.println(s1.codePointCount(0,s1.length())); // 1
      

String API

  • // 返回字符串代码代码单元个数
    int length();
    
    // 判断字符串是否为空
    boolean isEmpty();
    
    // 返回给定位置的代码单元
    char charAt(int index);
    
    // 判断两个字符串是否相等,区分大小写
    boolean equals(Object anObject);
    
    // 判断两个字符串是否相等,不区分大小写
    boolean equalsIgnoreCase(String anotherString);
    
    // 按字典顺序比较两个字符串大小,小于返回负数,大于返回正数,等于返回0
    int compareTo(String anotherString);
    
    // 字符串是否以prefix开头
    boolean startsWith(String prefix);
    
    // 字符串是否以suffix结尾
    boolean endsWith(String suffix);
    
    // 返回与字符串str匹配的第一个子串的开始位置
    int indexOf(String str);
    
    // 返回与字符串str匹配的最后一个子串的开始位置
    int lastIndexOf(String str);
    
    // 返回给定位置的子串,包括beginIndex,不包括endIndex
    String substring(int beginIndex, int endIndex);
    
    // 删除原字符串头部和尾部小于等于U+0020(空格)的字符
    String trim();
    
    // 删除原字符串头部和尾部空格
    String strip();
    
    // 指定类型数据转换为字符串对象
    String valueOf(指定类型);
    
    • String.valueOf方法传入null会产生空指针异常,因为直接传入null会调用以下方法

      • public static String valueOf(char[] data) {
                return new String(data);
        }
        
        • 会让char数组data=null,之后在调用构造方法时

          • public String(char[] value) {
                this((char[])value, 0, value.length, (Void)null);
            }
            
          • value.length会导致空指针异常

    • String.valueOf方法传入一个null对象则不会产生空指针异常,返回字符串对象"null"

      • Object tmp = null;
        String a = String.valueOf(tmp);  // a = "null"
        

构建字符串

  • StringBuilder(Java 5引入)

    • 非线程安全

    • 创建StringBuilder对象

      • StringBuilder底层通过创建一个byte数组(Java 9之后)作为字符串缓冲区。
      • 创建StringBuilder对象时,byte数组默认容量为16,可以自己指定容量,如果在创建StringBuilder对象时,将String对象作为参数,则创建的byte数组容量为默认容量16加上String对象长度
    • append方法

      • 该方法用于向StringBuilder对象追加字符串

      • public AbstractStringBuilder append(String str) {
            if (str == null) {
                return this.appendNull();
            } else {
                int len = str.length();
                this.ensureCapacityInternal(this.count + len);
                this.putStringAt(this.count, str);
                this.count += len;
                return this;
            }
        }
        
        • 首先判断字符串是否为null,如果是null,则在byte数组后加上四个字符 'n', 'u', 'l', 'l'

        • 否则,计算字符串的长度,然后调用ensureCapacityInternal方法检查StringBuilder对象的原byte数组的容量能不能装下新的字符串,如果装不下则对byte数组进行扩容

          • int oldCapacity = this.value.length >> this.coder;
            int newCapacity = (oldCapacity << 1) + 2;
            if (newCapacity - minCapacity < 0) {
            newCapacity = minCapacity;
            }
            
            • 首先让新容量为原容量的2倍+2,如果新容量仍然不够,则直接让新容量等于原字符串与追加字符串的总长度
          • 扩容会将原字符串复制到新的byte数组中

        • 之后,将追加字符串复制到byte数组中,并且计算新的字符串长度

        • 至此,append方法追加字符串结束

      • 由于append方法会判断当前byte数组容量是否足够,所以如果要追加多个字符串,应该判断大致容量,并且创建StringBuilder对象时指定该容量,减小扩容的次数,提高效率

    • delete方法

      • delete(int start, int end)可以删除指定位置的字符,如果end超过字符串总长度,则end=count,即会删除start之后所有的字符。
      • delete方法底层调用的是System.arraycopy方法,即删除元素是让数组中end之后的元素移位,覆盖start到end之间的元素
  • StringBuffer

    • 线程安全(方法上用synchronized修饰),但效率较低
    • StringBuffer和StringBuilder的父类都是抽象类AbstractStringBuilder