IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> 【Java】带你从零到一系列15 深入String类1 -> 正文阅读

[Java知识库]【Java】带你从零到一系列15 深入String类1

前言:String类是我们很常见的一种创建文本的类,但它也有许多有趣的地方,或者是可以深入了解一下的地方,所以我们本篇博客将带你深入String类。

String类表示文本,即一系列 Unicode字符。String类是不可变的,对String类的任何改变,都是返回一个新的String类对象。

每文一图:


一.创建字符串

对于字符串,我们先简单提一个问题:

问题:什么是字符,什么是字符串?

字符是以' '进行修饰的,在里面只能放一个字母或者汉字,不能多放;而字符串是以" "修饰的,里面可以放一个,也可以放多个字符或者汉字。而这些都属于常量(字符常量/字符串常量)

1.常见的构造 String 的方式

那么接下来就要进入String的世界了,首先要了解的是常见的构造 String 的方式,常见的String方式有三种:

public class TestDemo {
    public static void main(String[] args) {
        //方式一 直接创建赋值
        String str = "hello";
        System.out.println(str);
        
        //方式二 调用构造方法进行构造对象
        String str2 = new String("world");
        System.out.println(str2);
        
        //方式三 将数组转换为字符串
        char [] arr = {'a','b','c'};
        String str3 = new String(arr);
        System.out.println(str3);

    }
}

打印结果:

在官方文档上 (点我 我是官方文档) 我们可以看到 String 还支持很多其他的构造方式, 我们用到的时候去查就可以了。


2.字符串的习性

对于字符串,我们先来了解它的一些习性:

首先是String 也是引用类型.,比如String str = "hello"

public static void main(String[] args) {
        String str = "hello";
        String str2 = str;
        System.out.println(str);
        System.out.println(str2);
        //打印结果:
        //hello
        //hello
    }

这样的代码内存布局如下,所以String类是一种引用类型:

然后我们再试试看改一下str里面的内容:

 public static void main(String[] args) {
        String str = "hello";
        String str2 = str;
        System.out.println(str);
        System.out.println(str2);
        System.out.println("================");
        str = "world";
        System.out.println(str);
        System.out.println(str2);
        
        //打印
        //hello
        //hello
        //================
        //world
        //hello
    }

所以修改内容的时候,其实只是修改该引用指向的内容,而不是修改引用:


二.字符串比较相等

Java 中要想比较字符串的内容, 必须采用String类提供的equals方法:

    public static void main(String[] args) {
        String str1 = new String("Hello");
        String str2 = new String("Hello");
        System.out.println(str1.equals(str2));
        // System.out.println(str2.equals(str1)); 
        // 或者这样写也行
        // 执行结果true
    }

对于equals方法的写法,是这样子的:(a).equals(b)就是a和b比较。

就好像上面的代码提到的,a在前面和b在前面都可以,但是我们最好使用哪一个在前面呢?我们需要注意:

对比下面两种方式哪一种更好:

public class TestDemo {

    public static void main(String[] args) {
        String str = new String("Hello");
        // 方式一
        System.out.println(str.equals("Hello"));
        // 方式二
        System.out.println("Hello".equals(str));
    }

在这里,我们更推荐使用 “方式二”,一旦 str 是 null,方式一的代码会抛出异常,而方式二不会。“Hello” 这样的字面值常量, 本质上也是一个 String 对象, 完全可以使用 equals 等 String 对象的方法。

对比两者的运行:

