目录
一,字符串的不可变性
二,字符串修改
三, StringBuilder和StringBuffer
StringBuilder的介绍
四,StringBuider与StringBuffer的区别
String、StringBuffffer、StringBuilder的区别
续上
在上回String详解中我们知道了所有的截取,替换等都不是在它原本的String字符串上进行的,那为什么呢?接下来让我们继续进行探究
String是一种不可变对象,字符串中的内容是不可改变。字符串不可被修改,是因为:
1. String类在设计时就是不可改变的,String类实现描述中已经说明了
以下是来自jdk1.8中的String的部分实现:
我们看到,String所引用的对象都使用“ ”来进行,而只要是双引号所引起的对象都是常量,被放在内存中堆上的常量池
参考如下图:
public class Main {
public static void main(String[] args) {
String str1 = "abcde";
String str2 = new String("abcde");
System.out.println(str1 == str2);
}
}
下面我们以此代码为例,逐个分析
代码走到第一个String,在常量池放入了“”引住的对象
代码走到第二行,又有一个双引号,此时如果常量池有“abcde”就不会再放入该常量。
随后又new了一个Sring,于是又在堆上开辟一块内存
所以此时“==”比较的是str1与str2的内存地址,由图知并不相同,所以输出的值为false。
在明白这一点之后我们继续深入探究
public class Main {
public static void main(String[] args) {
String str1 = "abcde";
String str2 = new String("abcde");
String str3 = "abcde";
System.out.println(str1 == str3);
}
}
此时代码多了一个直接赋值的str3,此时str1和str3会不会相等呢?
同样的道理,程序走到str3处时读取到一个双引号于是在常量池中寻找,发现已有了该常量,所以不再新增,而这是并没有new一个新对象,所以并没有在堆上开辟新内存
所以这时二者地址便相同,输出的值也会是true
总结:双引号引起来的值就存放在字符串常量池当中。如果有,就不存储,直接返回字符串常量池的对象即可。
(字符串常量池底层是一个哈希表,叫做StringTable,是用c/c++编写的)
public static void main(String[] args) {
fifinal int array[] = {1,2,3,4,5};
array[0] = 100;
System.out.println(Arrays.toString(array));
// array = new int[]{4,5,6}; // 编译报错:Error:(19, 9) java: 无法为最终变量array分配值
}
public class Main {
public static void main(String[] args) {
String str = "abcd";
System.out.println(str);
}
}
即使String引用的对象被放入常量池,但其中也有value与haxi
也就是说这里我们有一个String对象,其有一个value与haxi
valu数组被final修饰
也就是说,上述代码和new String是一样的,都是一个对象。
它将以对象的方式去组织,会将字符串转变为一个数组。
简单认为它将以数组的形式去存储
value代表此时value只能引用这个对象,并且类比数组来说,是可以去修改它的下标的
而之所以String不能被修改
是因为它被封装起来了,而且并没有提供任何get或者set方法,final修饰让它不能被继承,所以只能在String这个类里面才能进行使用
那如果想要修改字符串中内容该如何操作?
public class Main {
public static void main(String[] args) {
String str = "hello";
str += "abc";
System.out.println(str);
}
}
很多人误以为这样是字符串的修改
非也,这其实是字符串的拼接
它并没有修改原来str的值,和上面一样,也是使用的新的对象。
而且效率不高,中间会产生很多临时对象
我们也可以试着用汇编查看
通过查看汇编,我们模仿着写出该代码:
public class Test {
public static void main(String[] args) {
String str = "hello";
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(str);
stringBuilder.append("abc");
str = stringBuilder.toString();
System.out.println(str);
}
}
输出结果同上
而且在这个过程当中,产生了4个对象
当将该代码放入循环时,每循环一次都将产生多个对象,低效率显而易见。
可通过以下代码进行验证
public static void main(Stri
long start = System.curr
String s = "";
for(int i = 0; i < 10000; ++
s += i;
}
long end = System.curre
System.out.println(end -
start = System.currentTimeMillis();
StringBuffffer sbf = new StringBuffffer("");
for(int i = 0; i < 10000; ++i){
sbf.append(i);
}
end = System.currentTimeMillis();
System.out.println(end - start);
start = System.currentTimeMillis();
StringBuilder sbd = new StringBuilder();
for(int i = 0; i < 10000; ++i){
sbd.append(i);
}
end = System.currentTimeMillis();
System.out.println(end - start);
}
由于String的不可更改特性,为了方便字符串的修改,Java中又提供StringBuilder和StringBuffffer类。StringBuilder和StringBuffffer是两个全新的类,其中String有的它们可以没有,String没有的它们可能又有。这两个类大部分功能是相同的,这里介绍 StringBuilder常用的一些方法,其它需要用到了大家可参阅Overview (Java Platform SE 8 ) (oracle.com)
方法 | 说明 |
StringBuffer append(String
str)
|
在尾部追加,相当于 String 的 += ,可以追加: boolean 、 char 、 char[] 、double、 flfloat 、 int 、 long 、 Object 、 String 、 StringBuffff 的变量
|
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 第一次出现的位置
|
int lastIndexOf(String str)
|
返回最后一次出现 str 的位置
|
int lastIndexOf(String str,int fromIndex)
|
从 fromIndex 位置开始找 str 最后一次出现的位置
|
StringBuffer insert(int
ofset, String str)
|
在 ofset 位置插入:八种基类类型 & 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 的方式返回
|
StringBuffffer 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(); // 将StringBuffffer以String的方式返回
System.out.println(str);
}
StringBuider与StringBuffer有什么区别?
public class Test {
public static void main(String[] args) {
StringBuilder stringBuilder = new StringBuilder("hello");
StringBuffer stringBuffer = new StringBuffer("hello");
stringBuffer.append("Buffer");
stringBuilder.append("Builder");
}
}
可以看到,无论是StringBuider还是StringBuffer都可以实例化并且调用append()方法,那我们如何得知二者的区别呢?
就由上述代码为例,我们查看二者调用appen的源码
Buffer:
Builder:
我们暂且不管Buffer中toStringCache那行
然后我们进行比较会发现二者源码只有一处不同:
Buffer处多了一个synchronized来修饰
而synchronized我们可以把它抽象为一把锁
在多线程的情况下只有有对象进入到方法中,synchronized就会为其“上锁”,待该对象调完该方法后“解锁”让下一个对象调用。而频繁的加锁和解锁会耗费资源,所以在不涉及多线程时一般使用Builder。
所以Buffer一般用于多线程情况
Builder一般用于单线程情况