手把手教你用数据结构实现-------------ArrayList、LinkedList集合

626 阅读13分钟

1、我们先来聊聊ArrayList

大家也知道面试以及开发中我们常用ArrayList集合存储元素,那么你知道为什么要使用ArrayList来存储元素吗?

一张图带你走进面试常问知识点???

1、为什么使用ArrayList?

ArrayList 是最常用的 List 实现类,内部是通过数组实现的,它允许对元素进行快速随机访问。

数组的缺点是每个元素之间不能有间隔,当数组大小不满足时需要增加存储能力,就要将已经有数组的数据复制到新的存储空间中。

简单来说:原生数组长度一旦确定,就不能改变。(数组方法少)集合弥补了这些缺点。 2、ArrayList和LinkList的区别?

图解查询操作比较:

ArrayList底层为数组,有一个大的内存空间存放元素,查找时只需在此块中查询即可,所以查询效率高。

LinkList底层为双向链表,每一个节点块为一个内存区域,当需要查找时,顺着每个节点一直查询下去,会导致好多小内存的开销,所以查询效率低。

图解增删操作比较:

ArrayList底层为数组,有一个大的内存空间存放元素,增删时会对索引以后的元素进行逐个移动,相当于程序run了几次,所以增删效率低。

LinkList底层为双向链表,每一个节点块为一个内存区域,当需要增删时,只需要断开该节点,释放此小块内存空间,指针连接下一个元素即可,不需要对所有元素进行移动,所以增删效率高。


画重点:

当从 ArrayList 的中间位置插入或者删除元素时,需要对数组进 行复制、移动、代价比较高。因此,它适合随机查找和遍历,不适合插入和删除。


3、手写实现ArrayList

使用动态数组实现ArrayList

主要设计接口:

动态数组的设计:

第一步:定义相关属性与无参构造方法来初始化容量

public class MyArrayList<T> {
	private static final int DEFAULT_CAPACITY=10;//定义数组初始化容量
	private int size;//数组下一次要添加元素的下标 
	private T[] elements;//数组元素
	
	//无参构造方法
	public MyArrayList() {
		//为集合创建初始化容量
		elements = (T[])new Object[DEFAULT_CAPACITY];
	}

第二步:添加元素方法

1、逐个添加元素

        //逐个进行添加元素
	public void add(T element) {
		elements[size++] = element;
	}

2、指定位置添加元素

	//指定位置处添加元素
	public void add(int index,T element) {
		//判断index是否越界
		if(index<0 || index>size) {
			throw new IndexOutOfBoundsException();
		}
		
		//从后往前遍历集合,找到对应位置,插入即可
		for(int i = size;i>index;i--) {
			//将元素统一往后面移动,上一个元素为i的值
			elements[i] = elements[i-1];
		}
		//将要添加的元素添加到指定的索引处
		elements[index] = element;
		//数组下标+1
		size++;
	}

第三步:扩容操作

如何扩容:创建一个新的动态数组,ArrayList扩容为原来的1.5倍,引用将从原数组指向新数组,并且原数组没了引用,将被GC回收掉。

