1. java面試基礎三:集合框架List面試題
1.1. java集合框架裏面List常見面試題
- 說一下Vector和ArrayList,LinkedList的聯繫和區別?分別使用場景
從兩點回答:
1、線程安全
ArrayList:底層是數組實現,線程不安全,好處就是查詢和修改非常快,但是增加和刪除慢。
LInkedList:底層是雙向鏈表,查詢和修改速度慢,但是增加和刪除速度快。
Vector:底層也是數組實現,線程安全的使用了Synchronized加鎖。
2、使用場景
Vector: 已經很少用了,
LinkedList:增加和刪除多用LinkedList。
ArrayList:查詢和修改多,用ArrayList.
- 如果要保證線程安全ArrayList應該如何做?
方案一:自己寫個包裝類,根據業務一般是add/update/remove加鎖。
方案二:用Collections.synchronizedList(new ArrayList<>());進行加鎖。
方案三:CopyOnWriteArrayList<>() 使用 ReentrantLock加鎖,思想是:先獲取原先的數組,獲取後通過ReentrantLock進行加鎖,然後把原來的數組copy到一個新的數組中,再將新增的數組元素添加進去,然後再去掉鎖,然後再將原來數組的地址指向新數組,再返回回去。
- CopyOnWriteArrayList和SynchronizedList實現線程安全有什麼區別?使用場景是怎樣的?
CopyOnWriteArrayList:執行修改操作,會拷貝一份新的數據,(add/set/remove).代價昂貴,修改好後,會將原來的集合指向新的集合來完成操作,使用ReentrantLock來保證不會有多個線程同時修改。
使用場景:適合讀操作遠遠大於寫操作的場景,讀操作是不需要加鎖的,直接獲取,但是刪除和增加需要加鎖,讀多寫少。
Collections.synchronizedList:線程安全的原因就是幾乎每個方法都是使用synchronized來同步加鎖的,
使用場景:寫操作性能比CopyOnWriteArrayList好,但是讀操作性能沒有CopyOnWriteArrayList好。
- CopyOnWriteArrayList的設計思想是怎樣的?有什麼缺點?
設計思想:讀寫分離+最終一致,
缺點:佔內存,由於寫時複製,內存裏面會存在兩個對象佔用內存,如果對象大則容易發生YongGC和FullGC。
1.2. LIst的擴容機制
- 說一下ArrayList的擴容機制
1、這個分版本,jdk1.7之前ArrayList的默認大小是10,jdk1.7之後是0
2、未指定集合容量,默認是0,如果已經指定大小則集合大小爲指定大小。
3、當集合new出來時,容量爲0,當第一次添加元素時,集合擴容爲10
4、當集合元素大於10時,擴容容量的大小=原始大小+原始大小/2
- 手寫一個簡單版的ArrayList(包含構造函數(有參和無參,add(obj) 、擴容機制))
import java.io.Serializable;
public class MyArrayList implements Serializable{
//使用這個字段,來判斷當前集合類是否被併發修改,即迭代器併發修改的fail-fast機制。
private transient int modCount=0;
//第一次擴容容量
private static final int DEFAULT_CAPACITY=10;
//用於初始化空的list
private static final Object[] EMPTY_ELEMENT_DATA= {};
//實際存儲的元素
transient Object[] elementData;//transient避免被序列化
//實際list集合大小,從0開始
private int size;
//構造方法
public MyArrayList() {
this.elementData= EMPTY_ELEMENT_DATA;
}
public MyArrayList(int initialCapcity) {
if(initialCapcity>0) {
this.elementData=new Object[initialCapcity];
}else if(initialCapcity==0) {
this.elementData=EMPTY_ELEMENT_DATA;
}else {
throw new IllegalAccessError("參數異常");
}
}
public boolean add(Object e) {
//判斷容量
ensureCapacityInternal(size+1);
//使用下標複製,尾部插入,這個是下標,還有一個容量。
elementData[size++]=e;
return true;
}
/**
* 計算容量,確保容量
* @Title: ensureCapacityInternal
* @Description:
* @param minCapacity
* @author:kaifan·Zhang
* @date 2020年3月11日
* @version 1.0
*/
private void ensureCapacityInternal(int minCapacity) {
//用於併發判斷
modCount++;
//如果是初次擴容,則使用默認容量。
if(elementData==EMPTY_ELEMENT_DATA) {
minCapacity=Math.max(DEFAULT_CAPACITY, minCapacity);
}
//是否需要擴容,需求的最少容量,大於現在數組的長度,則要擴容
if(minCapacity-elementData.length>0) {
int oldCapacity=elementData.length;
int newCapacity=oldCapacity+(oldCapacity>>1);
//如果新容量<最小容量,將最小
if(newCapacity-minCapacity<0) {
newCapacity =minCapacity;
}
//創建新數組
Object[] objects=new Object[newCapacity];
//將舊的數據cop到新的數組裏面。
System.arraycopy(elementData, 0, objects, 0, elementData.length);
//修改引用
elementData=objects;
}
}
/**
* 通過下標獲取對象。
* @Title: get
* @Description:
* @param index
* @return
* @author:kaifan·Zhang
* @date 2020年3月11日
* @version 1.0
*/
public Object get(int index) {
rangeCheck(index);
return elementData[index];
}
private void rangeCheck(int index) {
if(index>size||size<0) {
throw new IndexOutOfBoundsException("數據越界");
}
}
/**
* 判斷對象所在的位置
* @Title: indexOf
* @Description:
* @param o
* @return
* @author:kaifan·Zhang
* @date 2020年3月11日
* @version 1.0
*/
public int indexOf(Object o) {
if(o==null) {
for(int i=0;i<size;i++) {
if(elementData[i]==null) {
return i;
}
}
}else {
for(int i=0;i<size;i++) {
if(o.equals(elementData[i])) {
return i;
}
}
}
return -1;
}
/**
* 修改下標對應的值
* @Title: set
* @Description:
* @param index
* @param obj
* @return
* @author:kaifan·Zhang
* @date 2020年3月11日
* @version 1.0
*/
public Object set(int index,Object obj) {
rangeCheck(index);
Object oldValue=elementData[index];
elementData[index]=obj;
return oldValue;
}
/**
* 根據索引刪除下標對應的元素
* @Title: remove
* @Description:
* @param index
* @return
* @author:kaifan·Zhang
* @date 2020年3月11日
* @version 1.0
*/
public Object remove(int index) {
rangeCheck(index);
//用於併發判斷,當迭代器操作時,會有很多操作,
//當操作到最後會與剛進入方法時的modCount進行比較,如果不同,就會拋出異常
modCount++;
Object oldValue =elementData[index];
//計算要刪除的位置後面還有幾個元素
int numMoved=size-index-1;
if(numMoved>0) {
System.arraycopy(elementData, index+1, elementData, index, numMoved);
}
//將多出的位置設置爲null,沒有引用對象。垃圾收集器就可以回收。如果不置空,將會保存一個引用
//可能會造成內存泄露。
elementData[--size]=null;
return oldValue;
}
/**
* 獲取數組實際大小
* @Title: size
* @Description:
* @return
* @author:kaifan·Zhang
* @date 2020年3月11日
* @version 1.0
*/
public int size() {
return this.size;
}
}