你真的了解java.lang.String吗?
一直觉得自己的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");
Java字符串真的是不可变的吗?
今天在stackoverflow上看到这样的一个问题,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版本中substring产生的新字符串和原字符串共用的一个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数组的值是可以修改字符串的。
对问题的回答:String对象是不可变的,但这仅意味着你无法通过调用它的公有方法来改变它的值。这个问题也揭示了为什么反射技术在某些场景下非常实用,但在大多数情况下,你应该避免使用它。
那么在java 7u6版本为什么要做这样的修改呢?
虽然共用一个char数组可以节约内存空间,但这样会带来内存泄漏的问题。假如你有一个很长的母字符串,使用substring想从其中得到一个有用的子字符串,然后你只使用这个子字符串。这是你的母字符串就会成为垃圾被JVM回收掉,但是由于母字符串的char数组一直被字符串使用无法回收,整个母字符串的内容一致存在内存中,导致内存泄漏。
关于字符串常量池
在这之前,我一直在想Java的字符串常量池是存在什么地方,Heap?PermGen?并且写了一些测试代码证实了字符串常量池是存储在PermGen。但看到大牛的文章后,发现我的认识还是那么的狭小,下面是大牛对字符串常量池和String.intern()的总结:
- 不要在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()方法加入常量池。
参考文献:
http://java-performance.info/changes-to-string-java-1-7-0_06/
http://stackoverflow.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/
- 大小: 11.9 KB
分享到:
相关推荐
java.lang.NumberFormatException For inputstring 4294967295处理方法
解决 java.lang.NoSuchFieldError: STRING at org.jbpm.identity.hibernate.PermissionUserType. 不用jbpm的jbpm-identity.jar 用这个就好
NULL 博文链接:https://never-forget.iteye.com/blog/1833801
NULL 博文链接:https://xiaoyaoeric.iteye.com/blog/1494989
Type 异常报告 消息 Failed to convert ... nested exception is java.lang.IllegalStateException: Cannot convert value of type 'java.lang.String' to required type 'java.util.Date': no matching editors or co
commons-lang3.3.1.jar、Apache Commons包中的一个,包含了一些数据类型工具类,是java.lang.*的扩展。必须使用的jar包。为JRE5.0+的更好的版本所提供 Jar文件包含的类: META-INF/MANIFEST.MFMETA-INF/LICENSE....
commons-lang3.3.1.jar、Apache Commons包中的一个,包含了一些数据类型工具类,是java.lang.*的扩展。必须使用的jar包。为JRE5.0+的更好的版本所提供 Jar文件包含的类: META-INF/MANIFEST.MFMETA-INF/LICENSE....
了解 java.lang 包 掌握包装类 掌握String 和 StringBuffer 类 运用以下类的方法: Math Class Object
提供利用 Java 编程语言进行程序设计的基础类。最重要的类是 Object(它是类层次结构的根)和 Class(它的实例表示正在运行的应用程序中的类)。
boolean delete(java.lang.String sql, java.util.Map<java.lang.Integer,java.lang.Object> elements) 根据传入的参数删除单条记录的方法 boolean delete(java.lang.String sql, java.lang.Object[] elements)...
boolean delete(java.lang.String sql, java.util.Map<java.lang.Integer,java.lang.Object> elements) 根据传入的参数删除单条记录的方法 boolean delete(java.lang.String sql, java.lang.Object[] elements)...
dubbo-monitor监控中心,基于alibaba dubbo.jar开发,解决Decode rpc invocation failed: expected map/object at java.lang.String (Ljava/lang/String)、com.alibaba.dubbo.common.URL' could not be instantiated...
【JAVA】笔记(8)--- java.lang.String 精讲(csdn)————程序
java获取客户端ip(经过多次代理)提示StringUtils cannot be resolved 需要先 import org.apache.commons.lang3.StringUtils; /* 内含 common-lang3.jar commons-lang3-3.9-bin.zip commons-lang3-3.9-src.zip ...
java.lang.ExceptionInInitializerError Caused by: java.lang.IllegalArgumentException: input == null! at javax.imageio.ImageIO.read(ImageIO.java:1388) at com.pleanwar.fiying.FlyingObject.loadImage...
1. java.lang.IllegalStateException: No wrapped connection. 2.java.lang.IllegalStateException: Adapter is detached. 原因: 1.单线程一次执行一个请求可以正常执行,如果使用多线程,同时执行多个请求时就会...
java.lang.String getErrorMessage() 返回String类型的错误信息 java.lang.String getFullInfo() 返回String类型的详细阴历信息(例如:癸巳年七月廿,生肖:蛇) java.lang.String getLunarAnimal() 返回...
解决mongo数据插入时 报错问题 mogodb插入数据时报错Can't find a codec for class java.math.BigDecimal
学习目标 了解 java.lang 包 掌握包装类 掌握String 和 StringBuffer 类 运用以下类的方法: Math Class Object