        //扩容操作
	public void addCapacity(){
	    //判断index是否越界
		if(size<elements.length) {
			return;
		}else {
			//开始扩容(位运算:16>>3 是等价于16/2的3次方  16<<3 是等价于16*2的3次方)
			int newCapacity = elements.length+(elements.length>>1);// *1.5倍
			//为新的动态数组创建新容量
			T[] newElements = (T[])new Object[newCapacity];
			for(int i = 0;i<size;i++) {
				//将原集合的值赋值给新集合中
				newElements[i] = elements[i];
			}
			//将新集合赋给原集合(注意,这相当于把引用重新指向新集合中,原集合被GC当作垃圾回收)
			elements = newElements;
		}
	}

第四步:删除操作

//删除元素操作
	public T remove(int index) {
		//判断index是否越界
		if(index<0 || index>=size) {
			throw new IndexOutOfBoundsException();
		}else {
			T old = elements[index];
			for(int i = index;i<size;i++) {
			//数组元素逐个向前移动,进行赋值
				elements[i] = elements[i+1];
			}
			size--;
			//将最后一个元素置为null,因为最后一个元素向前移动了,原位置处还有引用。
			//置为null可以防止内存泄漏,回收引用,便于GC垃圾回收
			elements[size] = null;
			//返回原集合
			return old;
		}
	}

第五步:set方法:

//设置元素操作
	public int set(int index,T element) {
		//判断index是否越界
		if(index<0 || index>=size) {
		//抛出下标越界异常
	    	throw new IndexOutOfBoundsException();
		}else {
			elements[index] = element;
		}
		return 0;
	}

第六步:get方法:

//获取元素操作
	public T get(int index) {
		//判断index是否越界
		if(index<0 || index>=size) {
		//抛出下标越界异常
		throw new IndexOutOfBoundsException();
		}else {
			return elements[index];
		}
	}

第七步:获取集合元素操作

//获取集合大小操作
	public int size() {
		return size;
	}

第八步:清空集合操作(注意:防止内存泄漏)

    //清空集合操作
	public void clear() {
		//置为null可以防止内存泄漏,回收引用,便于GC垃圾回收
		for(int i = 0;i<size;i++) {
			elements[i] = null;
		}
		//设置size位0即可
		size=0;
	}

第九步:判空操作

//判断集合是否为空
	public boolean isEmpty() {
		return size == 0? true:false;
	}

第十步:toString操作

//重写toString方法
	@Override
	public String toString() {
		StringBuffer sb = new StringBuffer().append("{");
		for(int i = 0;i<size;i++) {
			sb.append(elements[i].toString()).append(",");
		}
		//修改最后一个字符
		sb.setCharAt(sb.length()-1,'}');
		return sb.toString();
	}

自定义实现ArrayList完整代码:

package com.baoji.shoudong_arrayList;

import java.util.Arrays;

/**
 * @author LinChi
 *
 * @param <T>
 */
public class MyArrayList<T> {
	private static final int DEFAULT_CAPACITY=10;//定义数组初始化容量
	private int size;//数组下一次要添加元素的下标 
	private T[] elements;//数组元素
	
