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还是private
  flags: (0x0021) ACC_PUBLIC, ACC_SUPER		
  // 类的相对路径,#11表示这是常量池中索引为11的值
  this_class: #11                         // com/lgt/Test
  // 父类的相对路径,可以看到父类是Object
  super_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对应上面的偏移量0
        line 9: 0
      // 局部变量表:介绍了方法中所有的参数信息
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/lgt/Test;

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
    Code:
      stack=1, locals=2, args_size=1
         0: ldc           #7                  // String Hello
         2: astore_1
         3: ldc           #9                  // String World
         5: astore_1
         6: return
      LineNumberTable:
        line 11: 0
        line 12: 3
        line 13: 6
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       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-3
  System.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等等,如果文章中有什么不对的地方还请大家指出共同探索。

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

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

相关文章

常见的螺纹防松措施有哪些?——SunTorque智能扭矩系统

智能扭矩系统-智能拧紧系统-扭矩自动控制系统-SunTorque 螺纹连接作为机械工程中常见的连接方式&#xff0c;其稳定性和可靠性对于整个机械系统的正常运行至关重要。然而&#xff0c;由于振动、冲击、温度变化等因素的影响&#xff0c;螺纹连接往往会出现松动现象&#xff0c;…

react中子传父信息

思路是&#xff1a; 在父组件定义一个函数接受参数&#xff0c;接收的参数用于接收子组件的信息&#xff0c;把函数传给子组件&#xff0c;子组件调用父亲传来的函数并把要告诉父亲的话传到函数中&#xff0c;就实现了子传父消息 import { useState } from reactimport { use…

C++学习/复习7--泛型编程/函数模板/类模板

一、泛型编程 1.Swap()函数的模板实现 二、函数模板 1.概念 2.格式 3.实例化 &#xff08;1&#xff09;隐式与显示 注意事项&#xff1a;隐式与显示类型转换会产生临时变量&#xff0c;临时变量有常性&#xff0c;所以形参前加const 三、类模板 1.定义 2.例1 3.例2 4.注意事…

nginx流量监控:goAccess安装与使用

关于goAccess GoAccess 是一款实时、快速的日志分析工具&#xff0c;专门设计用于分析Web服务器日志&#xff0c;特别是Nginx日志。 安装 &#xff08;1&#xff09;准备相关依赖 # Missing development libraries for ncursesw # centOS yum install -y ncurses-devel # U…

qmt量化交易策略小白学习笔记第7期【qmt策略之股票快照指标】

qmt策略之股票快照指标 qmt更加详细的教程方法&#xff0c;会持续慢慢梳理。 也可找寻博主的历史文章&#xff0c;搜索关键词查看解决方案 &#xff01; 感谢关注&#xff0c;需免费开通量化回测与咨询实盘权限&#xff0c;可以和博主联系&#xff01; 股票快照指标 提供标…

python双色球选号程序的实现与解析

新书上架~&#x1f447;全国包邮奥~ python实用小工具开发教程http://pythontoolsteach.com/3 欢迎关注我&#x1f446;&#xff0c;收藏下次不迷路┗|&#xff40;O′|┛ 嗷~~ 目录 一、引言&#xff1a;双色球选号游戏的魅力 二、程序设计与实现 1. 生成红色球号码 2. 生…

OpenHarmony迎来首个互联网技术统一标准,鸿蒙OS生态走向如何?

开源三年半&#xff0c;OpenHarmony(以下简称“开源鸿蒙”)迎来了新进展。在5月25日召开的「OpenHarmony开发者大会」上&#xff0c;鸿蒙官宣了开源鸿蒙设备统一互联技术标准。 一直以来&#xff0c;各行业品牌操作系统相互独立、难以协同,成为其互联互通的痛点。为进一步解决…

USST新生训练赛div2+div3题解

目录 前言题解部分B Ichihime and Triangle(800)题目大意题解代码实现 C Kana and Dragon Quest game(900)题目大意题解代码实现 J Squares and Cubes(800)题目大意题解代码实现 F Double Sort(1200)题目大意题解代码实现 I Minimize the Thickness(1100)题目大意题解代码实现 …

华为CE6851-48S6Q-HI升级设备版本及补丁

