【JAVA SE】 深度剖析『字符串String』以及相应的底层源代码

温馨提示

大家好我是Cbiltps,在我的博客中如果有难以理解的句意难以用文字表达的重点,我会有配图。所以我的博客配图非常重要!!!

而且很多知识在代码的注释里,所以代码注释也非常重要!!!

这篇文章首先跟大家讲一下String的一些简单法则

然后后面会针对String不可改变再写一篇文章作为这篇文章的补充

所以可以把这篇文章和下面一篇文章当成一个大类:String类

开篇介绍

其实本节的内容高校几乎是不讲的,也很少有人对其深究,

但是为了提升自己,了解 String 这个东西底层是如何实现的,就有了今天的文章。

本章的内容是你完全可以当做是对于内功的修炼,百利无一害!

今天画的内存图比较的烧脑,大家仔细看!!!

本章及下一篇文章的重点

  • 认识 String
  • 认识字符串常量池
  • 了解 String 类的基本用法
  • 熟练掌握 String 类的常见操作
  • 认识 StringBufferStringBuilder

正文开始


1. 深度剖析字符串


1.1 了解字符串

C语言中没有字符串类型的,在C++Java中有,叫String

简单的理解两个问题:

什么是字符串:使用双引号" ",可以若干字符,就是字符串常量

什么是字符:使用单引号' '只能有一个字符,就是字符常量

注意:在Java中没有字符串以\0结尾的说法!

紧接着,直接打开String源码,进去看里面的代码,发现是 final修饰 的,说明不能被继承
在这里插入图片描述

1.2 创建字符串

//方式1:
String str = "祥子";//字符串常量
//方式2:
String str2 = new String("花花");//调用构造方法定义
//方式3:把数组变成字符串
char[] chars = {
    
    'a', 'b', 'c'};
String str3 = new String(chars);
System.out.println(str3);

在官方文档上还有很多String的构造方法,大家可以区看看,博主只举了最多的例子!

1.3 String代码的内存布局

首先写一段举例的代码:

public class Main {
    
    

     public static void main(String[] args) {
    
    
        String str = "abcdef";
        String str2 = str;
        System.out.println(str);
        System.out.println(str2);
        /*打印出来是一样的!画一下内存图!*/
        
        /*其实这里的str和str2两个引用都是指向"abcdef"的,然后想一个问题,可以用str2修改"abcdef"吗?
        答案是不可以的,因为:它是字面值常量不能修改的!*/
        
        System.out.println("==============");
        str = "hello";//但是这里的修改是修改的指向!
        System.out.println(str);
        System.out.println(str2);
        /*打印出来不一样!画一下内存图!*/
    }
}

在这里插入图片描述

上面图解就是打印一样的原因,再看一下变化后(修改指向)的内存图:

在这里插入图片描述

然后,重新写一段代码:

public class Main {
    
    

    public static void func(String s,char[] array) {
    
    
        s = "xiangzi";
        array[0] = 'p';
    }
    
    public static void main(String[] args) {
    
    
        String str = "abcdef";
        char[] chars = {
    
    'b','i','t'};
        func(str,chars);
        System.out.println(str);
        System.out.println(Arrays.toString(chars));
    }
    /*上面的问题,一定要画草图了解一下!里面牵扯较复杂的指向问题!*/
    /*所以说,不是 传引用 就是可以改变实的值!你要看这个引用到底干啥了!!*/
}

上面一段代码的内存图及指向的改变请看下面的图:

在这里插入图片描述

所以说,不是 传引用 就是可以改变实的值!你要看这个引用到底干啥了!!


2. 字符串比较相等


2.1 内容比较

内容的比较用的就是 equals方法 进行比较的,大家直接去看源代码或者是文档怎么用就可以了!

下面说一下 equals方法 要注意的点:

 public static void main(String[] args) {
    
    
        String str1 = null;
        /*使用equals的时候要注意:一定要预防空指针异常!*/
        String str2 = "11";//如果想要修改的话,就必须通过反射修改
//        System.out.println(str1.equals(str2));//这样子就是空指针异常
        System.out.println(str2.equals(str1));//这里的str2不是空指针,就不会报错的
    }

2.2 地址比较

直接上代码(包括知识点以及拓展的内容):