	//无参构造方法
	public MyArrayList() {
		//为集合创建初始化容量
		elements = (T[])new Object[DEFAULT_CAPACITY];
	}
	//逐个进行添加元素
	public void add(T element) {
		add(size,element);
	}
	//指定位置添加元素
	public void add(int index,T element) {
		//判断index是否越界
		if(index<0 || index>size) {
			throw new IndexOutOfBoundsException();
		}
		//扩容操作
		addCapacity();
		//从后往前遍历集合,找到对应位置,插入即可
		for(int i = size;i>index;i--) {
			//将元素统一往后面移动,上一个元素为i的值
			elements[i] = elements[i-1];
		}
		elements[index] = element;
		size++;
	}
	//扩容操作
	public void addCapacity(){
		if(size<elements.length) {
			return;
		}else {
			//开始扩容(位运算:16>>3 是等价于16/2的3次方  16<<3 是等价于16*2的3次方)
			int newCapacity = elements.length+(elements.length>>1);// *1.5倍
			T[] newElements = (T[])new Object[newCapacity];
			for(int i = 0;i<size;i++) {
				//将原集合的值赋值给新集合中
				newElements[i] = elements[i];
			}
			//将新集合赋给原集合(注意,这相当于把引用重新指向新集合中,原集合被GC当作垃圾回收)
			elements = newElements;
		}
	}
	//删除元素操作
	public T remove(int index) {
		//判断index是否越界
		if(index<0 || index>=size) {
			throw new IndexOutOfBoundsException();
		}else {
			T old = elements[index];
			for(int i = index;i<size;i++) {
				elements[i] = elements[i+1];
			}
			size--;
			//将最后一个元素置为null,因为最后一个元素向前移动了,原位置处还有引用。
			//置为null可以防止内存泄漏,回收引用,便于GC垃圾回收
			elements[size] = null;
			//返回原集合
			return old;
		}
	}
	//设置元素操作
	public int set(int index,T element) {
		//判断index是否越界
		if(index<0 || index>=size) {
					throw new IndexOutOfBoundsException();
		}else {
			elements[index] = element;
		}
		return 0;
	}
	//获取元素操作
	public T get(int index) {
		//判断index是否越界
		if(index<0 || index>=size) {
			throw new IndexOutOfBoundsException();
		}else {
			return elements[index];
		}
	}
	//获取集合大小操作
	public int size() {
		return size;
	}
	//清空集合操作
	public void clear() {
		//置为null可以防止内存泄漏,回收引用,便于GC垃圾回收
		for(int i = 0;i<size;i++) {
			elements[i] = null;
		}
		//设置size位0即可
		size=0;
	}
	//判断集合是否为空
	public boolean isEmpty() {
		return size == 0? true:false;
	}
	//重写toString方法
	@Override
	public String toString() {
		StringBuffer sb = new StringBuffer().append("{");
		for(int i = 0;i<size;i++) {
			sb.append(elements[i].toString()).append(",");
		}
		//修改最后一个字符
		sb.setCharAt(sb.length()-1,'}');
		return sb.toString();
	}
}

敲黑板:

每次当对象没有了作用时,必须要赋值为null,更好的解决了内存泄漏问题,及时的让GC进行垃圾回收,增加可用的内存空间。

通过对源码的解析,自定义实现ArrayList的重要方法,学习东西一定要有深度!!!


2、我们下来来聊聊LinkedListList

上面我们详细提到了ArrayList和LinkedList的区别,接下来我们用双向链表实现LinkedList

1、创建节点类

package com.yueqian.list;
/**
 * 创建节点类
 * @author LinChi
 *
 * @param <E>
 */
public class ListNode<E> {
	//集合元素
	private E element;
	//集合索引位置
	private int index;
	//下一个节点对象
	private ListNode<E> next;
	
	public ListNode(int index, E element){
		this.index = index;
		this.element = element;
	}

	public E getElement() {
		return element;
	}

	public void setElement(E element) {
		this.element = element;
	}

	public int getIndex() {
		return index;
	}

	public void setIndex(int index) {
		this.index = index;
	}

	public ListNode<E> getNext() {
		return next;
	}

	public void setNext(ListNode<E> next) {
		this.next = next;
	}
	
}

2、LinkedList具体实现增删改查方法

package com.yueqian.list;

public class MyList<E> {
	//定义集合大小
	private int size;
	//定义第一个节点
	private ListNode<E> first;
	
