专栏首页程序那些事一文弄懂String的所有小秘密

一文弄懂String的所有小秘密

简介

String是java中非常常用的一个对象类型。可以说java中使用最多的就是String了。

那么String到底有哪些秘密呢?接下来本文将会一一讲解。

String是不可变的

String是不可变的,官方的说法叫做immutable或者constant。

在JDK9之前,String的底层其实是一个Char的数组。

private final char value[];

在JDK9之后,String的底层变成了Byte的数组。

private final byte[] value;

所有的String字面量比如”abc”都是String的实现。

考虑下面的赋值操作:

String a="abc";String b="abc";

对于java虚拟机来说,”abc”是字符串字面量,在JDK 7之后,这个字符串字面量是存储在java heap中的。而在JDK 7之前是有个专门的方法区来存储的。

有了“abc”,然后我们将“abc” 赋值给a和b。

可以看到这里a和b只是java heap中字符串的引用。

再看看下面的代码发生了什么:

String c= new String("abc");

首先在java heap中创建了“abc”,然后调用String的构造函数:

   public String(String original) {
        this.value = original.value;
        this.hash = original.hash;
    }

在构造函数中,String将底层的字符串数组赋值给value。

因为Array的赋值只是引用的赋值,所以上述new操作并不会产生新的字符串字面值。

但是new操作新创建了一个String对象,并将其赋值给了c。

String的不可变性还在于,String的所有操作都会产生新的字符串字面量。原来的字符串是永远不会变化的。

字符串不变的好处就在于,它是线程安全的。任何线程都可以很安全的读取字符串。

传值还是传引用

一直以来,java开发者都有这样的问题,java到底是传值还是传引用呢?

我想,这个问题可以从两方面来考虑。

首先对于基础类型int,long,double来说,对他们的赋值是值的拷贝。而对于对象来说,赋值操作是引用。

另一方面,在方法调用的参数中,全部都是传值操作。

public static void main(String[] args) {
    String x = new String("ab");
    change(x);
    System.out.println(x);}public static void change(String x) {
    x = "cd";}

我们看上面的例子,上面的例子输出ab。因为x是对“ab”的引用,但是在change方法中,因为是传值调用,所以会创建一个新的x,其值是“ab”的引用地址。当x被重新赋值之后,改变的只是拷贝之后的x值。而本身的x值是不变的。

substring() 导致的内存泄露

第一次看到这个话题,大家可能会很惊讶,substring方法居然会导致内存泄露?这个话题要从JDK 6开始讲起。

我们先看下JDK 6的实现:

String(int offset, int count, char value[]) {
    this.value = value;
    this.offset = offset;
    this.count = count;}public String substring(int beginIndex, int endIndex) {
    //check boundary
    return  new String(offset + beginIndex, endIndex - beginIndex, value);}

可以看到,JDK 6的substring方法底层还是引用的原始的字符串数组。唯一的区别就是offset和count不同。

我们考虑一下下面的应用:

String string = "abcdef";String subString = string.substring(1, 3);string = null;

虽然最后我们将String赋值为null,但是subString仍然引用了最初的string。将不会被垃圾回收。

在JDK 7之后,String的实现发送了变化:

public String(char value[], int offset, int count) {
    //check boundary
    this.value = Arrays.copyOfRange(value, offset, offset + count);}public String substring(int beginIndex, int endIndex) {
    //check boundary
    int subLen = endIndex - beginIndex;
    return new String(value, beginIndex, subLen);}

Arrays.copyOfRange将会拷贝一份新的数组,而不是使用之前的数组。从而不会发生上面的内存泄露的问题。

总结

虽然String是我们经常使用的对象,但是里面的原理还是值得我们了解的。

本文作者:flydean程序那些事

本文链接:http://www.flydean.com/string-all-in-one/

本文分享自微信公众号 - 程序那些事(flydean-tech),作者:flydean

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-05-09

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • JVM系列之:String.intern和stringTable

    StringTable是什么?它和String.intern有什么关系呢?在字符串对象的创建过程中,StringTable有起到了什么作用呢?

    程序那些事
  • Lambda表达式最佳实践

    Lambda表达式java 8引入的函数式编程框架。之前的文章中我们也讲过Lambda表达式的基本用法。

    程序那些事
  • 类型擦除type erasure

    泛型是java从JDK 5开始引入的新特性,泛型的引入可以让我们在代码编译的时候就强制检查传入的类型,从而提升了程序的健壮度。

    程序那些事
  • Top 10 Methods for Java Arrays1 声明一个array2 打印一个array3 从array创建一个list4 检查array中是否存在某个元素5 连接两个array6 D

    desperate633
  • QQ小程序支付

    首先是配置类,设置为包内访问权限,其实应该放于properties文件,或者直接配置在xml中,偷了个懒直接写在了代码中

    WindrunnerMax
  • 当我遵循了这 16 条规范写代码,同事只对我说了三个字: 666

    Many of the happiest people are those who own the least. But are we really so ha...

    良月柒
  • 这样规范写代码,同事直呼“666”

    java思维导图
  • 这样规范写代码,同事直呼“666”

    乔戈里
  • 这样规范写代码,同事直呼“666”

    用户1516716
  • 大数据算法设计模式(2) - 左外链接(leftOuterJoin) spark实现

    左外链接(leftOuterJoin) spark实现 package com.kangaroo.studio.algorithms.join; impor...

    用户1225216

扫码关注云+社区

领取腾讯云代金券