StringBuffer与StringBuilder

先看一张继承关系图:

对比

StringBuilderStringBufferString
是否线程安全
操作时是否会创建新对象
是否可变可变可变不可变,每次操作都会产生一个新的对象

源码

关于线程安全,看源码可知:

StringBuffer是线程安全的,StringBuffer是非线程安全的。

StringBuffer中是通过大量的synchronized关键字来保证线程安全:

由于只能单线程操作,所以从效率上来讲StringBuffer的效率没有StringBuilder高。

通常来讲,如果程序不要求线程安全,那么推荐使用StringBuilder。如果会在并发环境下使用,那么只能选择StringBuffer。

代码案例

StringBuilder的案例:

1
2
3
4
5
6
7
8
9
10
11
      public static void main(String[] args) {
StringBuilder sb = new StringBuilder(10);
sb.append("Runoob..");
System.out.println(sb);
sb.append("!");
System.out.println(sb);
sb.insert(8, "Java");
System.out.println(sb);
sb.delete(5,8);
System.out.println(sb);
}

输出:

1
2
3
4
Runoob..
Runoob..!
Runoob..Java!
RunooJava!

以上demo的流程图例如下:

StringBuffer的代码示例:

1
2
3
4
5
6
7
public static void main(String args[]){
StringBuffer sBuffer = new StringBuffer("生命在于运动");
sBuffer.append("aa");
sBuffer.append("bbb");
sBuffer.append("ccc");
System.out.println(sBuffer);
}

运行结果:

1
生命在于运动aabbbccc

equals与hashCode

hashCode与equals用来标识对象,两个方法协同工作可以用来判断两个对象是否相等。

根据生成的哈希将数据离散开来,可以使得存取元素更快。

Object类中的两个方法如下:

1
2
3
  public boolean equals(Object obj) {
return (this == obj);
}
1
 public native int hashCode();

对象通过Object.hashCode()来生成哈希值。Object.hashCode()的实现是默认为每一个对象生成不同的int数值,是一个native方法,一般与对象的内存地址有关。由于不可避免的会存在哈希冲突的情况,一次当hashCode相同时,还需要调用equals进行一次值的比较;但是如果hashCode不同,将直接判断objects不同,跳过equals,这加快了冲突处理的效率。

Object类中对hashCode和equals方法要求如下:

  1. 如果两个对象的equals的结果是相同的,则两个对象的hashCode的返回结果也必须是相同的;
  2. 任何时候覆写equals,都必须同时覆写hashCode。

在Map和Set类集合中,用到这两个方法时,首先判断hashCode的值,如果hash相等,则再判断equals的结果,

HashMap中的get方法判断代码如下:

1
2
3
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;

if中的e.hash == hash是先决条件,只有相等了才会执行后面的判断。equals不相等时并不要求hashCode也不相等,但是一个优秀的hash算法应当尽可能的让元素均匀分布,降低冲突的概率,即在equals不相等时尽可能使hashCode也不相等,这样&&或||短路操作一旦生效,就会极大的提高程序的执行效率。

如果自定义对象作为Map的键,那么必须要覆写hashCode与equals。要根据自己定义的对象的逻辑来重写equals和hashCode。

Set集合中存储的是不重复的对象,依据hashCode和equals进行判断,所以Set存储的自定义对象也必须覆写这两个方法。

参考资料

StringBuffer 和 StringBuilder 类