String(jdk1.8)源码解读,了解String在java的处理方式。

简介

在java语言中用String表示字符串,从下面定义可以看出String是final类型的,所以是不可变的,线程安全的,不可被继承。

public final class String implements java.io.Serializable, Comparable<String>, CharSequence

其中实现了三个实现接口: Serializable用于字符串序列化, Comparable用于字符串比较 ,CharSequence表示char值的一个可读序列。此接口对许多不同种类的char序列提供统一的自读访问。

主要的属性

    /** 存储字符串 */
    private final char value[];

    /** 缓存字符串对象的哈希值,默认值为0 */
    private int hash; // Default to 0

下面这个属性在jdk1.8中没有看到private final byte coder经查证发现是在jdk9中加入的一 个对象

  1. 字符串在 Java 的 String 类内部由一个包含该字符串中所有字符的 char[] 来表示,其中的每个字符 char 又是由 2 个字节组成,因为 Java 内部使用 UTF-16。举例来说,如果一个字符串含有英文字符,那么这些英文字符的前 8 比特都将为 0,因为一个ASCII字符都能被单个字节来表示。
  2. 当然有许多字符需要 16 比特,但从统计角度来说只需 8 比特的情况占大多数,例如:LATIN-1 ,因此这能成为一种改善内存占用及性能的一个机会。更重要的是:由于 JVM 存储字符串的方式导致 JVM 堆空间通常很大一部分都被字符串所占据。
  3. 大多数情况下,字符串实例常占用比它实际需要的内存多一倍的空间。
  4. Java 9 重新采纳字符串压缩这一概念。
  5. 这意味着无论何时我们创建一个所有字符都能用一个字节的 LATIN-1 编码来描述的字符串,都将在内部使用字节数组的形式存储,且每个字符都只占用一个字节。另一方面,如果字符串中任一字符需要多于 8 比特位来表示时,该字符串的所有字符都统统使用两个字节的 UTF-16 编码来描述。因此基本上能如果可能,都将使用单字节来表示一个字符。
  6. 现在的问题是:所有的字符串操作如何执行? 怎样才能区分字符串是由 LATIN-1 还是 UTF-16 来编码?为了处理这些问题,字符串的内部实现进行了一些调整。引入了一个 final 修饰的成员变量 coder, 由它来保存当前字符串的编码信息

来源

其中两种格式定义如下:

static final byte LATIN1 = 0;
static final byte UTF16  = 1;

主要的方法

// String的字符长度
public int length() {
    return value.length; // 返回char数组的长度
}
// 截取字符串(能取到beginIndex,取不到endIndex位置的字符)
public String substring(int beginIndex, int endIndex);
// 正则匹配
public boolean matches(String regex);
// 拆分字符串
public String[] split(String regex);
// 获得char数组
// 不指定编码会使用默认的编码
public byte[] getBytes();
public byte[] getBytes(String charsetName);
public byte[] getBytes(Charset charset);
// 比较字符串
// 单纯的比较(一个个字符去比较,不能使用==去比较,因为比较的是字符串的引用)
public boolean equals(Object anObject);
// 忽略大小写的比较
public boolean equalsIgnoreCase(String anotherString);

其他

String s1 = "www";
String s2 = "ccc";
String s3 = s1 + s2;

编译器其实会对+进行了转换的,转成了 StringBuilder 对象来操作了,首先使用 s1 创建 StringBuilder 对象,然后用 append方法连接 s2,最后调用toString方法完成。

但是在下面这种情况下

String s = "www";
for (int i = 0; i < 10; i++)
    s += i;

这样循环中每次都要创建 StringBuilder 对象,而且要调用toString方法,这样的执行效率显然比较低,而且增加了 GC 的压力。

把事情都丢给编译器是不友好的,为了能让程序执行更加高效,最好是我们自己来控制 StringBuilder 的实例,只创建一个 StringBuilder 实例,后面用append方法连接字符串