public class Main {
    
    
    public static void main4(String[] args) {
    
    
        String str1 = "hello";
        String str2 = new String("hello");
        System.out.println(str1 == str2);
        /*其实这里比较的不是内容,而是地址! 地址不一样,运行的结果就是false!*/

   /**然后下面做一点铺垫(涉及到JVM的知识):
    *
    * Class文件常量池:Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量
    * 池表(Constant Pool Table),用于存放编译期生成的各种字面量与符号引用,这部分内容将在类加
    * 载后存放到方法区的运行时常量池中。
    *
    * 运行时常量池:当程序把编译好的字节码文件加载到JVM当中后,会生成一个运行时常量池(在方法区,就是从磁盘到内存),
    * Class文件常量池会变成运行时常量池。
    *
    * 字符串常量池:本质是一个哈希表(StringTable),JDK1.8开始放在了堆里面,里面存的是驻留字符串的引用,
    * 在堆中的字符串实例被这个哈希表引用之后就等同被赋予了”驻留字符串”的身份,
    * 在JVM中字符串常量池被所有类共享。
    *
    * 什么是哈希表:其实就是一个数据结构,描述和组织数据的一种方式,非常的块!
    *  如果你想知道StringTable如何实现的,可以直接去看JVM的源代码,但是使用C++写的!
    */

来看一下上面代码的内存图:

在这里插入图片描述

然后看下面一段代码:

public static void main(String[] args) {
    
    
        String str1 = "hello";
        String str2 = "hello";
        System.out.println(str1 == str2);
        /*这里肯定就是true了!*/
    }

它的的内存图是这样的:

在这里插入图片描述

这里我画图的时候,str2 的右边的地址是 0x334,大家看的时候注意一下,改的话比较麻烦!

关于上面的知识点,如果想验证的话可以打开反汇编代码进行查看:

在这里插入图片描述
到了这里,字符串是如何创建的都知道了吧,下面就来欣赏一下源代码是怎样实现的!

在JVM的源代码里就可以看到:

一个元素是如何放进去的;

StringTable是如何创建的;

数据是如何转变成hash的等等。

这一切的一切使用C++写的,博主有的也看不懂!

看以看出JAVA是站在巨人的肩膀上的!

在这里插入图片描述

为了加强理解,继续往下看:

public class Main {
    
    
    public static void main(String[] args) {
    
    
        String str1 = "hello";
        String str2 = "he" + "llo";//此时 他两都是常量,编译的时候,就已经确定好了是"hello"
        String str3 = "he";
        String str4 = str3 + "llo";//此时str3是一个变量 -> 编译的时候,不知道是啥? str4不是一个完整的对象!
        /*拼接的hello并不是在常量池里面,拼接的是一个单独的对象!所以运行结果就是false!*/
        System.out.println(str1 == str4);
    }
}

上面的代码中 str2 的时候就是 “hello”,看下面的反汇编代码:

在这里插入图片描述

看一下内存图:

在这里插入图片描述

下面一个例子比较麻烦(重要):

public static void main(String[] args) {
    
    
        String str1 = "11";
        String str2 = new String("1") + new String("1");//这里创建的对象是StringBuilder对象!
        System.out.println(str1 == str2);
    }

直接上内存图:

在这里插入图片描述

而且,为了验证上面的图去看反汇编代码也是一样的:

在这里插入图片描述

那如果是这样的:

//代码1
public static void main(String[] args) {
    
    
        String str2 = new String("1")+new String("1");
        String str1 = "11";
        System.out.println(str1 == str2);//如果反过来依旧是false
    }

在看下面:

//代码2
public static void main(String[] args) {
    
    
        String str2 = new String("1")+new String("1");
        str2.intern();//手动入池
        String str1 = "11";
        System.out.println(str1 == str2);//这里就是true了
    }

到了这里,我就不画图了,我快废了,再也不想画图了…

其实原理解释简单的:

代码1和上面的演示的代码是一样的,

代码2会将拼成的 “11” 手动入池,

然后再创建字符串的时候就会检查是否有这个字符串直接指向即可!

到这里字符串比较的问题就写完了,大家好好看,好好学!

全文结束

其实写到这里就是可以的,但是,我想写的更全面一点,

但是篇幅太长不是一个好的解决方法!

后面我还会在写一篇文章,依旧是关于String的!

是关于String不可改变让其改变的内容!

大家敬请期待!再次感谢大家的点赞和关注!

猜你喜欢

转载自blog.csdn.net/Cbiltps/article/details/122535985