这篇文章主要介绍“分析JDK中String的存储区与不可变性”,在日常操作中,相信很多人在分析JDK中String的存储区与不可变性问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”分析JDK中String的存储区与不可变性”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!
站在用户的角度思考问题,与客户深入沟通,找到固阳网站设计与固阳网站推广的解决方案,凭借多年的经验,让设计与互联网技术结合,创造个性化、用户体验好的作品,建站类型包括:成都做网站、网站制作、企业官网、英文网站、手机端网站、网站推广、申请域名、网络空间、企业邮箱。业务覆盖固阳地区。
1. 数据存储区
String是一个比较特殊的类,除了new之外,还可以用字面常量来定义。为了弄清楚这二者间的区别,首先我们得明白JVM运行时数据存储区,这里有一张图对此有清晰的描述:
非共享数据存储区
非共享数据存储区是在线程启动时被创建的,包括:
程序计数器(program counter register)控制线程的执行;
栈(JVM Stack, Native Method Stack)存储方法调用与对象的引用等。
共享数据存储区
该存储区被所有线程所共享,可分为:
堆(Heap)存储所有的Java对象,当执行new对象时,会在堆里自动进行内存分配。
方法区(Method Area)存储常量池(run-time constant pool)、字段与方法的数据、方法与构造器的代码。
2. 两种实例化
实例化String对象:
public class StringLiterals { public static void main(String[] args) { String one = "Test"; String two = "Test"; String three = "T" + "e" + "s" + "t"; String four = new String("Test"); } }
javap -c StringLiterals反编译生成字节码,我们选取感兴趣的部分如下:
public static void main(java.lang.String[]); Code: 0: ldc #2 // String Test 2: astore_1 3: ldc #2 // String Test 5: astore_2 6: ldc #2 // String Test 8: astore_3 9: new #3 // class java/lang/String 12: dup 13: ldc #2 // String Test 15: invokespecial #4 // Method java/lang/String."": (Ljava/lang/String;)V 18: astore 4 20: return }
ldc #2表示从常量池中取#2的常量入栈,astore_1表示将引用存在本地变量1中。因此,我们可以看出:对象one、two、three均指向常量池中的字面常量"Test";对象four是在堆中new的新对象;如下图所示:
总结如下:
当用字面常量实例化时,String对象存储在常量池;
当用new实例化时,String对象存储在堆中;
操作符==比较的是对象的引用,当其指向的对象不同时,则为false。因此,开篇中的代码会出现通过new所创建String对象不一样。
3. 不可变String
String源码
JDK7的String类:
public final class String implements java.io.Serializable, Comparable, CharSequence { /** The value is used for character storage. */ private final char value[]; /** Cache the hash code for the string */ private int hash; // Default to 0 }
String类被声明为final,不可以被继承,所有的方法隐式地指定为final,因为无法被覆盖。字段char value[]表示String类所对应的字符串,被声明为private final;即初始化后不能被修改。常用的new实例化对象String s1 = new String("abcd");的构造器:
public String(String original) { this.value = original.value; this.hash = original.hash; }
只需将value与hash的字段值进行传递即可。
不可变性
所谓不可变性(immutability)指类不可以通过常用的API被修改。为了更好地理解不可变性,我们先来看《Thinking in Java》中的一段代码:
//: operators/Assignment.java // Assignment with objects is a bit tricky. import static net.mindview.util.Print.*; class Tank { int level; } public class Assignment { public static void main(String[] args) { Tank t1 = new Tank(); Tank t2 = new Tank(); t1.level = 9; t2.level = 47; print("1: t1.level: " + t1.level + ", t2.level: " + t2.level); t1 = t2; print("2: t1.level: " + t1.level + ", t2.level: " + t2.level); t1.level = 27; print("3: t1.level: " + t1.level + ", t2.level: " + t2.level); } } /* Output: 1: t1.level: 9, t2.level: 47 2: t1.level: 47, t2.level: 47 3: t1.level: 27, t2.level: 27 *///:~
上述代码中,在赋值操作t1 = t2;之后,t1、t2包含的是相同的引用,指向同一个对象。因此对t1对象的修改,直接影响了t2对象的字段改变。显然,Tank类是可变的。
也许,有人会说s = s.concat("ef");不是修改了对象s么?而事实上,我们去看concat的实现,会发现其返回的是新String对象(return new String(buf, true););改变的只是s1引用所指向的对象,如下图所示:
4. 反射
String的value字段是final的,可不可以通过过某种方式修改呢?答案是反射。在stackoverflow上有这样一段修改value字段的代码:
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
在上述代码中,为什么对象s2的值也会被修改,而对象s3的值却不会呢?根据前面的介绍,s1与s2指向同一个对象;所以当s1被修改后,s2也会对应地被修改。至于s3对象为什么不会?我们来看看substring()的实现:
public String substring(int beginIndex) { if (beginIndex < 0) { throw new StringIndexOutOfBoundsException(beginIndex); } int subLen = value.length - beginIndex; if (subLen < 0) { throw new StringIndexOutOfBoundsException(subLen); } return (beginIndex == 0) ? this : new String(value, beginIndex, subLen); }
当beginIndex不为0时,返回的是new的String对象;当beginIndex为0时,返回的是原对象本身。如果将String s3 = s1.substring(6);改为String s3 = s1.substring(0);,那么对象s3也会被修改了。
如果仔细看java.lang.String.java,我们会发现:当需要改变字符串内容时,String类的方法返回的是新String对象;如果没有改变,String类的方法则返回原对象引用。这节省了存储空间与额外的开销。
到此,关于“分析JDK中String的存储区与不可变性”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注创新互联网站,小编会继续努力为大家带来更多实用的文章!