1.相关概念
1.开篇
今天整理代码的时候突然看到String 和StringBuilder引发了一些思考,我们都知道二者都是对字符串的管理, 并且二者都提供很多对字符串增删改查等等的功能。但是重来都不建议当改动大(复杂)的字符串用String 来处理。这个是为什么呢。查找了一些资料和翻看了源码,整理了下,如果有哪里说错的,望大神出来指点
2.什么是字符串
字符串是什么,通俗易懂的来说就是由多个字符(0-9,a-z,A-Z,符号)组成的一系列内容。Java是通过char的数组进行管理这一串字符的。
2.String
1.定义:
TheStringclass represents character strings. All string literals in Java programs, such as"abc", are implemented as instances of this class.Strings are constant; their values cannot be changed after they are created.
StringApi
直接翻看Java api可以找到String类的所有信息,String是个类就是复合类型(不是标准类型),但是String 这个类是个常量类,只要创建了 String,他的值是没有办法改变的(所以一担对用String来操作字符串会里面开辟一个新的字符串出来)。而这里的常量不是指的整个String 而是说的String的字面量。
String a = "abc" //这里的"abc"会被放到.class 文件中的常量池中
String b=new String("abc") //String的另外一种情况,b指向的并不是常量池,是堆
2.常量
1.常量的基本概念
说了这么多常量,常量到底是什么呢?就是简单通过字面的解释,创建了不能改变也不会改变的量。由于是主要讲String所以基本常量的解释就不多说了,找了一篇挺好的文章,大家可以看下
介绍了java中的常量池
2.String 常量
由于String 有2种申明的方法,这2种方法表面上看上去一样,其实背后所做的操作都不一样,所以有必要拿出来记录下(来咯大量的例子来咯):
1 String s1 = "Hello";
2 String s2 = "Hello";
3 String s3 = "Hel" + "lo";
4 String s4 = "Hel" + new String("lo");
5 String s5 = new String("Hello");
6 String s6 = s5.intern();
7 String s7 = "H";
8 String s8 = "ello";
9 String s9 = s7 + s8;
10
11 System.out.println(s1 == s2); // true
12 System.out.println(s1 == s3); // true
13 System.out.println(s1 == s4); // false
14 System.out.println(s1 == s9); // false
15 System.out.println(s4 == s5); // false
16 System.out.println(s1 == s6); // true
刚入门的小伙伴看到这些肯定头晕了,不急慢慢一条解释是为什么:
- s1==s2 十分好理解,由于==是判断地址(不是判断值,判断地址地址地址重要的事情说三遍), 当编译的时候,系统自动在常量池里面存入了"Hello"这个值,由于常量池有复用的功能,自然就把这个常量的地址给了s2这个引用。
- s1==s3,这个其实和11 差不多注意一点系统老聪明了,常量池会自动优化拼接,拼接完发现一样就把原来的常量地址直接给了s3所以返回true
- s1==s4,s4有一部分属于常量池有一部分编译的时候系统根本不知道是啥,所以只能等到运行的时候把常量池里面的取出来然后带着新的字符串在堆种开辟了一块新的空间存放,千万不要问我存在堆的哪里,因为我也不知道阿!!鬼知道存哪里了阿但是肯定是新的一块。没毛病的~~
- s1==s9, 这个很有意思阿,为什么会不同呢,因为系统在编译的时他只知道s7 s8是个变量,他压根不知道里面有啥,他赋完地址就忘了阿!!只好等到运行的时候到s7 s8里面取值然后在堆种开辟新的空间。
- s4 == s5 不多讲.
6.s1 == s6,肯定有刚入门的人问s5.intern(); 这个是啥,这个就是把堆中的值放到常量池里面,同理常量池里面有复用的功能放进去的时候发现一样就直接把原来的地址拿出来~~所以还是一样的
还有一些情况什么“+”号拼接啦,上面推荐的文章都有,不多说了
上面的例子,图片就不画了,因为小编好懒的阿不愿意画图
2.String 源码分析
说了String在对字符串进行修改的时候会创建一个新的String,由于好奇背后怎么实现的,小编就随便着了一个例子:
String a = new String("abca");
String b=a.replace("a","e");
System.out.println(a); //abca
System.out.println(b); //ebce
对~就是随便拿了一个a.replace()来做分析,不知道这个method是干嘛的小伙伴自行去api网站看~
千万别问我 a的值为什么不改变,因为告诉你们字面量是常量不会变不会变所以需要一个新的String来保存结果
public String replace(CharSequence var1, CharSequence var2) {
return Pattern.compile(var1.toString(), 16).matcher(this).replaceAll(Matcher.quoteReplacement(var2.toString()));
}
其实这个方法很简单,就辣么一行,但是这一行把他详细看还是可以看到很多东西的,首先java在替换字符串的时候用的是正则表达式
什么是正则表达式咧(sjsu的小伙伴你们46b lab会教)附上链接:
正则表达式
首先创建了一个正则表达式的规则,没有错就是穿进去要修改的字符,然后在创建了一个matcher,matcher(this)是用来存放匹配的结果(参数代表要匹配的字符串,String的话当然就是自己本身去匹配了)
有没有匹配到,有没有找到对应的内容等等 附上链接:
Matcher
这里重点说一下这二个
matcher.find(); //部分匹配,通俗啊点讲就是查找这个字符串里面有没有匹配到内容,然后定位到剩下匹配到的内容
matcher.matches(); //全部匹配,就是把整串东西和规则进行匹配
Matcher.quoteReplacement(var2.toString()) //去除转义字符"\"和"$"
重点看replaceAll的实现:
public String replaceAll(String var1) {
this.reset();
boolean var2 = this.find();
if(!var2) {
return this.text.toString();
} else {
StringBuffer var3 = new StringBuffer();
do {
this.appendReplacement(var3, var1);
var2 = this.find();
} while(var2);
this.appendTail(var3);
return var3.toString();
}
}
这一串简单讲一下:
如果find()没有结果的话直接返回text(String的字面量),如果有匹配到
那就说明要替换了,那么这里是重点了,java开辟了一个新的StringBuffer!!(暂时理解为一个新的char[]).然后把一个接一个的把字符赋值上去,然后匹配的地方赋值新的值,就可以看出,String在做替换的操作的时候确实开辟了一个新的空间,而且看这段代码也可以看出为什么替换了2个a 因为他会一直找找找直到最后 懂吧~~
replace我重点看了其它的方法扫了下也差不多用到了正则表达式啦,有兴趣的小伙伴可以看看String其它的实现方法
3.StringBuilder
开篇就讲了StringBuilder也是用来管理字符串,但是他的最大区别就是可以改变里面的值,他不是常量,直接上重要代码:
public final class StringBuilder extends AbstractStringBuilder implements Serializable, CharSequence {
static final long serialVersionUID = 4383685877147921099L;
public StringBuilder() {
super(16);
}
public StringBuilder(int var1) {
super(var1);
}
public StringBuilder(String var1) {
super(var1.length() + 16);
this.append(var1);
}
public StringBuilder(CharSequence var1) {
this(var1.length() + 16);
this.append(var1);
}
StringBuilder继承了AbstractStringBuilder然后引入了系列化和char数列的接口
StringBuilder a= new StringBuilder("abc");
/*对应的构造方法*/
public StringBuilder(String var1) {
super(var1.length() + 16);
this.append(var1);
}
我们直接看AbstractStringBuilder的构造方法因为StringBuilder的构造方法也没做什么事阿:
AbstractStringBuilder(int var1) {
this.value = new char[var1];
}
可以看得出直接申明了一个char的数组但是重要的是他的大小是原本的大小+16,这个是为什么呢,因为说过Stringbuilder是可以改变原来的值所以可以在char[]里面添加更多的东西.当StringBuilder 对象的Length(字符串的长度)属性值超过Capacity属性的长度时,StringBuilder 对象内部会重新构造一个字符数组Capacity属性会变为新的大小
返回StringBuilder里面:
/*对应的构造方法*/
public StringBuilder(String var1) {
super(var1.length() + 16);
this.append(var1);
}
==>
public StringBuilder append(String var1) {
super.append(var1);
return this;
}
==>
public AbstractStringBuilder append(String var1) {
if(var1 == null) {
return this.appendNull();
} else {
int var2 = var1.length();
this.ensureCapacityInternal(this.count + var2);
var1.getChars(0, var2, this.value, this.count);
this.count += var2;
return this;
}
}
append是往char[]数组里面加东西,分析一下,首先看下有没有值过来没有直接返回,然后如果有值,获取长度,然后对长度进行判断
有没有超过容量
private void ensureCapacityInternal(int var1) {
if(var1 - this.value.length > 0) {
this.value = Arrays.copyOf(this.value, this.newCapacity(var1));
}
}
就像前面说的超过了,会有一个新的最大空间,看一下value是啥
char[] value;
然后就是往这个数组里面放内容了,把count(字符串的大小)给改变了
把Stringbuilder的append的方法分析了,其它方法可以自己去研究下都不难很容易懂的
结尾
第一次写技术整理,如果有写错的地方望大家指出我可以尽早改掉以免误人子弟~哈哈 我觉得我没说错啦~就是给一些入门的小伙伴扫扫盲 刚好今天整理到这些了 有兴趣的翻了下源码啦