【Java】String类

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
简介: 【Java】String类

1.字符串常量池

1.1 创建对象的思考

public class Test {
    public static void main(String[] args) {
        String str1 = "hello";
        String str2 = "hello";
        String str3 = new String("hello");
        String str4 = new String("hello");
        System.out.println(str1 == str2);
        System.out.println(str3 == str4);
        System.out.println(str1 == str3);
    }
}

运行结果:

去.png



大家看到运行结果后,可能会想这样一个问题,为什么 str1 == str2 是 true,而 str3 == str4 却是false 呢?


这就涉及到了常量池的概念,学习了常量池,这个问题也就迎刃而解了


在Java程序中,为了让程序的运行速度更快、更节省空间,Java 为 8 种基本数据类型和 String类都提供了常量池


常量池,肯定是存放常量的,那究竟什么是池了?


给大家举一个例子,大家也就明白了


比如: 小和尚打水


情况一:假设寺庙里面没有水缸,每次小和尚需要水的时候都要下山去打水


情况二:假设寺庙里面有水缸, 水缸里面打满了水,每次小和尚需要水的时候直接去水缸中取


情况二,就是池化技术的一种示例,水放在水缸中,随用随取,效率非常高


为了让程序的运行速度更快、更节省空间,Java中引入了:


Class文件常量池:每个 .Java 源文件编译后生成 .class 文件中都会保存当前类中的字面常量以及符号信息

运行时常量池:在 .class 文件被加载时,.class 文件中的常量池被加载到内存中称为运行时常量池,运行时常量池每个类都有一份

字符串常量池

我们本期讲的字符串所以主要讲的是字符串常量池


1.2 字符串常量池

字符串常量池 在 JVM 中是 StringTable 类,实际是一个固定大小的 HashTable,在不同 JDK 版本下字符串常量池的位置以及默认大小是不同的:

请.png



1.3 再谈创建对象

现在大多数人用的 jdk 版本都是 java8,那我们就拿 java8 来详谈


1.直接使用字符串常量进行赋值


public class Test {
    public static void main(String[] args) {
        String str1 = "hello";
        String str2 = "hello";
        System.out.println(str1 == str2);
    }
}

运行结果:

其.png



分析图:


前.png


"hello" 它是一个字符串常量,所以它需要放入字符串常量池。因为我们用的 jdk 版本都是 java8,所以它字符串常量池的位置在堆中。我们还知道双引号引起来的字符串是字符串对象,并且字符串在内存中是按数组存放的,所以编译器会自动创建一个字符串对象,并指向这个按数组存放的字符串。因为它是字符串常量所以需要放入字符串常量池中,但是不会在字符串常量池中直接存入编译器自动生成的字符串对象的地址,而是借助一个叫哈希桶的工具去存放这个字符串对象的地址,然后把哈希桶的地址存放在字符串常量池中。


当执行 str1="hello" 的时候直接把 "hello" 放进了字符串常量池中。当执行到 str2 = "hello" 的时候会去字符串常量池中看有没有这个字符串常量,如果有直接存放它的字符串对象的地址,如果没有就去把这个字符串常量入池,然后在存放它的字符串对象的地址


注:只会开辟一块堆内存空间,保存在字符串常量池中,然后str共享常量池中的String对象


2.通过new创建String类对象


public class Test {
    public static void main(String[] args) {
        String str1 = new String("hello");
        String str2 = new String("hello");
        System.out.println(str1 == str2);
    }
}

运行结果:


我.png


分析图:


为.png


编译器会自动为字符串常量生成一个字符串对象然后指向这个存储字符串的数组,然后利用哈希桶存放这个字符串对象的地址,将这个哈希桶的地址放入字符串常量池中。


String str1 = new  String("hello") ,new 肯定会创建一个对象,这个对象 vlue 属性中存放的是"hello"这个字符串常量的数组地址,str1里面存放的就是这个new出来的对象地址。


String str2 = new  String("hello") ,new 肯定会创建一个对象,这个对象 vlue 属性中存放的是"hello"这个字符串常量的数组地址,str2里面存放的就是这个new出来的对象地址。


注:只要是 new 就会产生一个新的对象 ,直接使用字符串常量进行赋值自动创建String类型对象的效率更高,而且更节省空间


学习完 直接使用字符串常量进行赋值 和 通过new创建String类对象,我们也就知道了上述提到的为什么 str1 == str2 是 true,而 str3 == str4 却是false 呢?


答:因为str1和str2是直接使用字符串常量进行赋值,str1和str2里面存储是编译器自动生成的字符串常量的对象,所以 str1 == str2 是 true。str3和str4是通过new创建String类对象,会分别创建一个对象,然后指向存储字符串的数组,str1和str2里面存储是通过new创建String类对象的地址,所以 str3 == str4 是 false


