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 使用“”创建字符串,在编译期就对常量池进行判断是否存在该字符串
    • 若存在则不创建直接返回对象引用
    • 若不存在,则现在常量池中创建该字符串实例,然后在返回实例的引用
    • ==编译期的常量池是静态常量池==
  • 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 6 String + 内存模型
    • JDK 7/8 的内存模型如下:

    JDK 7中,字符串常量池已经被转移至Java 堆中。

    JDK 7/8 String 内存模型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,其和操作系统交流频繁,加入这个类被继承重写的话,难免会是操作系统造成巨大的隐患
  • 最后字符串的不可变性使得同一字符串实例被多个线程共享,所以保障了多线程的安全性。而且类加载器要用到字符串,不可变性提供了安全性,以便正确的类被加载

posted @ 2020-07-07 21:16  GankByMY  阅读(101)  评论(0编辑  收藏  举报