【Java SE】String类(下)

简介: 在我们前面也对字符串进行了简单的使用,在Java当中,String是字符串类型,本质上也是一个类,这个类中提供了很多的方法,我们后续会学习到,现在先来简单看一下String类常见的构造方法

2.4 字符串替换

将一个字符串的内容替换成新的内容,但是还是会返回一个新的对象不会修改原有的对象,至于为什么后面会讲解:

public static void strReplace() {
        String str = "hello world";
        //替换所有的指定内容,不会修改原字符串
        System.out.println(str.replaceAll("l", "i"));
        //替换首个指定内容,不会修改原字符串
        System.out.println(str.replaceFirst("l", "i"));
}

2.5 字符串拆分

这里我们使用的是 String[ ] split(String regex) 方法

2.5.1 按指定字符拆分

public static void strSplit() {
        String str = "hello world is-is-123";
        String[] ret = str.split(" "); //按照空格拆分
        for (String x : ret) {
            System.out.println(x);
        }
        ret = str.split(" ", 3); //部分拆分
        for (String x : ret) {
            System.out.println(x);
        }
}

这个方法的返回值是一个String类型的数组,所以我们需要拿对应的数组来接收,接着可以遍历这个循环来打印这个数组的内容,如果看不懂这种打印,可以参考我之前的程序逻辑控制文章,第二个部分拆分怎么理解呢?就是我要按照指定的字符把他拆分成几段,如果是一段那这这个字符串就不会发生变化,大家下来可以自己尝试下,有些特殊字符可能需要用到转义才能正确拆分。

比如我们拆分 ip 地址和 路径:

public static void StrIntercepting() {
        //拆分ip地址,特殊字符作为分隔符可能无法正确切分,需要加上转义
        String str1 = "192.168.2.1";
        //因为\本身就是个特殊字符,所以需要转义成字面的\才能对.进行转义
        String[] ret = str1.split("\\."); 
        for (String x : ret) {
            System.out.println(x);
        }
        String str3 = "lqg\\work\\code";
        //这里因为\\表示一个\的字面意思,而一个\又是一个特殊字符
        //所以我们还需要\\来表示一个\字面意思,再用它来转义一个\从而才是真正的\
        ret = str3.split("\\\\");
        for (String x : ret) {
            System.out.println(x);
        }
}

2.5.2 多次拆分

    public static void strSplit() {
        //多次拆分
        String str2 = "name=Mike&age=24";
        String[] ret = str2.split("=");
        //第一种方法
        for (String x : ret) {
            String[] tmp = x.split("&");
            for (String s : tmp) {
                System.out.println(s);
            }
        }
        //第二种方法
        for (int i = 0; i < ret.length; i++) {
            String[] tmp = ret[i].split("&");
            for (int j = 0; j < tmp.length; j++) {
                System.out.println(tmp[j]);
            }
        }
    }

这个多次拆分我们可以分析一下,首先 split 方法返回的是一个数组,所以也就是每个数组里面放的是字符串,然后我们想要再次进行拆分就可以访问对应数组下标的字符串进行拆分,这样也就可以实现多次拆分,上面两种方法提供参考。

还有其他的一些方法比如 String trim() 方法,可以去掉字符串开头和结尾的空格字符等等,可以去查阅下帮助手册自行学习,上面已经介绍了一些常用的方法。

3、字符串常量池

3.1 什么是字符串常量池

字符串常量池在 JVM 中是StringTable类,实际上是一个固定大小的HashTable也就是哈希表(一种高效用来查找的数据结构,后续学习数据结构会详细讲解),在不同JDK版本下的字符串常量池的位置以及默认大小是不同的,但是我们今天是用JDK1.8约等于Java8,字符串常量池的位置在堆中,可以设置大小,有范围限制,默认是1009。

