List
首先說一下list的一些特點:list中的元素是有序并且有重復的!
以下內(nèi)容請結(jié)合源碼一起食用效果更佳!
ArrayList
底層是什么?
ArrayList的底層是
數(shù)組。
初始化參數(shù)有哪些?
- 默認容量是
10; - 當調(diào)用
new ArrayList(),即調(diào)用空的構(gòu)造函數(shù)的時候是默認初始化一個空數(shù)組。然后往數(shù)組添加元素也即調(diào)用add(E)或add(int, E)方法的時候才初始化數(shù)組容量為默認值10; - 當調(diào)用
new ArrayList(int initialCapacity),即調(diào)用指定數(shù)組容量的構(gòu)造函數(shù)的時候是默認初始化一個指定大小的數(shù)組;
擴容機制是怎樣的?
- 當添加新的元素的時候,如果
size+1大于當前數(shù)組的容量,默認擴容為原數(shù)組容量的1.5倍,這個1.5倍的說法不是很嚴謹,源碼中是int newCapacity = oldCapacity + (oldCapacity >> 1);,也即原容量+原容量右移一位,可以理解為原容量+原容量/2(即對2取整的結(jié)果)。
是否線程安全?
ArrayList是非線程安全的!為什么?
- 當不會觸發(fā)擴容的時候,新元素添加到數(shù)組中的操作是
elementData[size++] = e;,這里size++操作就不是線程安全的,因為它不是一個原子操作,多線程情況下很容易出現(xiàn)并發(fā)的問題! - 當觸發(fā)擴容的時候,可能會出現(xiàn)數(shù)組元素丟失的情況,具體是怎么個丟失法呢?假設數(shù)組容量是10,現(xiàn)在有兩個線程同時執(zhí)行到
grow()方法,在最后一步執(zhí)行數(shù)組拷貝以及重復賦值時,兩個線程都執(zhí)行了數(shù)組的拷貝,然后A線程先賦值elementData = Arrays.copyOf(elementData, newCapacity);,接著完成了add方法,此時數(shù)組elementData[10]這個位置就添加量新的元素,然后B線程再進行數(shù)組的賦值,此時就會將A線程添加的元素值給覆蓋掉的情況!這個過程好好想想還是可以理解的!
拓展:如果想要使用線程安全的ArrayList有沒有呢?
可選方案有:Vector、Collections.synchronizedList()、CopyOnWriteArrayList
小妙招:如果能確定ArrayList的初始容量,最好是使用new ArrayList(int initialCapacity)這個構(gòu)造函數(shù),避免數(shù)組擴容帶來額外的影響。
使用場景有哪些?
ArrayList的一個特性就是它的底層是基于數(shù)組的,數(shù)組是一種線性數(shù)據(jù)結(jié)構(gòu),在內(nèi)存中的存儲空間是連續(xù)的。
- 查詢的效率高,因為空間連續(xù),支持通過下標的隨機訪問;
- 增刪改的效率可能不是很好,因為要重新拷貝數(shù)組,當數(shù)組的元素特別多的時候,這個效率可想而知!
所以說,對于只是隨機訪問的,使用ArrayList是個不錯的選擇,效率高還安全!如果要執(zhí)行一些增刪改的操作,可以考慮使用LinkedList!
下一篇就說一下LinkedList!
彩蛋
多次執(zhí)行下面的代碼,看看有什么發(fā)現(xiàn)?
public class ArrayListUnsafeTest {
public static void main(String[] args) {
List<String> list = new ArrayList<>(10);
for (int i = 0; i < 5; i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0, 8));
System.out.println(list);
}, String.valueOf(i)).start();
}
}
}