String 是咱们使用最频繁的对象,使用不当会对内存、程序的性能形成影响,本篇文章全面介绍一下 Java 的 String 是如何演进的,以及使用 String 的注意事项。java
@Test public void testString() { String str1 = "abc"; String str2 = new String("abc"); String str3 = str2.intern(); System.out.println(str1 == str2); System.out.println(str2 == str3); System.out.println(str1 == str3); }
这段代码涉及了 Java 字符串的内存分配、新建对象和引用等方面的知识,输出结果是:git
false false true
String 对象的实现方式,在 Java 六、Java 7/八、Java 9 中都有很大的区别。下面是一张简要的对比图:github
String 对 char 数组进行了封装,主要有四个成员变量:express
String 对象能够经过 offset 和 count 在 char[] 数组中获取对应的字符串,这样作能够高效、快速地共享数组对象,节省内存空间,可是这种方法常常致使内存泄漏
。数组
这是由于,假若有一个很是大的字符串数组对象 a,后来有一个小的字符串引用仅引用其中不多的字符 b,那么会新建大的数组 char[],当 a 被释放后,char[] 的引用并不能被 GC,由于 b 还在引用。app
String 类去掉了 offset 和 count,String.substring 方法也再也不共享char[],从而解决了内存泄漏问题。函数
char[] → byte[]
,同时新增了coder
属性,标识字符编码。这是由于 char 字符占 16 位(2个字节),若是仅存储单字节编码的字符就很是浪费空间。性能
coder 属性的做用是标识字符串是否为 Latin-1(单字节编码),0 标识是 Latin-1,1 表明是 UTF-16。this
Java 11 中的 java.lang.String#substring(int, int) 方法以下:编码
public String substring(int beginIndex, int endIndex) { int length = length(); checkBoundsBeginEnd(beginIndex, endIndex, length); int subLen = endIndex - beginIndex; if (beginIndex == 0 && endIndex == length) { return this; } return isLatin1() ? StringLatin1.newString(value, beginIndex, subLen) : StringUTF16.newString(value, beginIndex, subLen); }
这是一个很重要的问题,相信大部分人都不能描述清楚,由于 JVM 的实现改了不少版……
在 JDK 1.7 以前,运行时常量池逻辑包含字符串常量池
,都存在方法区中,方法区在 HotSpot 虚拟机的实现为永久代
。
在 JDK 1.7 中,字符串常量池
→ 堆,运行时常量池仍然在方法区中。
在 JDK 1.8 中,HotSpot 移除了永久代,使用元空间(Metaspace)代替。这时候字符串常量池
在堆中,运行时常量池在元空间(Metaspace)。
元空间的本质和永久代相似,都是对 JVM 规范中方法区的实现
。不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存
。
在新版 JDK 实现(毕竟 Java 8 都已是老古董,Java 15 都发布了)中,字符串常量池是在堆中。
虽然我尚未在项目中实际应用过,不过这个函数应该还挺有用的,可以复用 Java 中的字符串常量。文章开头的代码中,System.out.println(str1 == str3);
返回 true,就是由于 java.lang.String#intern
方法检测到字符串常量池有这个对象时,可以直接复用字符串常量池的对象,不会额外建立字符串常量。
String str1 = "abc"; String str2 = new String("abc");
注意上面的代码中,new String("abc")
里面的字符串 abc
与 str1 的 abc
不一样,是在字符串常量池新建立的 abc
。
String.intern 的代码注释以下。
/** * Returns a canonical representation for the string object. * <p> * A pool of strings, initially empty, is maintained privately by the * class {@code String}. * <p> * When the intern method is invoked, if the pool already contains a * string equal to this {@code String} object as determined by * the {@link #equals(Object)} method, then the string from the pool is * returned. Otherwise, this {@code String} object is added to the * pool and a reference to this {@code String} object is returned. * <p> * It follows that for any two strings {@code s} and {@code t}, * {@code s.intern() == t.intern()} is {@code true} * if and only if {@code s.equals(t)} is {@code true}. * <p> * All literal strings and string-valued constant expressions are * interned. String literals are defined in section 3.10.5 of the * <cite>The Java™ Language Specification</cite>. * * @return a string that has the same contents as this string, but is * guaranteed to be from a pool of unique strings. * @jls 3.10.5 String Literals */ public native String intern();
coding 笔记、点滴记录,之后的文章也会同步到公众号(Coding Insight)中,但愿你们关注^_^
代码和思惟导图在 GitHub 项目中,欢迎你们 star!