深入理解String.intern
在 JAVA 语言中有8中基本类型和一种比较特殊的类型String
。这些类型为了使他们在运行过程中速度更快,更节省内存,都提供了一种常量池的概念。常量池就类似一个JAVA系统级别提供的缓存。
8种基本类型的常量池都是系统协调的,String类型的常量池比较特殊。它的主要使用方法有两种:
直接使用双引号声明出来的String
对象会直接存储在常量池中。
如果不是用双引号声明的String
对象,可以使用String
提供的intern
方法。intern
方法会从字符串常量池中查询当前字符串是否存在,若不存在就会将当前字符串放入常量池中
接下来我们主要来谈一下String#intern
方法。
下面是基于jdk1.7、1.6
一, intern 的实现原理
首先深入看一下它的实现原理。
1,JAVA 代码
/** * Returns a canonical representation for the string object. * <p> * A pool of strings, initially empty, is maintained privately by the * class <code>String</code>. * <p> * When the intern method is invoked, if the pool already contains a * string equal to this <code>String</code> object as determined by * the {@link #equals(Object)} method, then the string from the pool is * returned. Otherwise, this <code>String</code> object is added to the * pool and a reference to this <code>String</code> object is returned. * <p> * It follows that for any two strings <code>s</code> and <code>t</code>, * <code>s.intern() == t.intern()</code> is <code>true</code> * if and only if <code>s.equals(t)</code> is <code>true</code>. * <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. */
public native String intern();
String#intern
方法中看到,这个方法是一个 native 的方法,但注释写的非常明了。“如果常量池中存在当前字符串, 就会直接返回当前字符串. 如果常量池中没有此字符串, 会将此字符串放入常量池中后, 再返回”。
2,native 代码
在 jdk7后,oracle 接管了 JAVA 的源码后就不对外开放了,根据 jdk 的主要开发人员声明 openJdk7 和 jdk7 使用的是同一分主代码,只是分支代码会有些许的变动。所以可以直接跟踪 openJdk7 的源码来探究 intern
的实现。
native实现代码:
\openjdk7\jdk\src\share\native\java\lang\String.c
Java_java_lang_String_intern(JNIEnv *env, jobject this)
{ return JVM_InternString(env, this);
}
\openjdk7\hotspot\src\share\vm\prims\jvm.h
/*
* java.lang.String
*/
JNIEXPORT jstring JNICALL
JVM_InternString(JNIEnv *env, jstring str);
\openjdk7\hotspot\src\share\vm\prims\jvm.cpp
// String support ///
JVM_ENTRY(jstring, JVM_InternString(JNIEnv *env, jstring str)) JVMWrapper("JVM_InternString"); JvmtiVMObjectAllocEventCollector oam; if (str == NULL) return NULL; oop string = JNIHandles::resolve_non_null(str); oop result = StringTable::intern(string, CHECK_NULL);return (jstring) JNIHandles::make_local(env, result);
JVM_END
\openjdk7\hotspot\src\share\vm\classfile\symbolTable.cpp
oop StringTable::intern(Handle string_or_null, jchar* name, int len, TRAPS) { unsigned int hashValue = java_lang_String::hash_string(name, len); int index = the_table()->hash_to_index(hashValue); oop string = the_table()->lookup(index, name, len, hashValue); // Found if (string != NULL) return string; // Otherwise, add to symbol to table return the_table()->basic_add(index, string_or_null, name, len, hashValue, CHECK_NULL);
}
\openjdk7\hotspot\src\share\vm\classfile\symbolTable.cpp
oop StringTable::lookup(int index, jchar* name, int len, unsigned int hash) { for (HashtableEntry<oop>* l = bucket(index); l != NULL; l = l->next()) { if (l->hash() == hash) { if (java_lang_String::equals(l->literal(), name, len)) { return l->literal(); } } } return NULL;
}
它的大体实现结构就是:
- JAVA 使用 jni 调用c++实现的
StringTable
的intern
方法,StringTable
的intern
方法跟Java中的HashMap
的实现是差不多的, 只是不能自动扩容。默认大小是1009。- 要注意的是,String的String Pool是一个固定大小的
Hashtable
,默认值大小长度是1009,如果放进String Pool的String非常多,就会造成Hash
冲突严重,从而导致链表会很长,而链表长了后直接会造成的影响就是当调用String.intern
时性能会大幅下降(因为要一个一个找)。- 在 jdk6中
StringTable
是固定的,就是1009的长度,所以如果常量池中的字符串过多就会导致效率下降很快。在jdk7中,StringTable的长度可以通过一个参数指定:
-XX:StringTableSize=99991
二,jdk6 和 jdk7 下 intern 的区别
相信很多 JAVA 程序员都做做类似 String s = new String("abc")
这个语句创建了几个对象的题目。 这种题目主要就是为了考察程序员对字符串对象的常量池掌握与否。上述的语句中是创建了2个对象,第一个对象是”abc”字符串存储在常量池中,第二个对象在JAVA Heap中的 String 对象。
来看一段代码:
public static void main(String[] args) {String s = new String("1");s.intern();String s2 = "1";System.out.println(s == s2);String s3 = new String("1") + new String("1");s3.intern();String s4 = "11";System.out.println(s3 == s4);
}
打印结果是
jdk6 下false false
jdk7 下false true
具体为什么稍后再解释,然后将s3.intern();
语句下调一行,放到String s4 = "11";
后面。将s.intern();
放到String s2 = "1";
后面。是什么结果呢
public static void main(String[] args) {String s = new String("1");String s2 = "1";s.intern();System.out.println(s == s2);String s3 = new String("1") + new String("1");String s4 = "11";s3.intern();System.out.println(s3 == s4);
}
打印结果为:
jdk6 下false false
jdk7 下false false
1,jdk6中的解释
注:图中绿色线条代表 string 对象的内容指向。 黑色线条代表地址指向。
如上图所示。首先说一下 jdk6中的情况,在 jdk6中上述的所有打印都是 false
的,因为 jdk6中的常量池是放在 Perm 区中的,Perm 区和正常的 JAVA Heap 区域是完全分开的。上面说过如果是使用引号声明的字符串都是会直接在字符串常量池中生成,而 new 出来的 String 对象是放在 JAVA Heap 区域。所以拿一个 JAVA Heap 区域的对象地址和字符串常量池的对象地址进行比较肯定是不相同的,即使调用String.intern
方法也是没有任何关系的。
2,jdk7中的解释
再说说 jdk7 中的情况。这里要明确一点的是,在 Jdk6 以及以前的版本中,字符串的常量池是放在堆的 Perm 区的,Perm 区是一个类静态的区域,主要存储一些加载类的信息,常量池,方法片段等内容,默认大小只有4m,一旦常量池中大量使用 intern
是会直接产生java.lang.OutOfMemoryError
: PermGen space错误的。 所以在 jdk7 的版本中,字符串常量池已经从 Perm 区移到正常的 Java Heap 区域了。为什么要移动,Perm 区域太小是一个主要原因,当然据消息称 jdk8 已经直接取消了 Perm 区域,而新建立了一个元区域。应该是 jdk 开发者认为 Perm 区域已经不适合现在 JAVA 的发展了。
正式因为字符串常量池移动到 JAVA Heap 区域后,再来解释为什么会有上述的打印结果。
在第一段代码中,先看 s3和s4字符串。
String s3 = new String("1") + new String("1");
,这句代码中现在生成了2最终个对象,是字符串常量池中的“1” 和 JAVA Heap 中的 s3引用指向的对象。中间还有2个匿名的new String("1")
我们不去讨论它们。此时s3引用对象内容是”11″,但此时常量池中是没有 “11”对象的。接下来
s3.intern();
这一句代码,是将 s3中的“11”字符串放入 String 常量池中,因为此时常量池中不存在“11”字符串,因此常规做法是跟 jdk6 图中表示的那样,在常量池中生成一个 “11” 的对象,关键点是 jdk7 中常量池不在 Perm 区域了,这块做了调整。常量池中不需要再存储一份对象了,可以直接存储堆中的引用。这份引用指向 s3 引用的对象。 也就是说引用地址是相同的。最后String s4 = “11”; 这句代码中”11″是显示声明的,因此会直接去常量池中创建,创建的时候发现已经有这个对象了,此时也就是指向 s3 引用对象的一个引用。所以 s4 引用就指向和 s3 一样了。因此最后的比较 s3 == s4 是 true。
再看 s 和 s2 对象。
String s = new String("1");
第一句代码,生成了2个对象。常量池中的“1” 和 JAVA Heap 中的字符串对象。s.intern();
这一句是 s 对象去常量池中寻找后发现 “1” 已经在常量池里了。接下来
String s2 = "1";
这句代码是生成一个 s2的引用指向常量池中的“1”对象。 结果就是 s 和 s2 的引用地址明显不同。图中画的很清晰。
来看第二段代码,从上边第二幅图中观察。第一段代码和第二段代码的改变就是
s3.intern();
的顺序是放在String s4 = "11";
后了。这样,首先执行String s4 = "11";
声明 s4 的时候常量池中是不存在“11”对象的,执行完毕后,“11“对象是 s4 声明产生的新对象。然后再执行s3.intern();
时,常量池中“11”对象已经存在了,因此 s3 和 s4 的引用是不同的。第二段代码中的 s 和 s2 代码中,
s.intern();
,这一句往后放也不会有什么影响了,因为对象池中在执行第一句代码String s = new String("1");
的时候已经生成“1”对象了。下边的s2声明都是直接从常量池中取地址引用的。 s 和 s2 的引用地址是不会相等的。
小 结
从上述的例子代码可以看出 jdk7 版本对 intern 操作和常量池都做了一定的修改。主要包括2点:
将String常量池 从 Perm 区移动到了 Java Heap区
String#intern 方法时,如果存在堆中的对象,会直接保存对象的引用,而不会重新创建对象。
三,使用 intern
1,intern 正确使用例子
接下来我们来看一下一个比较常见的使用String#intern方法的例子。
代码如下:
static final int MAX = 1000 * 10000;
static final String[] arr = new String[MAX];public static void main(String[] args) throws Exception {Integer[] DB_DATA = new Integer[10];Random random = new Random(10 * 10000);for (int i = 0; i < DB_DATA.length; i++) {DB_DATA[i] = random.nextInt();}long t = System.currentTimeMillis();for (int i = 0; i < MAX; i++) {//arr[i] = new String(String.valueOf(DB_DATA[i % DB_DATA.length]));arr[i] = new String(String.valueOf(DB_DATA[i % DB_DATA.length])).intern();}System.out.println((System.currentTimeMillis() - t) + "ms");System.gc();
}
运行的参数是:-Xmx2g -Xms2g -Xmn1500M 上述代码是一个演示代码,其中有两条语句不一样,一条是使用 intern,一条是未使用 intern。结果如下图
2160ms
826ms
通过上述结果,我们发现不使用 intern 的代码生成了1000w 个字符串,占用了大约640m 空间。 使用了 intern 的代码生成了1345个字符串,占用总空间 133k 左右。其实通过观察程序中只是用到了10个字符串,所以准确计算后应该是正好相差100w 倍。虽然例子有些极端,但确实能准确反应出 intern 使用后产生的巨大空间节省。
细心的同学会发现使用了 intern 方法后时间上有了一些增长。这是因为程序中每次都是用了 new String 后,然后又进行 intern 操作的耗时时间,这一点如果在内存空间充足的情况下确实是无法避免的,但我们平时使用时,内存空间肯定不是无限大的,不使用 intern 占用空间导致 jvm 垃圾回收的时间是要远远大于这点时间的。 毕竟这里使用了1000w次intern 才多出来1秒钟多的时间。
2,intern 不当使用
看过了 intern 的使用和 intern 的原理等,我们来看一个不当使用 intern 操作导致的问题。
在使用 fastjson 进行接口读取的时候,我们发现在读取了近70w条数据后,我们的日志打印变的非常缓慢,每打印一次日志用时30ms左右,如果在一个请求中打印2到3条日志以上会发现请求有一倍以上的耗时。在重新启动 jvm 后问题消失。继续读取接口后,问题又重现。接下来我们看一下出现问题的过程。
1,根据 log4j 打印日志查找问题原因
在使用log4j#info打印日志的时候时间非常长。所以使用 housemd 软件跟踪 info 方法的耗时堆栈。
trace SLF4JLogger.
trace AbstractLoggerWrapper:
trace AsyncLogger
org/apache/logging/log4j/core/async/AsyncLogger.actualAsyncLog(RingBufferLogEvent) sun.misc.Launcher$AppClassLoader@109aca82 1 1ms org.apache.logging.log4j.core.async.AsyncLogger@19de86bb
org/apache/logging/log4j/core/async/AsyncLogger.location(String) sun.misc.Launcher$AppClassLoader@109aca82 1 30ms org.apache.logging.log4j.core.async.AsyncLogger@19de86bb
org/apache/logging/log4j/core/async/AsyncLogger.log(Marker, String, Level, Message, Throwable)
代码出在 AsyncLogger.location 这个方法上. 里边主要是调用了 return Log4jLogEvent.calcLocation(fqcnOfLogger);
和Log4jLogEvent.calcLocation()
Log4jLogEvent.calcLocation()的代码如下:
public static StackTraceElement calcLocation(final String fqcnOfLogger) { if (fqcnOfLogger == null) { return null; } final StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); boolean next = false; for (final StackTraceElement element : stackTrace) { final String className = element.getClassName(); if (next) { if (fqcnOfLogger.equals(className)) { continue; } return element; } if (fqcnOfLogger.equals(className)) { next = true; } else if (NOT_AVAIL.equals(className)) { break; } } return null;
}
经过跟踪发现是 Thread.currentThread().getStackTrace(); 的问题。
2, 跟踪Thread.currentThread().getStackTrace()的 native 代码,验证String#intern
Thread.currentThread().getStackTrace();native的方法:
public StackTraceElement[] getStackTrace() { if (this != Thread.currentThread()) { // check for getStackTrace permission SecurityManager security = System.getSecurityManager(); if (security != null) { security.checkPermission( SecurityConstants.GET_STACK_TRACE_PERMISSION); } // optimization so we do not call into the vm for threads that // have not yet started or have terminated if (!isAlive()) { return EMPTY_STACK_TRACE; } StackTraceElement[][] stackTraceArray = dumpThreads(new Thread[] {this}); StackTraceElement[] stackTrace = stackTraceArray[0]; // a thread that was alive during the previous isAlive call may have // since terminated, therefore not having a stacktrace. if (stackTrace == null) { stackTrace = EMPTY_STACK_TRACE; } return stackTrace; } else { // Don't need JVM help for current thread return (new Exception()).getStackTrace(); }
} private native static StackTraceElement[][] dumpThreads(Thread[] threads);
下载 openJdk7的源码查询 jdk 的 native 实现代码,列表如下【这里因为篇幅问题,不详细罗列涉及到的代码,有兴趣的可以根据文件名称和行号查找相关代码】:
\openjdk7\jdk\src\share\native\java\lang\Thread.c
\openjdk7\hotspot\src\share\vm\prims\jvm.h line:294:
\openjdk7\hotspot\src\share\vm\prims\jvm.cpp line:4382-4414:
\openjdk7\hotspot\src\share\vm\services\threadService.cpp line:235-267:
\openjdk7\hotspot\src\share\vm\services\threadService.cpp line:566-577:
\openjdk7\hotspot\src\share\vm\classfile\javaClasses.cpp line:1635-[1651,1654,1658]:
完成跟踪了底层的 jvm 源码后发现,是下边的三条代码引发了整个程序的变慢问题。
oop classname = StringTable::intern((char*) str, CHECK_0);
oop methodname = StringTable::intern(method->name(), CHECK_0);
oop filename = StringTable::intern(source, CHECK_0);
这三段代码是获取类名、方法名、和文件名。因为类名、方法名、文件名都是存储在字符串常量池中的,所以每次获取它们都是通过String#intern方法。但没有考虑到的是默认的 StringPool 的长度是1009且不可变的。因此一旦常量池中的字符串达到的一定的规模后,性能会急剧下降。
3,fastjson 不当使用 String#intern
导致这个 intern 变慢的原因是因为 fastjson 对String#intern方法的使用不当造成的。跟踪 fastjson 中的实现代码发现,
com.alibaba.fastjson.parser.JSONScanner#scanFieldSymbol()
if (ch == '\"') {bp = index;this.ch = ch = buf[bp];strVal = symbolTable.addSymbol(buf, start, index - start - 1, hash);break;
}
com.alibaba.fastjson.parser.SymbolTable#addSymbol():
/*** Constructs a new entry from the specified symbol information and next entry reference.*/
public Entry(char[] ch, int offset, int length, int hash, Entry next){characters = new char[length];System.arraycopy(ch, offset, characters, 0, length);symbol = new String(characters).intern();this.next = next;this.hashCode = hash;this.bytes = null;
}
fastjson 中对所有的 json 的 key 使用了 intern 方法,缓存到了字符串常量池中,这样每次读取的时候就会非常快,大大减少时间和空间。而且 json 的 key 通常都是不变的。这个地方没有考虑到大量的 json key 如果是变化的,那就会给字符串常量池带来很大的负担。
这个问题 fastjson 在1.1.24版本中已经将这个漏洞修复了。程序加入了一个最大的缓存大小,超过这个大小后就不会再往字符串常量池中放了。
[1.1.24版本的com.alibaba.fastjson.parser.SymbolTable#addSymbol() Line:113]代码
public static final int MAX_SIZE = 1024;if (size >= MAX_SIZE) {return new String(buffer, offset, len);
}
这个问题是70w 数据量时候的引发的,如果是几百万的数据量的话可能就不只是30ms 的问题了。因此在使用系统级提供的String#intern方式一定要慎重!
五,总结
本文大体的描述了 String#intern和字符串常量池的日常使用,jdk 版本的变化和String#intern方法的区别,以及不恰当使用导致的危险等内容,让大家对系统级别的 String#intern有一个比较深入的认识。让我们在使用和接触它的时候能避免出现一些 bug,增强系统的健壮性。
一、new String都是在堆上创建字符串对象。当调用 intern() 方法时,编译器会将字符串添加到常量池中(stringTable维护),并返回指向该常量的引用。
二、通过字面量赋值创建字符串(如:String str=”twm”)时,会先在常量池中查找是否存在相同的字符串,若存在,则将栈中的引用直接指向该字符串;若不存在,则在常量池中生成一个字符串,再将栈中的引用指向该字符串。
三、常量字符串的“+”操作,编译阶段直接会合成为一个字符串。如string str=”JA”+”VA”,在编译阶段会直接合并成语句String str=”JAVA”,于是会去常量池中查找是否存在”JAVA”,从而进行创建或引用。
四、对于final字段,编译期直接进行了常量替换(而对于非final字段则是在运行期进行赋值处理的)。
final String str1=”ja”;
final String str2=”va”;
String str3=str1+str2;
在编译时,直接替换成了String str3=”ja”+”va”,根据第三条规则,再次替换成String str3=”JAVA”
五、常量字符串和变量拼接时(如:String str3=baseStr + “01”;)会调用stringBuilder.append()在堆上创建新的对象。
六、JDK 1.7后,intern方法还是会先去查询常量池中是否有已经存在,如果存在,则返回常量池中的引用,这一点与之前没有区别,区别在于,如果在常量池找不到对应的字符串,则不会再将字符串拷贝到常量池,而只是在常量池中生成一个对原字符串的引用。简单的说,就是往常量池放的东西变了:原来在常量池中找不到时,复制一个副本放到常量池,1.7后则是将在堆上的地址引用复制到常量池。
String.intern()详细分析,JDK1.8
一.创建字符串分析
1.直接使用双引号创建字符串
判断这个常量是否存在于常量池,
如果存在,
判断这个常量是存在的引用还是常量,
如果是引用,返回引用地址指向的堆空间对象,
如果是常量,则直接返回常量池常量,
如果不存在,
在常量池中创建该常量,并返回此常量
String a1 = "AA";//在常量池上创建常量AAString a2 = "AA";//直接返回已经存在的常量AASystem.out.println(a1 == a2); //true
String a3 = new String("AA"); //在堆上创建对象AAa3.intern(); //在常量池上创建对象AA的引用String a4 = "AA"; //常量池上存在引用AA,直接返回该引用指向的堆空间对象,即a3System.out.println(a3 == a4); //false,如果这个例子不理解,请看完整篇文章再回来看这里
2.new String创建字符串
首先在堆上创建对象(无论堆上是否存在相同字面量的对象),
然后判断常量池上是否存在字符串的字面量,
如果不存在
在常量池上创建常量
如果存在
不做任何操作
String a1 = new String("AA");String a2 = new String("AA");System.out.println(a1 == a2); //false
//如果常量池上不存在常量AA,也不存在引用AA,则创建常量AAString a1 = new String("AA");System.out.println(a1 == a1.intern()); //false
3.双引号相加
判断这两个常量、相加后的常量在常量池上是否存在
如果不存在
则在常量池上创建相应的常量
如果存在
判断这个常量是存在的引用还是常量,
如果是引用,返回引用地址指向的堆空间对象,
如果是常量,则直接返回常量池常量,
String a1 = "AA" + "BB";//在常量池上创建常量AA、BB和AABB,并返回AABB
//常量池上存在常量AABBString a2 = "AABB";String a3 = "AA" + "BB";System.out.println(a2 == a3); //true
//常量池上存在引用AABBString a4 = new String("AA") + new String("BB"); //在堆上创建对象AA、BB和AABB,在常量池上创建常量AA和BBa4.intern();String a5 = "AA" + "BB";System.out.println(a4 == a5); //true
4.两个new String相加
首先会创建这两个对象以及相加后的对象
然后判断常量池中是否存在这两个对象的字面量常量
如果存在
不做任何操作
如果不存在
则在常量池上创建对应常量
//常量AA不存在,所以第一步在常量池中创建了常量AAString a2 = new String("AA") + new String("BB");String a3 = new String("A")+new String("A"); //创建对象AASystem.out.println(a3 == a3.intern()); //false
//只在堆上创建AABB对象,没有在常量池中创建常量AABBString a2 = new String("AA") + new String("BB");System.out.println(a2 == a2.intern()); //true
5.双引号字符串与new String字符串
首先创建两个对象,一个是new String的对象,一个是相加后的对象
然后判断双引号常量与new String的字面量在常量池是否存在
如果存在
不做操作
如果不存在
则在常量池上创建对象的常量
String a1 = "AABB";String a2 = "AA" + new String("BB");System.out.println(a1 == a2.intern());//trueSystem.out.println(a2 == a2.intern()); //false
二.String.intern()分析
判断这个常量是否存在于常量池。
如果存在
判断存在内容是引用还是常量,
如果是引用,
返回引用地址指向堆空间对象,
如果是常量,
直接返回常量池常量
如果不存在,
将当前对象引用复制到常量池,并且返回的是当前对象的引用
String a1 = "AA";System.out.println(a1 == a1.intern()); //trueString a2 = new String("B") + new String("B");a2.intern();String a3 = new String("B") + new String("B");System.out.println(a2 == a3.intern());//trueSystem.out.println(a3 == a3.intern());//falseString a4 = new String("C") + new String("C");System.out.println(a4 == a4.intern()); //true
三.总结
1.只在常量池上创建常量
String a1 = "AA";
2.只在堆上创建对象
String a2 = new String("A") + new String("A");
3.在堆上创建对象,在常量池上创建常量
String a3 = new String("AA");
4.在堆上创建对象,在常量池上创建引用
String a4 = new String("A") + new String("A");//只在堆上创建对象AAa4.intern();//将该对象AA的引用保存到常量池上
5.在堆上创建对象,在常量池上创建常量,在常量池上创建引用(不可能)
String a5 = new String("A") + new String("A");//只在堆上创建对象a5.intern();//在常量池上创建引用String a6 = "AA";//此时不会再在常量池上创建常量AA,而是将a5的引用返回给a6System.out.println(a5 == a6); //true
四.练习
String aa = "AA";//设置常量AA到常量池String bb = "BB";//设置常量BB到常量池String ccdd = "CC"+"DD";//设置常量CCDD到常量池String neeff = new String("EE")+new String("FF");//设置EE和FF到常量池。并且添加EE、FF和EEFF对象到堆String aabb = aa+bb;//添加AABB对象到堆String gghh = "GG"+new String("HH");//设置GG和HH常量到常量池,设置HH和GGHH对象到堆
// aa.intern();//啥事都不做,返回AA常量
// ccdd.intern();//啥事都不做,返回CCDD常量
// neeff.intern();//添加EEFF对象的引用到常量池,并返回EEFF对象
// aabb.intern();//添加AABB对象的引用到常量池,并返回AABB对象
// gghh.intern();//添加GGHH对象的引用到常量池,并返回GGHH对象System.out.println(aa.intern()==aa); //trueSystem.out.println(neeff.intern()=="EEFF");//trueSystem.out.println("EEFF"==neeff);//trueString nccdd = new String("CCDD");System.out.println(ccdd==nccdd);//falseSystem.out.println(ccdd==nccdd.intern());//trueSystem.out.println(aabb.intern()==aabb);//trueSystem.out.println(gghh==gghh.intern());//true
常见试题解答
有了对以上的知识的了解,我们现在再来看常见的面试或笔试题就很简单了:
Q:下列程序的输出结果:
String s1 = “abc”;
String s2 = “abc”;
System.out.println(s1 == s2);
A:true,均指向常量池中对象。
Q:下列程序的输出结果:
String s1 = new String(“abc”);
String s2 = new String(“abc”);
System.out.println(s1 == s2);
A:false,两个引用指向堆中的不同对象。
Q:下列程序的输出结果:
String s1 = “abc”;
String s2 = “a”;
String s3 = “bc”;
String s4 = s2 + s3;
System.out.println(s1 == s4);
A:false,因为s2+s3实际上是使用StringBuilder.append来完成,会生成不同的对象。
Q:下列程序的输出结果:
String s1 = “abc”;
final String s2 = “a”;
final String s3 = “bc”;
String s4 = s2 + s3;
System.out.println(s1 == s4);
A:true,因为final变量在编译后会直接替换成对应的值,所以实际上等于s4=”a”+”bc”,而这种情况下,编译器会直接合并为s4=”abc”,所以最终s1==s4。
Q:下列程序的输出结果:
String s = new String(“abc”);
String s1 = “abc”;
String s2 = new String(“abc”);
System.out.println(s == s1.intern());
System.out.println(s == s2.intern());
System.out.println(s1 == s2.intern());
A:false,false,true。
参考:
https://www.hollischuang.com/archives/1551
https://blog.csdn.net/u013366617/article/details/83618361
https://blog.csdn.net/soonfly/article/details/70147205
深入理解String.intern相关推荐
- 01.几张图轻松理解String.intern()
当调用 intern() 方法时,编译器会将字符串添加到方法区的运行常量池中(stringTable维护),并返回指向该常量的引用. 在翻<深入理解Java虚拟机>的书时,又看到了2-7的 ...
- Java基础提升篇:理解String 及 String.intern() 在实际中的应用
点击上方"好好学java",选择"置顶公众号" 优秀学习资源.干货第一时间送达! 好好学java java知识分享/学习资源免费分享 关注 精彩内容 你所需要的 ...
- Java提升篇:理解String 及 String.intern() 在实际中的应用
String的深入解析 首先String不属于8种基本数据类型,String是一个对象. 因为对象的默认值是null,所以String的默认值也是null:但它又是一种特殊的对象,有其它对象没有的一些 ...
- Java--深入理解字符串的String#intern()方法奥妙之处
学习背景 进入正文学习字符串的intern()方法之前,先给下这4个问题,看下自己是否都知道答案? 1.String s1 = "a" + "b"; //创建了 ...
- java字符串内存长度固定_深入理解 Java String#intern() 内存模型
大家知道,Java中string.intern()方法调用会先去字符串常量池中查找相应的字符串,如果字符串不存在,就会在字符串常量池中创建该字符串然后再返回. 字符串常量池是一个固定大小的HashMa ...
- string.intern()方法理解
String.intern()方法介绍 字符串常量池 在jdk7中,字符串常量池和静态变量都存储在堆中. 直接使用双引号声明出来的String对象会直接存储在常量池中. 如果不是用双引号声明的Stri ...
- string会传null吗_JVM 解剖公园(10): String.intern()
(给ImportNew加星标,提高Java技能) 编译:ImportNew/唐尤华 shipilev.net/jvm/anatomy-quarks/10-string-intern/ 1. 写在前面 ...
- c++ 返回string_JVM系列之:String.intern和stringTable
简介 StringTable是什么?它和String.intern有什么关系呢?在字符串对象的创建过程中,StringTable有起到了什么作用呢? 一切的答案都在本文中,快来看看吧. intern简 ...
- String.intern()
String.intern()是一个Navive方法,清楚以下几点便能完全理解其作用: 定义:如果字符串常量池中已经包含一个等于次String对象的字符串,则返回代表池中这个字符串的String对象: ...
最新文章
- 分布式缓存系统Memcached简介与实践
- 一个爬虫的故事:爬虫兄弟要活不下去了!!!
- 香农定理和奈奎斯特定理区别_「中考复习」三大变换之旋转(旋转的构造-托勒密定理)...
- 5、线程终止方式:pthread_cleanup_push/pthread_cleanup_pop()
- AAC音频基础知识及码流解析
- Halcon :畸变矫正与标定(2)
- 【开源项目】向Nginx-RTMP服务器推流
- 2018-2019-2 网络对抗技术 20165318 Exp 9 Web安全基础
- CentOS 6.9/7通过yum安装指定版本的Redis
- YUV格式学习:YUV422P、YV16、NV16、NV61格式转换成RGB24
- Ubuntu 10.10用LibreOffice替换OpenOffice
- Cloudflare通过UnstoppableDomains添加对“.crypto”域名的支持
- nodejs 模板引擎ejs的使用
- 信息系统项目管理师学习笔记
- p6spy 非常典型 启用SQL日志
- Python摇骰子-A05
- python学习笔记(字符串操作、字典操作、三级菜单实例)
- 北京物资学院计算机考研资料汇总
- 2021-2025年中国LED离线驱动器行业市场供需与战略研究报告
- 玩游戏计算机虚拟内存怎么设置,玩游戏时提示虚拟内存太小怎么办