String源码3103行。
publicfinalclassStringimplementsjava.io.Serializable,Comparable
可以看出String是final类型的,表示该类不能被其他类继承,同时该类实现了三个接口:java.io.Serializable Comparable
因为String实现了Serializable接口,所以支持序列化和反序列化支持
String字符串是常量,其值在实例创建后就不能被修改,
但字符串缓冲区支持可变的字符串,因为缓冲区里面的不可变字符串对象们可以被共享。(其实就是使对象的引用发生了改变)
Demo:
String s = "";
s = "123";
System.out.println(s);123
s="abc";
System.out.println(s);abc
其实这里的赋值并不是对s内容的修改,而是将s指向了新的字符串另外可以明确的一点:String其实是基于字符数组char[]实现的。并且其值不可改变。private final char value[];
使用StringBuffer和StringBuider构造一个String
作为String的两个“兄弟”,StringBuffer和StringBuider也可以被当做构造String的参数。
当然,这两个构造方法是很少用到的,因为当我们有了StringBuffer或者StringBuilfer对象之后可以直接使用他们的toString方法来得到String。
String对象可以通过“+”串联。串联后会生成新的字符串。也可以通过concat()来串联,实质上是调用了stringBuffer的append方法和 toString方法。
我们知道,Java是不支持重载运算符,(<,>,>=,<=,==,!=;+,-,*,/,%,++,-)
为什么不支持重载运算符?
个人觉得从项目管理和代码质量的角度来看,运算符重载可能导致代码的可读性降低,不宜维护,这与JAVA最初的设计思想不符。
有人说String的“+”是java中唯一的一个重载运算符。。藐视看起来像是重载了运算符,我个人认为他并没有真的运用重载运算符,其实这个只是JVM做的语法糖。
语法糖(Syntactic Sugar),也叫糖衣语法,是英国计算机科学家彼得·约翰·兰达(Peter J. Landin)发明的一个术语。指的是,在计算机语言中添加某种语法,这种语法能使程序员更方便的使用语言开发程序,同时增强程序代码的可读性,避免出错的机会;但是这种语法对语言的功能并没有影响。
Java7 提供了一个特殊的保护类型的构造方法:
String除了提供了很多公有的供程序员使用的构造方法以外,还提供了一个保护类型的构造方法(Java 7),我们看一下他是怎么样的:
String(char[] value, boolean share) {
// assert share : "unshared not supported";
this.value = value;
}
· 第一个,该方法多了一个参数:boolean share,其实这个参数在方法体中根本没被使用。注释说目前不支持false,只使用true。
那可以断定,加入这个share的只是为了区分于String(char[] value)方法,不加这个参数就没办法定义这个函数,只有参数是不同才能进行重载。
· 第二个区别就是具体的方法实现不同。我们前面提到过,String(char[] value)方法在创建String的时候会用到Arrays的copyOf方法将value中的内容逐一复制到String当中,而这个String(char[] value, boolean share)方法则是直接将value的引用赋值给String的value。
那么也就是说,这个方法构造出来的String和参数传过来的char[] value共享同一个数组。
优点:
· 性能好
这个很简单,一个是直接给数组赋值(相当于直接将String的value的指针指向char[]数组),一个是逐一拷贝。当然是直接赋值快了。
节约内存
Java1.7对subString方法进行了优化。
因前substring实现方法,有一个致命的缺点,对于sun公司的程序员来说是一个零容忍的bug,那就是他很有可能造成内存泄露。
源码如下:
1. public String substring(int beginIndex, int endIndex) {
2. if (beginIndex < 0) {
3. throw new StringIndexOutOfBoundsException(beginIndex);
4. }
5. if (endIndex > count) {
6. throw new StringIndexOutOfBoundsException(endIndex);
7. }
8. if (beginIndex > endIndex) {
9. throw new StringIndexOutOfBoundsException(endIndex - beginIndex);
10. }
11. return ((beginIndex == 0) && (endIndex == count)) ? this :
12. new String(offset + beginIndex, endIndex - beginIndex, value);
13. }
这样的方式是不合理的,举一个简单到例子
例如: 读取一个5000个字符的字符串,采用substring截取其中的30个字符,在这种情况下,30个字符在内存中还是使用了5000个字符。
这样导致的后果就是如果有一个很大很长的字符串我只需要其中的一小部分字符串用 substring实现的话,如果让你看似得到的“新”的短小字符串一直没被JVM 回收的话,那么相当这个最初的大字符串也没被回收,这样就造成了内存泄露。
新的JDK中substring之所以不存在这个问题了,是因为这个构造方法改成这样了:
1. public String(char value[], int offset, int count) {
2. if (offset < 0) {
3. throw new StringIndexOutOfBoundsException(offset);
4. }
5. if (count < 0) {
6. throw new StringIndexOutOfBoundsException(count);
7. }
8. // Note: offset or count might be near -1>>>1.
9. if (offset > value.length - count) {
10. throw new StringIndexOutOfBoundsException(offset + count);
11. }
12. this.offset = 0;
13. this.count = count;
14. this.value = Arrays.copyOfRange(value, offset, offset+count);
15. }
其中 value变量不再引用了而是重新新建了一个,所以没有这个问题了
String对象的三种比较方式:
==内存比较:直接对比两个引用所指向的内存值,精确简洁直接明了。
equals字符串值比较:比较两个引用所指对象字面值是否相等。
1、String 类是一个final 修饰的类所以这个类是不能继承的,也就没有子类。
2、String 类的成员变量都是final类型的并且没有提供任何方法可以来修改引用变量所引用的对象的内容,所以一旦这个对象被创建并且成员变量初始化后这个对象就不能再改变了,所以说String 对象是一个不可变对象。
3、使用“+”连接字符串的过程产生了很多String 对象和StringBuffer 对象所以效率相比直接使用StringBuffer 对象的append() 方法来连接字符效率低很多。
4、引用变量是存在java虚拟机栈内存中的,它里面存放的不是对象,而是对象的地址或句柄地址。
5、对象是存在java heap(堆内存)中的值
6、引用变量的值改变指的是栈内存中的这个引用变量的值的改变是,对象地址的改变或句柄地址的改变,而对象的改变指的是存放在Java heap(堆内存)中的对象内容的改变和引用变量的地址和句柄没有关系
StringBuffer源码605行
StringBuffer类继承自AbstractStringBuilder抽象类,实现Serializable序列化接口和CharSequence接口。
AbstractStringBuilder抽象类实现Appendabel,CharSequence接口。
StringBuffer的方法都加了synchronized关键字,线程安全。
// 默认为16个字符
public StringBuffer() {
super(16);
}
在上面的源代码中我们看到StringBuffer 的构造函数默认创建的大小为16个字符。
abstract class AbstractStringBuilder implements Appendable, CharSequence {
/**
* The value is used for character storage.
*/
// 这里我们看到,这个数组没有被final 修饰,所以引用变量的值可以改变,
//可以引用到其他数组对象
char[] value;
StringBuffer可变长度如何实现:
1. public synchronized StringBuffer append(String str) {
2. super.append(str);
3. return this;
4. }
第二步:AbstractStringBuilder抽象类体内
Java代码
1. /**
2. * AbstractStringBuilder属性有:
3. * char value[]; // The value is used for character storage(). 容量
4. * int count; // The count is the number of characters used.实际字符数
5. */
6. public AbstractStringBuilder append(StringBuffer sb) {
7. if (sb == null)
8. return append("null");
9. int len = sb.length();
10. int newCount = count + len;
11. if (newCount > value.length)
12. expandCapacity(newCount);// 若value存储容量不够需扩容。扩容方法省略暂不分析,基本上根据Arrays.copyOf()方法,复制指定的数组,以使副本具有指定的长度。到头来copyOf的源码一样是利用arraycopy方法来复制数组和扩容
13. sb.getChars(0, len, value, count); //
14.
15. count = newCount; // 更新新count值
16. return this;
17. }
AbstractStringBuilder里定义的getChars方法体,是对System.arraycopy方法 + 边界检查的进一步封装 而已。
总结:
1、StringBuffer 类被final 修饰所以不能继承没有子类
2、StringBuffer 对象是可变对象,因为父类的 value [] char 没有被final修饰所以可以进行引用的改变,而且还提供了方法可以修改被引用对象的内容即修改了数组内容。
3、在使用StringBuffer对象的时候尽量指定大小这样会减少扩容的次数,也就是会减少创建字符数组对象的次数和数据复制的次数,当然效率也会提升。
StringBuilder源码437行
除了线程安全问题,其他的和StringBuffer类似。