字符串拼接的多种方式

1. 原因:在公司的时候,自研的框架会使用到字符串的拼接,所以在这里想将多种拼接字符串的方式进行一个小的总结

2. 方式1:使用+号拼接(最不建议使用)

①不建议用+号的原因:String底层是常量char数组,具体不可变性,在jvm中是通过字符串常量池来进行存储的。由于底层对加号使用了运算符的重载(c++内容),他在每次拼接的时候都会创建StringBuilder对象,通过StringBuilder对象的append(Stirng str)进行拼接,再通过new String(StringBuilder)来创建String对象。但是这样频繁的创建对象是很消耗性能的,因此不推荐使用+号进行拼接操作。

3. 方式2:使用concat函数进行拼接

①代码举例

String strA = "Hello";
String strB = "world";
String concat = strA.concat(",").concat(strB);
System.out.println(concat);
//结果为hello,world

②底层源码调用函数图

③底层源码解析之横向调用(复制原String的值)

//String中concat函数
public String concat(String str) {
//如果添加的字符串为空字符串,返回本身
        int otherLen = str.length();
        if (otherLen == 0) {
            return this;
        }
        int len = value.length;
        char buf[] = Arrays.copyOf(value, len + otherLen);
//复制原来的String到新的buf数组中
-------------------------------------上面的是横向,下面是纵向
//复制添加的str到新的buf数组中
        str.getChars(buf, len);
        return new String(buf, true);
}
//在concat函数中调用了Arrays中copyOf函数
//将源数组全部复制到从索引0开始的copy数组中
public static char[] copyOf(char[] original, int newLength) {
        char[] copy = new char[newLength];
        System.arraycopy(original, 0, copy, 0,
                         Math.min(original.length, newLength));
        return copy;
}

