JVM-String

一、基本特性

在这里插入图片描述

1. 不可变性

String 类: 
   - 类被final修饰,不能被继承
   - 类中所有的属性被final修饰,不能去修改

1. 当对字符串重新赋值时,需要重写制定内存区域赋值,不能使用原有的value进行赋值
2. 当对现有的字符串进行连接操作时,需要重新制定内存区域赋值,不能使用原有的value进行赋值
3. 当调用String 的replace方法修改制定字符或字符串时,需要重新制定内存区域赋值,不能使用原有的value进行赋值

- 通过字面量的方式(区别于new)给一个字符串赋值,此时的字符串值声明在字符串常量池中
- 字符串常量池中,不会存放两个相同的元素
package com.erick.string.d01;

import org.junit.Test;

public class Demo01 {

    @Test
    public void method01() {
        String first = "erick";
        String second = "erick";

        /*字符串常量池中,不会保存同样的字符
         * 字符底层,是用byte[]填充数据的,数组不会扩容,只会再生成一个新的*/
        System.out.println(first == second); // 指向同一个引用地址

        second = "hehe";
        System.out.println(first == second); // 引用地址已经变化
    }

    @Test
    public void method02() {
        String first = "Erick";
        String second = "Erick";
        second += "nihao";
        System.out.println(first==second);// 已经重新分配了一个新的数组
    }

    @Test
    public void method03(){
        String first = "Erick";
        String second = first.replace("E", "a");

        // 上面其实是生成了一个新的byte[]来存储字符串
        System.out.println(first==second);

    }
}

2. String底层HashTable

  • 字符串常量池中是不会存储相同内容的字符串的
String 的 String Pool 是一个固定大小的HashTable, 默认值大小长度是1009

- HashTable底层: 数组加链表的结构
- 这里说的大小长度指的是 数组长度,而不是总node的数量, 总node的数量可以无限存储
- 不过数组每个索引位置都存储元素的时候,查询起来就会慢 , 因为需要链表的遍历

- 如果放进去的String很多,就会造成Hash冲突严重,从而导致链表很长
  链表如果很长,查询时候比较慢

-XX:StringTableSize=20000    可以设置StringTable的长度

- jdk6中StringTable是固定的,如果常量池中的字符串过多就会导致效率下降很快,最小值随便设置

- jdk7中,StringTable的长度默认是60013, 
- jdk8时,1009是可以设置的最小值
- jdk11 : 默认长度为 65536

3. String 的内存分配

- Java包含八种基本数据类型和一种比较特殊的类型String
- 为了使得这些数据类型在运行过程中速度更快,更节省内存,都提供了一种常量池的概念
- 常量池就类似一个Java系统级别提供的缓存,八种基本数据类型的常量都是系统协调的
- String类型的常量池比较特殊,它的主要使用方法有两种

1.  直接使用“”声明的String对象会直接存储在常量池中
2.  不是“”声明的String对象,可以使用String提供的 intern()方法
  • JDK8中,常量池 存放在堆里面

4. String操作

4.1 字符串唯一性

  • 一个字符串已经出现了,则后续如果字符串的字面量和上面相同,则直接去常量池中取
  • 常量池中,不会保存两个完全相同的两个字符串
    在这里插入图片描述

4.2 toString()

  • 任何类的toString()也会保存在常量池中

二、字符串拼接

1. 前端编译期间优化

    /*前端编译期间优化
     * 1. 会直接将first写成 "abc",存储在常量池中
     * 2. 将second的引用地址指向常量池的 "abc"*/
    @Test
    public void test01() {
        String first = "a" + "b" + "c";
        String second = "abc";
        System.out.println(first == second); // true
        System.out.println(first.equals(second)); // true
    }
  • 对应字节码的反编译文件来查看
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.erick.string.d01;

import org.junit.Test;

public class Demo02 {
    public Demo02() {
    }

    @Test
    public void test01() {
        String first = "abc";
        String second = "abc";
        System.out.println(first == second);
        System.out.println(first.equals(second));
    }
}
  • 从字节码的角度来看

在这里插入图片描述

2. 拼接前后出现了变量

  • 如果拼接符号的前后出现了变量,则相当于在堆空间中new String()
  • 既然new了对象,那就是不同的对象,因此也就是不同的地址值
  • 对象的具体的内容是拼接的结果
 字符串拼接:
 1. 如果拼接两边都是字符串常量或常量引用(比如final修饰),则使用的是编译期优化
 2. 如果拼接两边是变量,则会使用StringBuilder,并且来new对象

在这里插入图片描述

2.1 append和+的效率比较

效率快的原因:
1. String的字符串拼接,每次都会去创建一个新的StringBuilder, 然后将结果再次new String();
2. 内存中创建了较多的对象,就会引发了一定的GC

## 再次优化
StiringBuilder中,指定初始化的容量,因为底层也是数组保存数据的, 数组扩容的时候,也是一个动态的过程
, 同时,数组的不断抛弃,也会引发GC
package com.nike.erick.d07;

import org.junit.Test;

public class Demo01 {

    @Test
    public void test01() {
        long start = System.currentTimeMillis();
        String src = "";
        for (int i = 0; i < 100000; i++) {
            src += "a";
        }
        // 3569ms
        System.out.println(System.currentTimeMillis() - start);
    }

    @Test
    public void test02() {
        long start = System.currentTimeMillis();
        StringBuilder src = new StringBuilder();
        for (int i = 0; i < 100000; i++) {
            src.append("a");
        }
        // 23
        System.out.println(System.currentTimeMillis() - start);
    }
}

3. string.intern()

  • 判断字符串常量池中是否存在“javaeehadfd”的值
  • 如果存在,则返回该常量池中该值的地址
  • 如果不存在,则在常量池中加载一份,并返回该对象的地址值

3.1 如何保证变量s指向的是字符串常量池中的数据

  • intern(), 用调用该方法的String的内容和常量池中的string通过 equals来比较
  • 如果相同,则将常量池中的该string的引用返回
  • 如果不容,则在常量池中创建一个新的string,并返回
方式1: String s = "erick"; // 字面量定义的方式
方式2: String s = new String("erick").intern();
      String s = new StringBuilder("erick").toString().intern();

- 就是为了保证常量池中的字符串不会重复,这样就保证了查询时候的性能

3.2 new String(“abc”)到底创建了几个对象

  • 一个new的对象放在堆上
  • 一个常量池中的“abc”
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值