Java --- JVM之StringTable

目录

一、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 {@Testpublic void test1(){String s1 = "a";String s2 = "a";s2 = "b";System.out.println(s1 == s2);//falseSystem.out.println(s1);//aSystem.out.println(s2);//b}@Testpublic void test2(){String s1 = "a";String s2 = "a";s2 += "b";System.out.println(s1);//aSystem.out.println(s2);//ab}@Testpublic void test3(){String s1 = "a";String s2 = s1.replace('a','b');System.out.println(s1);//aSystem.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()方法,则主动将常量池中还没有的字符串对象放入池中,并返回此对象地址。

参考代码:

 @Testpublic void test1(){String s1 = "a" + "b" + "c";String s2 = "abc";System.out.println(s1 == s2);//trueSystem.out.println(s1.equals(s2));//true}@Testpublic 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);//trueSystem.out.println(s3 == s5);//falseSystem.out.println(s3 == s6);//falseSystem.out.println(s3 == s7);//falseSystem.out.println(s5 == s6);//falseSystem.out.println(s5 == s7);//falseSystem.out.println(s6 == s7);//false//intern(),判断字符串常量池中是否存在javaeehadoop值,有就返回该值地址,没有就重新加载一份。String s8 = s6.intern();System.out.println(s3 == s8);//true}

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

参考代码:

 @Testpublic 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}@Testpublic 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操作效率对比 

参考代码:

@Testpublic 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));//89long 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.hqwc.cn/news/178151.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

AWTK实现汽车仪表Cluster/DashBoard嵌入式GUI开发(七):FREERTOS移植

前言: 一般的GUI工程都需要一个操作系统,可能是linux,重量级的,也可能是FreeRTOS,轻量级的。 一句话理解那就是工程就是FreeRTOS task任务的集合。 一个main函数可以看到大框架: 很显然,除了第一个是硬件配置的初始化,中间最重要的部分就是要创建任务,把AWTK的应用…

XXX系统测试报告测试用例模板

XXX系统测试报告 编制&#xff1a; 2023-5-16 审核&#xff1a; 日期&#xff1a; 批准&#xff1a; 日期&#xff1a; 版本 修订时间 修订人 修订类型 修订章节 修订内容 *修订类型分为 A …

人工智能与大数据:驱动现代业务转型的双引擎

在当今数字化时代&#xff0c;人工智能&#xff08;AI&#xff09;和大数据已成为驱动业务和技术创新的关键力量。它们的结合不仅重塑了传统行业&#xff0c;也催生了新的商业模式和服务方式。 AI与大数据在零售行业的应用 在零售行业&#xff0c;AI和大数据的应用已经成为提…

ai语音电销机器人电销行业要怎么降低封号率?

工信部对电话营销电话的管控越来越严格&#xff0c;企业电销行业的发展受到了很多限制&#xff0c;因为电话销售人员在进行销售工作的时候&#xff0c;经常会因为各种原因触发封号机制&#xff0c;导致手机卡号被封&#xff0c;那企业电销行业要怎么降低封号率&#xff1f; 很多…

命令行中引导用户指定选择文档

背景 在python中&#xff0c;我们如果需要操作文档&#xff0c;则需要用户指定文档&#xff0c;那么&#xff0c;如何引导用户指定或者选择文档呢&#xff1f; 导入包 本次我们即将演示的代码&#xff0c;使用了 DebugInfo python包&#xff0c;我们需要导入 DebugInfo 包 …

三分钟学完Git版本控制常用指令

基本指令 git clone [url] 克隆远程仓库到本地 git clone https://gitee.com/mayun2023a/mprpc.git2.git checkout -b xxx 切换至新分支xxx&#xff08;相当于复制了remote的仓库到本地的xxx分支上) 3.修改或者添加本地代码&#xff08;部署在硬盘的源文件上&#xff09; 4.g…

2023年人工智能还好找工作吗?

人工智能的就业形势并不严峻&#xff0c;相反&#xff0c;很多岗位都是供不应求的状态&#xff0c;可以看一下下面的官方数据。 脉脉高聘人才智库发布《2023泛人工智能人才洞察》&#xff0c;对23年1-8月的人工智能行业现状进行了分析总结。 人工智能相关岗位数据&#xff1a…

vue中cli组件如何自定义定义

目录 创建自定义组件 注册并使用自定义组件 注册组件&#xff1a; 在需要使用该组件的页面或父组件中注册并引入自定义组件。 使用 Props 传递数据 总结步骤&#xff1a; 前言 在Vue CLI中使用自定义组件是构建交互式和模块化Web应用的重要一环。Vue CLI为开发者提供了使用…

PowerPoint技巧:如何将一张图片同时加到全部幻灯片里?

想把一张图片加到PPT每一张幻灯片的同一个位置&#xff0c;如果一张一张的添加就太耗时间了&#xff0c;一起来看看如何利用母版快速设置同时添加吧。 首先&#xff0c;打开需要编辑的PPT&#xff0c;在菜单栏依次点击【视图】→【幻灯片母版】&#xff1b; 打开母版后&#x…

(论文阅读34-39)理解CNN

34.文献阅读笔记 简介 题目 Understanding image representations by measuring their equivariance and equivalence 作者 Karel Lenc, Andrea Vedaldi, CVPR, 2015. 原文链接 http://www.cv-foundation.org/openaccess/content_cvpr_2015/papers/Lenc_Understanding_I…

C# 使用Microsoft.Office.Interop.Excel库操作Excel

1.在NuGet管理包中搜索&#xff1a;Microsoft.Office.Interop.Excel&#xff0c;如下图红色标记处所示&#xff0c;进行安装 2. 安装完成后&#xff0c;在程序中引入命名空间如下所示&#xff1a; using Microsoft.Office.Interop.Excel; //第一步 添加excel第三方库 usi…

python基于图卷积神经网络GCN模型开发构建文本数据分类模型(以论文领域数据为例)

GCN&#xff08;Graph Convolutional Network&#xff09;图卷积神经网络是一种用于处理图数据的深度学习模型。它是基于图结构的卷积操作进行信息传递和特征学习的。 GCN模型的核心思想是通过利用邻居节点的特征来更新中心节点的表示。它通过迭代地聚集邻居节点的信息&#x…