首页 > 代码库 > java学习笔记--类ArrayList和LinkedList的实现

java学习笔记--类ArrayList和LinkedList的实现

    在集合Collection下的List中有两个实现使用的很频繁,一个是ArrayList,另一个是LinkedList,在学习中肯定都会有这样的疑问:什么时候适合使用ArrayList,什么时候用LinkedList?这时,我们就需要了解ArrayList和LinkedList的底层的实现,下面,为了更好的了解它们具体是怎样实现的,我们来写自己的ArrayList 和LinkedList。

    ArrayList底层是基于数组实现的,数组在内存中是存储在连续的存储单元中,在数据查找的时候比较快,适用于不常进行插入数据和需要频繁的查找数据的操作,下面,我们将其实现(为了方便理解,不使用泛型,用Object存储数据):

import java.util.Arrays;

/**
 * 顺序存储结构:类ArrayList的实现
 * @author liuzb
 * 2017年8月8日 下午2:58:59
 */
public class SequenceStroeLinearList{

	/**
	 * 用于存储容器中实际存储的元素个数
	 */
	private int size = 0;
	
	/**
	 * 底层用于存储数据的容器
	 */
	private Object[] container;	
	
	/**
	 * 在顺序存储结构中,用于初始化
	 */
	private static final Object[] EMPTY_CONTAINER = {};
	
	/**
	 * 构造器,用于初始化存储数据的容器
	 */
	public SequenceStroeLinearList(){
		this.container = EMPTY_CONTAINER;
	}
	
	/**
	 * 构造器,用于初始化存储数据的容器
	 */
	public SequenceStroeLinearList(int minCapactiy) {
		if(minCapactiy > 0) {
			container = new Object[minCapactiy];
		}else if(minCapactiy == 0) {
			container = EMPTY_CONTAINER;
		}else {
			throw new ArrayIndexOutOfBoundsException();
		}
	}
	
	/**
	 * 向容器中添加一个元素
	 * @param index 添加元素位置
	 * @param element 待添加元素
	 */
	public void insert(int index ,Object element) {
		//要向一个数组中插入数据:1、数组的长度够不够  2、插入位置合不合法
		//如果插入位置不合法,抛出索引越界异常
		if(index > container.length || index <0) {
			throw new ArrayIndexOutOfBoundsException();
		}else {
			//插入位置合法
			//如果是在尾部插入数据
			if(index == container.length) {
				container = Arrays.copyOf(container, ++size);
				container[index] = element;
			}else {
				//如果不是在尾部插入数据,先用临时变量存储容器中的内容
				Object[] temp = container;
				//1、container指向一个新容器
				container = new Object[size+1];
				//将原数组的下标从0到index的元素复制到扩容后的容器中
				System.arraycopy(temp, 0, container, 0, index);
				//2、将index及其以后位置的数据整体向后移位
				for(int i = size ; i > index ; i--) {
					container[i] = temp[i-1];
				}
				//3、插入数据
				container[index] = element;
				//4、元素个数加一
				++size;
			}
		}
	}
	
	/**
	 *  向容器中添加数据
	 * @param obj 需要添加的对象
	 */
	
	public void add(Object obj) {
		insert(size,obj);
	}
	
	/**
	 * 容器中实际存储的元素个数
	 * @return
	 */
	public int size() {
		return size;
	}
	
	/**
	 * 获取指定索引位置的对象
	 * @param index 指定位置的索引
	 * @return 指定位置的对象
	 */
	public Object get(int index) {
		if(index <0 || index > size) {
			throw new ArrayIndexOutOfBoundsException();
		}
		return container[index];
	}
	
	/**
	 * 获取指定对象的索引
	 * @param obj 需要获取索引的对象
	 * @return 索引 
	 */
	public int indexOf(Object obj) {
		int index = -1;
		for(int i = 0; i<size ; i++) {
			if(container[i].equals(obj)) {
				index = i;
			}
		}		
		return index;
	}
	
