java高级——String字符串探索(在jvm底层中如何实现,常量池中怎么查看)

java高级——String字符串探索(在jvm底层中如何实现,常量池中怎么查看)

  • 文章介绍
  • 提前了解的知识点
    • 1. 常量池
    • 2. Jvm虚拟机
    • 3. 字节码
  • String类详解
    • 1. String对象在申明后将不可修改,是不可变类
    • 2. String进行相加相减等操作时一定会创建新的对象
    • 3. new String(“abc”)和直接 “abc”的区别
    • 4. String类常用的方法
    • 5. String中format方法(重点)
    • 6. String.join()方法
    • 7. StringBuilder和StringBuffer(重点)

文章介绍

   此文为java高级系列的第一篇,探索String字符串,包括但不限于字符串在jvm中如何存储和操作直接定义字符串和new String的区别、以及常量池的知识点。

提前了解的知识点

1. 常量池

参考:常量池详解

   常量池是java中比较重要的一个概念,作用是为了加快整个系统的性能,它的存在我们可以理解为内存复用,也就是享元模式的概念。简单来说就是这已经有一个椅子了,再有一个不是占地方嘛,我们可以共享这个椅子。

   常量池可分为三大类,目前我们只研究字符串常量池

  1. class文件常量池;
  2. 字符串常量池;
  3. 运行时常量池;

   如何知道一个类中的常量池有哪些东西呢,可以用javap -verbose 类名来查看。

2. Jvm虚拟机

   jvm又叫做虚拟机,也就是一个拟态的计算机,我们写完代码之后怎么运行是不管的,这个操作就是jvm来完成的,java文件最终会编译为字节码文件,而jvm执行的就是字节码文件,最终运行出我们想要的结果。(就当做是一个胃,吃完饭谁还管它怎么消化啊)

   jvm强大之处在于它能跨平台运行,也就是说无论是什么语言,只要能变成字节码文件(也就是class文件),jvm都能运行,这才是java能一直这么流行的核心所在。

3. 字节码

   字节码(Byte-code)是一种包含执行程序,由一序列 op 代码/数据对组成的二进制文件,是一种中间码

   上面可能有些抽象,学过编译原理的对二进制都不陌生,各种1001的数字,计算机最终识别的就是这玩意儿,本文只是会用到这方面的知识,并不深究,我们目前只需要知道,字节码文件是jvm识别并运行的,它所记录的内容很详细,详细到每一步执行了什么。

参考:字节码详解

String类详解

1. String对象在申明后将不可修改,是不可变类

   首先第一点,不可变类是什么意思?

在这里插入图片描述

   很简单,String是不可被继承的类,因为是被final修饰的,也就是一个顶级对象了。

   第二点,申明后不可修改是什么意思?虽然我们在编程中经常对一个字符串进行拼接或者更改内容的操作,但实际对象是一直在变化的,如下:

public class Test {public static void main(String[] args) {String a = "Hello";a = "World";}
}

   上面的代码创建了几个对象呢?大多数人都会认为是1个,我就定义了一个对象啊,但实际在底层,它创建了两个对象,这对应了上面所说的,String申明后不可修改,接下来用字节码验证。

   首先执行这段代码,找到编译后的class文件位置,在class文件位置下打开cmd;
   之后运行反编译代码查看常量池:

