Java --- JVM之StringTable

news2024/11/25 6:41:53

目录

一、String的基本特性

 二、String的内存分配

2.1、String内存分布图

三、字符串拼接操作 

 3.1、字符串拼接操作底层原理

3.2、拼接操作与append操作效率对比 

四、intern()方法 

4.1、intern()效率

五、StringTable的垃圾回收

一、String的基本特性

1、String字符串,使用一对""引起表示

2、String声明为Final的,不可被继承

3、String实现了Serializable接口:表示字符串是支持序列化的。实现Comparable接口:表示String可以比较大小。

4、String字符串在JDK8及以前内部定义了final char[] value用于存储字符串数据。jdk9时改为byte[]。

5、String代表不可变的字符串序列。①、当对字符串重新赋值时,需要重写指定内存区域赋值,不能使用原有的value进行赋值。②、当对现有的字符串进行连接操作时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值。③、当调用String的replace()方法修改指定字符或字符串时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值。

6、通过字面量的方式(区别于new)给一个字符串赋值,此时的字符串值声明在字符串常量池中。

参考代码:

public class StringTest {
    @Test
    public void test1(){
        String s1 = "a";
        String s2 = "a";
        s2 = "b";
        System.out.println(s1 == s2);//false
        System.out.println(s1);//a
        System.out.println(s2);//b
    }
    @Test
    public void test2(){
        String s1 = "a";
        String s2 = "a";
        s2 += "b";
        System.out.println(s1);//a
        System.out.println(s2);//ab
    }
    @Test
    public void test3(){
        String s1 = "a";
        String s2 = s1.replace('a','b');
        System.out.println(s1);//a
        System.out.println(s2);//b
    }
}

面试题:

public class StringTest02 {
    String s1 = new String("hello");
    char[] chars = {'t','o','m'};

    public void change(String s1,char chars[]){
        s1 = "test ok";
        chars[0] = 'a';
    }

    public static void main(String[] args) {
        StringTest02 str = new StringTest02();
        str.change(str.s1,str.chars);
        System.out.println(str.s1);//”hello“
        System.out.println(str.chars);//”aom“
    }
}

 7、字符串常量池是不会存储相同内容的字符串。

①、String的String Pool是一个固定大小的Hashtable,默认值大小长度是1009.如果放进String Pool的String非常多,就会造成Hash冲突严重,从而导致链表会很长,而链表长了后直接会造成的影响就是调用String.intern时性能会大幅度下降。

参考代码:

