Java中的String详解

涉及问题:

  • 问题一、String为什么是不可变的(final修饰)
  • 问题二、String与StringBuffer、StringBuilder

问题一解读:

  1. 为了实现字符串池
  2. 为了线程安全
  3. 为了实现String可以创建HashCode不可变性

final的用途: final可以修饰类,方法和变量,并且被修饰的类或方法:

  • 被final修饰的类不能被继承,即它不能拥有自己的子类。
  • 被final修饰的方法不能被重写。
  • final修饰的变量,无论是类属性、对象属性、形参还是局部变量,都需要进行初始化操作。

String被final修饰主要是为了”安全性“和”效率“的缘故。

jdk源码中,final修饰String类,代表了String的不可继承性;
使用私有并且不可变的数组(private final char value[];)来存储字符串,代表了被存储的数据不可更改性。

虽然final代表了不可变,但仅仅是引用地址不可变,并不代表了数组本身不会变,这就是为什么在声明常量的时候,一般不使用类、可变数组、集合等。

final也可以将数组本身改变的,这个时候,起作用的还有private,正是因为两者保证了String的不可变性。>

为什么保证String不可变:

因为只有当字符串是不可变的,字符串池才有可能实现。

  • 效率:
    字符串池的实现可以在运行时节约很多heap空间,因为不同的字符串变量都指向池中的同一个字符串。但如果字符串是可变的,那么String interning将不能实现,因为这样的话,如果变量改变了它的值,那么其它指向这个值的变量的值也会一起改变。

  • 安全问题:
    如果字符串是可变的,那么会引起很严重的安全问题。譬如,数据库的用户名、密码都是以字符串的形式传入来获得数据库的连接,或者在socket编程中,主机名和端口都是以字符串的形式传入。因为字符串是不可变的,所以它的值是不可改变的,否则黑客们可以钻到空子,改变字符串指向的对象的值,造成安全漏洞。

  • 线程安全
    因为字符串是不可变的,所以是多线程安全的,同一个字符串实例可以被多个线程共享。这样便不用因为线程安全问题而使用同步。字符串自己便是线程安全的。

  • hashCode不变:
    因为字符串是不可变的,所以在它创建的时候HashCode就被缓存了,不需要重新计算。这就使得字符串很适合作为Map中的键,字符串的处理速度要快过其它的键对象。这就是HashMap中的键往往都使用字符串。

字符串池:

JVM为了提升性能和减少内存开销,避免字符串的重复创建,其维护了一块特殊的内存空间,这就是我们今天要讨论的核心,即字符串池(String Pool)。字符串池由String类私有的维护。

Java中有两种创建字符串对象的方式:

  1. 采用字面值的方式赋值,即 String str = “aaa”;

采用字面值的方式创建一个字符串时,JVM首先会去字符串池中查找是否存在"aaa"这个对象,如果不存在,则在字符串池中创建"aaa"这个对象,然后将池中"aaa"这个对象的引用地址返回给字符串常量str,这样str会指向池中"aaa"这个字符串对象;如果存在,则不创建任何对象,直接将池中"aaa"这个对象的地址返回,赋给字符串常量。

  1. 采用new关键字新建一个字符串对象。即 String str = new String("aaa");

采用new关键字新建一个字符串对象时,JVM首先在字符串池中查找有没有"aaa"这个字符串对象,如果有,则不在池中再去创建"aaa"这个对象了,直接在堆中创建一个"aaa"字符串对象,然后将堆中的这个"aaa"对象的地址返回赋给引用str,这样,str就指向了堆中创建的这个"aaa"字符串对象;如果没有,则首先在字符串池中创建一个"aaa"字符串对象,然后再在堆中创建一个"aaa"字符串对象,然后将堆中这个"aaa"字符串对象的地址返回赋给str引用,这样,str指向了堆中创建的这个"aaa"字符串对象。

这两种方式在性能和内存占用方面存在着差别。

问题二解读:

  1. 线程安全性
  • String 中的对象是不可变的,也就可以理解为常量,线程安全。
  • AbstractStringBuilder 是 StringBuilder 与StringBuffer 的公共父类,定义了一些字符串的基本操作,如 expandCapacity、append、insert、indexOf 等公共方法。
  • StringBuffer 对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。
  • StringBuilder 并没有对方法进行加同步锁,所以是非线程安全的。
  1. 性能
  • 每次对 String 类型进行改变的时候,都会生成一个新的 String 对象,然后将指针指向新的 String 对象。
  • StringBuffer 每次都会对 StringBuffer 对象本身进行操作,而不是生成新的对象并改变对象引用。相同情况下使用StirngBuilder 相比使用 StringBuffer 仅能获得 10%~15% 左右的性能提升,但却要冒多线程不安全的风险。

你可能感兴趣的:(Java中的String详解)