java基礎-集合

以下純屬個人見解,如有不妥請及時指正以免誤導他人!!!未完待續….

1.java集合中常用的類都有哪些,簡單介紹一下?
參見6


2.ArrayList和LinkedList的區別,它們的原理?
     ArrayList和LinkedList都是List系的實現類,但是兩者的實現原理不同:
ArrayList是在動態數組的基礎上實現的,既然是數組,那麼隨機訪問的性能就會快,但是插入或者刪除數據就會比較慢,比方說:如果操作是在某一位置插入元素的話,首先會判斷size+1,是否需要擴容,如果需要就要擴容!注意擴容是需要進行數組複製拷貝的,然後,對elementData進行操作,操作過程是System.arraycopy對於index索引位置原數組中此位置後的元素移到 index + 1後的位置,空出index索引下的數組位置放置插入的元素!需要進行擴容、數組拷貝,所以才存在大家說的隨機訪問快,但是插入或者刪除等修改會慢;下面看一下它擴容的代碼細節:

private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);//擴容1.5倍
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);//並且將原數組數據進行復制到新的數組
    }

所以,初始化的時候默認爲空數組,當第一次add元素的時候會進行容量初始化,如果已知需要的集合大小,最好初始化的時候置入構造函數,可以提升性能,避免頻繁擴容。

private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
 public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
private void ensureCapacityInternal(int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            //初始化爲DEFAULT_CAPACITY = 10
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);
    }

LinkedList的實現則是基於鏈表,不僅實現了List而且也實現了Deque,所以是雙端鏈表;它的基礎在於它的私有內部類:

private static class Node<E> {
        E item;
        Node<E> next;
        Node<E> prev;

        Node(Node<E> prev, E element, Node<E> next) {
            this.item = element;
            this.next = next;
            this.prev = prev;
        }
    }

這是LinkedList實現鏈表的關鍵,一個節點包含着本身存儲的數據和前後繼節點,所以它的隨機訪問是遍歷鏈表來查找節點,相較於ArrayList的數組結構基於角標訪問性能差一些;但是它的增刪操作卻性能相對高一些,畢竟它只需要更改鏈表中節點的數據以及相應的前後繼節點的變動,而不需要像ArrayList那樣挪動數組來實現。

此外還需注意,ArrayList和LinkedList都是非線程安全的集合類。


3.HashMap是什麼數據結構,他是怎麼實現的?Java8做了什麼改進?

//HashMap維護了一個Entry類型的數組,一個Entry就是一個key-value對象
transient Entry[] table;

而Entry又是HashMap中的靜態內部類 ,由代碼可知它又含有成員變量Entry next維繫成鏈表結構,代碼如下:

static class Entry<K,V> implements Map.Entry<K,V> {
    final K key;
    V value;
    Entry<K,V> next;
    final int hash;

    Entry(int h, K k, V v, Entry<K,V> n) {
        value = v;
        next = n;
        key = k;
        hash = h;
    }

    public final K getKey() {
        return key;
    }

    public final V getValue() {
        return value;
    }

    public final V setValue(V newValue) {
    V oldValue = value;
        value = newValue;
        return oldValue;
    }
}

所以HashMap的數據結構就是由數組+鏈表構成的。

JDK8 新增了紅黑樹特性,參考下文:
https://blog.csdn.net/u011240877/article/details/53358305


4.Hashtable和HashMap的區別?
參見本文第九題


5.Arrays和Collections的使用?
Arrays此類包含用來操作數組(比如排序和搜索)的各種方法。此類還包含一個允許將數組作爲列表來查看的靜態工廠。
Collections此類提供了一系列方法操作集合對象,例如:排序,轉化線程安全集合對象等。


6.List、Map和Set他們的特點是什麼?他們都有哪些典型子類?
List和Set都實現了Collection接口,List的實現類常使用的主要有ArrayList,CopyOnWriteArrayList, LinkedList, Vector,其中ArrayList、LinkedList是按照元素插入的順序,但是他們是非線程安全的,並且他們兩個實現原理也不相同(請看本文第二題),Vector則是線程安全的集合類,它與ArrayList同樣是基於動態數組原理實現,只不過他的操作元素的方法使用了synchronized關鍵字,所以線程安全,如非必要存在競爭,不要使用,同步會有些許的性能消損。CopyOnWriteArrayList同樣也是ArrayList的一個線程安全的變體,但是它和Vector不同的是它使用顯示鎖

public boolean add(E e) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            newElements[len] = e;
            setArray(newElements);
            return true;
        } finally {
            lock.unlock();
        }
    }

Set特點是不存在重複元素。它的主要實現類有ConcurrentSkipListSet, CopyOnWriteArraySet, HashSet, LinkedHashSet, TreeSet ;其中HashSet是常用的,它由哈希表支持,其實它的實現是藉助於HashMap,遍歷無序,且線程不同步的類。TreeSet的無參構造實例是按照自然排序,也可以使用帶有Comparator參數的構造實現自定義排序規則的Set。LinkedHashSet則是哈希表+鏈表實現,迭代有序。

