ArrayList和LinkedList的区别、优缺点以及应用场景详解(一)

ArrayList和LinkedList都是实现了List接口的容器类,用于存储一系列的对象引用。下面我们通过源码(模拟)来分析下它们的底层实现。
首先,需要一个接口List(模拟):

public interface List {
	
	//返回线性表的大小,及数据元素的个数
	int size();
	
	
	//返回线性表中序号为i的数据元素
	
	Object get(int i);
	
	//如果线性表为空返回true,否则返回false
	boolean isEmpty();
	
	
	//判断线性表是否包含元素e
	boolean contains();
	
	
	//返回元素e在线性表中的序号
	int indexOf(Object e);
	
    //将元素e插入到线性表中的i位置
	void add(int i,Object e);
	
	//将元素e插入到线性表中的末尾
	void add(Object e);
	
	//将元素e插入到元素obj之前
	boolean addBefore(Object obj,Object e);
	
	//将元素e插入到元素obj之后
	boolean addAfter(Object obj,Object e);
	
	
	//删除线性表中序号为i的元素,并返回值
	Object remove(int i);
	
	//删除线性表中第一个与e相同的元素
	Object remove(Object e);
	
	//替换线性表中序号为i的数据元素为e,返回元数据元素
	Object replace(int i,Object obj);
		
	
}

接下来,我们实现顺序表ArrayList(模拟)

import java.util.Arrays;

import com.cshg.exception.MyArrayIndexOutOfBoundsException;

public class ArrayList  implements List{
	private Object[] elementData; //底层就是一个数组
	
	private int size;  //元素个数
	
	
	
	public ArrayList() {
		//没有指定长度,默认为10
		this(10);
	}
	
	

	public ArrayList(int initialCapacity) {
		this.elementData = new Object[initialCapacity];
	}


	//集合大小
	@Override
	public int size() {
		return size;
	}
	
	//获取下标为i的元素
	@Override
	public Object get(int i) {
		if(i < 0 || i >= size){
			return new MyArrayIndexOutOfBoundsException("下标越界异常:"+i); 
		}
		return elementData[i];
	}
	
	//是否为空
	@Override
	public boolean isEmpty() {
		
		return size == 0;
	}
	//包含
	@Override
	public boolean contains(Object o) {
		return indexOf(o) >= 0;
	}
	
	@Override
	public int indexOf(Object o) {
		if(o == null){
			for(int i = 0; i< size; i++)
				if(elementData[i] == null)
					return i;
		}else{
			for(int i = 0; i< size; i++)
				if(o.equals(elementData[i]))
					return i;
		}
		return -1;
	}
	public void rangeCheck(int i) throws MyArrayIndexOutOfBoundsException {
		if(i < 0 || i >= size){
			throw new MyArrayIndexOutOfBoundsException("下标越界异常:"+i); 
		}
	}
    public void rangeCheckForAdd(int i) throws MyArrayIndexOutOfBoundsException {
    	if(i < 0 || i > size){
			throw new MyArrayIndexOutOfBoundsException("下标越界异常:"+i); 
		}
	}
	@Override
	public void add(int i, Object e) throws MyArrayIndexOutOfBoundsException {
		rangeCheckForAdd(i);
		//元素后移
		for (int j = size; j > i; j--) {
			elementData[j] = elementData[j-1];
		}
		elementData[i] = e;
		size++;
		//扩容
		grow(size);
	}
	
	//扩容
	public void grow(int size){
		if(size > elementData.length){
			int number = (int)(size + size>>1);
			Object[] newArray = new Object[number];
			newArray = Arrays.copyOf(elementData,number);
			elementData = newArray;
		}
	}

	@Override
	public void add(Object o) {
		elementData[size] = o;
		size++;
		//扩容
		grow(size);
	}

	@Override
	public boolean addBefore(Object obj, Object e)  throws MyArrayIndexOutOfBoundsException {
		int i = indexOf(obj);
		if(i > -1){
			add(i,e);
			return true;
		}
		return false;
	}

	@Override
	public boolean addAfter(Object obj, Object e) throws MyArrayIndexOutOfBoundsException {
		int i = indexOf(obj);
		if(i > -1){
			add(i+1,e);
			return true;
		}
		return false;
	}

	@Override
	public Object remove(int i) throws MyArrayIndexOutOfBoundsException {
		rangeCheck(i);
		//前移
		for (int j = i; j <size-1; j++) {
			elementData[j] = elementData[j+1];			
		}
		elementData[size-1] = null;
		size--;
		return elementData;
	}

	@Override
	public Object remove(Object e) throws MyArrayIndexOutOfBoundsException {
		int i = indexOf(e);
		if(i > -1){
			remove(i);
		}
		return elementData;
	}

	@Override
	public Object replace(int i, Object obj) throws MyArrayIndexOutOfBoundsException{
		rangeCheck(i);
		elementData[i] = obj;
		return elementData;
	}

}

分析:
成员变量

    private Object[] elementData; //底层就是一个数组
	
	private int size;  //元素个数

size表示elementData数组的实际元素个数,elementData.length表示数组的最大集合容量。
构造函数

public ArrayList() {
		//没有指定长度,默认为10
		this(10);
}
public ArrayList(int initialCapacity) {
		this.elementData = new Object[initialCapacity];
}

无参构造函数,调用有参构造函数,并设置默认长度。
操作方法
add操作

	public void add(Object o) {
		elementData[size] = o;
		size++;
		//扩容
		dilatation(size);
	}