	/**
	 * 集合大小
	 * @return
	 */
    public int size(){
    	return size;
    }
    /**
     * 添加元素
     */
    public boolean add(E e){
    	//根据传入元素,构建新节点
    	ListNode<E> newNode = new ListNode<E>(size, e);
    	//得到最后一个节点
    	ListNode<E> lastNode = getLastNode();
    	//判断列表现在是否空列表
    	if(lastNode == null){
    		first = newNode;
    		//累加size
        	size++;
    		return true;
    	}
    	//列表不为空
    	lastNode.setNext(newNode);
    	//累加size
    	size++;
    	return true;
    }
    /**
     * 插入元素
     */
    public void add(int index, E element){
    	//判断非法输入
    	if(index < 0 || index > size){
    		throw new IndexOutOfBoundsException();
    	}
    	//可能是空列表
    	//判断是否添加都到末尾
    	if(index == size){
    		add(element);
    		return;
    	}
    	//至少有一个元素
    	//是否向 index = 0 的位置添加
    	if(index == 0){
    		//新建节点
    		ListNode<E> newNode = new ListNode<E>(0, element);
    		//记录first当前指向的第一个节点
    		ListNode<E> reNode = first;
    		//整理角标
    		changeIndex(0, true);
    		//让frist指向新节点
    		first = newNode;
    		//让新节点记录旧的第一个节点
    		newNode.setNext(reNode);
    		
    		size++;
    		return;
    	}
    	
    	//至少有两个元素
    	//新建节点
		ListNode<E> newNode = new ListNode<E>(index, element);
		//记录当前列表中index位置的节点,准备是新节点的下一个节点
		ListNode<E> nextNode = this.getIndexNode(index);
		//记录上一个节点
		ListNode<E> priNode = this.getIndexNode(index - 1);
		//整理角标
		changeIndex(index, true);
		//上一个节点存储新节点
		priNode.setNext(newNode);
		//让新节点存储下一个节点
		newNode.setNext(nextNode);
		size++;
    	
    }
    /**
     * 按照指定位置添加集合元素
     * @param c
     * @return
     */
    public boolean addAll(int index,MyList<? extends E> c) {
    	 //非法判断
    	if(c == null) {
    		throw new NullPointerException();
    	}
    	if(c.isEmpty()) {
    		return false;
    	}
    	for(int i = c.size-1;i>=0;i--) {
    		this.add(index,c.get(i));
    	}
    	return true;
    }
    
