深入了解String相关内容
文章内容是根据文末的文章总结而来,内容不全面也不缜密:
一、定义
publ从该类的声明中我们可以看出String是final类型的,表示该类不能被继承,同时该类实现了三个接口:java.io.Serializable、 Comparable<String>、 CharSequence:
public final class String implements java.io.Serializable, Comparable<String>, CharSequence{}
二、属性
private final char value[];
pri 这是一个字符数组,并且是final类型,他用于存储字符串内容,所以从final这个关键字中我们可以看出,String的内容一旦被初始化了是不能被更改的。String其实就是用char[]实现的。 例如 String s = “a”; s = “b” ,这并不是对s的修改,而是改变了S的引用,重新指向了新的字符串,以下的图片可以较好的解释:
定义一个字符串:
String s = "abcd";
s
中保存了string对象的引用。下面的箭头可以理解为“存储他的引用”。
使用变量来赋值变量
String s2 = s;
s2保存了相同的引用值,因为他们代表同一个对象。
字符串连接
s = s.concat("ef");
s
中保存的是一个重新创建出来的string对象的引用。
所以一旦一个string对象在内存(堆)中被创建出来,他就无法被修改。特别要注意的是,String类的所有方法都没有改变字符串本身的值,都是返回了一个新的对象。如果你需要一个可修改的字符串,应该使用StringBuffer 或者 StringBuilder。否则会有大量时间浪费在垃圾回收上,因为每次试图修改都有新的string对象被创建出来。
riva
private static final long serialVersionUID = -6849794470754667710L;
private static final ObjectStreamField[] serialPersistentFields = new ObjectStreamField[0];
static 因为String实现了Serializable接口,所以支持序列化和反序列化支持。Java的序列化机制是通过在运行时判断类的serialVersionUID来验证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地相应实体(类)的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常(InvalidCastException)。
Java语言提供了对字符串连接运算符的特别支持(+),该符号也可用于将其他类型转换成字符串。字符串的连接实际上是通过StringBuffer
或者StringBuilder
的append()
方法来实现的,字符串的转换通过toString
方法实现,该方法由 Object 类定义,并可被 Java 中的所有类继承。
另外为什么String被设计成final?
1.字符串池是方法区中的一部分特殊存储。当一个字符串被被创建的时候,首先会去这个字符串池中查找,如果找到,直接返回对该字符串的引用。
2.缓存hashcode: Java中经常会用到字符串的哈希码(hashcode)。例如,在HashMap中,字符串的不可变能保证其hashcode永远保持一致,这样就可以避免一些不必要的麻烦。这也就意味着每次在使用一个字符串的hashcode的时候不用重新计算一次,这样更加高效。
3.安全性: String被广泛的使用在其他Java类中充当参数。比如网络连接、打开文件等操作。如果字符串可变,那么类似操作可能导致安全问题
4.不可变对象天生就是线程安全的,因为不可变对象不能被改变,所以他们可以*地在多个线程之间共享。不需要任何同步处理。
总之,String
被设计成不可变的主要目的是为了安全和高效。所以,使String
是一个不可变类是一个很好的设计。
另外JDK6和JDK7中对subString()的不同实现:
JDK 6中的substring中:当调用substring方法的时候,会创建一个新的string对象,但是这个string的值仍然指向堆中的同一个字符数组。这两个对象中只有count和offset 的值是不同的。
下面是证明上说观点的Java源码中的关键代码:
//JDK 6
String(int offset, int count, char value[]) {
this.value = value;
this.offset = offset;
this.count = count;
}
public String substring(int beginIndex, int endIndex) {
//check boundary
return new String(offset + beginIndex, endIndex - beginIndex, value);
}
JDK 6中的substring导致的问题
如果你有一个很长很长的字符串,但是当你使用substring进行切割的时候你只需要很短的一段。这可能导致性能问题,因为你需要的只是一小段字符序列,但是你却引用了整个字符串(因为这个非常长的字符数组一直在被引用,所以无法被回收,就可能导致内存泄露)。在JDK 6中,一般用以下方式来解决该问题,原理其实就是生成一个新的字符串并引用他。
x = x.substring(x, y) + ""
JDK 7 中的substring
上面提到的问题,在jdk 7中得到解决。在jdk 7 中,substring方法会在堆内存中创建一个新的数组。
Java源码中关于这部分的主要代码如下:
//JDK 7
public String(char value[], int offset, int count) {
//check boundary
this.value = Arrays.copyOfRange(value, offset, offset + count);
}
public String substring(int beginIndex, int endIndex) {
//check boundary
int subLen = endIndex - beginIndex;
return new String(value, beginIndex, subLen);
}
参考:
http://www.hollischuang.com/archives/99
http://www.hollischuang.com/archives/1230
http://www.hollischuang.com/archives/1246
http://www.hollischuang.com/archives/1249
http://www.hollischuang.com/archives/1232
http://www.hollischuang.com/archives/1261