JVM学习(十三):面试中绕不开的String

目录

一、String 的基本特性

1.1 String类的声明 

1.2 String的存储方式在jdk9中的变更

1.3 Stirng 的不可变性 

二、String的内存分配

2.1 字符串常量池是什么 

2.2 底层原理与默认值 

2.3 字符串常量池所在位置 

三、字符串的拼接操作 

3.1 拼接操作结果存放位置

3.2 字节码角度剖析拼接操作 

四、intern的理解

4.1 intern是什么 

4.2 不同jdk版本的intern的逻辑 

五、G1收集器中的String去重操作 

5.1 去重的目的 

5.2 去重的实现

六、面试题


 

一、String 的基本特性

1.1 String类的声明 

   

        String实现了Serializable接口,表示字符串是支持序列化的;

        实现了Comparable接口,表示String可以比较大小

1.2 String的存储方式在jdk9中的变更

        我们知道,String在jdk8中使用 final char []来存储,在jdk9中使用final byte []来存储,我们在官方文档上看看这样的用意:

        文档地址:https://openjdk.org/jeps/254 

        翻译之后得到的结果:

动机:

        String类的当前实现将字符存储在字符数组中,每个字符使用两个字节(16位)。从许多不同的应用程序收集的数据表明,字符串是堆使用的主要组成部分,而且,大多数String对象只包含Latin-1字符。这些字符只需要一个字节的存储空间,因此这些String对象的内部字符数组中有一半的空间是未使用的

描述:

        我们建议将String类的内部表示形式从UTF-16字符数组更改为字节数组加上编码标志字段。新的String类将根据字符串的内容存储编码为ISO-8859-1/Latin-1(每个字符一个字节)或UTF-16(每个字符两个字节)的字符。编码标志将指示使用哪种编码。

        与字符串相关的类,如AbstractStringBuilder、StringBuilder和StringBuffer,将被更新为使用相同的表示,HotSpot VM的固有字符串操作也是如此。

        这是一个纯粹的实现更改,没有更改现有的公共接口。没有计划添加任何新的公共api或其他接口。

        到目前为止所做的原型工作证实了预期的内存占用减少、GC活动的大量减少以及在某些极端情况下的轻微性能下降。

        结论:String 再也不用char[]来存储了,而是改成了byte[]加上编码标记,节约了一些空间。

1.3 Stirng 的不可变性 

        String被声明为final,不可继承。String代表不可变的字符序列。

  • 当对字符串重新赋值时,需要重写指定内存区域赋值,不能使用原有的value进行赋值
  • 当对现有的字符串进行连接操作时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值。
  • 当调用string的replace()方法修改指定字符或字符串时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值。

二、String的内存分配

2.1 字符串常量池是什么 

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

        常量池就类似一个Java系统级别提供的缓存。8种基本数据类型的常量池都是系统协调的,String类型的常量池比较特殊。它的主要使用方法有两种:

  • 直接使用双引号声明出来的String对象会直接存储在常量池中。
    • 比如:String info = "atguigu.com" ;
  • 如果不是用双引号声明的String对象,可以使用String提供的intern()方法

2.2 底层原理与默认值 

        字符串常量池中是不会存储相同内容的字符串的。它是一个Hashtable,如果放进其中的String非常多,就会造成严重的Hash冲突,从而导致链表太长,而链表长了后直接会造成的影响就是当调用string.intern() 时性能会大幅下降。

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

  • 在jdk6中,StringTable是固定的,就是1009的长度,所以如果常量池中的字符串过多就会导致效率下降很快,StringTableSize的值没有要求;
  • 在jdk7中,StringTable的长度默认值是60013,StringTableSize的值没有要求;
  • jdk8开始,1009是可设置的最小值。

2.3 字符串常量池所在位置 

  • Java 6及以前,字符串常量池存放在永久代
  • Java 7 中,oracle的工程师对字符串池的逻辑做了很大的改变,即将字符串常量池的位置调整到Java堆内。所有的字符串都保存在堆(Heap)中,和其他普通对象一样,这样可以让你在进行调优应用时仅需要调整堆大小就可以了。字符串常量池概念原本使用得比较多,但是这个改动使得我们有足够的理由让我们重新考虑在Java 7 中使用String.intern ()
  • Java8中引入了元空间,字符串常量池仍在堆中 

        String Pool为什么会有这样的调整? 我们看看官网的解答:

In JDK 7, interned strings are no longer allocated in the permanent generation of the Java heap, but are instead allocated in the main part of the Java heap (known as the young and old generations), along with the other objects created by the application. This change will result in more data residing in the main Java heap, and less data in the permanent generation, and thus may require heap sizes to be adjusted. Most applications will see only relatively small differences in heap usage due to this change, but larger applications that load many classes or make heavy use of the String.intern() method will see more significant differences.

        翻译过来就是:

在JDK 7中,内部字符串不再在Java堆的永久代中分配,而是与应用程序创建的其他对象一起在Java堆的主要部分(称为年轻代和老代)中分配。此更改将导致更多的数据驻留在主Java堆中,而更少的数据驻留在永久生成中,因此可能需要调整堆大小。由于此更改,大多数应用程序在堆使用方面只会看到相对较小的差异,但是加载许多类或大量使用String.intern()方法的大型应用程序将会看到更显著的差异。

三、字符串的拼接操作 

3.1 拼接操作结果存放位置

  • 常量与常量的拼接结果在常量池,原理是编译期优化
    @Test
    public void test1(){
        String s1 = "a" + "b" + "c";//编译期优化:等同于"abc"
        String s2 = "abc"; //"abc"一定是放在字符串常量池中,将此地址赋给s2
        /*
         * 最终.java编译成.class,再执行.class
         * String s1 = "abc";
         * String s2 = "abc"
         */
        System.out.println(s1 == s2); //true
        System.out.println(s1.equals(s2)); //true
    }

  

  • 常量池中不会存在相同内容的常量
  • 只要其中有一个是变量,结果就在堆中。变量拼接的原理是StringBuilder
    @Test
    public void test5(){
        String s1 = "javaEEhadoop";
        String s2 = "javaEE";
        String s3 = s2 + "hadoop";
        System.out.println(s1 == s3);//false

        final String s4 = "javaEE";//s4:常量
        String s5 = s4 + "hadoop";
        System.out.println(s1 == s5);//true
    }
  • 如果拼接的结果调用intern()方法,则主动将常量池中还没有的字符串对象放入池中,并返回此对象地址

3.2 字节码角度剖析拼接操作 

    @Test
    public void test3(){
        String s1 = "a";
        String s2 = "b";
        String s3 = "ab";
        
        String s4 = s1 + s2;//
        System.out.println(s3 == s4);//false
    }

        如下的s1 + s2 的执行细节:(变量s是临时定义的)

  1.  StringBuilder s = new StringBuilder();
  2. s.append("a")
  3. s.append("b")
  4. s.toString()  --> 约等于 new String("ab")

        在jdk5.0之后使用的是StringBuilder,在jdk5.0之前使用的是StringBuffer。

        需要注意的是,字符串拼接操作不一定使用的是StringBuilder!如下面代码,如果拼接符号左右两边都是字符串常量或常量引用,则仍然使用编译期优化,即非StringBuilder的方式。

@Test
    public void test4(){
        final String s1 = "a";
        final String s2 = "b";
        String s3 = "ab";
        String s4 = s1 + s2;
        System.out.println(s3 == s4);//true
    }

        所以,针对于类、方法、基本数据类型、引用数据类型的量的结构时,能使用上final的时候建议使用上。 

四、intern的理解

4.1 intern是什么 

        先看看官方文档:

         老规矩,翻译一下:

        返回字符串对象的规范表示形式。

        字符串池最初为空,由String类私有地维护。

        当调用intern方法时,如果池中已经包含一个由equals(object)方法确定的与此String对象相等的字符串,则返回池中的字符串。否则,将此String对象添加到池中,并返回对该String对象的引用。

        由此可见,对于任意两个字符串s和t,s.intern () == t.intern() 当且仅当s.equals(t)为真时为真。

        所有字面值字符串和字符串值常量表达式都被internned。字符串字面值在Java™语言规范的第3.10.5节中定义。

        通俗点讲,Interned String就是确保字符串在内存里只有一份拷贝,这样可以节约内存空间,加快字符串操作任务的执行速度。注意,这个值会被存放在字符串内部池(String Intern Pool) 。 

4.2 不同jdk版本的intern的逻辑 

        jdk1.6中,将这个字符串对象尝试放入串池。

  • 如果串池中有,则并不会放入。返回已有的串池中的对象的地址
  • 如果没有,会把此对象复制一份,放入串池,并返回串池中的对象地址

        jdk1.7起,将这个字符串对象尝试放入串池:

  • 如果串池中有,则并不会放入。返回已有的串池中的对象的地址
  • 如果没有,则会把对象的引用地址复制一份,放入串池,并返回串池中的引用地址 
public class StringIntern1 {
    public static void main(String[] args) {

        String s = new String("1");
        s.intern();
        String s2 = "1";
        System.out.println(s == s2); //jdk6:false  jdk7/8:false

        String s3 = new String("1") + new String("1");  //s3变量记录的地址为:new String("11")
        //执行完上一行代码以后,字符串常量池中,是否存在"11"呢?答案:不存在!!
        s3.intern();

        String s4 = "11";
        System.out.println(s3 == s4);   //jdk6:false  jdk7/8:true
    }
}

         拓展:

public class StringIntern1 {
    public static void main(String[] args) {
        String s5 = new String("1") + new String("1");//new String("11")
        //执行完上一行代码以后,字符串常量池中,是否存在"11"呢?答案:不存在!!
        String s6 = "11";//在字符串常量池中生成对象"11"
        String s7 = s5.intern();
        System.out.println(s6 == s5);//false
        System.out.println(s7 == s6);//true
    }
}

五、G1收集器中的String去重操作 

5.1 去重的目的 

        先看官方文档的说明:JEP 192: String Deduplication in G1 

        翻译一下:

        许多大规模Java应用程序目前都存在内存瓶颈。

        测量表明,在这些类型的应用程序中,大约25%的Java堆活动数据集被String对象消耗。此外,这些String对象中大约有一半是重复的,其中重复意味着string1.equals(string2)为真。在堆上有重复的String对象本质上只是浪费内存。本项目将在G1垃圾收集器中实现自动和连续的字符串重复数据删除,以避免浪费内存并减少内存占用。 

5.2 去重的实现

        当垃圾收集器工作的时候,会访问堆上存活的对象。对每一个访问的对象都会检查是否是候选的要去重的string对象。如果是,把这个对象的一个引用插入到队列中等待后续的处理。一个去重的线程在后台运行,处理这个队列。处理队列的一个元素意味着从队列删除这个元素,然后尝试去重它引用的string对象。

        使用一个hashtable来记录所有的被String对象使用的不重复的char数组。当去重的时候,会查这个hashtable,来看堆上是否已经存在一个一模一样的char数组。如果存在,string对象会被调整引用那个数组,释放对原来的数组的引用,最终会被垃圾收集器回收掉。如果查找失败,char数组会被插入到hashtable,这样以后的时候就可以共享这个数组了。 

六、面试题

1、String str = new String("a");  会创建几个对象?

        两个:

  • new关键字在堆空间创建的
  • 字符串常量池中的对象"ab"

2、String str = new String("a") + new String("b"); 会创建几个对象?

  • 对象1:new StringBuilder()
  • 对象2: new String("a")
  • 对象3: 常量池中的"a"
  • 对象4: new String("b")
  • 对象5: 常量池中的"b"

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

es elasticsearch 九 索引index 定制分词器 type结构后期弃用原因 定制动态映射 动态映射模板 零停机重建索引