④底层源码解析之System.arraycopy(对数组进行控制,比copyOf更加灵活)

  • 一维数组测试System.arraycopy是否为同一存储空间

    char[] one_char1 = new char[]{'a','b','c'};
    char[] one_char2 = new char[5];
    //举例:将one_char1中从索引1到索引2(长度为2)的值复制到one_char2的索引为3和索引为4的地方
    //被复制数组;源数组被复制的内容的开头索引;
    //复制数组;复制数组放置被复制的内容的开头索引;被复制的内容的索引个数
    //注意:①其中三个数字参数不能为负数;②运行时容易发生数组越界
    System.arraycopy(one_char1,1,one_char2,3,2);
    for(char value:one_char2){
      System.out.println("value2="+value);
      System.out.println();
    }
    //结果为空,空,空,b,c
    //测试修改one_char[2]的值是否会修改one_char[1]的值
    one_char2[3] = 't';
    for(char value:one_char1){
      System.out.println("value1="+value);
    }
    //one_char1结果依然为a,b,c;
    //可见one_char[1]这里并不会随one_char2修改而被修改,说明两个数组不是同一个存储地址,也就是我们常说的浅拷贝,或者说值传递
  • 二维数组测试System.arraycopy是否为同一存储空间

    char[][] two_char1 = new char[][]{{'a','b','c'},{'e','f','g'},{'h','i','j'}};
    char[][] two_char2 = new char[3][5];
    //举例:将two_char1中从第一行到第二行(行数为2)的值复制到two_char2的第一行中;如果该行不够,则存储至下一行
    //被复制数组;源数组被复制的内容的行数索引;
    //复制数组;复制数组放置被复制的内容的行数索引;被复制的内容的行数个数
    //注意:①其中三个数字参数不能为负数;②运行时容易发生数组越界
    System.arraycopy(two_char1,0,two_char2,1,2);
    for(char[] one_Array:two_char2){
      for(char value:one_Array){
         System.out.println("value2="+value);
      }
    two_char2[0][0] = 't';
    for(char[] one_Array:two_char1){
     for(char value:one_Array){
          System.out.println("value2="+value);
      }
    }
    //修改two_char2的值,two_char1的值也随之而改变
    //two_char1的值被改变,two_char2的值也相对应被改变,说明他们是相同的存储空间,也就是我们所说的深度拷贝,或者说地址传递

    ⑤底层源码解析之纵向调用(复制concat(xxx)中xxx的值)

    //String中concat函数
    public String concat(String str) {
    //如果添加的字符串为空字符串,返回本身
          int otherLen = str.length();
          if (otherLen == 0) {
              return this;
          }
          int len = value.length;
          char buf[] = Arrays.copyOf(value, len + otherLen);
    -------------------------------------上面的是横向,下面是纵向
          str.getChars(buf, len);   //调用了下面的getchars函数
          return new String(buf, true);
    }
    //String中getchars函数
    void getChars(char dst[], int dstBegin) {
    //类似的原理,还是调用了上面的本地函数System.arraycopy
          System.arraycopy(value, 0, dst, dstBegin, value.length);
      }

    3.方式3:使用StringBuilder进行拼接

    ①代码举例

    StringBuilder strA = new StringBuilder("hello,");
    strA.append("world");
    System.out.println(strA);
    //结果为hello,world

    ②底层源码调用函数图

    ③源码解析

    public StringBuilder append(String str) {
          super.append(str);
          return this;
    }
    //AbstractStringBuilder是StringBuilder的父类,由super.append(str)进入该方法
    public AbstractStringBuilder append(String str) {
          if (str == null)
              return appendNull();
          int len = str.length();
    //count表示底层char[]数组中元素的个数
    //该方法暂不细说,下回分解
          ensureCapacityInternal(count + len);
    //和上面的getchars一样,调用的是底部的system.arrayCopy函数
          str.getChars(0, len, value, count);
          count += len;
          return this;
      }  

    4.方式4:使用StringBuffer拼接

    ①区别:使用StringBuffer拼接来说,和StringBuilder类似,他都是调用他们的抽象父类AbstractStringBuilder中的方法,只是StringBuffer上的方法都使用了synchronized关键字罢了

    ②使用场景:要求线程安全的情况下使用

    5.方式5:使用StringJoiner来进行拼接(需要同一种分隔符进行多次拼接时)

    ①使用举例1(不带前后缀)

    StringJoiner sj = new StringJoiner(",","[","]");
    sj.add("hello").add("world").add("yangshengyuan");
    //结果为hello,world,yangshengyuan

    ②使用举例2(带前后缀)

    StringJoiner sj = new StringJoiner(",","[","]");
    sj.add("hello").add("world").add("yangshengyuan");
    //结果为[hello,world,yangshengyuan]

    ③源码解析

    //StringJoiner中add方法
    public StringJoiner add(CharSequence newElement) {
          prepareBuilder().append(newElement);
          return this;
    }
    //StringJoiner中prepareBuilder方法
    private StringBuilder prepareBuilder() {
    //value是StringBuilder类型的,用于存储整个的数据,也就是StringJoiner的add方法底层就是用StringBuilder的append方法来实现的
    //如果value为空,则添加为前缀;反之则添加分隔符
       if (value != null) {
           value.append(delimiter);
       } else {
           value = new StringBuilder().append(prefix);
       }
       return value;
    }

    6.引发写这篇文章的代码(通过反射先类中任意属性都能通过分隔符进行拼接)

    //因为整个项目是用ORM框架,他需要一种格式进行拼接和输出,希望能够不要每次模型修改的时候都要修改这个函数,因此在这里使用反射让他能够不管你实体类添加多少属性我都能这样拼接和输出,保证了代码的健壮性吧

    public String getString() throws IllegalArgumentException,     IllegalAccessException {
          StringJoiner sj = new StringJoiner(",");
          Class<ScoreResultmap> srClass = ScoreResultmap.class;
          Field[] fields = srClass.getDeclaredFields();
          for(Field field:fields) {
              String value = String.valueOf(field.get(this));
              sj.add(value);
          }
          return sj.toString();
    }

    7.总结:本文共介绍了4种不同的字符串拼接的方法、使用场景以及他们的源码分析。关于ensureCapacityInternal方法我将会在下次进行概述,感谢大家花时间观看我的文章。

03-05 15:01