計算機筆記(9.13)
java容器
容器中大部分的底層實現的資料結構
容器中的設計模式
迭代器模式 介面卡模式
Arraylist
擴容 預設的大小是10 一般是擴容1.5倍
HashMap的原始碼分析
String
概覽
重點就是不可變 String 被宣告為 final,因此它不可被繼承。
內部使用 char 陣列儲存資料,該陣列被宣告為 final,這意味著 value 陣列初始化之後就不能再引用其它陣列。並且 String 內部沒有改變 value 陣列的方法,因此可以保證 String 不可變。
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
為什麼要搞成不可變?? 1. 可以快取 hash 值
因為 String 的 hash 值經常被使用,例如 String 用做 HashMap 的 key。不可變的特性可以使得 hash 值也不可變,因此只需要進行一次計算。
- String Pool 的需要
如果一個 String 物件已經被建立過了,那麼就會從 String Pool 中取得引用。只有 String 是不可變的,才可能使用 String Pool。
- 安全性
String 經常作為引數,String 不可變性可以保證引數不可變。例如在作為網路連線引數的情況下如果 String 是可變的,那麼在網路連線過程中,String 被改變,改變 String 物件的那一方以為現在連線的是其它主機,而實際情況卻不一定是。
- 執行緒安全
String 不可變性天生具備執行緒安全,可以在多個執行緒中安全地使用。
String, StringBuffer and StringBuilder
- 可變性 String 不可變 StringBuffer 和 StringBuilder 可變
- 執行緒安全 String 不可變,因此是執行緒安全的 StringBuilder 不是執行緒安全的 StringBuffer 是執行緒安全的,內部使用 synchronized 進行同步
String Pool
字串常量池(String Pool)儲存著所有字串字面量(literal strings),這些字面量在編譯時期就確定。不僅如此,還可以使用 String 的 intern() 方法在執行過程中將字串新增到 String Pool 中。
當一個字串呼叫 intern() 方法時,如果 String Pool 中已經存在一個字串和該字串值相等(使用 equals() 方法進行確定),那麼就會返回 String Pool 中字串的引用;否則,就會在 String Pool 中新增一個新的字串,並返回這個新字串的引用。
下面示例中,s1 和 s2 採用 new String() 的方式新建了兩個不同字串,而 s3 和 s4 是通過 s1.intern() 方法取得一個字串引用。intern() 首先把 s1 引用的字串放到 String Pool 中,然後返回這個字串引用。因此 s3 和 s4 引用的是同一個字串。
String s1 = new String("aaa");
String s2 = new String("aaa");
System.out.println(s1 == s2); // false
String s3 = s1.intern();
String s4 = s1.intern();
System.out.println(s3 == s4); // true
如果是採用 “bbb” 這種字面量的形式建立字串,會自動地將字串放入 String Pool 中。
String s5 = "bbb";
String s6 = "bbb";
System.out.println(s4 == s5); // true
在 Java 7 之前,String Pool 被放在執行時常量池中,它屬於永久代。而在 Java 7,String Pool 被移到堆中。這是因為永久代的空間有限,在大量使用字串的場景下會導致 OutOfMemoryError 錯誤。
new String(“abc”)
使用這種方式一共會建立兩個字串物件(前提是 String Poll 中還沒有 “abc” 字串物件)。
- “abc” 屬於字串字面量,因此編譯時期會在 String Poll 中建立一個字串物件,指向這個 “abc” 字串字面量;
- 而使用 new 的方式會在堆中建立一個字串物件。
建立一個測試類,其 main 方法中使用這種方式來建立字串物件。
public class NewStringTest {
public static void main(String[] args) {
String s = new String("abc");
}
}
使用 javap -verbose 進行反編譯,得到以下內容:
// ...
Constant pool:
// ...
#2 = Class #18 // java/lang/String
#3 = String #19 // abc
// ...
#18 = Utf8 java/lang/String
#19 = Utf8 abc
// ...
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=3, locals=2, args_size=1
0: new #2 // class java/lang/String
3: dup
4: ldc #3 // String abc
6: invokespecial #4 // Method java/lang/String."<init>":(Ljava/lang/String;)V
9: astore_1
// ...
在 Constant Poll 中,#19 儲存這字串字面量 “abc”,#3 是 String Poll 的字串物件,它指向 #19 這個字串字面量。在 main 方法中,0: 行使用 new #2 在堆中建立一個字串物件,並且使用 ldc #3 將 String Poll 中的字串物件作為 String 建構函式的引數。
以下是 String 建構函式的原始碼,可以看到,在將一個字串物件作為另一個字串物件的建構函式引數時,並不會完全複製 value 陣列內容,而是都會指向同一個 value 陣列。
public String(String original) {
this.value = original.value;
this.hash = original.hash;
}