目录 索引index 定制分词器 Type底层结构及弃用原因 定制 dynamic mapping 定制dynamic mapping template 动态映射模板 零停机重建索引 生产环境应该度别名数据 索引index Put /index Stings 分片 Mapping 映射 Aliases 别名 增加 Put my_index2 { "se…

软考A计划-试题模拟含答案解析-卷七

点击跳转专栏>Unity3D特效百例点击跳转专栏>案例项目实战源码点击跳转专栏>游戏脚本-辅助自动化点击跳转专栏>Android控件全解手册点击跳转专栏>Scratch编程案例 👉关于作者 专注于Android/Unity和各种游戏开发技巧,以及各种资源分享&am…

从C语言到C++_12(string相关OJ题)(leetcode力扣)

上一篇已经讲了string类的接口函数,然后根据查文档刷了牛客和力扣58最后一个单词的长度, 还有力扣415字符串相加,这篇继续跟着查文档来刷力扣题,体会C刷题的方便。 目录 917. 仅仅反转字母 - 力扣(LeetCode&#xf…

Linux 实操篇-组管理和权限管理

Linux 实操篇-组管理和权限管理 Linux 组基本介绍 在linux 中的每个用户必须属于一个组,不能独立于组外。在linux 中每个文件有所有者、所在组、其它组的概念。 所有者所在组其它组改变用户所在的组 文件/目录所有者 一般为文件的创建者,谁创建了该文件&#x…

计算机视觉:卷积核的运行过程

本文重点 我们前面从直观角度理解了卷积神经网络的卷积在特征提取的作用,本节课程我们从数学角度来看一下,卷积是如何计算的? 计算步骤 1. 将卷积核与输入图像的某一部分进行逐元素相乘。 2. 将相乘后的结果求和,得到卷积核在该部分的输出值。 3. 重复以上步骤,将卷积核…

【shiro】shiro整合JWT——3.执行流程

前言 shiro整合JWT系列,主要记录核心思路–如何在shiroredis整合JWTToken。 上一篇中,主要讲如何在shiro框架中配置Jwt,以及token执行的流程。 该篇主要梳理整个代码的执行流程。 ps:本文主要以记录核心思路为主,以下…

uCOSii消息邮箱管理

uCOSii消息邮箱管理 (MESSAGE MAILBOX MANAGEMENT) 消息邮箱主要用于中断和任务之间进行邮件传递,或者是在任务与任务之间进行邮件交换。 我个人觉得,了解uCOSii消息邮箱的几个重要函数,还是有必要的。不是所有人都给我们测试案例。 1、重…

R语言混合效应(多水平/层次/嵌套)模型及贝叶斯实现技术

回归分析是科学研究中十分重要的数据分析工具。随着现代统计技术发展,回归分析方法得到了极大改进。混合效应模型(Mixed effect model),即多水平模型(Multilevel model)/分层模型(Hierarchical Model)/嵌套…

如何快速运用R语言实现生物群落(生态)数据统计分析与绘图

R 语言作的开源、自由、免费等特点使其广泛应用于生物群落数据统计分析。生物群落数据多样而复杂,涉及众多统计分析方法。本次以生物群落数据分析中的最常用的统计方法回归和混合效应模型、多元统计分析技术及结构方程等数量分析方法为主线,通过多个来自…

Linux:查看进程。

Linux:查看进程。 windows linux TTY如果是?说明是不是终端(控制台)启动的,而是系统内部自己启动的。 TIME是启动Linux后,这个进程一共占用了cpu多少时间00…

QT 设计ROS GUI界面订阅和发布话题

QT 设计ROS GUI界面订阅和发布话题 主要参考下面的博客 ROS项目开发实战(三)——使用QT进行ROS的GUI界面设计(详细教程附代码!!!) Qt ROS 相关配置请看上一篇博客 首先建立工作空间和功能包&a…

【探索】机器指令翻译成 JavaScript

前言 前些时候研究脚本混淆时,打算先学一些「程序流程」相关的概念。为了不因太枯燥而放弃,决定想一个有趣的案例,可以边探索边学。 于是想了一个话题:尝试将机器指令 1:1 翻译 成 JavaScript,这样就能在浏览器中&am…

Java程序设计入门教程-- if 条件语句

目录 单分支选择语句(if) 双分支选择语句(if…else) 嵌套if语句 单分支选择语句(if) 情形 当判断条件满足时,执行语句体S,而不满足则什么都不做。 格式 if (条件判断表…

【计算机视觉 | 目标检测】术语理解6:ViT 变种( ViT-H、ViT-L ViT-B)、bbox(边界框)、边界框的绘制(含源代码)

文章目录 一、ViT & ViT变种1.1 ViT的介绍1.2 ViT 的变种 二、bbox(边界框)三、边界框的绘制 一、ViT & ViT变种 1.1 ViT的介绍 ViT,全称为Vision Transformer,是一种基于Transformer架构的视觉处理模型。传统的计算机视…

java企业工程项目管理系统平台源码(三控:进度组织、质量安全、预算资金成本、二平台:招采、设计管理)

工程项目管理软件(工程项目管理系统)对建设工程项目管理组织建设、项目策划决策、规划设计、施工建设到竣工交付、总结评估、运维运营,全过程、全方位的对项目进行综合管理 工程项目各模块及其功能点清单 一、系统管理 1、数据字典&#…

回调函数与钩子函数的区别,另QT中connect函数的实现,lambda的使用

1、钩子函数是回调函数的一种 广泛来说两者都是一样的 严格来说 钩子函数的函数名早已被定义好,只是函数内部需要用户在应用层来定义, 1)可以完全通过宏来实现系统是否调用该函数(底层不封闭,修改宏的参数实现是否编…

【2023 · CANN训练营第一季】MindSpore模型快速调优攻略 第二章——MindSpore调试调优

1.生态迁移 生态迁移工具使用示例 生态迁移工具技术方案 不同框架间模型定义前端表达差别巨大(相同算子的API技术难点 、 算子功能、模型构建方式差别较大); 对于同一框架,不管前端表达差异如何,最终对应的计算 图是相似的。因此提出&#x…

Kubernetes部署+kubesphere管理平台安装

Kubernetes官网;kubesphere官网 不论是Kubernetes官网还是找的其它部署步骤,基本都是推荐搭建集群的方式,是为了实现高可用.....等等,这样一来至少需要两台或三台的服务器来搭建,这样对我们的成本也是非常大的&#xf…

Axure教程——直方图(中继器)

本文将教大家如何用AXURE用中继器制作直方图 一、效果介绍 如图: 预览地址:https://yjkepz.axshare.com 下载地址:https://download.csdn.net/download/weixin_43516258/87842701 二、制作方法 (1)制作刻度表 设计5个刻…

CSDN上海城市开发者社区线下活动纪实

引言 5月27号中午,很高兴能和现CSDN副总裁、前微软 Azure 工程团队首席研发经理、技术畅销书《编程之美》及《构建之法》的作者邹欣邹老师,以及CSDN的 “上海城市开发者社区” 的部分成员齐聚一堂,参加CSDN上海城市开发者社区自5月初成立以来…