    /**
     * 指定位置处获得元素
     * @param index
     * @return
     */
    public E get(int index){
    	//获取指定的节点
    	ListNode<E> indexNode = getIndexNode(index);
    	return indexNode.getElement();
    }
    /**
     * 格式化字符串集合
     */
    public String toString(){
    	//判断是否空列表
    	if( size == 0){
    		return "{}";
    	}
    	
    	StringBuffer sbuf = new StringBuffer();
    	sbuf.append("{");
    	for (int i = 0; i < this.size; i++) {
			sbuf.append(get(i)).append(", ");
		}
    	//去掉最后一个,
    	sbuf.delete(sbuf.length()-2, sbuf.length());
    	sbuf.append("}");
    	return sbuf.toString();
    }
    /**
     * 按照索引位置处展示集合元素
     * @param fromIndex
     * @param toIndex
     * @return
     */
    public MyList<E> subList(int fromIndex, int toIndex){
    	//过滤非法输入
    	if(fromIndex < 0 || toIndex > size || fromIndex >= toIndex){
    		throw new IndexOutOfBoundsException();
    	}
    	
    	MyList<E> subList = new MyList<E>();
    	for (int i = fromIndex; i < toIndex; i++) {
			subList.add(this.get(i));
		}
    	return subList;
    	
    }
    /**
     * 判断集合是否为null
     * @return
     */
    public boolean isEmpty(){
    	if(this.size == 0){
    		return true;
    	}
    	return false;
    }
    /**
     * 添加所有
     * @param c
     * @return
     */
    public boolean addAll(MyList<? extends E> c){
    	//过滤非法输入
    	if(c == null){
    		throw new NullPointerException();
    	}
    	//c 是否 empty
    	if( c.isEmpty() ){
    		return false;
    	}
    	
    	//循环 c 挨个将c的元素加入this
    	for (int i = 0; i < c.size(); i++) {
			this.add(c.get(i));
		}
    	return true;
    }
    /**
     * 按照指定位置删除集合中的元素
     * @param index
     * @return
     */
    public E remove(int index){
    	//过滤非法输入
    	if(index < 0 || index >= size){
    		throw new IndexOutOfBoundsException();
    	}
    	//获取返回结果
    	E result = this.get(index);
    	
    	//判断是否移除最后一个节点
    	if(index == size - 1){
    		//是否只有一个元素
    		if( size <= 1){
    			this.first = null;
    		}else{
    			//找到倒数第二个节点,并且置空倒数第二个节点对最后一个节点的引用
    			this.getIndexNode(size - 2).setNext(null);
    		}
    	}else if(index == 0){
    		//判断是否移除第一个节点 index = 0 ,当前至少有2个元素
    		//获取第二个节点的引用
    		ListNode<E> node = this.getIndexNode(1);
    		//变更角标, 减 1 
    		this.changeIndex(1, false);
    		//让first直接获取第二个元素节点
    		first = node;
    	}else{
    		//删除中间节点, 至少有三个元素
    		//获取index之前的节点
    		ListNode<E> priNode = this.getIndexNode(index - 1);
    		//获取index之后的节点
    		ListNode<E> nextNode = this.getIndexNode(index + 1);
    		//变更角标, 减 1 
    		this.changeIndex(index + 1, false);
    		//让之前的节点绕过index位置,直接获取nextNode节点的引用
    		priNode.setNext(nextNode);
    	}
    	size--;
    	return result;
    }
    /**
     * 删除集合元素
     * @param c
     * @return
     */
    public boolean removeAll(MyList<?> c){
    	//判断集合是否为null
    	if(c == null) {
    		throw new NullPointerException();
    	}
    	//判断集合中是否为null列表
    	if(c.isEmpty()) {
    		return false;
    	}
    	//定义返回结果
    	boolean result = false;
    	//循环删除c集合中的元素
    	for(int i = 0;i<c.size;i++) {
    		boolean flag = this.remove(c.get(i));
    		result = result || flag;
    		while(flag) {    			
    			flag = this.remove(c.get(i));
    		}
    	}
    	return result;
    }
    /***
     * 按照集合对象找出索引位置
     * @param o
     * @return
     */
    public int indexOf(Object o){
    	int result = -1;
    	
    	for (int i = 0; i < this.size; i++) {
    		//判断是否同一个对象
    		if(this.get(i) == o){
    			result = i;
    			break;
    		}
    		//过滤null元素
    		if(this.get(i) == null){
    			continue;
    		}
    		//判断是否内容一样
    		if(this.get(i).equals(o)){
    			result = i;
    			break;
    		}
    	}
    	
    	return result;
    }
    /**
     * 寻找最后一个相同元素返回的索引位置
     * @param o
     * @return
     */
    public int lastIndexOf(Object o) {
//    	int result = -1;
//    	
//    	for (int i = this.size-1; i >=0 ; i--) {
//    		//判断是否同一个对象
//    		if(this.get(i) == o){
//    			result = i;
//    			break;
//    		}
//    		//过滤null元素
//    		if(this.get(i) == null){
//    			continue;
//    		}
//    		//判断是否内容一样
//    		if(this.get(i).equals(o)){
//    			result = i;
//    			break;
//    		}
//    	}
//    	return result;
    	int result = -1;
    	for(int i= 0;i<this.size;i++) {
    		int index = this.size-i-1;
    		//判断是否为同一个对象
    		if(this.get(index) == o) {
    			result = index;
    			break;
    		}
    		//判断是否为null
    		if(this.get(index) == null) {
    			continue;
    		}
    		//判断是否内容一样
    		if(this.get(index).equals(o)){
    			result = index;
    			break;
    		}
    	}
    	return result;
    }
    /**
     * 在集合中删除对象
     * @param o
     * @return
     */
    public boolean remove(Object o) {
    	//查找当前对象
    	int index = this.indexOf(o);
    	if(index == -1) {
    		return false;
    	}
    	this.remove(index);
    	return true;
    }
    /**
     * 清除所有元素
     */
    public void clear() {
    	this.size = 0;
    	this.first = null;
    }
    /**
     * 为集合指定位置设置值
     * @param index
     * @param element
     * @return
     */
    public E set(int index, E element) {
    	ListNode<E> node = this.getIndexNode(index);
    	E result = node.getElement();
    	//替换
    	node.setElement(element);
    	return result;
    }
    /**
     * 判断集合中是否包含
     * @param o
     * @return
     */
    public boolean contains(Object o) {
//    	for(int i = 0;i<size;i++) {
//    	if(o == this.get(i)) {
//    		return true;
//    	}
//    	if(o.equals(this.get(i))) {
//    		return true;
//    		}
//    	}
//    	return false;
    	return this.indexOf(o) == -1?false:true;
    }
    /**
     * 判断集合中是否包含其他集合
     * @param c
     * @return
     */
    public boolean containsAll(MyList<?> c) {
    	//判断对象是否为null
    	if(c == null) {
    		throw new NullPointerException();
    	}
    	for(int i = 0;i<c.size;i++) {
    		if(!(this.contains(c.get(i)))) {
    			return false;
    		}
    	}
    	return true;
    }
    public Object[] toArray() {
    	//判断当前对象是否为null
    	if(this.isEmpty()) {
    		return new Object[0];
    	}
    	//创建数组对象
    	Object[] result = new Object[this.size];
    	for(int i = 0;i<size;i++) {
    		result[i] = this.get(i);
    	}
    	return result;
    }
   public boolean retainAll(MyList<?> c) {
	   if(c == null) {
		   throw new NullPointerException();
	   }
	   if(c.isEmpty()) {
		   this.clear();
		   return true;
	   }
	   boolean result = false;
	   int thisSize = this.size;
	   for(int i = thisSize;i>=0;i--) {
		   if(!c.contains(c.get(i))) {
			   this.remove(i);
			   result = true;
		   }
	   }
	   return result;
   }

