Java字符串

Posted by Liber Sun on September 28, 2018

String

String是不可变的,注意这里的不可变是修饰对象,String的值是可以变的

String s=”abcd”;//一个对象 String s=”abcdel;//另一个对象

String 是被声明为final类,不可被继承。 不提供setter方法, 所有的字段均为final、private。 构造函数也是private。(这里可以引出 构造函数可以有public、priavte 但是没有返回值,必须是void的)

存储

在 Java 8 中,String 内部使用 char 数组存储数据。

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final char value[];
}

在 Java 9 之后,String 类的实现改用 byte 数组存储字符串,同时使用 coder 来标识使用了哪种编码。

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final byte[] value;

    /** The identifier of the encoding used to encode the bytes in {@code value}. */
    private final byte coder;
}

其value为fianl修饰,说明在初始化之后就不能再引用其他数组,同时String内部无修改value数组的方法,因此保证了String是不可变的。

不可变的好处

  1. 可以缓存hash值

因为 String 的 hash 值经常被使用。 例如 String 用做 HashMap 的 key。不可变的特性可以使得 hash 值也不可变,因此只需要进行一次计算。

  1. String Pool 的需要

如果一个 String 对象已经被创建过了,那么就会从 String Pool(位于堆中) 中取得引用。 只有 String 是不可变的,才可能使用 String Pool。

  1. 安全性

String 经常作为参数,String 不可变性可以保证参数不可变。例如在作为网络连接参数的情况下如果 String 是可变的,那么在网络连接过程中,String 被改变,改变 String 对象的那一方以为现在连接的是其它主机,而实际情况却不一定是。

  1. 线程安全

String 不可变性天生具备线程安全,可以在多个线程中安全地使用。

String的equals()和hashCode()

这里我们特别注意字符串的equals()的调用,String::equals 是覆盖了Object的equals函数

只要字符串内容相同,字符串则equals()相等

这里我们特别注意字符串的hashcode的调用,String::hashCode 是覆盖了Object的hashCode函数

    public static int hashCode(byte[] value) {
        int h = 0;
        for (byte v : value) {
            h = 31 * h + (v & 0xff);
        }
        return h;
    }

从代码可以看出,只要他们value一致,他们的hashCode()就是一致的。但是他们的hashCode()一致,不能说明他们value一致,比如字符串”Aa”和”BB”。(在理想情况下:为不同的对象产生不相等的hashCode,但是这种理想情形是非常困难的,因为hachCode的生成是非随机生成的,它有一定的规律,同时这个规律就是每个hash算法的优势所在。))

特点: 相同前缀的字符串所生成的hash值是相邻的

String StringBuffer String Builder

String是不可变字符串,位于常量池中。 StringBuilderStringBuffer是两个常用的操作字符串的类,他们是一种可变字符串(具有扩容机制,需要预设定大小,默认值为16),位于堆中。 StringBuilder是线程不安全的,而StringBuffer是线程安全的,这意味着StringBuffer中许多操作都用到了Synchronized修饰。

字符串常量池

字符串常量池的诞生是为了提升效率和减少内存分配。可以说我们编程有百分之八十的时间在处理字符串,而处理的字符串中有很大概率会出现重复的情况。

字符串常量池是在中。

字符串常量池(String Pool)保存着所有字符串字面量(literal strings),这些字面量在编译时期就确定。不仅如此,还可以使用 String 的 intern() 方法在运行过程中将字符串添加到 String Pool 中。

JVM为了减少字符串对象的重复创建,维护了一段特别的内存称为字符串常量池,每次通过字面量形式创建字符串时,会首先取字符串常量池中读取是否已存在,已存在则将引用返回。

我们要首先认识到String 是不可变的,是final修饰的。因此多个变量可以共享同一个对象,由此才有字符串常量池的说法。同时因为是final不可变类型,也出现了有效防止字符串被修改的情况

当然String对象的不可变设计,可以在Hash类型进行缓存hashCode时,执行效率提升。

final String h="sunlingzhi";
String s="sunlingzhi";//指向常量池
String y="sunlingzhi";
System.out.println(s==y);//true 
String z=new String("sunlingzhi");//指向堆空间
System.out.println(s==z);//false  s和z不是同一个对象

String v=s+y; //stringBuilder.append() 非final字段在运行期进行赋值处理 指向堆

String a=h+"sunlingzhi"//常量池  final字段编译期会进行常量替换 指向常量池
//额外注意hashCode和equals函数
//s、y、z  他们三者两两的以上函数 都为true

string的 intern() 方法

检测字符串常量池是否存在对应字符串,若存在,则返回池中的字符串,若不存在,该方法会添加原字符串在存储堆中的引用到字符串池,再返回该引用。

String a=new String("test");//指向堆  创建了两个对象,常量池中的”test”和堆中对象。
String s=a.intern();//指向常量池
System.out.println(s==a);//false

String str2 = new String("str")+new String("01");//str 01 都会进入常量池,字符串的+ 是通过StringBuilder append 后 再 ToString()的 ,str01只会在堆中
str2.intern();//此时常量池不存在str01 因此intern之后,添加原字符串在存储堆中的引用到字符串池,再返回该引用。
String str1 = "str01";//str01在常量池中的引用 是指向堆中的字符串“str01”
System.out.println(str2==str1);//true

new String(“abc”)

使用这种方式一共会创建两个字符串对象(前提是 String Pool 中还没有 “abc” 字符串对象)。

  • “abc” 属于字符串字面量,因此编译时期会在 String Pool 中创建一个字符串对象,指向这个 “abc” 字符串字面量;
  • 而使用 new 的方式会在堆中创建一个字符串对象。

创建一个测试类,其 main 方法中使用这种方式来创建字符串对象。 创建包括一个堆对象,包括常量池中的abc

public class NewStringTest {
    public static void main(String[] args) {
        String s = new String("abc");
    }
}