引言
上一篇博文,我们了解了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
效果不大,但是的确有一点点的优化。希望对大家有所帮助。