文章目录 升级前准备工作笔记本和交换机设备配置互联地址启用FTP设备访问FTP设备升级系统版本及补丁 升级前准备工作 使用MobaXterm远程工具连接设备&#xff0c;并作为FTP服务器准备升级所需的版本文件及补丁文件 笔记本和交换机设备配置互联地址 在交换机接口配置IP&#…

LAMP源码编译安装——CentOS7

文章目录 LAMP是什么LAMP软件组件LinuxApacheMySQLPHP 源码安装Apache一、准备工作二、安装环境依赖包三、配置软件模块四、编译及安装五、优化配置文件路径六、添加httpd系统服务&#xff08;有两种方法&#xff09;方法一&#xff1a;方法二&#xff1a; 七、修改httpd 服务配…

LabVIEW软件需求分析文档内容和编写指南

编写LabVIEW软件需求分析文档&#xff08;Software Requirements Specification, SRS&#xff09;是软件开发的关键步骤之一。以下是详细的内容结构、编写指南和注意事项&#xff1a; 内容结构 引言 项目背景&#xff1a;简要介绍项目背景和目的。 文档目的&#xff1a;说明需…

利用NewGIS平台将FME模板发布为接口

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 前言 一、模板编写 二、发布模板 三、接口获取 四、移动端调用 ​​​​​ 前言 在实际的应用生产过程中&#xff0c;尤其是移动端GIS软件的开发&#xff0c;针对一些闭…

C++学习笔记(21)——继承

目录 1. 继承的概念及定义1.1 继承的概念1.2 继承定义1.2.1 定义格式1.2.2 继承关系和访问限定符1.2.3 继承基类成员访问方式的变化 继承的概念总结&#xff1a; 2. 基类和派生类对象赋值转换3.继承中的作用域4.派生类的默认成员函数知识点&#xff1a;派生类中6个默认成员函数…

头文件大小写引发的报错

jenkins下打包编译报错如下&#xff0c;提示编译zynqCan.c时找不到“syscfgpll/sysCfgpll.h”文件。 但IDE下编译是没有报错也没有警告的&#xff0c;工程中也存在文件“syscfgpll/sysCfgPll.h”。 仔细观察发现&#xff0c;报错说的是找不到头文件“syscfgpll/sysCfgpll.h”…

蓝桥杯物联网竞赛_STM32L071_18_长短按键检测

长短按键的检测是国赛题里面遇到的&#xff0c;省赛没出过有两种实方法 定时器配置&#xff1a; 定时器的话要比delay准确&#xff0c;其中tim7定时器的准度最高 定时器预分配配置32 - 1&#xff0c;计数周期是10000 - 1这样做那么32MHZ/32也就是一秒钟记录10^6的数&#xf…

c++ vector实现出现的一些问题

目录 前言&#xff1a; 浅拷贝问题: typename指定类型&#xff1a; 前言&#xff1a; 最近学习了c vector的使用&#xff0c;然后也自己实现了一下vector的部分重要的功能。然后在其中出现了一些问题&#xff0c;在这就主要记录一下我解决哪些bug。 浅拷贝问题: 在实现res…

Weblogic SSRF漏洞 [CVE-2014-4210]

漏洞复现环境搭建请参考 http://t.csdnimg.cn/svKal docker未能成功启动redis请参考 http://t.csdnimg.cn/5osP3 漏洞原理 Weblogic的uddi组件提供了从其他服务器应用获取数据的功能并且没有对目标地址做过滤和限制&#xff0c;造成了SSRF漏洞&#xff0c;利用该漏洞可以向内…

若依跳转(新增)页面,在菜单中不显示的页面

在router.js文件中 跳转方式 this.$router.push(/monitor/b/b)

路由引入实验(思科)

华为设备参考&#xff1a;路由引入实验&#xff08;华为&#xff09; 技术简介 路由引入技术在网络通信中起着重要的作用&#xff0c;能够实现不同路由协议之间的路由传递&#xff0c;并在路由引入时部署路由控制&#xff0c;实现路径或策略的控制 实验目的 不同的路由协议之…

二十三篇:未来数据库革新:AI与云原生的融合之旅

未来数据库革新&#xff1a;AI与云原生的融合之旅 1. 智能数据库管理&#xff1a;AI的魔法 在数字化时代&#xff0c;数据库技术作为信息管理的核心&#xff0c;正经历着前所未有的变革。AI&#xff08;人工智能&#xff09;和云原生技术的融合&#xff0c;正在重新定义数据库…