注:会开辟两块堆内存空间,字符串"hello"保存在字符串常量池中,然后用常量池中的String对象给新开辟 的String对象赋值


3.intern方法


intern 方法的作用是手动将创建的String对象添加到常量池中


①未手动将创建的String对象添加到常量池中


public class Test {
    public static void main(String[] args) {
        char[] arr = new char[]{'a','b','c'};
        String str1 = new String(arr);
        String str2 = "abc";
        System.out.println(str1==str2);
    }
}

运行结果:


五.png


解析:将数组作为参数去实例化一个 String 对象,因为数组不是字符串常量,所以不会放入常量池,那么str1里面存储的就是实例化的那个String对象地址。str2="abc","abc"是个字符串常量所以会入常量池,str2里面存放的是编译器自动生成的字符串常量对象,所以当 str1==str2 返回的是false


②手动将创建的String对象添加到常量池中


public class Test {
    public static void main(String[] args) {
        char[] arr = new char[]{'a','b','c'};
        String str1 = new String(arr);
        str1.intern();
        String str2 = "abc";
        System.out.println(str1==str2);
    }
}

运行结果:

玩.png



解析:手动将str1对象添加到常量池,我们知道字符串常量是用数组存储的,所以当我们String str2 = "abc"这个代码时,会先去常量池里面看有没有"abc"这个字符串常量,因为字符串常量是用数组存储的,所以常量池中有这个字符串常量那么就不会入池,str2直接存储指向"abc"地址的对象地址

2.字符串的不可变性

String是一种不可变对象,字符串中的内容是不可改变,字符串不可被修改


阿.png


String类中的字符实际保存在内部维护的value字符数组中:


String类被final修饰,表明该类不能被继承

value被final修饰,表明value自身的值不能改变,即不能引用其它字符数组,但是其引用空间中的内容可以修改。

value被private修饰,表明value只能在本类使用,而且String类没有对外提供 getValue 和 setValue 的方法,所以在外部无法访问和修改value数组的内容

很多人会认为 value 不能被修改的原因是final,final修饰的变量不能改值,那下面我们就来证明value 不能修改跟 final 无关


证明 value 不能被修改跟 final 无关:


呃.png


通过上面的代码也就证明了被 final 修饰的数组是可以修改的,因为被 final 修饰的不能改变里面存储的内容,arr 里面存储的是 new 一个数组对象的地址,那么 arr 也就不能修改成其他的数组对象地址了,但是数组的内容可以改变,因为 arr 里面存的是地址


注:final修饰类表明该类不想被继承,final修饰引用类型表明该引用变量不能引用其他对象,但是其引用对象中的内容是可以修改的。


为什么 String 要设计成不可变的?


方便实现字符串对象池,如果 String 可变,那么对象池就需要考虑写时拷贝的问题了

不可变对象是线程安全的

不可变对象更方便缓存 hash code, 作为 key 时可以更高效的保存到 HashMap 中.

3.字符串修改

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


public class Test {
    public static void main(String[] args) {
        String str = "abc";
        str += "def";
        System.out.println(str);
    }
}

如果要对String类型对象修改建议尽量使用StringBuffer或者StringBuilder


3.1 StringBuilder

由于String的不可更改特性,为了方便字符串的修改,Java中又提供StringBuilder和StringBuffer类。这两个类大部分功能是相同的,这里介绍 StringBuilder常用的一些方法


StringBuff append(String str) :在尾部追加,相当于String的+=,可以追加:boolean、char、char[]、 double、float、int、long、Object、String、StringBuff的变量


rcharAt(int index) :cha获取index位置的字符


int length():获取字符串的长度


StringBuffer reverse():反转字符串


StringBuffer deleteCharAt(int index):删除index位置字符


StringBuilder 在修改字符串内容的时候会直接在该对象中修改,不会重新创建新对象:

の.png



画图解析:

额.png



解析:"abc"是一个字符串常量对象,所以它会入池,当把"abc"作为实例化 StringBuilder 的参数的时候,会自动创建一个比"abc"大一些的数组空间然后把abc拷贝进去,这个拷贝的数组不会入池。所以它可以直接在改变里面的值,也不会创建一个新的对象。当"abc"没有使用的时候编译器会自动回收


StringBuffer和StringBuilder的区别:StringBuffer采用同步处理,属于线程安全操作。StringBuilder没有采用同步处理,属于线程不安全操作。这两个类大部分功能是相似的(更详细的内容会放在多线程部分讲解)



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