该函数插入数据时,会在后面插入,这就保证了数据的有序性。

	public void add(int i, Object e) throws MyArrayIndexOutOfBoundsException {
		rangeCheckForAdd(i);
		//元素后移
		for (int j = size; j > i; j--) {
			elementData[j] = elementData[j-1];
		}
		elementData[i] = e;
		size++;
		//扩容
		dilatation(size);
	}

该函数用于中间插入数据,但是插入该数据之前,还要做很多次的元素后移操作,即自i至最后的每个元素都必须向后挪一个位置,因此中间进行数据插入的性能很低。
扩容

    //扩容
	public void grow(int size){
		if(size > elementData.length){//如果实际元素个数大于数组的最大容量时,则进行扩容
			int number = (int)(size + size>>1);
			Object[] newArray = new Object[number];
			newArray = Arrays.copyOf(elementData,number);
			elementData = newArray;
		}
	}

进行add操作的时候,会调用扩容的方法,当size>elementData.length时,则进行扩容, 扩容后大小为原始容量的 1.5 倍。且看实现:新建一个长度为原来1.5倍的数组,将之前的数组的元素拷贝给新数组,最后将新数组赋值给elementData,即elementData的指针指向新数组。
remove操作

	@Override
	public Object remove(int i) throws MyArrayIndexOutOfBoundsException {
		rangeCheck(i);
		//前移
		for (int j = i; j <size-1; j++) {
			elementData[j] = elementData[j+1];			
		}
		elementData[size-1] = null;
		size--;
		return elementData;
	}

该函数会将第i个元素进行删除,在删除之前还要做很多次的元素前移操作,即自i至最后的每个元素都必须向前挪一个位置,因此中间进行数据删除的性能很低。
get操作

    //获取下标为i的元素
	@Override
	public Object get(int i) {
		if(i < 0 || i >= size){
			return new MyArrayIndexOutOfBoundsException("下标越界异常:"+i); 
		}
		return elementData[i];
	}

直接调用数组随机访问,性能很好。
单链表
在这里插入图片描述
单链表代码模拟实现

public class MyLinkedList {
	
	private MyNode head;
	private int size;
	
	public int size(){
		return size;
	}
	public boolean isEmpty(){
		return size == 0;
	}
	
	public void addHead(Object data){
		MyNode newNode = new MyNode(data);
		newNode.next = head;
		head = newNode;
		size++;
	}
	
	public void add(Object data,int position) throws MyArrayIndexOutOfBoundsException{
		checkPosition(position);
		if(position == 0){
			addHead(data);
		}else{
			MyNode cur = getCur(position);
			MyNode newNode = new MyNode(data);
			newNode.next = cur.next;//当前节点的next赋给新节点的next
			cur.next = newNode;//当前节点next指向新节点
			size++;
		}
	}
	
	public void delHead(){
		head = head.next;
	}
	public void delete(int position) throws MyArrayIndexOutOfBoundsException{
		checkPosition(position);
	    if(position == 0){
			delHead();
		}else{
			MyNode cur = getCur(position);
			cur.next = cur.next.next;
			size--;
		}
	}
	public void checkPosition(int position) throws MyArrayIndexOutOfBoundsException{
		if(position<0 || position > size){
			throw new MyArrayIndexOutOfBoundsException("-1", "指针越界");
		}
	}
	
	public MyNode getCur(int position) throws MyArrayIndexOutOfBoundsException{
		checkPosition(position);
		MyNode cur = head;
		for (int i = 1; i < position; i++) {
			cur = cur.next;  //一直往后遍历,直到找到插入的位置的上个节点(在此称为当前节点)
		}
		return cur;
	}
	public Object get(int position) throws MyArrayIndexOutOfBoundsException{
		return getCur(position+1).getValue();
	}
	

	
	class MyNode{
		Object value;
		MyNode next; //下一个指针
		public MyNode(Object value){
			this.value = value;
		}
		public MyNode(Object value,MyNode next){
			this.value = value;
			this.next = next;
		}
		public Object getValue(){
			return value;
		}
		
		
	}
}

add操作
1.addHead

	public void addHead(Object data){
		MyNode newNode = new MyNode(data);
		newNode.next = head;
		head = newNode;
		size++;
	}

添加头节点:创建一个新节点,新节点的后继指向原先的头节点,引用变量head指向新节点。请看图解:
在这里插入图片描述

2.add

	public void add(Object data,int position) throws MyArrayIndexOutOfBoundsException{
		checkPosition(position);
		if(position == 0){
			addHead(data);
		}else{
			MyNode cur = getCur(position);
			MyNode newNode = new MyNode(data);
			newNode.next = cur.next;//当前节点的next赋给新节点的next
			cur.next = newNode;//当前节点next指向新节点
			size++;
		}
	}
	public MyNode getCur(int position) throws MyArrayIndexOutOfBoundsException{
		checkPosition(position);
		MyNode cur = head;
		for (int i = 1; i < position; i++) {
			cur = cur.next;  //一直往后遍历,直到找到插入的位置的上个节点(在此称为当前节点)
		}
		return cur;
	}

插入数据:找到插入的位置的上一个节点(我们就叫它为当前节点吧),将当前节点的后继赋给新节点的后继,当前节点的后继指向新节点。图解:
在这里插入图片描述

remove操作
deleteHead

	public void delHead(){
		head = head.next;
	}

删除头结点:head = head.next,结合下图理解。
在这里插入图片描述
如上图所示,dataA所在的节点为头节点,删除头节点之后,那么头节点就变成了dataB所在的节点了,写成代码不就是head = head.next;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值