D:\env\IDEA\Java-study\string-01\target\classes\com\lgt>javap -verbose Test.class
// Classfile属性为你的字节码文件绝对地址
Classfile /D:/env/IDEA/Java-study/string-01/target/classes/com/lgt/Test.class// 最后修订时间以及文件大小Last modified 2024519; size 442 bytes// SHA加密哈希值,代表文件唯一标识,也有可能是md5加密(我是jdk17,之前可能使用md5)SHA-256 checksum 1a2c5dc3f8ce4ae72046ade88aad0cdb5dc8ed1d0f1f1d6ba725605acfd9a0e0// 源文件名称Compiled from "Test.java"
public class com.lgt.Test// 副版本号minor version: 0		// 主版本号major version: 52		// 访问标识,标明你这是类还是接口,用什么修饰符修饰的,比如public还是privateflags: (0x0021) ACC_PUBLIC, ACC_SUPER		// 类的相对路径,#11表示这是常量池中索引为11的值this_class: #11                         // com/lgt/Test// 父类的相对路径,可以看到父类是Objectsuper_class: #2                         // java/lang/Object// 分别代表接口数量、静态或实例变量数量、方法数量、属性数量interfaces: 0, fields: 0, methods: 2, attributes: 1
// 重点:常量池-----------------------------------------------------------
// #数字代表常量的索引值,从1开始
// =后跟常量的访问标识
// 最后是常量的值
Constant pool:#1 = Methodref          #2.#3          // java/lang/Object."<init>":()V#2 = Class              #4             // java/lang/Object#3 = NameAndType        #5:#6          // "<init>":()V#4 = Utf8               java/lang/Object#5 = Utf8               <init>#6 = Utf8               ()V#7 = String             #8             // Hello#8 = Utf8               Hello#9 = String             #10            // World#10 = Utf8               World#11 = Class              #12            // com/lgt/Test#12 = Utf8               com/lgt/Test#13 = Utf8               Code#14 = Utf8               LineNumberTable#15 = Utf8               LocalVariableTable#16 = Utf8               this#17 = Utf8               Lcom/lgt/Test;#18 = Utf8               main#19 = Utf8               ([Ljava/lang/String;)V#20 = Utf8               args#21 = Utf8               [Ljava/lang/String;#22 = Utf8               a#23 = Utf8               Ljava/lang/String;#24 = Utf8               SourceFile#25 = Utf8               Test.java// 下面是类中每个方法的描述,下面是构造方法
{public com.lgt.Test();// 方法描述:形参列表和返回值,v代表没有返回值descriptor: ()V// 方法标识:public方法flags: (0x0001) ACC_PUBLIC// 方法code属性Code:// stack:操作数栈最大数量// locals:局部变量表长度,上面我们就定义了一个变量// args_size:方法接收参数长度stack=1, locals=1, args_size=1// 下面的数字表示偏移量,冒号后面为操作码,最后为操作树// aload表示字节码指令,意思为压栈操作0: aload_0// 表示执行方法,#1代表常量池中Object的init初始化方法1: invokespecial #1                  // Method java/lang/Object."<init>":()V// 结束操作4: return// 行号表:字节码指令的偏移量和java源代码中的行号一一对应关系LineNumberTable:// line9表示java文件中第九行,0对应上面的偏移量0line 9: 0// 局部变量表:介绍了方法中所有的参数信息LocalVariableTable:Start  Length  Slot  Name   Signature0       5     0  this   Lcom/lgt/Test;public static void main(java.lang.String[]);descriptor: ([Ljava/lang/String;)Vflags: (0x0009) ACC_PUBLIC, ACC_STATICCode:stack=1, locals=2, args_size=10: ldc           #7                  // String Hello2: astore_13: ldc           #9                  // String World5: astore_16: returnLineNumberTable:line 11: 0line 12: 3line 13: 6LocalVariableTable:Start  Length  Slot  Name   Signature0       7     0  args   [Ljava/lang/String;3       4     1     a   Ljava/lang/String;
}
SourceFile: "Test.java"

参考:
字节码指令详解
JAVA字节码文件之第三篇(访问标识)
javap命令详解
Java字节码常量池深入剖析

   上面为字节码文件详解,包含了常用的大多数属性解释,本文已经足够,如果要完全理解上面说的String创建两个对象的问题,还是得仔细看一下上面这段解释。
在这里插入图片描述

   上图表明底层是创建了两个对象,一个是Hello,一个是World,但有人就纳闷了,我明明就只有一个对象a呀,哪来的两个。注意了,我们现在探讨的是jvm的底层哦,下图确实说明只有一个变量a,但那是一个字面量,也就是一个key而已,两行代码分别对应了两个对象的地址。

在这里插入图片描述

   这里大家记着一句话,非常好使:

   只要是使用双引号赋值的字符串,那么在编译的时候,java会将其放在方法区中的字符串常量池中,在放入前会进行检查,如果有内容相同的则返回地址,这就是享元模式的雏形。

2. String进行相加相减等操作时一定会创建新的对象

   java中字符串操作大致分为两类,一类是编译时操作,也就是用双引号赋值的字符串其实在编译时就执行并放入了常量池,一类则是运行时操作,也就是在程序运行时产生的字符串,这种字符串最后也会放入常量池,只不过执行步骤不一样。

   一般没有完全使用双引号定义的字符串,都在运行时才会产生,就是我们对字符串进行加减或者拼接分割操作等,这时候java会先在堆中创建一个对象,之后再去常量池中寻找,如果有则直接拿来相应的地址,但是,这已经在堆中存在了一个对象,这也就是为什么一定会创建新对象的原因。

   为什么要说这个呢,看一个例子大家就明白了。

