java String 分析
Java String 源码分析(JDK 8)
- 字符串常量池
字符串常量池存在于运行时常量池中(JDK 7 之前存在于运行时常量池,JDK 7 已将其转移到堆中)。
字符串常量池的存在使 JVM 提高了性能和减少了内存开销。
- String 源码
String 类是用 final 修饰的,这意味着 String 不能被继承,而且所有的成员方法都默认为 final 方法。
/**
*...
* Strings are constant; their values cannot be changed after they
* are created. String buffers support mutable strings.
* Because String objects are immutable they can be shared.
*...
*/
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
/** Cache the hash code for the string */
private int hash; // Default to 0
/** use serialVersionUID from JDK 1.0.2 for interoperability */
private static final long serialVersionUID = -6849794470754667710L;
/**
* Class String is special cased within the Serialization Stream Protocol.
*
* A String instance is written into an ObjectOutputStream according to
* <a href="{@docRoot}/../platform/serialization/spec/output.html">
* Object Serialization Specification, Section 6.2, "Stream Elements"</a>
*/
private static final ObjectStreamField[] serialPersistentFields =
new ObjectStreamField[0];
......
}
- String 类实现的接口
- java.io.Serializable:这个序列化接口仅用于标识序列化的语意
- Comparable:实现对两个实例化对象比较大小
- CharSequence:实现只读的字符序列,包括 length(), charAt(int index), subSequence(int start, int end) 这几个 API 接口
- String 的成员属性
- value[]:char 数组用于储存 String 的内容
- hash: String 实例化的 hashcode 的一个缓存,String 的哈希码被频繁使用,将其缓存起来,每次使用就没必要再次去计算,这也是一种性能优化的手段。也是 String 被设计为不可变的原因之一。
- serialPersisitentFields:指定默认的序列化字段
创建字符串的两种基本形式
创建字符串
- String s1 = “1”;
- s1 使用
“”
创建字符串,在编译期就对常量池进行判断是否存在该字符串 - 若存在则不创建直接返回对象引用
- 若不存在,则现在常量池中创建该字符串实例,然后在返回实例的引用
- ==编译期的常量池是静态常量池==
- s1 使用
- String s2 = new String(“1”);
- s2 使用 new 创建字符串,JVM 会首先检查字符串常量池
- 若存在,则直接复制该对象的副本,然后将堆中对象的地址赋值给引用 s2
- 若不存在,则实例化该字符串并且将其放到常量池中,然后在堆中复制该对象副本,然后将堆中对象的地址赋值给引用 s2
“+” 连接形式创建字符串
-
String s1 = “1” + “2” + “3”;
- 使用包含常量的字符串连接创建时也是常量,编译期就能确定了,直接入字符串常量池,同样需要判断是否已经存在该字符串
-
String s2 = “1” + “3” + new String(“1”) + “4”
- 当
+
连接字符含有变量时,是在运行期才确定的 - 首先连接操作最开始如果都是字符串常量,便以后将尽可能多的字符串常量连接在一起,形成新的字符串常量参与后续的连接
- 接下来从左向右依次进行,对于不同的字符串,首先以最左边的字符串为参数创建 StringBuilder 对象,然后因此对邮编进行 append 操作
- 最后将 StringBuilder 对象通过 toString() 方法转换成 String 对象
- 注意:中间的多个字符串常量不会自动拼接
- 实际实现过程:==String s2 = new StringBuilder(“13”).append(new String(“1”)).append(“4”).toString();==
- 当
-
String s3 = new String(“1”) + new String (“1”);
- 过程和 s2 类似。。。
- JDK 6 的内存模型如下:
JDK 6 的常量池在永久代,永久代和 Java 堆是两个完全分开的区域。
JDK 6 String + 内存模型- JDK 7/8 的内存模型如下:
JDK 7中,字符串常量池已经被转移至Java 堆中。
JDK 7/8 String 内存模型
关于 equals 和 ==
- 对于
==
- 如果作用于基本数据类型的变量(byte, short, char, int, long, float, double, boolean),则直接比较其存储的“值”是否相等
- 如果作用与引用类型的变量(String),则比较的是所指向的地址(即是否指向同一个对象)
- equals 方法是基类 Object 中的方法,因此对于所有的继承与 Object 的类都会有该方法,在 Object 类中,equals 方法是用来比较两个对象的引用是否相等
- 对于 equals
- equals 方法不能作用与基本数据类型的变量
- 如果没有对 equals 方法进行重写,则比较的是引用类型的变量所指向的对象的地址
- String 类对 equals 方法进行了重写,用来比较指向的字符串对象所存储的字符串是否相等
- 其他的诸如 Double, Date, Integer 等,都对 equals 方法进行了重写用来比较指向的对象所存储的内容是否相等
安全性
- 首先 String 被许多 Java 类用来当参数,如果字符串可变,那么会引起各种严重错误和安全漏洞
- 在这 String 作为核心类,很多的内部方法的实现都是本地调用的,即调用操作系统本地 API,其和操作系统交流频繁,加入这个类被继承重写的话,难免会是操作系统造成巨大的隐患
- 最后字符串的不可变性使得同一字符串实例被多个线程共享,所以保障了多线程的安全性。而且类加载器要用到字符串,不可变性提供了安全性,以便正确的类被加载