在Java程序中,类似于:1,2,3,3.14,"hello" 等字面常量经常被使用,为了程序的运行速度更快,更节省内存,Java为8中基本数据类型和String类都提供了常量池,我们现在只讲述字符串常量池,至于更详细池的了解会在学习JVM的时候进行讲解。

3.2 从内存的角度理解创建String对象

由于常量池在不同的版本是可能不一样的,目前是在Java8上分析:

我们先来看这样一段代码:

public static void main(String[] args) {
        String s1 = "hello";
        String s2 = "hello";
        String s3 = new String("hello");
        String s4 = new String("hello");
        System.out.println(s1 == s2); // true
        System.out.println(s1 == s3); // false
        System.out.println(s3 == s4); // false
    }

我们尝试着先来简单分析一下这段代码:

首先 s1 是用常量字符串进行构造,所以会先去字符串常量池存放的地址找对应的链表节点里面对应的对象有没有指向 "hello" 这个字符串(简单理解就是看有没有对应的字符串的对象),如果没有,先用一个哈希桶,每个桶中是链表的结构,链表节点里面包含存储着这个字符串对象的地址,和哈希值,以及其他东西,并且字符串常量池中存放着这个节点的地址,而我们知道字符串对象中有两部分,一部分是数组一部分是哈希,所以数组的那部分就存了 "hello" 字符串的地址

当 s2 创建的时候,发现已经有了 "hello" 字符串对象,所以在创建 s2 这个引用的时候,指向的就是 s1 指向的对象,即不用再新创建新对象了

当 s3 创建的时候,我们还是会去字符串常量池中找,发现有对应的字符串对象,但是我们是 new对象,也就是 new 代表着新,所以会新建一个对象,而这个对象是String类型,里面有两个部分,由于已经存在了 "hello" 字符串,所以 s3 引用的对象里面数组那部分存的是这个字符串的地址,本质上是新创建了一个对象,但是对象里面的数组还是指向那个字符串

当 s4 创建的时候,跟 s3 一模一样,这里我就不多说,下面我们就来看真实的内存图:

所以通过上图我们也可以看出,只要是new对象,都是唯一的!

还可以看到,使用常量串字符串创建String类型对象的效率更高,并且不用创建新对象还更节省空间,用户也可以将创建的字符串对象通过 intern 方式添加进字符串常量池中。

3.3 intern 方法

这个方法是一个 native 方法(native 方法指底层使用C++实现的,看不到实现的原码),这个方法的作用是手动将创建的String对象添加到常量池中。

public static void main(String[] args) {
        char[] ch = new char[] {'a', 'b', 'c'};
        String s1 = new String(ch); // s1对象并不在常量池中
        s1.intern(); // s1.intern();调用之后,会将s1对象的引用放入到常量池中
        String s2 = "abc"; // "abc" 在常量池中存在了,s2创建时直接用常量池中"abc"的引用
        System.out.println(s1 == s2);
}

这串代码我们可以来简单分析一下:首先 s1 是用字符数组构造的,所以value指向的就是堆中把已有的数组拷贝一份,指向新拷贝的数组,即s1对象并不会放在池中,而后面调用了 intern  方法,也就是将 s1 对象的引用放到常量池,而 s2 发现常量池中有对应这个字符串的对象,就会直接引用 s1 对象的地址。

假设没有使用 intern 这个方法,s1引用的对象与s2并不相同,如果使用了则 s1 与 s2 引用的对象相同!

3.4 一道面试题

在JDK1.8中,请解释一下对象实例化的区别(常量池中都不存在当前字符串):

String str = "hello"; :这个只会开辟一块空间,会把字符串保存在常量池中,然后str共享常量池中的String对象,如果有,则直接引用这个对象。

String str = new String("hello"); :这会开辟两块堆内存空间,字符串"hello"保存在字符串常量池中,然后用常量池中的String对象给新开辟 的String对象赋值。。

String str = new String(new char[] {'h', 'e', 'l', 'l', 'o'}); :先在堆中创建一个String对象,然后利用 coypof 将重新开辟数组空间将参数字符串数组中内容拷贝到String对象中。

