String 对象是我们使用最频繁的一个对象类型,但它的性能问题却是最容易被忽略的。String 对象作为 Java 语言中重要的数据类型,是内存中占据空间最大的一个对象。高效地使用字符串,可以提升系统的整体性能。
String 存储变化
随着 Java 版本的更迭,工程师们对 String 对象做了大量的优化,来节省存储空间。
1、在 Java6 以及之前的版本中,String 对象是对 char 数组进行了封装实现的对象,主要有四个成员变量:char 数组、偏移量 offset、字符数量 count、哈希值 hash。String 对象是通过 offset 和 count 两个属性来定位 char[]数组,获取字符串。这么做可以高效、快速地共享数组对象,同时节省内存空间,但这种方式很有可能会导致内存泄漏。
public final class String
implements java.io.Serializable, Comparable, CharSequence
{
/** The value is used for character storage. */
private final char value[];
/** The offset is the first index of the storage that is used. */
private final int offset;
/** The count is the number of characters in the String. */
private final int count;
/** Cache the hash code for the string */
private int hash; // Default to 0
...
}
2、 从 Java7 版本开始到 Java8 版本,Java 对 String 类做了一些改变。String 类中不再有 offset 和 count 两个变量了。这样的好处是 String 对象占用的内存稍微少了些,同时,String.substring 方法也不再共享 char[],从而解决了使用该方法可能导致的内存泄漏问题。
public final class String
implements java.io.Serializable, Comparable, CharSequence {
private final char value[];
/** Cache the hash code for the string */
private int hash; // Default to 0
.....
}
3、从 Java9 版本开始,工程师将 char[]字段改为了 byte[]字段,又维护了一个新的属性 coder,它是一个编码格式的标识。
public final class String
implements java.io.Serializable, Comparable, CharSequence {
@Stable
private final byte[] value;
private final byte coder;
/** Cache the hash code for the string */
private int hash; // Default to 0
.....
}
至于为何要这样改?我们知道一个 char 字符占 16 位,2 个字节。这个情况下,存储单字节编码内的字符(占一个字节的字符)就显得非常浪费。JDK1.9 的 String 类为了节约内存空间,于是使用了占 8 位,1 个字节的 byte 数组来存放字符串。
而新属性 coder 的作用是,在计算字符串长度或者使用 toCharArray()函数时,我们需要根据这个字段,判断如何计算字符串长度。coder 属性默认有 0 和 1 两个值,0 代表 Latin-1(单字节编码),1 代表 UTF-16。如果 String 判断字符串只包含了 Latin-1,则 coder 属性值为 0,反之则为 1。
public char[] toCharArray() {
return isLatin1() ? StringLatin1.toChars(value)
: StringUTF16.toChars(value);
}
private boolean isLatin1() {
return COMPACT_STRINGS && coder == LATIN1;
}
static final byte LATIN1 = 0;
static final byte UTF16 = 1;
以前学习 String 源码时,只关注了 JDK8 的源码,对于 JDK9 的改动没有了解,既然把 char[] 改为了 byte[] 数组,那么我第一时间就想去了解 String 是如何存储汉字的?
关于这部分内容可以参考网友的这篇文章《Java中如何存储汉字》,通俗易懂。
String学习
关于字符串的优化,首先需要学习字符串常量池和 String 源码,在此基础上,非常容易理解字符串的优化。
这里推荐几篇我之前写的文章:
Java 中方法区与常量池
Java 基础:String 类源码分析
Java 基础:String——常量池与 intern
String优化
1、构建超长字符串
String str = "abcdef";
for(int i=0; i