你真的了解java.lang.String吗?
程序员文章站
2024-03-23 15:12:04
...
[b]你真的了解java.lang.String吗?[/b]
一直觉得自己的Java基础技术还算可以,自从看了一些大牛的博客后,发现自己对Java的理解还只是皮毛。之前看过JVM的一些知识以及java.lang.String的源码,以为自己对它很了解,但真的是这样吗?在这之前我对字符串的认识一直停留在很基础的认识上:
1、String对象是不可变的,一旦创建内容将不可能被修改。
2、JVM中存在一个字符串常量池,String.intern()方法可以把运行时产生的字符串放入常量池中。
3、对于以下代码有:s1和s2指向同一个对象,s3和s1指向不同的对象但值相等。
[b]Java字符串真的是不可变的吗?[/b]
今天在*上看到这样的一个问题,Java字符串真的是不可变的吗?并贴出了一段修改字符串的代码。
看到代码后,我恍然大悟。虽然看过String的源码,知道String的内部是一个final的char数组,也知道final数组的值可以修改,可以通过反射机制去修改实例的一些变量值,但从来没有想到过字符串的值可以通过这种方式修改。
接着看了下面的一些回答,了解到最后一行代码System.out.println(s3); // World在java 7u6版本之前会输出Java!对比了一下java 6u45和java 7u45的String的源码,发现在java 6u45版本中substrin*生的新字符串和原字符串共用的一个char数组,只是构造字符串时修改了offset和count的值。而在java 7u45的版本的源码中并不存在offset和count这两个常量,在substring的时候是从原char数组中拷贝出来了this.value = Arrays.copyOfRange(value, offset, offset+count)。这样的话在java 7u6之前,调用字符串的substring的时候,产生的字符串和原字符串实际是共用了同一个char数组,可以节约内存空间。
下图是java 7u6之前版本中s1、s2、s3字符串在JVM的存储结构。所以通过反射直接修改char数组的值是可以修改字符串的。
[img]http://dl2.iteye.com/upload/attachment/0094/0738/9af1105a-5cdc-30df-ae18-9afef3acc199.png[/img]
对问题的回答:String对象是不可变的,但这仅意味着你无法通过调用它的公有方法来改变它的值。这个问题也揭示了为什么反射技术在某些场景下非常实用,但在大多数情况下,你应该避免使用它。
[b]那么在java 7u6版本为什么要做这样的修改呢?[/b]
虽然共用一个char数组可以节约内存空间,但这样会带来内存泄漏的问题。假如你有一个很长的母字符串,使用substring想从其中得到一个有用的子字符串,然后你只使用这个子字符串。这是你的母字符串就会成为垃圾被JVM回收掉,但是由于母字符串的char数组一直被字符串使用无法回收,整个母字符串的内容一致存在内存中,导致内存泄漏。
[b]关于字符串常量池[/b]
在这之前,我一直在想Java的字符串常量池是存在什么地方,Heap?PermGen?并且写了一些测试代码证实了字符串常量池是存储在PermGen。但看到大牛的文章后,发现我的认识还是那么的狭小,下面是大牛对字符串常量池和String.intern()的总结:
[list]
[*]不要在Java 6及以前的版本使用String.intern()将字符串放入常量池,因为这是的字符串常量池是存储在固定大小的内存区(PermGen)
[*]Java 7和8中将字符串常量池存储在堆内存中。
[*]Java 7和8中可以通过JVM参数-XX:StringTableSize来控制字符串常量池的大小。
[*]-XX:StringTableSize的默认值在Java 7中是1009,在Java 8中大概是25~50K
[*]在多线程情况下可以随意使用Stirng.intern()方法。8个写线程只比1个写线程的负载多了17%,1个写7个读线程只比一个线程的负载多了9%
[*]字符串常量池不是线程隔离的
[*]虽然Java 7+对String.intern()做了很多优化,但是它还是花费CPU资源。在简单的例子中没有调用String.intern()方法的程序比调用了的程序要快3.5倍。所以不需要对所有的字符串调用String.intern()方法,只需要对经常被使用的字符串(例如:省、市等)调用String.intern()方法加入常量池。
[/list]
[b]参考文献:[/b]
http://java-performance.info/changes-to-string-java-1-7-0_06/
http://*.com/questions/20945049/is-a-java-string-really-immutable
http://java-performance.info/string-intern-in-java-6-7-8/
http://java-performance.info/string-intern-java-6-7-8-multithreaded-access/
http://java-performance.info/string-intern-java-7-8-part-3/
一直觉得自己的Java基础技术还算可以,自从看了一些大牛的博客后,发现自己对Java的理解还只是皮毛。之前看过JVM的一些知识以及java.lang.String的源码,以为自己对它很了解,但真的是这样吗?在这之前我对字符串的认识一直停留在很基础的认识上:
1、String对象是不可变的,一旦创建内容将不可能被修改。
2、JVM中存在一个字符串常量池,String.intern()方法可以把运行时产生的字符串放入常量池中。
3、对于以下代码有:s1和s2指向同一个对象,s3和s1指向不同的对象但值相等。
String s1 = "Hello World";
String s2 = "Hello World";
String s3 = new String("Hello World");
[b]Java字符串真的是不可变的吗?[/b]
今天在*上看到这样的一个问题,Java字符串真的是不可变的吗?并贴出了一段修改字符串的代码。
String s1 = "Hello World";
String s2 = "Hello World";
String s3 = s1.substring(6);
System.out.println(s1); // Hello World
System.out.println(s2); // Hello World
System.out.println(s3); // World
Field field = String.class.getDeclaredField("value");
field.setAccessible(true);
char[] value = (char[])field.get(s1);
value[6] = 'J';
value[7] = 'a';
value[8] = 'v';
value[9] = 'a';
value[10] = '!';
System.out.println(s1); // Hello Java!
System.out.println(s2); // Hello Java!
System.out.println(s3); // World
看到代码后,我恍然大悟。虽然看过String的源码,知道String的内部是一个final的char数组,也知道final数组的值可以修改,可以通过反射机制去修改实例的一些变量值,但从来没有想到过字符串的值可以通过这种方式修改。
接着看了下面的一些回答,了解到最后一行代码System.out.println(s3); // World在java 7u6版本之前会输出Java!对比了一下java 6u45和java 7u45的String的源码,发现在java 6u45版本中substrin*生的新字符串和原字符串共用的一个char数组,只是构造字符串时修改了offset和count的值。而在java 7u45的版本的源码中并不存在offset和count这两个常量,在substring的时候是从原char数组中拷贝出来了this.value = Arrays.copyOfRange(value, offset, offset+count)。这样的话在java 7u6之前,调用字符串的substring的时候,产生的字符串和原字符串实际是共用了同一个char数组,可以节约内存空间。
下图是java 7u6之前版本中s1、s2、s3字符串在JVM的存储结构。所以通过反射直接修改char数组的值是可以修改字符串的。
[img]http://dl2.iteye.com/upload/attachment/0094/0738/9af1105a-5cdc-30df-ae18-9afef3acc199.png[/img]
对问题的回答:String对象是不可变的,但这仅意味着你无法通过调用它的公有方法来改变它的值。这个问题也揭示了为什么反射技术在某些场景下非常实用,但在大多数情况下,你应该避免使用它。
[b]那么在java 7u6版本为什么要做这样的修改呢?[/b]
虽然共用一个char数组可以节约内存空间,但这样会带来内存泄漏的问题。假如你有一个很长的母字符串,使用substring想从其中得到一个有用的子字符串,然后你只使用这个子字符串。这是你的母字符串就会成为垃圾被JVM回收掉,但是由于母字符串的char数组一直被字符串使用无法回收,整个母字符串的内容一致存在内存中,导致内存泄漏。
[b]关于字符串常量池[/b]
在这之前,我一直在想Java的字符串常量池是存在什么地方,Heap?PermGen?并且写了一些测试代码证实了字符串常量池是存储在PermGen。但看到大牛的文章后,发现我的认识还是那么的狭小,下面是大牛对字符串常量池和String.intern()的总结:
[list]
[*]不要在Java 6及以前的版本使用String.intern()将字符串放入常量池,因为这是的字符串常量池是存储在固定大小的内存区(PermGen)
[*]Java 7和8中将字符串常量池存储在堆内存中。
[*]Java 7和8中可以通过JVM参数-XX:StringTableSize来控制字符串常量池的大小。
[*]-XX:StringTableSize的默认值在Java 7中是1009,在Java 8中大概是25~50K
[*]在多线程情况下可以随意使用Stirng.intern()方法。8个写线程只比1个写线程的负载多了17%,1个写7个读线程只比一个线程的负载多了9%
[*]字符串常量池不是线程隔离的
[*]虽然Java 7+对String.intern()做了很多优化,但是它还是花费CPU资源。在简单的例子中没有调用String.intern()方法的程序比调用了的程序要快3.5倍。所以不需要对所有的字符串调用String.intern()方法,只需要对经常被使用的字符串(例如:省、市等)调用String.intern()方法加入常量池。
[/list]
[b]参考文献:[/b]
http://java-performance.info/changes-to-string-java-1-7-0_06/
http://*.com/questions/20945049/is-a-java-string-really-immutable
http://java-performance.info/string-intern-in-java-6-7-8/
http://java-performance.info/string-intern-java-6-7-8-multithreaded-access/
http://java-performance.info/string-intern-java-7-8-part-3/
上一篇: antD+Vue后台管理项目总结
下一篇: 关于调用第三方接口.......