引言
上一篇博文,我們瞭解了LinkedList與ArrayList的底層構造和效率問題。在這篇博文中,我自己寫了兩個自己的數據結構來感受效率問題,這些代碼的由來源於我在某易的師兄的提問。所以我做了以下整理,希望對大家有所啓發,其實我們自己也能寫底層的源碼。筆者目前整理的一些blog針對面試都是超高頻出現的。大家可以點擊鏈接:http://blog.csdn.net/u012403290
需求
需要有一個固定長度的數據結構用於存儲數據,在數據插入到閥值的時候,移除最老的數據。使用數組與雙向鏈表實現。
基於數組實現:
package com.brickworkers;
/**
*
* @author Brickworker
* Date:2017年4月18日上午11:35:01
* 關於類MyArrayList.java的描述:數組結構
* Copyright (c) 2017, brcikworker All Rights Reserved.
*/
public class MyArrayList<T> {
private int size;//定義MyArrayList存儲數據的數量
private Object[] table;//底層用數組存儲
//構造函數,指定數組容量
public MyArrayList(int capacity) {//構建一個固定容量的MyArrayList
if(capacity >= 0)
table = new Object[capacity];
else
throw new IllegalArgumentException("capacity is not illegal"+ capacity);
}
//新增,只插入到數組尾部
public void add(T t){
//如果數據量到閥值,那麼觸發移除
if(size == table.length)
removeOldest();
table[size] = t;//數據放到數組最後面
size ++;
}
//移除最老的
public void removeOldest(){
//數組整體前移,覆蓋最老的數據
for (int i = 0; i < size - 1; i++) {
table[i] = table[i+1];//把整個數組往前移動一位
}
size --;
}
}
這代碼中我沒有繼承和實現任何接口,其實大家如果嘗試寫的話,可以繼承Iterable和實現AbstractList來實現,這樣的話,你就可以重寫集合方法,同時還可以用增強for循環來遍歷。不過如果要精簡還是向上面這段代碼一樣。
基於雙向鏈表實現
package com.brickworkers;
/**
*
* @author Brickworker
* Date:2017年4月18日上午11:35:19
* 關於類MyLinkedList.java的描述:雙向鏈表結構
* Copyright (c) 2017, brcikworker All Rights Reserved.
*/
public class MyLinkedList<T> {
private int size;//MyLinkedList中真實存在的數據
//避免麻煩,定義首節點和尾節點
private Node<T> startNode;
private Node<T> endNode;
private int MAX_SIZE;//最大容量
//定義雙向節點
private static class Node<T>{//靜態內部類
public T date;
public Node<T> prev;
public Node<T> next;
public Node(T t, Node<T> p, Node<T> n) {//節點構造函數
this.date = t;
this.prev = p;
this.next = n;
}
}
//構造函數
public MyLinkedList(int capacity) {//指定容量,自定義兩個節點不計算容量
if(capacity >=0 ){
MAX_SIZE = capacity;
startNode = new Node<T>(null, null, null);//起始節點
endNode = new Node<T>(null, startNode, null);//尾節點
startNode.next = endNode;//鏈接兩個節點
size = 0;
}else
throw new IllegalArgumentException("capacity is not illegal"+ capacity);
}
//添加到鏈表結尾
public void add(T t){
//如果數據存儲達到閥值,那麼觸發移除操作
if(size == MAX_SIZE)
removeOldest();
//新建包含t數據的節點,並把它插入到最後(注意我說的最後不包括自定義兩個節點)
endNode.prev = endNode.prev.next = new Node<T>(t, endNode.prev, endNode);
size ++;
}
//移除最老的節點
private void removeOldest(){
//避免惡意數據
if(startNode.next == endNode){
throw new IllegalArgumentException("can not remove Node, the size is 0");
}
//移除頭結點
Node<T> p = startNode.next; //p就是頭結點(注意我說的頭結點不包括自定義的兩個節點)
startNode.next = p.next;
p.next.prev = startNode;
size --;
}
}
上面這段就是用雙向鏈表來實現,其中核心的就是一個Node的靜態內部類,配合一個插入和移除的方法。進行測試:
package com.brickworkers;
public class MyListTest {
static void testList(int size, int forsize){
long arrStartTime = System.currentTimeMillis();
MyArrayList<Integer> myArrayList = new MyArrayList<Integer>(size);
for (int i = 0; i < forsize; i++) {
myArrayList.add(i);
}
System.out.println("數組結構耗時:"+(System.currentTimeMillis() - arrStartTime));
long linkStartTime = System.currentTimeMillis();
MyLinkedList<Integer> myLinkedList =new MyLinkedList<Integer>(size);
for (int i = 0; i < forsize; i++) {
myLinkedList.add(i);
}
System.out.println("雙向鏈表結構耗時:"+(System.currentTimeMillis() - linkStartTime));
}
public static void main(String[] args) {
testList(10000, 1000000);//容量設置爲10000, 循環插入1000000次
}
}
//輸出結果:
//數組結構耗時:6154
//雙向鏈表結構耗時:22
從上面的結果可以看出,數據的開銷非常巨大。我們考慮爲什麼MyArrayList開銷如此之大呢?核心問題其實是出在移除一個最老的數據後數組整體移動的原因,整個數組的移動開銷是非常大的。所以數組實現雖然可行,但是不合理。
我們考慮一下需求,容量一定的時候循環插入,當容量飽和的時候就需要開始移除數據。那麼我們可以考慮在數組飽和之後,把新增的數據覆蓋即將要移除的數據中。那麼其實就是不移動數組,而是移動了數據的下標,我修改了MyArrayList如下:
修改之後的MyArrayList
package com.brickworkers;
/**
*
* @author Brickworker
* Date:2017年4月18日上午11:35:01
* 關於類MyArrayList.java的描述:數組結構
* Copyright (c) 2017, brcikworker All Rights Reserved.
*/
public class MyArrayList<T> {
private int size;
private Object[] table;
private int pointer; //數組引用
//構造函數,指定數組容量
public MyArrayList(int capacity) {
if(capacity >= 0)
table = new Object[capacity];
else
throw new IllegalArgumentException("capacity is not illegal"+ capacity);
}
//新增插入到引用位置
public void add(T t){
table[pointer] = t;//按指針指向的地方進行插入
trimPointer();//指針使用之後需要進行指針調整
if(size != table.length)//如果數據飽和,size不再增加
size++;
}
//調整指針位置
public void trimPointer(){
//如果指針指向最後就回撥到最前
if(pointer == table.length - 1){
pointer = 0;//指針歸0
}else{
pointer++;//指針往前移動一位
}
}
}
修改之後的MyArrayList修改的核心是修改了remove的實現,用最新的數據去覆蓋最老的數據。測試數據量與上面相同的情況下,測試結果如下:
//數組結構耗時:12
//雙向鏈表結構耗時:22
發現這個時候數組的效率比雙向鏈表還要高,那麼我們雙向鏈表如果也和數組一樣實現會怎麼樣呢?以下是我修改之後的雙向鏈表實現:
修改之後的MyLinkedList
package com.brickworkers;
import javax.swing.tree.DefaultTreeCellEditor.EditorContainer;
/**
*
* @author Brickworker
* Date:2017年4月18日上午11:35:19
* 關於類MyLinkedList.java的描述:雙向鏈表結構
* Copyright (c) 2017, brcikworker All Rights Reserved.
*/
public class MyLinkedList<T> {
private int size;
//避免麻煩,定義首節點和尾節點
private Node<T> startNode;
private Node<T> endNode;
private int MAX_SIZE;
private Node<T> pointerNode;//目標指針
//定義雙向節點
private static class Node<T>{
public T date;
public Node<T> prev;
public Node<T> next;
public Node(T t, Node<T> p, Node<T> n) {
this.date = t;
this.prev = p;
this.next = n;
}
}
//構造函數
public MyLinkedList(int capacity) {
if(capacity >=0 ){
MAX_SIZE = capacity;
startNode = new Node<T>(null, null, null);//起始節點
endNode = new Node<T>(null, startNode, null);//尾節點
startNode.next = endNode;//鏈接兩個節點
size = 0;
}
else
throw new IllegalArgumentException("capacity is not illegal"+ capacity);
}
//添加到鏈表結尾
public void add(T t){
//如果雙向鏈表中存儲的數據達到閥值之後,就直接把頭節點移動到尾部進行值覆蓋
if(size == MAX_SIZE){
removeFirst2Last();
pointerNode.date = t;
}else{//如果沒有達到閥值的話,那麼就新增一個節點放置雙向鏈表最後
endNode.prev = endNode.prev.next = new Node<T>(t, endNode.prev, endNode);
size ++;
}
}
//把頭結點移動到尾部
private void removeFirst2Last(){
if(startNode.next == endNode){
throw new IllegalArgumentException("can not remove Node, the size is 0");
}
pointerNode = startNode.next;
startNode.next = pointerNode.next;//解決最頭上節點
pointerNode.next.prev = startNode;//解決指針節點的原本後節點
pointerNode.next = endNode;//
pointerNode.prev = endNode.prev;//解決指針節點
endNode.prev.next = pointerNode;//解決尾節點之前的節點
endNode.prev = pointerNode;//最後解決尾節點
}
}
和數組的實現方式一樣,在雙向鏈表中當數據飽和之後就需要把最老的節點移動到最前面來,並進行值覆蓋。測試數據和原先還是一樣,以下是測試結果:
//數組結構耗時:12
//雙向鏈表結構耗時:20
效果不大,但是的確有一點點的優化。希望對大家有所幫助。