| | String | StringBuffer | StringBuilder |
| :-- | — | — | — |
| 类是否可变 | 不可变(Final) | 可变 | 可变 |
| 功能介绍 | 每次对String的操作都会在“常量池”中生成新的String对象 | 任何对它指向的字符串的操作都不会产生新的对象。每个StringBuffer对象都有一定的缓冲区容量,字符串大小没有超过容量时,不会分配新的容量,当字符串大小超过容量时,自动扩容 | 功能与StringBuffer相同,相比少了同步锁,执行速度更快 |
| 线程安全性 | 线程安全 | 线程安全 | 线程不安全 |
| 使用场景推荐 | 单次操作或循环外操作字符串 | 多线程操作字符串 | 单线程操作字符串 |
追问1:这三者在效率上怎么说?
StringBulider > StringBuffer > String
String <(StringBuffer,StringBuilder)的原因?
-
String:字符串常量
-
StringBuffer:字符串变量(有同步锁)
-
StringBuilder:字符串变量(无同步锁)
从上面的名字可以看到,String是"字符串常量",也就是不可改变的对象。源码如下:
public final class String{}
对于上面这句话的理解你可能会产生这样一个疑问 ,比如这段代码:
String str = “唐伯虎”;
str = str + “点香烟”;
System.out.print(str); // result : “唐伯虎点香烟”
我们明明改变了String型的变量str啊,为什么说是没有改变呢?我们来看一下这张对String操作时内存变化的图:
我们可以看到,初始String值为"唐伯虎",然后在这个字符串后面加上新的字符串"点香烟",这个过程是需要重新在栈堆内存中开辟内存空间的,最终得到了"唐伯虎点香烟"字符串也相应的需要开辟内存空间,这样短短的两个字符串,却需要开辟三次内存空间,不得不说这是对内存空间的极大浪费,执行效率同理。
为了应对经常性操作字符串的场景,Java才提供了其他两个操作字符串的类 —— StringBuffer、StringBuilder
。
他们俩均属于字符串变量,是可改变的对象,每当我们用它们对字符串做操作时,实际上是在一个对象上操作的,这样就不会像String一样创建一些而外的对象进行操作了,速度自然就相对快了。
我们一般在StringBuffer、StringBuild类上的主要操作是 append 和 insert 方法,这些方法允许被重载,以接受任意类型的数据。每个方法都能有效地将给定的数据转换成字符串,然后将该字符串的字符追加或插入到字符串缓冲区中。append 方法始终将这些字符添加到缓冲区的末端;而 insert 方法则在指定的点(index)添加字符。
- StringBuilder一个可变的字符序列是JDK1.5新增的。此类提供一个与 StringBuffer 兼容的 API,但不保证同步。该类被设计用作 StringBuffer 的一个简易替换,用在字符串缓冲区被单个线程使用的时候(这种情况很普遍)。
- 如果可能,建议优先采用StringBuilder类,因为在大多数实现中,它比 StringBuffer 要快。且两者的方法基本相同。然而在应用程序要求线程安全的情况下,则必须使用 StringBuffer 类。
String 类型和 StringBuffer、 StringBuild类型的主要性能区别其实在于 String 是不可变的对象(final), 因此在每次对 String 类型进行改变的时候其实都等同于在堆中生成了一个新的 String 对象,然后将指针指向新的 String 对象,这样不仅效率低下,而且大量浪费有限的内存空间,所以经常改变内容的字符串最好不要用 String 。因为每次生成对象都会对系统性能产生影响,特别是当内存中的无引用对象过多了以后, JVM 的 GC 开始工作,那速度是一定会相当慢的。另外当GC清理速度跟不上new String的速度时,还会导致内存溢出Error,会直接kill掉主程序!报错如下:
Caused by: java.lang.OutOfMemoryError: GC overhead limit exceeded
Exception in thread “I/O dispatcher 3797236” java.lang.OutOfMemoryError: GC overhead limit exceeded
追问2:那StringBuffer和StringBuilder线程安全主要差在哪里呢?
StringBuffer和StringBuilder可以算是双胞胎了,这两者的方法没有很大区别。但在线程安全性方面,StringBuffer允许多线程进行字符操作。这是因为在源代码中StringBuffer的很多方法都被关键字synchronized 修饰了,而StringBuilder没有。
synchronized的含义:
每一个类对象都对应一把锁,当某个线程A调用类对象O中的synchronized方法M时,必须获得对象O的锁才能够执行M方法,否则线程A阻塞。一旦线程A开始执行M方法,将独占对象O的锁。使得其它需要调用O对象的M方法的线程阻塞。只有线程A执行完毕,释放锁后。那些阻塞线程才有机会重新调用M方法。这就是解决线程同步问题的锁机制。 > 了解了synchronized的含义以后,大家可能都会有这个感觉。多线程编程中StringBuffer比StringBuilder要安全多了 ,事实确实如此。如果有多个线程需要对同一个字符串缓冲区进行操作的时候,StringBuffer应该是不二选择。
注意
:是不是String也不安全呢?事实上不存在这个问题,String是不可变的。线程对于堆中指定的一个String对象只能读取,无法修改。试问:还有什么不安全的呢?
实际应用场景中:
-
如果不是在循环体中进行字符串拼接的话,直接使用 String 的 “+” 就好了;
-
单线程循环中操作大量字符串数据 → StringBuilder.append();
-
多线程循环中操作大量字符串数据 → StringBuffer.append();
面试题2:请你说一下Error 和 Exception 区别是什么?
=================================================================================================
Error 和 Exception 都是 Throwable 的子类,在Java中只有Throwable类型的实例才可以被抛出或者捕获,它是异常处理机制的基本类型。
-
Exception和Error体现了java平台设计者对不同异常情况的分类,Exception是程序正常运行中,可以预料的意外情况,可能并且应该被捕获,进行相应的处理。
-
Error是指正常情况下,不大可能出现的情况,绝大部分的Error都会导致程序处于非正常的、不可恢复的状态。既然是非正常情况,不便于也不需要捕获。常见的比如OutOfMemoryError之类都是Error的子类。
-
Exception又分为可检查(checked)异常和不可检查(unchecked)异常。可检查异常在源代码里必须显式的进行捕获处理,这是编译期检查的一部分。不可检查时异常是指运行时异常,像NullPointerException、ArrayIndexOutOfBoundsException之类,通常是可以编码避免的逻辑错误,具体根据需要来判断是否需要捕获,并不会在编译期强制要求。
======================================================================================
-
==
: 它的作用是判断两个对象的地址是不是相等。即,判断两个对象是不是同一个对象。(基本数据类型 == 比较的是值,引用数据类型 == 比较的是内存地址
) -
equals()
: 它的作用也是判断两个对象是否相等。但它一般有两种使用情况:
情况1
:类没有覆盖 equals() 方法。则通过 equals() 比较该类的两个对象时,等价于调用了Object类的equals() 方法,也就是通过“==”比较这两个对象。
// Object类中的equals() 方法
public boolean equals(Object obj) {
return (this == obj);
}
情况2
:类覆盖了 equals() 方法。一般,我们都会覆盖 equals() 方法来两个对象的内容相等;若它们的内容相等,则返回 true (即,认为这两个对象相等)。
// String类中的equals() 方法,已覆盖,用于比较内容
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
追问1:如果我们不重写equals() 方法,会怎么样?
举例说明:
重点说明:是否重写Object类中的equals方法,会对结果造成的影响
public static void main(String[] args) {
// 字符串比较
String a = “陈哈哈”;
String b = “陈哈哈”;
if (a == b) {// true a==b
System.out.println(“a==b”);
}
if (a.equals(b)) {// true a.equals(b)
System.out.println(“a.equals(b)”);
}
// StringBuffer 对象比较,由于StringBuffer没有重写Object的equal方法,因此结果出现错误
StringBuffer c = new StringBuffer(“陈哈哈”);
StringBuffer d = new StringBuffer(“陈哈哈”);
if (c == d) {// false c != d
System.out.println(“c == d”);
} else {
System.out.println(“c != d”);
}
if (c.equals(d)) { // false 调用了Object类的equal方法
System.out.println(“StringBuffer equal true”);
}else {
System.out.println(“StringBuffer equal false”);
}
}
总结
无论是哪家公司,都很重视高并发高可用的技术,重视基础,重视JVM。面试是一个双向选择的过程,不要抱着畏惧的心态去面试,不利于自己的发挥。同时看中的应该不止薪资,还要看你是不是真的喜欢这家公司,是不是能真的得到锻炼。其实我写了这么多,只是我自己的总结,并不一定适用于所有人,相信经过一些面试,大家都会有这些感触。
最后我整理了一些面试真题资料,技术知识点剖析教程,还有和广大同仁一起交流学习共同进步,还有一些职业经验的分享。
了解详情https://docs.qq.com/doc/DSmxTbFJ1cmN1R2dB
out.println(“StringBuffer equal false”);
}
}
总结
无论是哪家公司,都很重视高并发高可用的技术,重视基础,重视JVM。面试是一个双向选择的过程,不要抱着畏惧的心态去面试,不利于自己的发挥。同时看中的应该不止薪资,还要看你是不是真的喜欢这家公司,是不是能真的得到锻炼。其实我写了这么多,只是我自己的总结,并不一定适用于所有人,相信经过一些面试,大家都会有这些感触。
最后我整理了一些面试真题资料,技术知识点剖析教程,还有和广大同仁一起交流学习共同进步,还有一些职业经验的分享。
[外链图片转存中…(img-XDHiInpp-1724472603864)]
了解详情https://docs.qq.com/doc/DSmxTbFJ1cmN1R2dB