在这里插入图片描述

   我们对a变量进行了截取操作,按照以往的理解,a对象已经被更改了,a应该也输出World,但实际并没有,这也是我们最开始常见的一个错误,一定要将操作后的字符串重新赋值给a才算完事儿,不知道你有没有犯过这个错误呢?

3. new String(“abc”)和直接 “abc”的区别

   如果对上面的例子明白了之后就会很好理解这两个的区别。
   直接用双引号赋值的字符串会在编译时放入常量池,而new String一定会先在堆中存放一个对象,后面去常量池寻找对应的字符串。
在这里插入图片描述

   看一下上面这个例子,一般只有纯双引号操作的字符串,最后才相等,其它操作基本都会重新创建字符串,而这里要介绍一个特殊的方法,intern();这个方法的原理是先在字符串常量池找内容相同的字符串,如果有则返回地址,没有则在常量池放入,返回新地址,所以比较的结果是true。

4. String类常用的方法

  1. endsWith:判断字符串是否以指定的后缀结束
  2. startsWith,判断字符串是否以指定的前缀开始
  3. equals,字符串相等比较,不忽略大小写
  4. equalsIgnoreCase,字符串相等比较,忽略大小写
  5. indexOf,取得指定字符在字符串的位置
  6. lastIndexOf,返回最后一次字符串出现的位置
  7. length,取得字符串的长度
  8. replaceAll,替换字符串中指定的内容,注意赋值原字符串
  9. split,根据指定的表达式拆分字符串
  10. substring,截子串
  11. trim,去前尾空格
  12. valueOf,将其他类型转换成字符串
  13. contains,检测字符串包含其它字符串

5. String中format方法(重点)

   这是String类中最为强大的方法之一,能对字符串进行多种复杂和高级操作,比如补0操作,日期格式化,进制转换都能做,不要在操作数字的时候,如果有在数字前后补0操作时直接拼接字符串啦。
在这里插入图片描述

参考:JAVA字符串格式化-String.format()的使用

6. String.join()方法

   这个方法是用指定的分割符将一串字符拼接起来,比如将123拼接位1-2-3这种,使用起来也很简单。

  String join = String.join("-", "1", "2", "3");// join = 1-2-3System.out.println("join = " + join);

   为什么要单独把这个方法拎出来呢,首先这是一个平常被大家忽略的方法,其实在某些指定场景下,这个方法使用起来要更加的方便高效。从底层代码来看,join方法会先创建一个固定大小的String数组for循环进行拼接,所以效率相对于下面两个方法稍微快一些。

在这里插入图片描述

   但它的局限性也比较大,不适合在循环中操作,同时在比较复杂的场景也不适合,因为每次操作都返回一个新的字符串,需要不断地赋值,体验稍差,最需要注意的一点是,大多数jdk版本都不会识别null的情况,如果是null不会报错,但会把null打印出来。

7. StringBuilder和StringBuffer(重点)

   这两个方法介绍起来也简单,首先记住一句话,多次操作不会创建新的对象

   这个就是说你不断拼接字符串的过程中,对象始终是一个,不会在创建新的对象,这对于操作大的字符串非常有效,同时对于有些要再循环中拼接或者需要复杂判断得出的字符串非常有用。

   不同点:

StringBuilderStringBuffer
线程不安全线程安全
效率较快效率相对较低

   其实大多数人堆线程安全不安全的概念还不是很清楚,所谓线程安全就是在操作时需要排队,一个人操作完后另一个人才能继续操作,StringBuffer之所以相对较慢是因为底层的方法都有synchronized 关键字修饰,安全了效率肯定相对较慢啦。

   如何选择这两个方法,大多数我们使用的都是StringBuilder,因为效率较快,如果你的系统是薪酬或者医院或者管理系统,建议使用StringBuffer,这些系统都是安全第一的。
在这里插入图片描述

   简单说说为什么会有线程不安全的问题,首先这两个类都继承的是AbstractStringBuilder,虽然两者在实现上有些许不同,StringBuffer 每次获取 toString 都会直接使用缓存区的 toStringCache 值来构造一个字符串,而 StringBuilder 则每次都需要复制一次字符数组,再构造一个字符串。但是最终都是在AbstractStringBuilder进行字符串的处理,java的源码大多数都喜欢将一些对象定义为全局变量,不会创建新的对象,只是不断地在改变变量的值,这是一种优化效率的常见手段,如果线程并发过多,有时候就可能出现内容不正确的问题,就是A线程给value赋值aaa正准备拼接,结果B线程正好将value值改为了bbb,那A线程中就出现了字符bbb。这也是StringBuffer的方法有synchronized 关键字的原因。

   这次对于String的探索就到这里,之后还会对java中多个重要知识点进行研究深入,比如抽象类、集合、map、读写流、Stream等等,如果文章中有什么不对的地方还请大家指出共同探索。

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

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