public class StringTest04 {
    public static void main(String[] args) {
        try {
            FileWriter fileWriter = new FileWriter("hello.txt");
            for (int i = 0; i < 100000; i++) {
                int length = (int) (Math.random() * (10) + 1);
                fileWriter.write(getString(length) + "\n");
            }
            fileWriter.close();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
    public static String getString(int length){
        String str = "";
        for (int i = 0; i < length; i++) {
            int num = (int) (Math.random() * (90 - 65 + 1) + 65) + (int) (Math.random() * 2) * 32;
            str += (char)num;

        }
        return str;
    }
}
public class StringTest03 {
    public static void main(String[] args)  {
        //参数设置:-XX:StringTableSize=1009
//        System.out.println("String参数设置开始");
//        try {
//            Thread.sleep(100000);
//        } catch (InterruptedException e) {
//            throw new RuntimeException(e);
//        }
        BufferedReader bufferedReader = null;
        try {
             bufferedReader = new BufferedReader(new FileReader("hello.txt"));
            long start = System.currentTimeMillis();
            String data;
            while ((data = bufferedReader.readLine()) != null){
                data.intern();
            }
            long end = System.currentTimeMillis();
            System.out.println("花费的时间为:" + (end - start));//参数1009.时间91ms。参数1000009,时间35ms
        } catch (IOException e) {
            throw new RuntimeException(e);
        }finally {
            if (bufferedReader != null){
                try {
                    bufferedReader.close();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }

}

②、使用-XX:StringTableSize可设置StringTable的长度。

③、在jdk6中的StringTable的是固定的,就是1009的长度,所以如果常量池中的字符串过多就会导致效率下降很快。StringTableSize设置没有要求。

④、在jdk7中,StringTable的默认长度是60013,jdk8及以后1009是设置的最小值。

以jdk8测试:

 二、String的内存分配

1、Java语言中有八大基本数据类型和特殊的String类型,这些类型为了使它们在运行过程中速度更快,更节省内存,都提供了一种常量池概念。

2、常量池类似一个Java系统级别提供缓存。8种基本数据类型的常量池都是系统协调的,String类型的常量池比较特殊。

      ①、直接使用双引号声明出来的String对象会直接存储在常量池中。

      ②、不使用双引号声明的String对象,可以使用String提供的intern()方法。

3、Java6及以前,字符串常量池存放在永久代中。

4、Java7中将字符串常量池的位置调整到Java堆中

     ①、所有的字符串都保存在堆中,和其他普通对象一样,这样可以在进行调优应用时仅需要调整堆大小就可以了。

     ②、字符串常量池概念原本使用的比较多,但改动后可以重新考虑在Java7中使用String.intern()

5、Java8元空间,字符串常量在堆。

2.1、String内存分布图

三、字符串拼接操作 

1、常量与常量的拼接结果是在常量池,原理是编译期优化。

2、常量池中不会存在相同内容的常量。

3、只要其中一个是变量,结果就在堆中。变量的拼接的原理是StringBuilder。

4、如果拼接的结果调用intern()方法,则主动将常量池中还没有的字符串对象放入池中,并返回此对象地址。

参考代码:

 @Test
    public void test1(){
        String s1 = "a" + "b" + "c";
        String s2 = "abc";
        System.out.println(s1 == s2);//true
        System.out.println(s1.equals(s2));//true
    }
    @Test
    public void test2(){
        String s1 = "javaee";
        String s2 = "hadoop";
        String s3 = "javaeehadoop";
        String s4 = "javaee" + "hadoop";//编译期优化
        //拼接字符串的前后出现变量,相当于在堆空间中new String(),
        String s5 = s1 + "hadoop";
        String s6 = "javaee" + s2;
        String s7 = s1 + s2;
        System.out.println(s3 == s4);//true
        System.out.println(s3 == s5);//false
        System.out.println(s3 == s6);//false
        System.out.println(s3 == s7);//false
        System.out.println(s5 == s6);//false
        System.out.println(s5 == s7);//false
        System.out.println(s6 == s7);//false
        //intern(),判断字符串常量池中是否存在javaeehadoop值,有就返回该值地址,没有就重新加载一份。
        String s8 = s6.intern();
        System.out.println(s3 == s8);//true
    }

 3.1、字符串拼接操作底层原理

参考代码:

 @Test
    public void test3(){
       String s1 = "a";
       String s2 = "b";
       String s3 = "ab";
        /**
         * s1 + s2执行步骤
         * ①、StringBuilder s = new StringBuilder();
         * ②、s.append("a")
         * ③、s.append("b")
         * s.toString() ---> 约等于 new String("ab");
         */
       String s4 = s1 + s2;
        System.out.println(s3 == s4);//false
    }
    @Test
    public void test4(){
        /**
         * 字符串拼接操作不一定使用StringBuilder()
         * 如拼接符号左右两边都是字符串常量或常量引用,则仍然使用编译期优化
         * 针对于final修饰的类、方法、基本数据类型,引用数据类型的量的结构时,能使用final时建议使用
         */
        final String s1 = "a";
        final String s2 = "b";
        String s3 = "ab";
        String s4 = s1 + s2;
        System.out.println(s3 == s4);//true
    }

3.2、拼接操作与append操作效率对比 

参考代码:

@Test
    public void test5(){
        /**
         *通过StringBuilder的append()的方式添加字符串的效率要远高于字符串拼接
         * ①、StringBuilder的append()方式,只需要创建一个StringBuilder对象,而字符串拼接则需要创建多个StringBuilder和String对象
         * ②、使用String的字符串拼接方式,内存中创建了较多的StringBuilder和String对象,内存占用更大,如垃圾回收效率要更频繁
         * 优化:在基本确定要添加的字符串的长度不高于某个限定值highlevel,可以使用构造器new StringBuilder(参数)
         */
        long start = System.currentTimeMillis();
        method1(10000);
        long end = System.currentTimeMillis();
        System.out.println("method1花费时间为:" + (end - start));//89
        long start1 = System.currentTimeMillis();
        method2(10000);
        long end1 = System.currentTimeMillis();
        System.out.println("method2花费时间为:" + (end1 - start1));//0
    }
    public void method1(int highLevel){
        String str = "";
        for (int i = 0; i < highLevel; i++) {
            str = str + "a";
        }
    }
    public void method2(int highLevel){
        StringBuilder stringBuilder = new StringBuilder();
        for (int i = 0; i < highLevel; i++) {
            stringBuilder = stringBuilder.append("a");
        }
    }

四、intern()方法 

如果不是双引号声明的String对象,可以使用String提供的intern():intern方法会从字符串常量池中查询当前字符串是否存在,如不存在就会将当前字符串放入常量池中。

public class StringTest07 {
    //以jdk8为例
    public static void main(String[] args) {
        String s = new String("1");
        s.intern();//调用此方法之前,常量池中已经有“1"
        String s1 = "1";
        System.out.println(s == s1);//false
        //s3变量的记录地址为new String(”11“),但在常量池中没有创建的”11“
        String  s3 = new String("1") + new String("1");
        s3.intern();//该方法执行完就生成11,但没有在常量池中创建”11“,而是创建一个指向堆空间中new String(”11“)的地址
        String s4 = "11";//使用的是上行代码生成的”11“ --》即常量池中生成的”11“的地址
        System.out.println(s3 == s4);//true
    }
}

 总结:在jdk6中,如果串池中有,就返回已有的串池中的对象的地址,如果没有,就是将这个对象复制一份,放入串池,并返回这个对象的地址。从jdk7起,如果串池中有,就返回已有的串池中的对象的地址,如果没有,就是将这个对象的引用地址复制一份,放入串池,并返回串池中的引用地址。

4.1、intern()效率

参考代码:

public class StringTest08 {
    static final int MAX_COUNT = 1000 * 10000;
    static final String[] arr = new String[MAX_COUNT];
    public static void main(String[] args) {
        int[] data = {1, 2, 3, 4, 5, 6, 7, 8, 9};
        long start = System.currentTimeMillis();
        for (int i = 0; i < MAX_COUNT; i++) {
            //arr[i] = new String(String.valueOf(data[i % data.length]));
            arr[i] = new String(String.valueOf(data[i % data.length])).intern();
        }
        long end = System.currentTimeMillis();
        System.out.println("花费的时间为:" + (end - start));
        try {
            Thread.sleep(100000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.gc();
    }
}

 结论:对于程序中大量存在的字符串,尤其其中存在很多重复字符串时,使用intern()可以节省很多内存空间。

五、StringTable的垃圾回收

参考代码:

参数设置:-Xms15m -Xmx15m -XX:+PrintStringTableStatistics -XX:+PrintGCDetails

public class StringTest09 {
    public static void main(String[] args) {
        for (int i = 0; i < 1000000; i++) {
            String.valueOf(i).intern();
        }
    }
}

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1213996.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C++二分查找算法:数组中占绝大多数的元素

题目 设计一个数据结构&#xff0c;有效地找到给定子数组的 多数元素 。 子数组的 多数元素 是在子数组中出现 threshold 次数或次数以上的元素。 实现 MajorityChecker 类: MajorityChecker(int[] arr) 会用给定的数组 arr 对 MajorityChecker 初始化。 int query(int left, …

【算法】区间(差分约束)

题目 给定 n 个区间 [ai,bi] 和 n 个整数 ci。 你需要构造一个整数集合 Z&#xff0c;使得 ∀i∈[1,n]&#xff0c;Z 中满足 ai≤x≤bi 的整数 x 不少于 ci 个。 求这样的整数集合 Z 最少包含多少个数。 输入格式 第一行包含整数 n。 接下来 n 行&#xff0c;每行包含三个…

21 Linux 自带的LED驱动

一、Linux 自带 LED 驱动使能 其实 Linux 内核自带 LED 抢夺那个&#xff0c;但在此之前需要配置 Linux 驱动来使能 LED 驱动。 输入以下命令&#xff1a; cd linux/atk-mpl/linux/my_linux/linux-5.4.31 make menuconfig 根据以下路径找到 LED 驱动&#xff1a; → Device D…

622.设计循环队列(LeetCode)

思路 先确定什么情况为空&#xff0c;什么情况为满。 这里有两种解决方案&#xff0c; 1.留一个空间空置&#xff0c;当rear1 front时 &#xff0c;则队列为满 &#xff08;这里我们选用方案一&#xff09; 2.增加一个size变量记录数据个数&#xff0c;size 0则为空&#xff…

asp.net数字档案管理系统VS开发sqlserver数据库web结构c#编程web网页设计

一、源码特点 asp.net 数字档案管理系统 是一套完善的web设计管理系统&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。开发环境为vs2010&#xff0c;数据库为sqlserver2008&#xff0c;使用c#语 言开发。 asp.net数字档案系统1 应用技…

通过右键用WebStorm、Idea打开某个文件夹或者在某一文件夹下右键打开当前文件夹用上述两个应用

通过右键用WebStorm、Idea打开某个文件夹或者在某一文件夹下右键打开当前文件夹用上述两个应用 通过右键点击某个文件夹用Idea打开 首先打开注册表 win R 输入 regedit 然后找到HKEY_CLASSES_ROOT\Directory\shell 然后右键shell 新建一个项名字就叫 Idea 第一步&#xf…

【FPGA】zynq 单端口RAM 双端口RAM 读写冲突 写写冲突

RAMRAM读写分类RAM原理及实现RAM三种读写模式不变模式写优先读优先 单端口 RAM伪双端口 RAM真双端口 RAM读写冲突和写写冲突读写冲突写写冲突总结&#xff1a; RAM RAM 的英文全称是 Random Access Memory&#xff0c;即随机存取存储器&#xff0c;简称随机存储器&#xff0c;…

ProtoBuf的学习和使用(C++)

ProtoBuf的学习和使用---C ⼀、初识ProtoBuf序列化和反序列化的概念ProtoBuf是什么?ProtoBuf工作特点 二、主要学习思路三、快速上手四、proto3语法详解1.字段规则2.消息类型的定义与使⽤实际操练 3.enum枚举类型enum注意事项enum实操 4.Any类型Any类型实操 5.oneof类型oneof类…

滴滴 Redis 异地多活的演进历程

为了更好的做好容灾保障&#xff0c;使业务能够应对机房级别的故障&#xff0c;滴滴的存储服务都在多机房进行部署。本文简要分析了 Redis 实现异地多活的几种思路&#xff0c;以及滴滴 Redis 异地多活架构演进过程中遇到的主要问题和解决方法&#xff0c;抛砖引玉&#xff0c;…

Unity Meta Quest 一体机开发(六):HandGrabInteractor 和 HandGrabInteractable 知识点

文章目录 &#x1f4d5;教程说明&#x1f4d5;HandGrabInteractor⭐HandGrabAPI⭐HandWristPoint⭐GripPoint⭐PinchPoint⭐PinchArea⭐HandGrabVisual⭐HandGrabGlow &#x1f4d5;HandGrabInteractable⭐Support Grab Type⭐Pinch Grab Rules 和 Palm Grab Rules⭐Unselect M…

【SpringBoot3+Vue3】三【实战篇】-后端(优化)

目录 一、登录优化-redis 1、SpringBoot集成redis 1.1 pom 1.2 yml 1.3 测试程序&#xff08;非必须&#xff09; 1.4 启动redis&#xff0c;执行测试程序 2、令牌主动失效&#xff08;代码优化&#xff09; 2.1 UserController设置token到redis 2.2 登录拦截器Log…

下载huggingface预训练模型到本地并调用

写在前面 在大模型横行的时代&#xff0c;无法在服务器上连接外网的研究僧真的是太苦逼了&#xff0c;每次想尝试类似于CLIP&#xff0c;BLIP之类的大模型都会得到“requests.exceptions.ConnectionError: (MaxRetryError("HTTPSConnectionPool(host‘huggingface.co’, …

Win11系统安装或执行程序时提示:文件系统错误(-1073740771)解决方案

有用户反映&#xff0c;exe文件无法执行或者无法安装&#xff0c;报错如图所示&#xff1a; 解决方法&#xff1a; 方法一&#xff1a; 1.打开控制面板&#xff0c;可以采用”搜索“→”控制面板“的方式 2.控制面板选择“用户账户”&#xff0c;再选择“更改用户账户控制设…

TiDB单机集群模拟生产环境

1、先部署环境&#xff0c;安装5.4.3版本&#xff0c;详细的安装步骤见官方文档&#xff1a;单机集群模拟生产环境安装教程 配置文件topo.yaml global:user: "tidb"ssh_port: 22deploy_dir: "/tidb-deploy"data_dir: "/tidb-data"monitored:no…

【数据结构】线段树(点修区查)

数据结构-线段树&#xff08;点修区查&#xff09; 前置知识 分治递归二叉树 思路 我们需要维护一个支持单点修改&#xff0c;区间查询的数据结构&#xff0c;并且要求在线&#xff0c;一般使用线段树解决。 线段树是一个二叉树形的数据结构。 线段树的思想很简单&#xff0c…

Python---数据序列中的公共方法

公共方法就是 支持大部分 数据 序列。 常见公共方法---简单 运算符描述支持的容器类型合并字符串、列表、元组*复制字符串、列表、元组in元素是否存在字符串、列表、元组、字典not in元素是否不存在字符串、列表、元组、字典 案例&#xff1a; 合并 代码&#xff1a; # …

阿里云99元VS腾讯云88元,双11云服务器价格战,谁胜谁负?

在2023年的双十一优惠活动中&#xff0c;阿里云推出了一系列令人惊喜的优惠活动&#xff0c;其中包括99元一年的超值云服务器。本文将带您了解这些优惠活动的具体内容&#xff0c;以及与竞争对手腾讯云的价格对比&#xff0c;助您轻松选择最适合的云服务器。 99元一年服务器优…

使用SSH和SCP传输文件———详细入门教学实践

确保你已经在本地机器上安装了SSH客户端和SCP工具。 获取远程虚拟机的IP地址或主机名以及登录凭据&#xff08;用户名和密码或私钥&#xff09;。 打开终端&#xff08;命令提示符&#xff09;并输入以下命令来传输文件&#xff1a; scp /本地路径/文件 用户名远程虚拟机IP地…

ubuntu设置脚本开机自启动

rc-local.service flexmitd1:~$ cd /lib/systemd/system/ flexmitd1:/lib/systemd/system$ ls |grep rc-local.service rc-local.service rc-local.service.d flexmitd1:/lib/systemd/system$ pwd /lib/systemd/system flexmitd1:/lib/systemd/system$确保有rc-local.service文…

深入理解JMM(Java内存模型)

一、什么是JMM? Java内存模型(Java Memory Model简称JMM)是一种抽象的概念&#xff0c;并不真实存在&#xff0c;它描述的一组规则或者规范。通过这些规则、规范定义了程序中各个变量的访问方式。jvm运行的程序的实体是线程&#xff0c;而每个线程运行时&#xff0c;都会创建一…