字符串常量池: 字符串常量池在JVM中是StringTable类,实际是一个固定大小的HashTable。为了使程序的运行速度更快、更节省内存,Java为8种基本数据类型和String类都提供了常量池。
StringBuilder和StringBuffer: 当对字符串进行修改的时候,需要使用 StringBuffer 和 StringBuilder 类。和 String 类不同的是,StringBuffer 和 StringBuilder 类的对象能够被多次的修改,并且不产生新的未使用对象。
文章目录
一、字符串常量池
字符串常量池是一种池化技术的体现。常见的池化技术有:数据库连接池、线程池、内存池等。
为了节省存储空间,提高程序的运行效率,Java中引入了:
- Class文件常量池: 每个.Java源文件编译后生成.Class文件中会保存当前类中的字面常量以及符号信息
- 运行时常量池: 在.Class文件被加载时,.Class文件中的常量池被加载到内存中称为运行时常量池,运行时常量池每个类都有一份。
- 字符串常量池
注: 字符串常量池不同JDK版本下的位置以及默认大小是不同的。
JDK版本 | 字符串常量池位置 | 大小 |
---|---|---|
Java6 | 方法区 | 固定大小:1009 |
Java7 | 堆 | 可设置,没有大小限制,默认大小:60013 |
Java8 | 堆 | 可设置,有范围限制,最小是1009 |
- 字符串常量池只有一份,是全局共享。
- 字符串常量池中的元素会随着程序运行不断增多。
- 当类加载时,字节码文件中的常量池也被加载到JVM中,称为运行时常量池,同时会将其中的字符串常量保存在字符串常量池中。
- 字符串常量池中的内容,一部分来自运行时常量池,一部分来自程序动态添加。
1、字符串的创建
1.1 字符串常量进行赋值
public static void main(String[] args) {
String s1 = "CSDN";
String s2 = "CSDN";
System.out.println(s1 == s2); //运行结果:true
}
** 注:** 在字节码文件加载时,“CSDN”的常量串就已经创建好了,并保存在字符串常量池中。当使用String s1 = “CSDN”;
创建对象时,在字符串常量池中找,找到需要的字符串便将字符串的引用赋给s1。
1.2 通过new创建String类对象。
public static void main(String[] args) {
String s1 = "CSDN";
String s2 = "CSDN";
String s3 = new String("Hello");
String s4 = new String("CSDN");
}
new: 在堆上开辟String对象大小的空间,并将对象中成员初始化为0,然后将空间首地址压栈。
dup: 将栈顶元素,String对象空间的首地址拷贝一份到栈顶备用。
ldc: 将常量池中的“Hello”对象引用拷贝到栈顶。
invokespecial: 调用String类构造方法,取走栈顶2个元素。
astore2: 用栈顶元素给s2赋值。
注: 使用new创建的对象都是唯一的。并且通过常量串创建String对象效率更高,更节省空间。
1.3 intern方法
intern
是一个native方法(Native方法指:底层使用C++实现的,看不到其实现的源代码),该方法的作用是手
动将创建的String对象添加到常量池中。
//未使用intern方法
public static void main(String[] args) {
char[] ch = new char[]{'a', 'b', 'c'};
// s1对象并不在常量池中
String s1 = new String(ch);
String s2 = "abc";
System.out.println(s1 == s2);
}
//使用intern方法
public static void main(String[] args) {
char[] ch = new char[]{'a', 'b', 'c'};
String s1 = new String(ch);
// s1.intern();调用之后,会将s1对象的引用放入到常量池中
s1.intern();
String s2 = "abc";
System.out.println(s1 == s2);
}
二、String类不可修改
String是一种不可变对象. 字符串中的内容是不可改变。
- String类在设计的时候就设计为不可修改的。
① String类被final修饰,表明不能被继承。
② value被final修饰,表明自身的值不能改变,即不能引用其它字符数组,但是其引用空间中的内容可以修改。 - 所有涉及到可能修改字符串内容的操作都是创建一个新对象,操作的都是新对象。
注:String设计成不可变的原因
- 方便字符串对象池。如果 String 可变, 那么对象池就需要考虑何时深拷贝字符串的问题。
- 不可变对象是线程安全的。
- 不可变对象更方便缓存 hash code, 作为 key 时可以更高效的保存到 HashMap 中。
1、修改字符串
尽量避免对字符串进行修改,因为String类是不可修改的,所有的修改操作都是会创建新的对象,效率低下。
字节码文件的第19行有StringBuilder.toString
方法,方法源码如下:
可以看出是新建了一个字符串。
下面用一个示例来看下这个效率有多慢:
public static void main(String[] args) {
long start = System.currentTimeMillis();
//对String进行修改
String s = "";
for(int i = 0; i < 100000; ++i){
s += i;
}
long end = System.currentTimeMillis();
System.out.println(end - start);
//对StringBuffer进行修改
start = System.currentTimeMillis();
StringBuffer sbf = new StringBuffer("");
for(int i = 0; i < 100000; ++i){
sbf.append(i);
}
end = System.currentTimeMillis();
System.out.println(end - start);
//对StringBuilder进行修改
start = System.currentTimeMillis();
StringBuilder sbd = new StringBuilder();
for(int i = 0; i < 100000; ++i){
sbd.append(i);
}
end = System.currentTimeMillis();
System.out.println(end - start);
}
三、StringBuffer 和 StringBuilder 类
由于String的不可更改特性,为了方便字符串的修改,Java中又提供StringBuilder和StringBuffer类。这两个类大部分功能是相同的。
- 在使用 StringBuffer 类时,每次都会对 StringBuffer 对象本身进行操作,而不是生成新的对象,所以如果需要对字符串进行修改推荐使用 StringBuffer。
- StringBuilder 类在 Java 5 中被提出,它和 StringBuffer 之间的最大不同在于 StringBuilder 的方法不是线程安全的(不能同步访问)。
- StringBuilder 相较于 StringBuffer 有速度优势,所以多数情况下建议使用 StringBuilder 类。
public static void main(String[] args) {
StringBuilder sb = new StringBuilder(10);
sb.append("CSDN..");
System.out.println(sb);
//在sb字符串
sb.append("!");
System.out.println(sb);
//在sb字符串6的位置后添加Java
sb.insert(6, "Java");
System.out.println(sb);
//删除5到8位的字符
sb.delete(5,8);
System.out.println(sb);
}
介绍一些StringBuilder类的方法
方法 | 说明 |
---|---|
StringBuilder append(String str) | 在字符串尾部追加指定的序列,相当于String的+= |
char charAt(int index) | 获取index位置的字符 |
int length() | 获取字符串的长度 |
int capacity() | 获取底层保存字符串空间总的大小 |
void ensureCapacity(int mininmumCapacity) | 扩容 |
void setCharAt(int index, char ch) | 将index位置的字符设置为ch |
int indexOf(String str) | 返回str第一次出现的位置 |
int indexOf(String str, int fromIndex) | 从fromIndex位置开始查找str第一次出现的位置 |
StringBuff insert(int offset, String str) | 在offset位置插入:八种基类类型 & String类型 & Object类型数据 |
StringBuffer deleteCharAt(int index) | 删除index位置字符 |
StringBuffer delete(int start, int end) | 删除[start, end)区间内的字符 |
StringBuffer replace(int start, int end, String str) | 将[start, end)位置的字符替换为str |
String substring(int start) | 从start开始一直到末尾的字符以String的方式返回 |
String substring(int start,int end) | 将[start, end)范围内的字符以String的方式返回 |
StringBuffer reverse() | 反转字符串 |
String toString() | 将所有字符按照String的方式返回 |
示例:
public static void main(String[] args) {
StringBuilder sb1 = new StringBuilder("hello");
StringBuilder sb2 = sb1;
// 追加:即尾插-->字符、字符串、整形数字
sb1.append(' '); // hello
sb1.append("world"); // hello world
sb1.append(123); // hello world123
System.out.println(sb1); // hello world123
System.out.println(sb1 == sb2); // true
System.out.println(sb1.charAt(0)); // 获取0号位上的字符 h
System.out.println(sb1.length()); // 获取字符串的有效长度14
System.out.println(sb1.capacity()); // 获取底层数组的总大小
sb1.setCharAt(0, 'H'); // 设置任意位置的字符 Hello world123
sb1.insert(0, "Hello world!!!"); // Hello world!!!Hello world123
System.out.println(sb1);
System.out.println(sb1.indexOf("Hello")); // 获取Hello第一次出现的位置
System.out.println(sb1.lastIndexOf("hello")); // 获取hello最后一次出现的位置
sb1.deleteCharAt(0); // 删除首字符
sb1.delete(0,5); // 删除[0, 5)范围内的字符
String str = sb1.substring(0, 5); // 截取[0, 5)区间中的字符以String的方式返回
System.out.println(str);
sb1.reverse(); // 字符串逆转
str = sb1.toString(); // 将StringBuffer以String的方式返回
System.out.println(str);
}
String和StringBuilder最大的区别在于String的内容无法修改,而StringBuilder的内容可以修改。
注: String和StringBuilder类不能直接转换,如需转换,可以使用如下方法:
- String变为StringBuilder: 利用StringBuilder的构造方法或append()方法。
- StringBuilder变为String: 调用toString()方法。