Map則是鍵值對集合,主要實現類由HashMap,LinkedHashMap,ConcurrentHashMap,TreeMap,Hashtable。


7.常見的有序集合?
比如說:帶有放入順序的ArrayXXX,LinkedXXX,以及TreeXXX等都是有序集合。


8.如何實現集合的有序性?
通過一下實例代碼,看一下:

Collections.sort(result, new Comparator<ReviewDetailBean>() {
    public int compare(ReviewDetailBean o1, ReviewDetailBean o2) {
        return o2.getTpgs() - o1.getTpgs();
    }
});

只要是實現Comparator接口,自定義compare方法完成對List的特定排序。


9.HashMap、Hashtable和ConcurrentHashMap的區別? 2018-4-3
HashMap是無序散列,並且它是非線程安全的!如果想用有序的,要用TreeMap默認是鍵的自然排序,以及LinkedHashMap爲鏈表實現,順序可預知。HashMap的底層數據結構是數組+鏈表,數組是Node[] table,而Node是它的內靜態部類,這個結構就很熟悉了這是鏈表存有next後繼節點。HashMap如果往同一鍵上加多個值,取到的是最後放進去的那個。

static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        V value;
        Node<K,V> next;
}

而Hashtable由它非駝峯式的命名就知道他是一個歷史遺留類。但是它是線程安全的,因爲它的方法public synchronized V put(K key, V value) 等使用了synchronized 關鍵字修飾所以是線程安全的,但是synchronized 鎖得是Hashtable的new實例對象,所以效率會慢。

而相比起線程同步的ConcurrentHashMap則是使用了分段鎖的實現,效率會比Hashtable高很多,因此如果要使用線程同步的Map,可以首先考慮ConcurrentHashMap。

其他讓Map線程安全的方法有:

Collections.synchronizedMap(Map m)


10.HashSet的源碼實現:

private transient HashMap<E,Object> map;

// Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object();

/**
 * Constructs a new, empty set; the backing <tt>HashMap</tt> instance has
 * default initial capacity (16) and load factor (0.75).
 */
public HashSet() {
    map = new HashMap<E,Object>();
}
public Iterator<E> iterator() {
    return map.keySet().iterator();
}

public boolean add(E e) {
    return map.put(e, PRESENT)==null;
}

由以上源碼可見,HashSet是藉助HashMap對象實現的,利用HashMap的keySet()。只不過,它的key-value爲add的元素E 與 靜態常量PRESENT 。


11.ArrayList在foreach的時候remove元素爲什麼會拋出異常?
https://blog.csdn.net/kevin_king1992/article/details/79888918


12.ArrayList的擴容機制?

//第一次添加元素容量初始爲默認值10
private static final int DEFAULT_CAPACITY = 10;
private void grow(int minCapacity) {

    int oldCapacity = elementData.length;
    //oldCapacity >> 1,擴容原來的一半;
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    // 並且會原先數組的拷貝到擴容後的數組,並賦值給transient Object[] elementData;
    elementData = Arrays.copyOf(elementData, newCapacity);
}

13.HashMap的擴容機制是什麼樣子的?
HashMap的初始值是16,默認的負載因子是0.75

static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
static final float DEFAULT_LOAD_FACTOR = 0.75f;

看一下put方法中陸續調用到了resize方法:

if (oldCap > 0) {
     if (oldCap >= MAXIMUM_CAPACITY) {
         threshold = Integer.MAX_VALUE;
         return oldTab;
     }
     //newCap = oldCap << 1 擴容到原來2倍
     else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
              oldCap >= DEFAULT_INITIAL_CAPACITY)
         newThr = oldThr << 1; // double threshold
 }
 else if (oldThr > 0) // initial capacity was placed in threshold
     newCap = oldThr;
 else {               // zero initial threshold signifies using defaults
     newCap = DEFAULT_INITIAL_CAPACITY;
     newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
 }

14.CopyOnWriteArrayList實現原理?

public boolean add(E e) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            newElements[len] = e;
            setArray(newElements);
            return true;
        } finally {
            lock.unlock();
        }
    }

從添加元素的方法來看,是利用了顯示鎖進行加鎖控制,然後將數據數組進行了複製,長度加一,以來存放添加的數據。

 final transient ReentrantLock lock = new ReentrantLock();

 private transient volatile Object[] array;//數據數組

再來看一下get方法,可見:

public E get(int index) {
    return get(getArray(), index);
}

private E get(Object[] a, int index) {
    return (E) a[index];
}

get方法並不需要加鎖,以達到高效高吞吐量。volatile 修飾的array保證了可見性。


發佈了98 篇原創文章 · 獲贊 115 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章