4、StringBuilder 和 StringBuffer

4.1 为什么String对象不可变?

前面的很多方法确实证明了String对象的不可变,都会产生一个新的对象,都不会在原有的基础上修改,这是为什么呢?我们再次看一下String类中的原码:

我们先来看String类被 final 修饰了,表示这个类不能被继承,跟里面的 value 数组能不能被修改没有关系,接着再来看 value 数组被 final 修饰了!

那么 final 修饰的数组,表示数组引用的地址不能被改变!也就是 value 引用的对象不能改变,但是可以改变引用对象里面的值,也就是可以改变数组存储的内容!

所以字符串真正不能被改变的原因是前面的 priavte 权限修饰符,表示这个成员变量只能在该类中被访问,而且 String 类并没有提供 getValue 和 setValue 方法!也即没有提供能让你操作这个字符串的方法,你在外部压根访问不到这个数组,你如何修改?这才是String对象不可变的根本原因!

如果要修改字符串的内容如何修改呢?借助StringBuilder和StringBuffer类!

4.2 介绍StringBuilder 和 StringBuffer

我们要尽量避免直接对String类型对象进行修改,因为String类是不能修改的,所有的修改都会创建新对象,效率非常低下。

为了方便字符串的修改,Java就提供了如上两个类,但是这两个类的大部分操作方式是相同的,下面我们就来简单了解下里面常用的方法,用的时候查一下即可:

  • StringBuff append(String str) :在尾部追加,相当于String的+=,可以追加:boolean、char、char[]、 double、float、int、long、Object、String、StringBuff的变量
  • char charAt(int index):获取index位置的字符
  • void setCharAt(int index, char ch):将index位置的字符设置为ch
  • StringBuff insert(int offset, String str):在offset位置插入:八种基类类型 & String类型 & Object类型数据
  • StringBuffer deleteCharAt(int index):删除index位置字符
  • StringBuffer reverse():反转字符串

这些太多了,就不一一列举,下来可以自己查一下。

String和StringBuilder最大的区别在于String的内容无法修改,而StringBuilder的内容可以修改。频繁修改字符串的情况考虑使用StringBuilder。

注意:String 和 StringBuilder类不能直接转换。如果要想互相转换,可以采用如下原则:

  • String 变为 StringBuilder:  利用StringBuilder的构造方法或append()方法
  • StringBuilder 变为 String:  调用toString()方法。

4.3 三种字符串类型的区别

String 的内容不可修改,StringBuffer 与 StringBuilder 的内容可以修改。

StringBuffer 与 StringBuilder大部分功能是相似的。

StringBuffer 采用同步处理,属于线程安全操作,而 StringBuilder 未采用同步处理,属于线程不安全操作。(后期学习会接触多线程,目前了解即可)