相关文章

【ARM+Codesys案例】树莓派+Codesys软PLC方案在包装行业灌装旋盖机的应用

ARM系列支持&#xff1a;全志T3、RK3568、树莓派 机型定义&#xff1a;双工位旋盖机 旋盖机主要适用于不同规格的材质及不同规格的盖、旋&#xff08;轧&#xff09;盖。适用螺旋盖、防盗盖、防撞盖、压入盖等。压力可方便调整&#xff0c;根据瓶盖大小设置取盖位。结构紧凑、…

Linux--进程间通信(2)(有名管道)

目录 1.原理 2.创建命名管道 3.使用命名通道实现简单的通信 4.使用创建的命名管道 1.原理 匿名管道没有名称&#xff0c;它们是通过句柄在父进程和子进程之间传递的。这意味着匿名管道只能用于具有父子关系的进程之间。 但如果程序之间没关系&#xff0c;那么这时候就要用…

2024 年“泰迪杯”A 题:生产线的故障自动识别与人员配置--第四题(用遗传算法解决生产线排班问题--matlab代码)

问题背景&#xff1a; 问题四&#xff1a;根据实际情况&#xff0c;现需要扩大生产规模&#xff0c;将生产线每天的运行时间从 8 小时增加 到 24 小时不间断生产&#xff0c;考虑生产线与操作人员的搭配&#xff0c;制定最佳的操作人员排班方案&#xff0c;要求满足以下条件&am…

RedHat9网络配置设计

目录 一、实验目的 二、实验过程 1、配置新网络接口 2、多网卡配置网络 3、网络接口的绑定&#xff0c;进行远程访问 4、配置网络接口的组合 一、实验目的 本次实验的目的是使用nmcli命令工具配置网络&#xff0c;ens160配置多个网卡&#xff0c;进行网络接口的绑定与组合…

Redis 主从搭建简单教程

安装单机 首先拿到安装包 wget https://download.redis.io/releases/redis-7.0.15.tar.gz然后进行解压 tar -zxvf redis-7.0.15.tar.gz 然后创建一个文件夹myredis将原始配置文件进行备份 mkdir /myrediscp redis.conf /myredis/redis7.conf 将配置文件复制进去 最后使用vim编…

室内也可以用北斗定位?还能用RTK?

室内卫星顾名思义&#xff0c;就是在室内有遮挡环境中的卫星定位技术&#xff0c;众所周知&#xff0c;目前全球几大GNSS定位系统已经很完善&#xff0c;但是GNSS有个致命的弱点&#xff0c;就是地面如果有遮挡就没有信号&#xff0c;在这样的条件下&#xff0c;在室内定位场景…

RT_Thread内核源码分析(一)——CM3内核和上下文切换

目录 一、程序存储分析 1.1 CM3内核寻址空间映射 1.2 程序静态存储和动态执行 二、CM3内核相关知识 2.1 操作模式和特权极别 2.2 环境相关寄存器 2.2.1 通用寄存器组&#xff0c; 2.2.2 状态寄存器组 2.2.3 模式切换环境自动保存 2.2.4 函数调用形参位置 2.3 …

信息安全基础(补充)

&#xff09;的内容主要有数据备份、数据修复、系统恢复等。响应&#xff08;Respons&#xff09;的内容主要有应急策略、应急机制、应急手段、入侵过程分析及安全状态评估等。 面向数据挖掘的隐私保护技术主要解决高层应用中的隐私保护问题&#xff0c;致力于研究如何根据不同…

android studio 导入github里的项目后提示:Add Configuration

原文链接&#xff1a;https://blog.csdn.net/weixin_45677723/article/details/125940912 从github上面clone项目&#xff0c;出现下图问题&#xff1a; 解决问题&#xff1a; 我这个的情况是因为多文件嵌套了&#xff0c;我用Android Studio打开的是A文件&#xff0c;而B项…

移除重复节点