    public static void main(String[] args) {
        String str = null;
        // 方式一
        System.out.println(str.equals("Hello"));  // 执行结果 抛出 java.lang.NullPointerException 异常
        // 方式二
        System.out.println("Hello".equals(str));  // 执行结果 false
    }
    

所以我们总结得出:

当我们需要对比两个字符串的时候,可以使用equals方法,但是最好将已知的字符串放在前面,不会导致抛出异常。如果想要检测异常的,也可以把已知字符串放到后面。

然后我们除了用equals对比,我们直接对比,看看接下来这个代码,这个代码输出的会是什么呢:

public static void main(String[] args) {
        String str = "hello";
        String str2 = new String("hello");
        System.out.println(str == str2);
        //输出:false
    }

String 使用 == 比较并不是在比较字符串内容, 而是比较两个引用是否是指向同一个对象.

对于这里的str和str2,是指自身的引用是否相同,所以对于这里他们两个引用的hello不同,引用也就不一样了。但这里我们想说的不是引用,而是引出一个概念:字符串常量池。

什么是池?

"池" 是编程中的一种常见的, 重要的提升效率的方式, 我们会在未来的学习中遇到各种 “内存池”, “线程池”, "数据库连接池"

比如:
现实生活中有一种女神称为 “绿茶”,在和高富帅谈着对象的同时,还可能和别的屌丝搞暧昧。这时候这个屌丝被称为 “备胎”. 那么为啥要有备胎? 因为一旦和高富帅分手了,就可以立刻找备胎接盘,这样效率比较高如果这个女神同时在和很多个屌丝搞暧昧,那么这些备胎就称为备胎池

概念
Class文件常量池int a = 10; 放磁盘上的
运行时常量池当程序把编译好的字节码文件,加载到JVM当中后,会生成一个运行时常量池 [方法区] 、实际上是Class文件常量池在运行时加载出来的。
字符串常量池主要存放字符串常量,本质是一个哈希表(String Talbe),是存放双引号引起了的字符串常量,实际上是存放在堆上的。

什么是哈希表?

是一种数据结构,描述和组织数据的一种方式。(在这里我们只简单介绍一下,后续还会有关于哈希表的文章。)

比如说:对于一组数据 12 45 2 7 15 92 ,我们要查找他们之间的一个关键字。我们可以选择顺序查找,也就是一个一个对比去查找,但是时间复杂度可以达到O(n),第二种是排序+二分查找,时间复杂度相对少一些。而这里,我们的哈希表,是这样子的:

这就是哈希表的大概理解,接下来我们进入字符串常量池中。


三.字符串常量池

还是这代码,但是这次,我们要刨析它在底层中,是如何创建如何实现的,并且我们看一下这个字符串常量池,又是如何相关的。

public static void main(String[] args) {
        String str = "hello";
        String str2 = new String("hello");
        System.out.println(str == str2);
        //输出:false
    }

首先对于这一段代码我们先做一个图,待会就在这个图中进行讲解,这里有栈,还有堆,而在上面说到字符串常量池是在堆里的,所以这里也画出来了:

然后我们来创建str,而对于创建的过程,其实是这样子的:
在这里插入图片描述
这样子就建立好了这个str的逻辑,然后这是第一次创建的时候,是没有的时候,但是当我们有的时候,又是另一种情况了,这里我们继续我们的代码:

在这里插入图片描述
这里就是当第二次的时候,是怎么样运行的,当然,在我们的哈希表中,有一个null,这里实际上是一个next域,也就是可以像我们上面那样,像一个链表一样接下去,放在同一个String Table中,比如这样:

这就是我们对于这一段简简单单的代码的解读,然后我们这里只是纸上说说,是不是真的,我们可以去打一个断点调试起来,看一看内存中的量:

实际上也是如此,strstr2中的指向是不一样的,但是其指向的对象中的val域是一样的,都是指向了字符串"hello"的。所以这就是字符串常量池的底层原理。

那么上面的是有new一个String的,但是现在我们如果不new呢,比如下面的代码,他又是什么样子的,我们来看一下:

    public static void main(String[] args) {
        String str = "hello";
        String str2 = "hello";
        System.out.println(str == str2);
        //打印true
    }

那么这里既然没有新建对象,那么在创建好第一个"hello"之后,在第二次的时候,就会在常量池中找到这个"hello",所以就直接指向了这个常量,不用再创建了:在这里插入图片描述
然后我们还有下面的一些情况:

第一种:字符串是拼接的