	/**
	 * 容器中是否包含某个元素
	 * @param obj
	 * @return false 不包含   true 包含
	 */
	public boolean contains(Object obj) {
		return indexOf(obj) == -1 ? false :true;
	}
	
	/**
	 * 从容器中移除指定索引的元素
	 * @param index 需要移除元素的索引
	 * @return 移除
	 */
	public boolean remove(Integer index) {
		boolean flag = true;
		// 非法索引,抛出异常
		if (index < 0 || index > size) {
			flag = false;
			throw new ArrayIndexOutOfBoundsException("移除指定索引元素失败,索引值非法");
		} else {
			// 索引合法
			for (int i = index; i < size-1; i++) {
				//将index到size的元素依次往前移位
				container[i] = container[i + 1];
			}
			// 将末尾元素值赋为 null
			container[size-1] = null;
			// 元素个数减一
			-- size;
		}
		return flag;
	}
	
	/**
	 * 移除指定元素
	 * @param obj 需要移除的元素 
	 * @return  true :移除成功      false:移除失败
	 */
	public boolean remove(Object obj){
		if(contains(obj)) {
			remove(indexOf(obj));
			return true;
		}
		return false;
	}
	
	
}

在实现中,用到了util包下面的Arrays帮助类,此处也可以使用System.arrayCpy()。

    写好该类后,对该类进行测试:

技术分享

从实现结果看,我们基本实现了ArrayList的功能,此处重要的两个方法是插入数据和移除数据,当然本程序也有bug,就是remove方法。

    LinkedList底层是基于链表实现的,在数据插入和删除时速度较快,适用于频繁进行插入和删除的操作,技术分享这里我们实现一个单链表:

/**
 * 自定义链式存储列表 :单链表
 * @author liuzb
 * 2017年8月9日 上午11:24:07
 */
public class MySingleLinkedList {
	
	/**
	 * 单链表中的首节点
	 */
	private Node header;
	/**
	 * 单链表中的尾节点
	 */
	private Node tail;
	
	/**
	 * 单链表中实际存储元素的个数
	 */
	private int size;
	
	/**
	 * 内部类,用于封装节点需要的数据和下一个节点的地址
	 * @author liuzb
	 * 2017年8月9日 上午11:24:43
	 */
	private class Node{
		/**
		 * 当前链表存储的数据
		 */
		private Object data;
		/**
		 * 当前节点存储的下一个节点的地址
		 */
		private Node next;

		/**
		 * 无参构造器,用于节点的初始化
		 */
		public Node() {
			
		}
		/**
		 * 有参构造器,用于节点的初始化
		 * @param date 节点存储的值
		 * @param next 节点中保存的下一个节点的地址
		 */
		public Node(Object data,Node next) {
			this.data = data;
			this.next = next;
		}
		/**
		 * 获取当前节点的存储的值
		 * @return 节点值
		 */ 
		public Object getData() {
			return data;
		}
	}
	
	/**
	 * 单链表头插入法
	 * @param item 需要存储的数据
	 */
    public void addHeader(Object item) {
    	//定义一个节点
    	Node node = null;
    	//如果原链表是空表
    	if(size == 0) {
    		//构建一个新的节点,节点的下一个节点指向null
    		node = new Node(item,null);
    		//头结点和尾节点都指向新节点
    		header = node;
    		tail = header;
    	}else {
    		//如果原链表不是空表,定义一个新节点,新节点的下一个节点指向原来的头结点
    		node = new Node(item,header);
    		//新节点变成了头结点
    		header = node;
    	}
    	//元素的个数加一
    	size ++;
    }
	
    /**
     * 单链表为插入法
     * @param item 需要出入的元素
     */ 
    public void addLast(Object item) {
    	//创建一个新节点
    	Node node = new Node(item,null);
    	// 如果原来的链表是空表
    	if(size == 0) {
    		//单链表的头结点和尾节点都指向新节点
    		tail = header = node;
    	}else {
    		//原来的尾节点的下一个节点指向新节点
    		tail.next = node;
    		//新节点变成了尾节点
    		tail = node;
    	}
    	//链表的元素个数加一
    	size ++;
    }
    
