字符串拼接的多种形式
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 办法我将会在下次进行概述,感激大家花工夫观看我的文章。