    public static void main(String[] args) {
        String str = "hello";
        String str2 = "he"+"llo";
        //此时 两者都是常量,在编译的时候就会拼接好确定是"hello"
        System.out.println(str == str2);
        //所以打印true
    }

第二种:变量拼接字符串

    public static void main(String[] args) {
        String str = "hello";
        String str2 = "he"+"llo";
        String str3 = "he";
        String str4 = str3+"llo";
        //此时str3是一个变量 ———编译的时候不知道是什么,所以不确定
        System.out.println(str == str4);
        //所以打印false
    }

四.理解字符串不可变

字符串是一种不可变对象,它的内容不可改变。String 类的内部实现也是基于 char[] 来实现的,但是 String 类并没有提供 set 方法之类的来修改内部的字符数组。

比如说这个代码:

    public static void main(String[] args) {
        String str = "hello" ;
        str = str + " world" ;
        str += "!!!" ;
        System.out.println(str);
        // 执行结果hello world!!!
    }

别看它只有一点点,因为字符串不可变,所以导致了在拼接其他字符串的时候,产生的是新的对象。

这里短短的代码,产生了5个对象。

形如 += 这样的操作, 表面上好像是修改了字符串, 其实不是. 内存变化如下:

+= 之后 str 打印的结果却是变了,但是不是 String 对象本身发生改变,而是 str 引用到了其他的对象。

引用相当于一个指针,里面存的内容是一个地址。我们要区分清楚当前修改到底是修改了地址对应内存的内容发生改变了,还是引用中存的地址改变了。

那么如果实在需要修改字符串, 例如, 现有字符串 str = "Hello" , 想改成 str = "hello" , 该怎么办?这时候我们就有一个反射可以把它搞定。

使用 “反射” 这样的操作可以破坏封装, 访问一个类内部的 private 成员

IDEA 中 ctrl + 左键 跳转到 String 类的定义,可以看到内部包含了一个 char[] ,保存了字符串的内容。

    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        String str = "Hello";
        
        // 获取 String 类中的 value 字段. 这个 value 和 String 源码中的 value 是匹配的. 
        Field valueField = String.class.getDeclaredField("value");
        // 将这个字段的访问属性设为 true
        valueField.setAccessible(true);
        // 把 str 中的 value 属性获取到. 
        char[] value = (char[]) valueField.get(str);
        // 修改 value 的值
        
        
        value[0] = 'h';
        System.out.println(str);
        
        //打印hello

    }

对于这个代码,其实我们在这篇文章中暂时不需要理解那么多,只需要知道有这种反射的存在就好。

关于反射:反射是面向对象编程的一种重要特性, 有些编程语言也称为 “自省”。

指的是程序运行过程中,获取/修改某个对象的详细信息(类型信息,属性信息等),相当于让一个对象更好的 “认清自己”。Java 中使用反射比较麻烦一些。

为什么 String 要不可变?(不可变对象的好处是什么?)

  1. 方便实现字符串对象池,如果 String 可变,那么对象池就需要考虑何时深拷贝字符串的问题了。
  2. 不可变对象是线程安全的。
  3. 不可变对象更方便缓存 hash code,作为 key 时可以更高效的保存到 HashMap 中。

所以,对于String是不可变的,我们有了一定的了解,而且在平时中,因为这个不可变,我们有一些代码也不应该写,比如:

 public static void main(String[] args) {
        String str = "hello" ;
        for(int x = 0; x < 1000; x++) {
            str += x ;
        }
        System.out.println(str);
    }

这种代码会产生大量的临时对象,效率比较低!!!


这就是本篇深入String类1的全部内容啦,接下来我们还有深入String类2,里面会讲解到关于String中优化的几个点。欢迎关注。一起学习,共同努力!也可以期待这个系列接下来的博客噢。

链接:都在这里! Java SE 带你从零到一系列

还有一件事:

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2021-12-06 15:07:02  更:2021-12-06 15:08:20 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 4:28:48-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码