    /**
     * 向单链表中添加元素
     * @param item 待添加的元素
     * @return 
     */
    public void add(Object item) {
    	//方法中默认使用尾插入法插入元素
    	addLast(item);
    }    
    /**
     * 移除指定位置的元素
     * @param index 需要移除元素的索引
     * @return true:移除成功   false:移除失败
     */
    public boolean remove(int index) {
    	boolean flag = false;
    	//如果索引非法,抛出异常
		if (index < 0 || index >= size) {
			throw new IndexOutOfBoundsException("移除元素的索引越界");
		} else {
			//如果移除的是头结点
			if (index == 0) {
				//原头结点的下一个节点变成了头结点
				header = header.next;
			}else if(index == size-1) {
				//如果删除的是尾节点,先获取原尾节点的前一个节点
				Node node = getNodeByIndex(index-1);
				//将原尾节点的前一个节点存储的下一个节点地址信息置为null
				node.next = null;
				//原尾节点的前一个节点变成了尾节点
				tail = node;
			}else {
				
				//删除的既不是头结点,也不是尾节点,将需要删除的数据先暂时存储
				Node removeNode = getNodeByIndex(index);
				//获取需要删除数据的前一个节点
				Node node = getNodeByIndex(index - 1);
				//将前一个节点的下一个节点指向需要删除的节点的下一个节点
				node.next = removeNode.next;
			}
			//元素个数减一
			size -- ;

			flag = true;
		}
		return flag;
    }
    
    /**
     * 获取指定索引的节点
     * @param index 需要获取的节点的索引值
     * @return 节点对象
     */
    public Node getNodeByIndex(int index) {
    	Node current = header;
    	for(int i = 0;i < size && current != null;i++) {
    		if(index == i) {
    			return current;
    		}
    		current = current.next;
    	}
    	return null;
    }
    
    /**
     * 获取单链表中元素个数
     * @return 元素个数
     */
	public int size() {
		return size;
	}
	
	/**
	 * 获取指定索引的节点值
	 * @param index 需要获取的节点的索引
	 * @return 节点值
	 */
	public Object get(int index) {
		//索引非法,抛出异常
		if(index < 0 || index >= size) {
			throw new ArrayIndexOutOfBoundsException("无法获取该节点的值");
		}
		return getNodeByIndex(index).getData();
	}
	
	/**
	 * 向链表指定位置插入数据
	 * @param index 待插入位置
	 * @param item 待插入数据
	 * @return true 插入成功    false 插入失败
	 */
	public boolean insert(int index,Object item) {
		boolean flag = true;
		if(index < 0 || index >size) {
			flag = false;
			throw new ArrayIndexOutOfBoundsException();
		}
		//如果插入到头结点前
		if(index == 0) {
			header = new Node(item,header);
		}else if(index == size) {
			//插入到尾节点后
			//定义一个新的节点
			Node node = new Node(item,null);
			//尾节点的下一个节点指向新节点
			tail.next = node;
			//新节点变成了尾节点
			tail = node;
		}else {
			//在首节点和尾节点之间插入节点
			//获取出入位置的节点
			Node indexNode = getNodeByIndex(index);
			//定义新节点,新节点的下一个节点指向原插入位置的节点
			Node newNode = new Node(item,indexNode);
			//获取插入位置的前一个节点
			Node node = getNodeByIndex(index-1);
			//插入位置的前一个节点的下一个节点指向新节点
			node.next = newNode;
		}
		//元素个数加一
		size ++ ;
		
		return flag;
	}
    
}

写好代码后,对代码进行测试:

技术分享

以上是个人拙见,如有错误,请指出,谢谢!

本文出自 “刘紫兵的博客” 博客,请务必保留此出处http://zibing.blog.51cto.com/8177516/1954864

java学习笔记--类ArrayList和LinkedList的实现