String 类
定义
package java.lang;
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
.....
}
String 类是 java lang 包下的一个类
被 final 修饰的一个常量类,不能被任何类继承,被创建后,该对象的字符序列是不可变的,包含该类后续的所有方法都不能修改该对象,直至该对象被销毁,该类的一些方法,看似改变了字符串,其实内部是创建了一个新的字符串。
因为字符串对象是不可变的,所以它们可以被共享。
该类实现了 Serializable接口,这是一个序列化标志
String 字符串存储于常量池中,创建字符串时,先去字符串常量池查找是否包含该字符串,如果没有,就实例化该对象放入常量池。
基本属性
/**用来存储字符串 */
private final char value[];
/** 缓存字符串的哈希码 */
private int hash; // Default to 0
/** 实现序列化的标识 */
private static final long serialVersionUID = -6849794470754667710L;
String 类其实是一个 char[]
构造方法 ![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mRju3cql-1606400767128)(C:\Users\LENOVO\AppData\Roaming\Typora\typora-user-images\image-20201125154153789.png)]](https://i-blog.csdnimg.cn/blog_migrate/e706816d40cc8cc1b94e8c7e3aa334db.png#pic_center)
String s = new String(new char[]{'a','b','c'});
equals()
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String aString = (String)anObject;
if (coder() == aString.coder()) {
return isLatin1() ? StringLatin1.equals(value, aString.value)
: StringUTF16.equals(value, aString.value);
}
}
return false;
}
String 重写了 equals()
常用方法
# 获取字符串长度
lenth();
# 比较值是否相等
equals()
# 转换为 大写
toUpperCase()
# 转换为小写
toLowerCase()
# 去掉前后空格
trim();
# 将字符串放入常量池
intern()
# 获取字符的下标
indexOf()
# 将字符串连接到末尾
concat(String str)
# 比较两个字符串某些位置上是否相等
boolean regionMatches(int toffset, String other, int ooffset, int len)
常量池
-
程序计数器:也成为 PC 寄存器,线程私有。
保存的是程序当前执行的指令的地址(也可以说保存下一条指令读的所在存储单元的地址),
当 CPU 需要执行指令时,需要从程序计数器中得到当前执行的指令的所在存储单元的地址,然后根据地址获取指令,在得到指令后,程序计数器便自动加 1 ,或者根据转移指令指向下一条指令的地址,如此循环,直至执行完所有的指令。
-
虚拟机栈:基本数据类型、对象的引用都放在这里,线程私有。
-
本地方法栈:虚拟机栈是为执行 java 方法服务的,而本地方法栈是为执行本地方法(Native Method)服务的
-
方法区:存储每个类的信息(包括类的名称、方法、字段)、静态变量、常量、编译后的代码。
-
常量池:保存编译期间生成的字面量。
-
堆:用来存储对象本身
jvm 常量池中维护了一个字符串缓冲区,用于存放运行时产生的各种字符串,缓冲区的字符串不重复。
创建 String 有几种方式:
字面量
字面量或者字符串拼接时,先在常量池中查找是否存在该字符串,如果存在就直接引用,避免重复创建,如果没有就创建该对象,并放入常量池。
new 的方式
new关键字创建时,直接在堆中创建一个新对象,变量所引用的都是这个新对象的地址,但是如果通过new关键字创建的字符串内容在常量池中存在了,会将堆中创建的对象指向常量池的引用;但是反过来,如果通过new关键字创建的字符串对象在常量池中没有,那么通过new关键词创建的字符串对象是不会额外在常量池中维护的。
知识点
intern() 方法
// 这是一个本地方法
public native String intern();
当调用intern方法时,如果池中已经包含一个与该
String
确定的字符串相同equals(Object)
的字符串,则返回该字符串。否则,将此String
对象添加到池中,并``返回此对象的引用。调用一个String对象的intern()方法,如果常量池中有该对象了,直接返回该字符串的引用(存在堆中就返回堆中,存在池中就返回池中),如果没有,则将该对象添加到池中,并返回池中的引用。
String str1 = "hello";//字面量 只会在常量池中创建对象
String str2 = str1.intern();
System.out.println(str1==str2);//true
String str3 = new String("world");//new 关键字只会在堆中创建对象
String str4 = str3.intern();
System.out.println(str3 == str4);//false
String str5 = str1 + str2;//变量拼接的字符串,会在常量池中和堆中都创建对象
String str6 = str5.intern();//这里由于池中已经有对象了,直接返回的是对象本身,也就是堆中的对象
System.out.println(str5 == str6);//true
String str7 = "hello1" + "world1";//常量拼接的字符串,只会在常量池中创建对象
String str8 = str7.intern();
System.out.println(str7 == str8);//true
String 真的不可变吗?
private final char value[];
value 被 final 修饰,只能保证引用不被改变,但是 value 所指向的堆中的数组,才是真实的数据,只要能够操作堆中的数组,依旧能改变数据。而且 value 是基本类型构成,那么一定是可变的,即使被声明为 private,我们也可以通过反射来改变。
String str = "vae";
//打印原字符串
System.out.println(str);//vae
//获取String类中的value字段
Field fieldStr = String.class.getDeclaredField("value");
//因为value是private声明的,这里修改其访问权限
fieldStr.setAccessible(true);
//获取str对象上的value属性的值
char[] value = (char[]) fieldStr.get(str);
//将第一个字符修改为 V(小写改大写)
value[0] = 'V';
//打印修改之后的字符串
System.out.println(str);//Vae
通过前后两次打印的结果,我们可以看到 String 被改变了,但是在代码里,几乎不会使用反射的机制去操作 String 字符串,所以,我们会认为 String 类型是不可变的。
String 类为什么要这样设计成不可变呢?我们可以从性能以及安全方面来考虑:
- 安全
- 引发安全问题,譬如,数据库的用户名、密码都是以字符串的形式传入来获得数据库的连接,或者在socket编程中,主机名和端口都是以字符串的形式传入。因为字符串是不可变的,所以它的值是不可改变的,否则黑客们可以钻到空子,改变字符串指向的对象的值,造成安全漏洞。
- 保证线程安全,在并发场景下,多个线程同时读写资源时,会引竞态条件,由于 String 是不可变的,不会引发线程的问题而保证了线程。
- HashCode,当 String 被创建出来的时候,hashcode也会随之被缓存,hashcode的计算与value有关,若 String 可变,那么 hashcode 也会随之变化,针对于 Map、Set 等容器,他们的键值需要保证唯一性和一致性,因此,String 的不可变性使其比其他对象更适合当容器的键值。
- 性能
- 当字符串是不可变时,字符串常量池才有意义。字符串常量池的出现,可以减少创建相同字面量的字符串,让不同的引用指向池中同一个字符串,为运行时节约很多的堆内存。若字符串可变,字符串常量池失去意义,基于常量池的String.intern()方法也失效,每次创建新的 String 将在堆内开辟出新的空间,占据更多的内存。
比较 String 和 StringBuffer 的 equals 是否相等?
StringBuilder 不是线程安全的,效率高
StrngBuffer 是线程安全的,效率低String a = “hello”;
StringBuffer s = new StringBuffer(“hello”);
a.equals(s) // false
String 转化为 基本数据类型
String s = “-1”;
Integer a = Integer.valueOf(s);
int b = Integer.parseInt(s);
Integer.valueOf (String s) 和 Integer.parseInt(String s) 的源码解读
// parseInt 方法有两个参数,第二个参数是进制,默认是 10进制
public static int parseInt(String s) throws NumberFormatException {
return parseInt(s,10);
}
// 也可以直接调用该方法,指定要转换的进制
public static int parseInt(String s, int radix)
throws NumberFormatException
{
...
}
// valueOf(s) 方法调用 parseInt(), 返回 Integer 类型
public static Integer valueOf(String s) throws NumberFormatException {
return Integer.valueOf(parseInt(s, 10));
}
StringBuffer 和 StringBuilder 源码分析
相同的继承关系,都继承自 AbstractStringBuilder
// @since 1.0
public final class StringBuffer
extends AbstractStringBuilder
implements java.io.Serializable, Comparable<StringBuffer>, CharSequence
{
...
}
// @since 1.5
public final class StringBuilder
extends AbstractStringBuilder
implements java.io.Serializable, Comparable<StringBuilder>, CharSequence
{
...
}
StringBuilder 调用 append() 方法时的源码分析
StringBuilder sb = new StringBuilder("1");
public StringBuilder() {
super(16); // 调用无参构造方法,默认初始化容量是 16
}
@HotSpotIntrinsicCandidate
public StringBuilder(String str) {
super(str.length() + 16); // 如果没有指定,默认初始化容量是 16 + 字符串长度
append(str); // 调用父类的 append 方法
}
// 调用父类的 append 方法
public AbstractStringBuilder append(String str) {
if (str == null) {
return appendNull();
}
int len = str.length();
ensureCapacityInternal(count + len);
putStringAt(count, str);
count += len;
return this;
}
// 判断是否需要扩容
private void ensureCapacityInternal(int minimumCapacity) {
// overflow-conscious code
int oldCapacity = value.length >> coder;
if (minimumCapacity - oldCapacity > 0) {
value = Arrays.copyOf(value,
newCapacity(minimumCapacity) << coder);
}
}
// 扩容大小是原来的2倍
private int newCapacity(int minCapacity) {
// overflow-conscious code
int oldCapacity = value.length >> coder;
int newCapacity = (oldCapacity << 1) + 2;
if (newCapacity - minCapacity < 0) {
newCapacity = minCapacity;
}
int SAFE_BOUND = MAX_ARRAY_SIZE >> coder;
return (newCapacity <= 0 || SAFE_BOUND - newCapacity < 0)
? hugeCapacity(minCapacity)
: newCapacity;
}
StringBuffer 在调用 append() 时的源码分析
@Override
@HotSpotIntrinsicCandidate
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}
两者都是调用 AbstractStringBuilder 的 append(),StringBuffer 最早是 JDK 1 就出现了,append 方法加了同步标识,StringBuilder 出现于 JDK 1.5,因此在多线程下, StringBuffer 安全性高于 StringBuilder, 而效率就相对低了。
微信公众号搜索【MAMBA 碎碎念】, 关注我的公众号!