关于String和intern()详解

1.String是Java中最常用的类,是不可变的(Immutable), 那么String是如何实现Immutable呢,String为什么要设计成不可变呢?

前期准备

首先,要有字符串常量池的概念。然后知道String是怎么和常量池打交道的。这里的武器就是intern(),看一下javadoc:

/**
 * 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&trade; 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();

即常量池存在,返回常量池中的那个对象,常量池不存在,则放入常量池,并返回本身。由此推断两个公式:

str.intern() == str //证明返回this本身,证明常量池不存在。
str.intern() != str //证明返回常量池中已存在的对象,不等于新建的对象。

2.代码测试

 通过=号创建对象,运行时只有一个对象存在。

/**
 * @author Ryan Miao  * 等号赋值,注意字面量的存在  */ @Test public void testNewStr() throws InterruptedException { //str.intern(): 若常量池存在,返回常量池中的对象;若常量池不存在,放入常量池,并返回this。 //=号赋值,若常量池存在,直接返回常量池中的对象0xs1,如果常量池不存在,则放入常量池,常量池中的对象也是0xs1 String s1 = "RyanMiao";//0xs1 Assert.assertTrue(s1.intern() == s1);//0xs1 == 0xs1 > true Thread.sleep(1000*60*60); }


通过new创建对象时,参数RyanMiao作为字面量会生成一个对象,并存入字符创常量池。而后,new的时候又将创建另一个String对象,所以,最好不要采用这种方式使用String, 不然就是双倍消耗内存。

/**
 * @author Ryan Miao  *  * 暴露的字面量(literal)也会生成对象,放入Metaspace  */ @Test public void testNew(){ //new赋值,直接堆中创建0xs2, 常量池中All literal strings and string-valued constant expressions are interned, // "RyanMiao"本身就是一个字符串,并放入常量池,故intern()返回0xab String s2 = new String("RyanMiao"); Assert.assertFalse(s2.intern() == s2);//0xRyanMiao == 0xs2 > false }

当字符创常量池不存在此对象的的时候,返回本身。

/**
 * @author Ryan Miao  * 上栗中,由于字面量(literal)会生成对象,并放入常量池,因此可以直接从常量池中取出(前提是此行代码运行之前没有其他代码运行,常量池是干净的)  *  * 本次,测试非暴露字面量的str  */ @Test public void testConcat(){ //没有任何字面量为"RyanMiao"暴露给编译器,所以常量池没有创建"RyanMiao",所以,intern返回this String s3 = new StringBuilder("Ryan").append("Miao").toString(); Assert.assertTrue(s3.intern() == s3); //true }

针对常量池中已存在的字符串

/**
 * @author Ryan Miao  * 上栗中,只要不暴露我们最终的字符串,常量池基本不会存在,则每次新建(new)的时候,都会放入常量池,intern并返回本身。即常量池的对象即新建的对象本身。  *  * 本次,测试某些常量池已存在的字符串  */ @Test public void testExist(){ //为毛常量池存在java这个单词 //s4 == 0xs4, intern发现常量池存在,返回0xexistjava String s4 = new StringBuilder("ja").append("va").toString(); Assert.assertFalse(s4.intern() == s4); //0xexistjava == 0xs4 > false //int也一开始就存在于常量池中了, intern返回0xexistint String s5 = new StringBuilder().append("in").append("t").toString(); Assert.assertFalse(s5.intern()==s5); // 0xexistint == 0xs5 > false //由于字面量"abc"加载时,已放入常量池,故s6 intern返回0xexistabc, 而s6是新建的0xs6 String a = "abc"; String s6 = new StringBuilder().append("ab").append("c").toString(); Assert.assertFalse(s6.intern() == s6); //0xexistabc == 0xs6 > false }

3. String是如何实现Immutable的?

Immutable是指String的对象实例生成后就不可以改变。相反,加入一个user类,你可以修改name,那么就不叫做Immutable。所以,String的内部属性必须是不可修改的。

1.1 私有成员变量

String的内部很简单,有两个私有成员变量:

/** The value is used for character storage. */
private final char value[]; /** Cache the hash code for the string */ private int hash; // Default to 0

而后并没有对外提供可以修改这两个属性的方法,没有set,没有build。

1.2 Public的方法都是复制一份数据

String有很多public方法,要想维护这么多方法下的不可变需要付出代价。每次都将创建新的String对象。比如,这里讲一个很有迷惑性的concat方法:

public String concat(String str) { int otherLen = str.length(); if (otherLen == 0) { return this; } int len = value.length; char buf[] = Arrays.copyOf(value, len + otherLen); str.getChars(buf, len); return new String(buf, true); }

从方法名上看,是拼接字符串。这样下意识以为是原对象修改了内容,所以对于str2 = str.concat("abc"),会认为是str2==str。然后熟记String不可变定律的你肯定会反对。确实不是原对象,确实new了新String。同样的道理,在其他String的public方法里,都将new一个新的String。因此就保证了原对象的不可变。说到这里,下面的结果是什么?

String str2 = str.concat("");
Assert.assertFalse(str2 == str);

按照String不可变的特性来理解,这里str2应该是生成的新对象,那么肯定不等于str.所以是对的,是false。面试考这种题目也是醉了,为了考验大家对String API的熟悉程度吗?看源码才知道,当拼接的内容为空的时候直接返回原对象。因此,str2==str是true。

由于String被声明式final的,则我们不可以继承String,因此就不能通过继承来复写一些关于hashcode和value的方法。

 

4. String为什么要设计成Immutable?

一下内容来自http://www.kogonuso.com/2015/03/why-string-is-immutable-or-final-class.html#sthash.VgLU1mDY.dpuf. 发现百度的中文版本基本也是此文的翻译版。

缓存的需要

String是不可变的。因为String会被String pool缓存。因为缓存String字面量要在多个线程之间共享,一个客户端的行为会影响其他所有的客户端,所以会产生风险。如果其中一个客户端修改了内容"Test"为“TEST”, 其他客户端也会得到这个结果,但显然并想要这个结果。因为缓存字符串对性能来说至关重要,因此为了移除这种风险,String被设计成Immutable。

HashMap的需要

HashMap在Java里太重要了,而它的key通常是String类型的。如果String是mutable,那么修改属性后,其hashcode也将改变。这样导致在HashMap中找不到原来的value。

多线程中需要

string的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); }

如果String是可变的,即修改String的内容后,地址不变。那么当多个线程同时修改的时候,value的length是不确定的,造成不安全因素,无法得到正确的截取结果。而为了保证顺序正确,需要加synchronzied,但这会得到难以想象的性能问题。

保证hashcode

这和上条中HashMap的需要一样,不可变的好处就是hashcode不会变,可以缓存而不用计算。

classloader中需要

The absolutely most important reason that String is immutable is that it is used by the class loading mechanism, and thus have profound and fundamental security aspects. Had String been mutable, a request to load "java.io.Writer" could have been changed to load "mil.vogoon.DiskErasingWriter"

String会在加载class的时候需要,如果String可变,那么可能会修改加载中的类。

总之,安全性和String字符串常量池缓存是String被设计成不可变的主要原因。


转载-----http://www.cnblogs.com/woshimrf/p/why-string-is-immutable.html
 

转载于:https://www.cnblogs.com/technologykai/articles/9056324.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值