淺談Java的數據結構

先上完整圖

數據結構:計算機中存儲、組織數據的方式。

分類:主要是三類,所有的容器數據結構都是這3個基本接口的擴展實現。

其中(Legacy)標註的代表線程安全,

可以用順口溜:喂(Vector-對標ArrayList),S(Stack,StringBuffer-對標StringBuilder)H(HashTable-對標HashMap,Properties-繼承自HashTable)E(Enum枚舉,有EnumSet和EnumMap兩個子類)記憶。

  • (interface)Iterator(可迭代,線性)
  • (interface)Map (k,v結構,非線性)
  • (interface)Comparable(可比較)

Iterator:迭代器

這個接口的名稱就很能說明一切了,繼承或實現該接口就擁有了可迭代的能力,但這個更多是作爲標識作用,並不算數據結構,數據結構是從Collection開始的,類似的還有Comparable和Cloneable

接口的方法:

boolean hasNext();//是否有下一個數據

E next();//下一個數據

//移除迭代器返回的數據,並可以選擇是否取出,1.8以後使用,這個操作在<算法>一書中受到了批判,屬於用力過猛的方法,
default void remove() {
        throw new UnsupportedOperationException("remove");
    }


//hasNext()和next()方法的封裝
default void forEachRemaining(Consumer<? super E> action) {
        Objects.requireNonNull(action);
        while (hasNext())
            action.accept(next());
    }

Collection:單列數據

 

List:有序(先後),主要有兩大分支,LinkedList和ArrayList

LinkedList:底層是鏈表,並不複雜抓住鏈表本質看源碼

核心代碼:以初始化方法一覽

1.初始化,本質是調用addAll()方法,其中index爲0,也就是說沒有擴容的說法,則也符合鏈表的特性


 public boolean addAll(int index, Collection<? extends E> c) {
        checkPositionIndex(index);

        Object[] a = c.toArray();
        int numNew = a.length;
        if (numNew == 0)
            return false;

        Node<E> pred, succ;
        if (index == size) {
            succ = null;
            pred = last;
        } else {
            succ = node(index);
            pred = succ.prev;
        }

        for (Object o : a) {
            @SuppressWarnings("unchecked") E e = (E) o;
            Node<E> newNode = new Node<>(pred, e, null);
            if (pred == null)
                first = newNode;
            else
                pred.next = newNode;
            pred = newNode;
        }

        if (succ == null) {
            last = pred;
        } else {
            pred.next = succ;
            succ.prev = pred;
        }

        size += numNew;
        modCount++;
        return true;
    }

ArrayList:本質是數組

核心代碼:
1.初始化

//默認容量是10
 private static final int DEFAULT_CAPACITY = 10;

  直接添加數據  
  public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            // defend against c.toArray (incorrectly) not returning Object[]
            // (see e.g. https://bugs.openjdk.java.net/browse/JDK-6260652)
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // replace with empty array.
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }
這裏可以看到它其實是將c變成了數組,而且我發現toArray()方法是屬於Collection的,
則意味這在LinkedList也有,但LinkedList並沒有實現它,而是使用父類的,
而且測試下來確實可以使用,這讓我有點好奇,LinkedList的數據是如何變成數組的,原理還有待探究.



先創建數組,這裏可以看到是可以指定容量的。
   public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }




2.添加數據以及被動擴容。可以看到如果數據存滿了,就會觸發擴容

 private void add(E e, Object[] elementData, int s) {
        if (s == elementData.length)
            elementData = grow();
        elementData[s] = e;
        size = s + 1;
    }

 private Object[] grow(int minCapacity) {
        return elementData = Arrays.copyOf(elementData,
                                           newCapacity(minCapacity));
    }

 private int newCapacity(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
   
     //這裏可以看到擴容的規則是擴容爲原來的容量的3/2,即增加1/2數據的容量

        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity <= 0) {
            if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
                return Math.max(DEFAULT_CAPACITY, minCapacity);
            if (minCapacity < 0) // overflow
                throw new OutOfMemoryError();
            return minCapacity;
        }
        return (newCapacity - MAX_ARRAY_SIZE <= 0)
            ? newCapacity
            : hugeCapacity(minCapacity);
    }

Vector:線程安全的ArrayList,所以對應的特性都有

核心代碼:
1.初始化,與ArrayList完全一致

2.線程安全Synchronize的體現在對數的操作(不包括初始化)

   public synchronized boolean add(E e) {
        modCount++;
        add(e, elementData, elementCount);
        return true;
    }


  public synchronized E get(int index) {
        if (index >= elementCount)
            throw new ArrayIndexOutOfBoundsException(index);

        return elementData(index);
    }


 public synchronized E remove(int index) {
        modCount++;
        if (index >= elementCount)
            throw new ArrayIndexOutOfBoundsException(index);
        E oldValue = elementData(index);

        int numMoved = elementCount - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--elementCount] = null; // Let gc do its work

        return oldValue;
    }

Set:數學概念集合的抽象,並不是二圖所說的有序,因爲我沒找到更好的圖了,抱歉。Set子類有實現了有序的,但是在實現繼承或實現Sorted的基礎上。

HashSet:底層以HashMap實現,這裏就沒有進行排序

核心代碼;

1.初始化

  <1> public HashSet(Collection<? extends E> c) {
        map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
        addAll(c);
     }

 <2>  public HashSet(int initialCapacity) {
        map = new HashMap<>(initialCapacity);
    }

可以很清楚的看到底層是HashMap,可以直接放數據集合或什麼都不放(這裏沒放源碼),也可以先指定容量

2.添加

   private static final Object PRESENT = new Object();

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

這裏可以看到HashSet的值其實是存放在了HashMap<K,V>結構的K裏面,也就是索引,而不是值V裏面,
值是固定的PRESENT對象,因爲HashMap的K是不能重複的,這樣就直接保證了HashSet數據的不重複。

3.遍歷 

 public Iterator<E> iterator() {
        return map.keySet().iterator();
    }
這裏可以直接看到是直接使用HashMap的方法進行遍歷

TreeSet:二叉樹實現的Set

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章