干了两晚,带你探索那讲不清的字符串

1 篇文章 0 订阅
1 篇文章 0 订阅

常量池(后期专门找一篇说一下)

1、class文件中

​ 通过命令:javap -verbose XXX

Constant pool:
   #1 = Methodref          #23.#50        // java/lang/Object."<init>":()V
   #2 = Methodref          #22.#51        // com/luban/ziya/string/TestIntern.test5:()V
   #3 = String             #52            // 1
....

​ 在硬盘上

2、运行时常量池

​ 一般说的常量池就是指这儿

​ InstanceKlass的一个属性

​ ConstantPool* _constants;

​ 方法区(元空间)

image-20201208224511502

3、字符串常量池

​ 即String Pool,但是JVM中对应的类是StringTable,底层实现是一个hashtable,看代码

class StringTable : public Hashtable<oop, mtSymbol> {
……

​ 在堆区

Hashtable是如何存储字符串的

例如 name = “aaa”,sex = “man”,zhiye = “teacher”

假设 name进行hashValue后是 11,sex的hashValue = 13,zhiye的hashValue = 11,

存储方式可以参考下图

image-20201209174138595

根据key从hashtable中查数据

​ 1、将key通过hash算法计算成hashValue(name:11)

​ 2、根据11去hashtable中去找,如果index=11关联的元素只有一个,直接返回

​ 3、如果是多个,根据链表进行遍历,比对key


java中的字符串在jvm中是如何存储的

StringTable的Key的生成

1、根据字符串以及字符串的长度计算出hashValue

2、根据hashValue计算出index,这个index就是key

hashValue = hash_string(name, len);
index = hash_to_index(hashValue);

// Pick hashing algorithm
unsigned int StringTable::hash_string(const jchar* s, int len) {
  return use_alternate_hashcode() ? AltHashing::murmur3_32(seed(), s, len) :
                                    java_lang_String::hash_code(s, len);
}

// Bucket handling
int hash_to_index(unsigned int full_hash) {
  int h = full_hash % _table_size;
  assert(h >= 0 && h < _table_size, "Illegal hash value");
  return h;
}
StringTable的Value的生成

将Java的String类的实例instanceOopDesc(String类在JVM中的存在形式)封装成HashtableEntry(就是一个结构体)

struct HashtableEntry {
        INT_PTR hash;
        void* key;    //hashValue
        void* value;   //instanceOopDesc
        HashtableEntry* //next;
    };
HashtableEntry<oop, mtSymbol>* entry = new_entry(hashValue, string()); // 这里的string()指的就是instanceOopDesc
add_entry(index, entry);

template <class T, MEMFLAGS F> HashtableEntry<T, F>* Hashtable<T, F>::new_entry(unsigned int hashValue, T obj) {
  HashtableEntry<T, F>* entry;

  entry = (HashtableEntry<T, F>*)BasicHashtable<F>::new_entry(hashValue);
  entry->set_literal(obj);
  return entry;
}

​ **String有时候会存储到StringTable,有的时候不会,**后面会有StringBuffer的案例,可以看到对象的值直接就是,而不是指向常量池,这个与与intern还有渊源,

​ 下面是周志明大佬书中的一段内容(这本书强烈建议Java开发者读一下 这本书强烈建议Java开发者读一下 这本书强烈建议Java开发者读一下

image-20201210141112307

String.hashcode()

​ String类重写了hashcode方法

public int hashCode() {
    int h = this.hash;
    if (h == 0 && this.value.length > 0) {
        char[] val = this.value;

        for(int i = 0; i < this.value.length; ++i) {
            h = 31 * h + val[i];
        }

        this.hash = h;
    }

    return h;
}

​ 可以看出String的hashcode与String的内容是有关系的,因此下面的代码的hashcode是相等的

public class TestHashcode {

    public static void main(String[] args) {
        String s1 = "11";
        String s2 = new String("11");

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

不同方式创建字符串在JVM中的存在形式

​ String 是用一个字符数组来存储值的,基本类型的数组的元信息存放在TypeArrayKlass中,它的对象是存在TypeArrayOopDesc中。

 private final char value[];

字符数组在JVM中是怎样存在的

运行以下代码,通过HSDB查看

public class CharArray {

    public static void main(String[] args) {
        char[] arr = new char[]{'1', '2'};

        while (true);
    }
}

可以看到元信息是存储在TypeArrayKlass中

image-20201209191542981


一个双引号

以下代码生成了几个oop

String s1 = "1"; 

生成两个oop 一个String

1、TypeArrayOopDesc char数组

2、InstanceOopDesc String对象

证明

debug以下代码

image-20201209192236012

此时内存中的char[] 和String的数量如下图

image-20201209192410285

当执行完第20行后,结果如下图,可以看到二者均 +1

image-20201209192500259

接下来图解一下,以以下代码为例

public static void test1() {
		String s1 = "11";
}

image.png


两个双引号

以下代码生成了几个oop

String s1 = "2";
String s2 = "2";

此时你是不是想说三个

继续debug以下代码

image-20201209192839616

执行完24行时

image-20201209192908646

执行完25行时

image-20201209192935980

可以发现增加了两个😆

看图

public static void test1() {
		String s1 = "11";
		String s2 = "11";
}

​ 当执行String s2 = “11”;的时候发现11的位置已经有value11了,所以就直接指向了同一个String对象,所以还是两个oop,一个String

image.png


一个new String

以下代码生成几个oop

String s1 = new String("3");

继续debug

image-20201209194847034

image-20201209194908923

创建过程:

​ 1、去字符串常量池中去查,如果有直接返回对应的String对象,如果没有,就会创建String对象、char数组对象。

​ 2、将这个String对象对应的InstanceOopDesc封装成HashtableEntry,作为StringTable的value进行存储。

​ 3、new String又在堆区创建一个对象,但是是没有数据的,char数组这时候直接指向常量池。

图解

image.png

image-20201211134538790

三个oop,两个String


两个new String

以下代码生成几个oop

String s1 = new String("4");
String s2 = new String("4");

image-20201209195105798

执行完38行后

image-20201209195127191

执行完39行后

image-20201209195144030

图解

image.png

三个String,四个oop

拼接字符串底层实现

双引号 + 双引号

public static void test() {
        String s1 = "1";
        String s2 = "1";

        String s = s1 + s2; 
        // 底层实现是new StringBuilder().append("1").append("1").toString();
}

​ 通过上述方法得到,创建了两个String ,4个oop,想不通了。。。,继续分析,首先看一下下图StringBuilder的toString()方法,是不是更懵逼了,明明new String()了。

image-20201209232604083

​ 不要慌,此时注意一下这里的构造函数的参数有三个,可以对比一下和一个参数的构造函数有啥区别。debug走起

image-20201209233509359

执行完10行

image-20201209233646592

​ 可以看到只有一个String,一个char,说白了就是没有在常量池生成记录

执行完12行

image-20201209233727123


看一个特例,看一下会输出什么,注意final

public static void test3() {
        final String s1 = "3";
        final String s2 = "3";
        String s = s1 + s2;
        
        String str = "33";

        System.out.println(s == str); //true
    }

此时在编译的时候就被优化为String s = 33;

双引号 + new String

public static void test1() {
		String s1 = "aa";
		String s2 = new String("bb");
		String s = s1 + s2;
}

下图是三行代码执行后的结果,创建了4个String ,3个char

image-20201210145754968

image-20201210145808853

image-20201210145826667

看内存图吧,明白一点,画得不对的话请指正。

image-20201210223507542


下面这种写法结果是不一样的

public static void main(String[] args) {
    String s1 = "aa" + new String("bb");
}

image-20201210145611480

可以看到创建了3个String ,3个char,此时这种new String的时候只创建了1个String对象。看内存图

image-20201211133526737

new String + new String

String s1 = new String("aa") + new String("bb");

image-20201210112147652

这个应该好理解了,我相信各位大佬都看得懂。

intern做了什么

1、去常量池中找字符串,有,直接返回

2、没有,就会String对应的InstanceOopDesc封装成HashtableEntry,存储

​ 再看一下大佬的讲解

image-20201210141112307


继续看几个案例,

String s1 = new String("aa") + new String("bb");
String s2 = s1.intern();
System.out.println(s1 == s2);//true

内存图

image-20201211134513817


再看一个

String s1 = "aabb";
String s2 = new String("aa") + new String("bb");
String s3 = s1.intern();

内存图

image-20201211134434129


再来一个

String s1 = new String("aa") + new String("bb");
String s3 = s1.intern();
String s2 = "aabb";

image-20201211134404050



​ 差不多了吧,几乎是涵盖了所有的案例,干了两晚够底层,够详细。

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 11
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值