    private ListNode<E> getLastNode(){
    	if(this.first == null){
    		return null;
    	}
    	
    	ListNode<E> node = first;
    	while(node.getNext() != null){
    		node = node.getNext();
    	}
    	return node;
//    	return getIndexNode(size - 1);
    }
    
    private ListNode<E> getIndexNode(int index){
    	//判断是否参数非法
    	if(index < 0 || index >= size){
    		throw new IndexOutOfBoundsException();
    	}
    	
    	//循环获取指定位置的节点
    	ListNode<E> node = first;
    	while(node.getIndex() != index){
    		node = node.getNext();
    	}
    	return node;
    }
    
    /**
     * 
     * @param index
     * @param flag == true  加角标,  否则 减角标
     */
    private void changeIndex(int index, boolean flag){
    	//判断是否参数非法
    	if(index < 0 || index >= size){
    		throw new IndexOutOfBoundsException();
    	}
    	
    	ListNode<E> node = getIndexNode(index);
    	while(node != null){
    		if(flag){
    			node.setIndex(node.getIndex() + 1);
    		}else{
    			node.setIndex(node.getIndex() - 1);
    		}
    		node = node.getNext();
    	}
    }
}

3、测试LinkedList集合

package com.yueqian.list;

import java.util.ArrayList;
import java.util.List;

public class TestList {

	public static void main(String[] args) {
		List<String> list = new ArrayList<String>();
//		List<String> list2 = new ArrayList<String>();
//		MyList<String> list = new MyList<String>();
//		MyList<String> list2 = new MyList<String>();
		list.add("a");
		list.add("x");
		list.add("b");
		list.add("c");
		list.add("b");
		list.add("d");
		list.add("e");
//		list2.add("a");
//		list2.add("e");
//		list2.add("3");
//		list2.add("4");
//		System.out.println(list.addAll(3,list2));
//		System.out.println(list);
//		System.out.println(list.lastIndexOf("b"));
//		System.out.println(list.contains("f"));
//		System.out.println(list.toString());
//		System.out.println(list.containsAll(list2));
//		System.out.println(list.set(1,"g"));
		System.out.println(list.toString());
		list.clear();
		System.out.println(list.toString());
//		System.out.println(list.addAll(list2));
	}
}

写在最后

推荐自己的Github地址:github.com/Lmobject

您的支持与关注是对作者最大的信任,写作容易,坚持不易。谢谢!!!