JDK 8源码解析——String中的intern()方法

String是最常用的类之一,也许就是太常见了,因此导致经常忽略它存在,即使在观察堆得时候发现String类以及char[]所占的内存比较多的时候,也潜意识的跳过了它们。其实,String类以及char[]也还是有优化的空间的。

String的源码提供了非常丰富的方法,最多的就是对char[]数组的处理。对数组的处理,几乎就是复制数组和转码的各种组合。本来觉得看下去以及没啥惊喜了,指到发现以下这个方法:

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

这一个看似普通的本地方法, 返回的是该Sting对象所指向的常量在常量池的地址。这里就不得不分析以下复杂的String对象关系了。也是导致为啥我们经常约定判断String对象是否相等时,不是用的“==”而是用equals()方法呢?是不是所有的时候2个String对象一定是不相等的呢?要说明这个问题,理解intern()方法就非常重要了。

intern()被调用的时候,首先是查看常量池中是否存在等于该String对象value的字符串,如果存在,则返回该字符串的地址,如果不存在就将该String对象的作为新的常量,那么新常量的地址和String对象的地址就是同一个了。这里有个前提就是在JDK1.7之后,常量池并入Java堆中。因此,上述的场景是处于JDK1.7之后的环境,我的验证环境为JDK1.8。

public class App {
    public static void main(String[] args) {
        String s1 = new String("aaa");
        String s2 = s1.intern();

        System.out.println(s1 == s2);
    }
}

 根据上面对于intern()描述,第一印象结果应该是true,但是实际结果却是false。这个就很奇怪了,此时参考美团技术团队的文章终于找到了原因(https://tech.meituan.com/2014/03/06/in-depth-understanding-string-intern.html)。其实,

String s1 = new String("aaa");

 中引入"aaa"作为参数的时候,因此在后面创建s1对象时,在常量池中时有了"aaa"常量,此时调用intern()返回的地址和s1不一致的。为了验证这个观点,写了以下代码:

public class App {
    public static void main(String[] args) {
        char[] chars = new char[]{'a', 'a', 'a'};
        String s1 = new String(chars);
        String s2 = s1.intern();

        System.out.println(s1 == s2);
    }
}

此时返回的结果就是true。

那么这个函数有什么用呢?其实就可以考虑用来优化Java堆。创建一个100万大小的数组,随机插入数字String,观察堆的情况和运行时间,记录3次运行数据。

public class App {
    private static long MAX = 100 * 10000;

    public static void main(String[] args) {
        long startTime = System.currentTimeMillis();
        List<String> strArr = new ArrayList<>();
        Random random = new Random();
        for (long i = 0; i < MAX; i++) {
            strArr.add(String.valueOf(random.nextInt() % (100 * 10)));
//            strArr.add(String.valueOf(random.nextInt() % (100 * 10)).intern());
        }
        long endTime = System.currentTimeMillis();
        System.out.println("Loop is end, Spending time is " + (endTime - startTime) / 1000.0 + "s");
    }
}
Java堆中String对象数量和大小以及运行时间【未使用intern()】
序号数量大小运行时间
11,005,25824,126kb0.151s
21,005,24624,126kb0.164s
31,005,24524,125kb0.197s
平均值1,005,25024,126kb0.171s
Java堆中String对象数量和大小以及运行时间【使用intern()】
序号数量大小运行时间
145,0771,081kb0.215s
245,1951,084kb0.209s
345,0771,081kb0.204s
平均值45,1161082kb0.209s

从上面的数据对比可以得知,合理使用intern()确实有降低堆大小的作用,但是也会带来额外的系统开销。因此intern()需要根据场景进行使用,不可滥用。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值