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;
	}
    
}

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

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

时间: 2024-08-11 12:21:14

java学习笔记--类ArrayList和LinkedList的实现的相关文章

Java学习笔记之ArrayList基本用法

更多信息可关注我的个人博客:贱贱的梦想 ArrayList简介 ArrayList是一个其容量能够动态增长的动态数组.它继承了AbstractList,实现了List.RandomAccess, Cloneable, java.io.Serializable. 基本的ArrayList,长于随机访问元素,但是在List中间插入和移除元素时较慢.同时,ArrayList的操作不是线程安全的!一般在单线程中才使用ArrayList,而在多线程中一般使用Vector或者CopyOnWriteArray

Java学习笔记--类和对象

1.介绍面向对象的编程 面向对象是现在主流的编程样例,它替代了以前C语言使用时的"结构体",Java是一门面向对象的语言,所以需要熟悉面向对象的概念.面向对象的程序由很多对象组成,每个函数对于用户而言,都有特殊的功能.程序里面的很多对象都可以直接从公共库里面拿来直接用.不用去研究这些功能怎么去实现的.传统的结构的编程由一系列算法步骤来解决问题.一旦这些步骤确定下来,也要同时确定存储数据的方式.也就是数据结构一开始学习的:算法+数据结构=程序. 先决定算法,再决定使用什么样的结构来存储数

JAVA学习笔记-模拟ArrayList容器的底层实现

package MyArrayList;import java.util.*;/** * 模拟实现JDK中的ArrayList类 * @author iwang * */public class MyArrayList { /** * The value is used for Object storage. */ private Object[] value; /** * The size is the number of Object used. */ private int size; p

Java学习笔记_23_List接口实现类

23.List接口实现类: List接口继承了Collection接口,它是一个允许存在重复项的有序集合. 1>实现类ArrayList: ArrayList类支持可随需要而增长的动态数组.数组列表以一个原大小被创建,当超过了它的大小, 类集自动增大,当对象被删除后,数组就可以缩小. 优点:ArrayList类对于使用索引取出元素用较高的效率,他可以用索引快速定位对象. 缺点:ArrayList类对于元素的删除或插入速度较慢. 构造方法: · ArrayList(): 构造一个初始容量为10的空

Java学习笔记_25_Collections类

25.Collections类: Collections类是一个工具类,用来对集合进行操作,它主要是提供一些排序算法,包括随机排序.反相排序等. Collections类提供了一些静态方法,实现了基于List容器的一些常用算法. Collections的一些方法列表: · void sort(List): 对List内的元素进行排序. · void shuffle(List): 对List内的元素随机排序. · void reverse(List): 对List内的元素进行逆序排列. · voi

非专业码农 JAVA学习笔记 6java工具类和算法-string

续<非专业码农 JAVA学习笔记 5 java工具类和算法> 五.字符串string 字符串和字符的差别:字符串双引号括起来”n”,字符用单引号括起来,表示一种符号’\n’ 1.string的主要方法和属性 类 方法或者属性 备注 定义string Stirng s=new string(“值”),string s=”值” 属性 string.length:string的长度为字节 方法startswith,endswith s.startwith(“值”)-以值为开头,s.endswith(

Java学习笔记——File类之文件管理和读写操作、下载图片

Java学习笔记——File类之文件管理和读写操作.下载图片 File类的总结: 1.文件和文件夹的创建 2.文件的读取 3.文件的写入 4.文件的复制(字符流.字节流.处理流) 5.以图片地址下载图片 文件和文件夹 相关函数 (boolean) mkdir() 创建此抽象路径名指定的目录  (boolean) mkdirs() 创建此抽象路径名指定的目录,包括所有必需但不存在的父目录. (boolean) delete() 删除此抽象路径名表示的文件或目录 (boolean) createNe

java学习笔记07--日期操作类

java学习笔记07--日期操作类 一.Date类 在java.util包中定义了Date类,Date类本身使用非常简单,直接输出其实例化对象即可. [java] view plaincopy public class T { public static void main(String[] args) { Date date  = new Date(); System.out.println("当前日期:"+date); //当前日期:Thu May 16 23:00:57 CST 

非专业码农 JAVA学习笔记 3 抽象、封装和类(2)

(2).静态域-放在内存公共存储单元,不放在特定的对象,用static修饰 (续上一篇<非专业码农 JAVA学习笔记 3 抽象.封装和类(1)>...) (3).静态初始器-由static引导的一对大括号括起来的语句组,作用跟构造函数相似 (4).最终域-final引导的,值在整个过程都不发生改变的 5.方法 (1)方法的定义:修饰词1 修饰词2…返回值类型 方法名(参数) throw[异常列表] 这里个人经验就是注意定义了返回值的方法,要在方法体里面增加return 该类型变量:此外遇到if