题目链接 移除重复节点 题目描述 注意点 链表未排序链表长度在[0, 20000]范围内链表元素在[0, 20000]范围内 解答思路 使用Set存储访问过的链表中出现的节点值&#xff0c;当遍历到链表的某个节点在Set中出现过&#xff0c;则需要将该节点的前一个节点next指针指向该节点的…

InternLM2-Math-Plus全面升级,全尺寸最强的开源数学模型

总览 数学能力是大语言模型推理水平的重要体现。上海人工智能实验室在推出领先的开源数学模型InternLM2-Math的三个月之后对其进行了升级&#xff0c;发布了全新的 InternLM2-Math-Plus。升级后的 InternLM2-Math-Plus 在预训练和微调数据方面进行了全面的优化&#xff0c;显著…

【二叉树】非递归实现前中后序遍历

目录 前言 算法思想 非递归实现前序遍历 过程分析 代码 非递归实现中序遍历 过程分析 代码 非递归实现后序遍历 过程分析 代码 前言 1&#xff09;前序&#xff1a;根 左子树 右子树 2&#xff09;中序&#xff1a;左子树 根 右子树 3&#xff09;后序&#xff1…

邮箱调用接口的服务有哪些?怎么配置接口?

邮箱调用接口安全性如何保障&#xff1f;使用邮箱服务器的方法&#xff1f; 邮箱调用接口为各种应用和系统提供了便捷的电子邮件发送与接收功能。选择合适的邮箱调用接口服务可以大大提升工作效率和用户体验。本AokSend将探讨一些主要的邮箱调用接口服务。 邮箱调用接口&…

MySQL(进阶)--索引

目录 一.存储引擎 1.MySQL体系结构​编辑 2.存储引擎简介 3.存储引擎特点 (1.InnoDB (2.MyISAM (3.Memory 4.存储引擎选择 二.索引 1.索引概述 2.索引结构 3.索引分类 4.索引语法 (1.创建索引 (2.查看索引 (3.删除索引 5.SQL性能分析 (1.SQL执行频率 (2.慢查…

【Sql Server】随机查询一条表记录,并重重温回顾下自定义函数的封装和使用

大家好&#xff0c;我是全栈小5&#xff0c;欢迎来到《小5讲堂》。 这是《Sql Server》系列文章&#xff0c;每篇文章将以博主理解的角度展开讲解。 温馨提示&#xff1a;博主能力有限&#xff0c;理解水平有限&#xff0c;若有不对之处望指正&#xff01; 目录 前言随机查询语…

Android 中资源文件夹RES/RAW和ASSETS的使用区别

文章目录 1、res/raw 文件夹1.1、特点1.2、使用方法1.3、示例&#xff1a; 2. assets 文件夹2.1、特点2.2、使用方法2.3、示例&#xff1a; 3、使用场景3.1、res/raw 使用场景3.2、assets 使用场景 4、比较与选择5、文件夹选择的建议6、 示例代码总结6.1、res/raw 示例6.2、ass…

电瓶车进电梯识别报警摄像机

随着电动车的普及&#xff0c;越来越多的人选择电动车作为出行工具。在诸多场景中&#xff0c;电梯作为一种常见的交通工具&#xff0c;也受到了电动车用户的青睐。然而&#xff0c;电动车进入电梯时存在一些安全隐患&#xff0c;为了提高电动车进电梯的安全性&#xff0c;可以…

小程序自动化辅助渗透脚本(2024)

简介 1.还在一个个反编译小程序吗&#xff1f; 2.还在自己一个个注入hook吗&#xff1f; 3.还在一个个查看找接口、查找泄露吗&#xff1f; 现在有自动化辅助渗透脚本了&#xff0c;自动化辅助反编译、自动化注入hook、自动化查看泄露 注&#xff1a;本工具仅用于学习交流&…

Java中的JSON神器,如何轻松玩转复杂数据结构

哈喽&#xff0c;大家好&#xff0c;我是木头左&#xff01; 一、揭秘JSON世界的基石 在Java的世界中&#xff0c;JSON&#xff08;JavaScript Object Notation&#xff09;是一种轻量级的数据交换格式&#xff0c;它基于文本&#xff0c;易于阅读和编写&#xff0c;同时也易于…

站内信设计

参考文章&#xff1a;https://cloud.tencent.com/developer/article/1684449 b站站内信业务设计&#xff1a; 消息的类型分为&#xff1a; 1、系统消息 2、、点赞、回复等用户行为之间的消息(事件提醒) 3、用户之间的消息 系统消息 用一个用户消息表可以吗&#xff1f; 可…