相关文章
|
12天前
|
Kubernetes jenkins 持续交付
从代码到k8s部署应有尽有系列-java源码之String详解
本文详细介绍了一个基于 `gitlab + jenkins + harbor + k8s` 的自动化部署环境搭建流程。其中,`gitlab` 用于代码托管和 CI,`jenkins` 负责 CD 发布,`harbor` 作为镜像仓库,而 `k8s` 则用于运行服务。文章具体介绍了每项工具的部署步骤,并提供了详细的配置信息和示例代码。此外,还特别指出中间件(如 MySQL、Redis 等)应部署在 K8s 之外,以确保服务稳定性和独立性。通过本文,读者可以学习如何在本地环境中搭建一套完整的自动化部署系统。
38 0
|
14天前
|
安全 Java API
告别繁琐编码,拥抱Java 8新特性:Stream API与Optional类助你高效编程,成就卓越开发者!
【8月更文挑战第29天】Java 8为开发者引入了多项新特性,其中Stream API和Optional类尤其值得关注。Stream API对集合操作进行了高级抽象,支持声明式的数据处理,避免了显式循环代码的编写;而Optional类则作为非空值的容器,有效减少了空指针异常的风险。通过几个实战示例,我们展示了如何利用Stream API进行过滤与转换操作,以及如何借助Optional类安全地处理可能为null的数据,从而使代码更加简洁和健壮。
42 0
|
2天前
|
Java
java的类详解
在 Java 中,类是面向对象编程的核心概念,用于定义具有相似特性和行为的对象模板。以下是类的关键特性:唯一且遵循命名规则的类名;描述对象状态的私有属性;描述对象行为的方法,包括实例方法和静态方法;用于初始化对象的构造方法;通过封装保护内部属性;通过继承扩展其他类的功能;以及通过多态增强代码灵活性。下面是一个简单的 `Person` 类示例,展示了属性、构造方法、getter 和 setter 方法及行为方法的使用。
|
6天前
|
Java API 开发者
【Java字节码操控新篇章】JDK 22类文件API预览:解锁Java底层的无限可能!
【9月更文挑战第6天】JDK 22的类文件API为Java开发者们打开了一扇通往Java底层世界的大门。通过这个API,我们可以更加深入地理解Java程序的工作原理,实现更加灵活和强大的功能。虽然目前它还处于预览版阶段,但我们已经可以预见其在未来Java开发中的重要地位。让我们共同期待Java字节码操控新篇章的到来!
|
4天前
|
Java
Java 对象和类
在Java中,**类**(Class)和**对象**(Object)是面向对象编程的基础。类是创建对象的模板,定义了属性和方法;对象是类的实例,通过`new`关键字创建,具有类定义的属性和行为。例如,`Animal`类定义了`name`和`age`属性及`eat()`、`sleep()`方法;通过`new Animal()`创建的`myAnimal`对象即可调用这些方法。面向对象编程通过类和对象模拟现实世界的实体及其关系,实现问题的结构化解决。
|
4天前
|
Java API 开发者
【Java字节码的掌控者】JDK 22类文件API:解锁Java深层次的奥秘,赋能开发者无限可能!
【9月更文挑战第8天】JDK 22类文件API的引入,为Java开发者们打开了一扇通往Java字节码操控新世界的大门。通过这个API,我们可以更加深入地理解Java程序的底层行为,实现更加高效、可靠和创新的Java应用。虽然目前它还处于预览版阶段,但我们已经可以预见其在未来Java开发中的重要地位。让我们共同期待Java字节码操控新篇章的到来,并积极探索类文件API带来的无限可能!
|
2天前
|
Java 程序员
Java编程中的对象和类: 初学者指南
【9月更文挑战第9天】在Java的世界中,对象和类构成了编程的基石。本文将引导你理解这两个概念的本质,并展示如何通过它们来构建你的程序。我们将一起探索类的定义,对象的创建,以及它们如何互动。准备好了吗?让我们开始这段Java的旅程吧!
|
8天前
|
存储 C++
C++(五)String 字符串类
本文档详细介绍了C++中的`string`类,包括定义、初始化、字符串比较及数值与字符串之间的转换方法。`string`类简化了字符串处理,提供了丰富的功能如字符串查找、比较、拼接和替换等。文档通过示例代码展示了如何使用这些功能,并介绍了如何将数值转换为字符串以及反之亦然的方法。此外,还展示了如何使用`string`数组存储和遍历多个字符串。
|
11天前
|
存储 Java
Java编程中的对象和类
在Java的世界中,“对象”与“类”是构建一切的基础。就像乐高积木一样,类定义了形状和结构,而对象则是根据这些设计拼装出来的具体作品。本篇文章【8月更文挑战第31天】 将通过一个简单的例子,展示如何从零开始创建一个类,并利用它来制作我们的第一个Java对象。准备好让你的编程之旅起飞了吗?让我们一起来探索这个神奇的过程!
|
14天前
|
缓存 安全 Java
Java String类
Java String类
12 0