【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 未采用同步处理,属于线程不安全操作。(后期学习会接触多线程,目前了解即可)

相关文章
|
14天前
|
存储 JavaScript Java
Java 中的 String Pool 简介
本文介绍了 Java 中 String 对象及其存储机制 String Pool 的基本概念,包括字符串引用、构造方法中的内存分配、字符串文字与对象的区别、手工引用、垃圾清理、性能优化,以及 Java 9 中的压缩字符串特性。文章详细解析了 String 对象的初始化、内存使用及优化方法,帮助开发者更好地理解和使用 Java 中的字符串。
Java 中的 String Pool 简介
|
20天前
|
缓存 安全 Java
java 为什么 String 在 java 中是不可变的?
本文探讨了Java中String为何设计为不可变类型,从字符串池的高效利用、哈希码缓存、支持其他对象的安全使用、增强安全性以及线程安全等方面阐述了不可变性的优势。文中还通过具体代码示例解释了这些优点的实际应用。
java 为什么 String 在 java 中是不可变的?
|
1天前
|
JSON Java Apache
Java基础-常用API-Object类
继承是面向对象编程的重要特性,允许从已有类派生新类。Java采用单继承机制,默认所有类继承自Object类。Object类提供了多个常用方法,如`clone()`用于复制对象,`equals()`判断对象是否相等,`hashCode()`计算哈希码,`toString()`返回对象的字符串表示,`wait()`、`notify()`和`notifyAll()`用于线程同步,`finalize()`在对象被垃圾回收时调用。掌握这些方法有助于更好地理解和使用Java中的对象行为。
|
14天前
|
存储 Java
Java 11 的String是如何优化存储的?
本文介绍了Java中字符串存储优化的原理和实现。通过判断字符串是否全为拉丁字符,使用`byte`代替`char`存储,以节省空间。具体实现涉及`compress`和`toBytes`方法,前者用于尝试压缩字符串,后者则按常规方式存储。代码示例展示了如何根据配置决定使用哪种存储方式。
|
1月前
|
存储 缓存 安全
java 中操作字符串都有哪些类,它们之间有什么区别
Java中操作字符串的类主要有String、StringBuilder和StringBuffer。String是不可变的,每次操作都会生成新对象;StringBuilder和StringBuffer都是可变的,但StringBuilder是非线程安全的,而StringBuffer是线程安全的,因此性能略低。
46 8
|
1月前
|
Java
在Java中如何将基本数据类型转换为String
在Java中,可使用多种方法将基本数据类型(如int、char等)转换为String:1. 使用String.valueOf()方法;2. 利用+运算符与空字符串连接;3. 对于数字类型,也可使用Integer.toString()等特定类型的方法。这些方法简单高效,适用于不同场景。
54 7
|
27天前
|
存储 编译器 C语言
【c++丨STL】string类的使用
本文介绍了C++中`string`类的基本概念及其主要接口。`string`类在C++标准库中扮演着重要角色,它提供了比C语言中字符串处理函数更丰富、安全和便捷的功能。文章详细讲解了`string`类的构造函数、赋值运算符、容量管理接口、元素访问及遍历方法、字符串修改操作、字符串运算接口、常量成员和非成员函数等内容。通过实例演示了如何使用这些接口进行字符串的创建、修改、查找和比较等操作,帮助读者更好地理解和掌握`string`类的应用。
48 2
|
1月前
|
Java 开发者
在 Java 中,一个类可以实现多个接口吗?
这是 Java 面向对象编程的一个重要特性,它提供了极大的灵活性和扩展性。
65 1
|
3月前
|
Java 索引
java基础(13)String类
本文介绍了Java中String类的多种操作方法,包括字符串拼接、获取长度、去除空格、替换、截取、分割、比较和查找字符等。
46 0
java基础(13)String类
|
2月前
|
Java
【编程基础知识】(讲解+示例实战)方法参数的传递机制(值传递及地址传递)以及String类的对象的不可变性
本文深入探讨了Java中方法参数的传递机制,包括值传递和引用传递的区别,以及String类对象的不可变性。通过详细讲解和示例代码,帮助读者理解参数传递的内部原理,并掌握在实际编程中正确处理参数传递的方法。关键词:Java, 方法参数传递, 值传递, 引用传递, String不可变性。
68 1
【编程基础知识】(讲解+示例实战)方法参数的传递机制(值传递及地址传递)以及String类的对象的不可变性