深入理解java中的ArrayList和LinkedList
杂谈最基本数据结构--"线性表":
表结构是一种最基本的数据结构,最常见的实现是数组,几乎在每个程序每一种开发语言中都提供了数组这个顺序存储的线性表结构实现.
什么是线性表?
由0个或多个数据元素组成的有限序列.如果没有元素,称为空表,如果存在多个元素,则第一个元素无前驱,最后一个元素无后继,其他元素元素都有且只有一个前驱和后继.
ArrayList和LinkedList
ArrayList和LinkedList是顺序存储结构和链式存储结构的表在java语言中的实现.
ArrayList提供了一种可增长数组的实现,使用ArrayList,因为内部使用数组实现,所以,它的优点是,对于get和set操作调用花费常数时间.缺点是插入元素和删除元素会付出昂贵的代价.因为这个操作会导致后面的元素都要发生变动,除非操作发生在集合的末端.
鉴于这个缺点,如果需要对表结构的前端频繁进行插入,删除操作,那么数组就不是一个好的实现,为此就需要使用另一种结构,链表,而LinkedList就是基于一种双链表的实现,使用它的优点就是,对于元素的插入,删除操作开销比较小,无论是在表的前端还是后端.但是缺点也显而易见,对于get操作,它需要从表的一端一个一个的进行查找,因此对get的调用花费的代价也是很昂贵的.
理解这两个集合最好的方式就是自己去实现它,下面我们通过代码来实现自己的arrayList和LinkedList.
实现ArrayList
通过拜读java中ArrayList的源码可以发现,其实实现一个ArrayList并不难,借鉴源码,我们也可以写出一个简易版的ArrayList集合表结构.
MyArrayList:
package com.wang.list;import java.util.Iterator;public class MyArrayList<T> implements Iterable<T> {//初始化集合的容量private static final int DEFAULT_CAPACITY=10;//当前元素的个数private int theSize;//使用一个数组来实现这个Listprivate T [] theItems;public MyArrayList(){clear();}//清空集合元素,初始化时使用public void clear(){theSize=0;ensureCapacity(DEFAULT_CAPACITY);}//通过传入的int值大小决定是否需要扩充集合空间大小public void ensureCapacity(int newCapacity) {if(newCapacity<theSize)return;T[] old=theItems;theItems=(T[]) new Object[newCapacity];for(int i=0;i<size();i++){theItems[i]=old[i];}}//返回当前集合元素的个数public int size() {return theSize;}//返回当前集合是否为空public boolean isEmpty(){return size()==0;}public void trimToSize(){ensureCapacity(size());}//获取指定索引下的元素值public T get(int index){if(index<0||index>=size())throw new ArrayIndexOutOfBoundsException("索引越界");return theItems[index];}//修改指定索引上的元素值public T set(int index,T value){if(index<0||index>=size())throw new ArrayIndexOutOfBoundsException("索引越界");T old=theItems[index];theItems[index]=value;return old;}//添加元素,直接指定元素,默认添加在数组末尾public boolean add(T value){add(size(),value);return true;}//添加元素,指定添加位置和元素值public void add(int index, T value) {//如果数组元素个数已经达到指定size();那么扩展该数组,使数组容量加倍if(theItems.length==size()){ensureCapacity(size()*2+1);}//从末尾元素向前遍历,一直到index处,使index处之后的所有元素向后移动一位for(int i=theSize;i>index;i--){theItems[i]=theItems[i-1];}theItems[index]=value;//添加完元素,是集合元素个数加一theSize++;}//移除指定索引处的元素值,public T remove(int index){T val=theItems[index];for(int i=index;i<size()-1;i++){theItems[i]=theItems[i+1];}theSize--;return val;}//重写迭代器的方法,使用下面的静态内部类实现,注意static,如果没有该关键字,会使内部类访问不到外部的成员 @Overridepublic Iterator<T> iterator() {return new ArrayListIterator();}private class ArrayListIterator implements java.util.Iterator<T>{private int current=0;@Overridepublic boolean hasNext() {return current<size();}@Overridepublic T next() {return theItems[current++];}@Overridepublic void remove() {MyArrayList.this.remove(--current);}}}
现在来写一个测试类来看看我们自己写的集合好不好用:
package com.wang.list;import java.util.Iterator;public class TestMyArrayList {public static void main(String[] args) {MyArrayList<String> list=new MyArrayList<>();list.add("hello");list.add("world");list.add("I");list.add("am");list.add("a");list.add("list");System.out.println("List的元素个数为:"+list.size());System.out.println("list的第二个元素个数为:"+list.get(1));//将第5个元素"a",替换为"the".list.set(4, "the");System.out.println("替换后的第五个元素为:"+list.get(4));//使用iterator方式遍历ListIterator<String> it = list.iterator();while(it.hasNext()){System.out.println(it.next());}} }
打印结果:
List的元素个数为:6
list的第二个元素个数为:world
替换后的第五个元素为:the
hello
world
I
am
the
list
实现LinkedList
LinkedList是一个双链表的结构,在设计这个这个表结构时,我们需要考虑提供3个类.
1.MyLinkedList类本身.
2.Node类,该类作为静态的内部类出现,包含一个节点上的数据,以及到前一个节点和后一个节点的链.
3.LinkedListIterator类,也是一个私有类,实现Iterator接口,提供next().hannext().remove()方法.
MyLinkedList:
package com.wang.list;import java.util.Iterator;public class MyLinkedList<T> implements Iterable<T> {//保存元素个数private int theSize;//记录修改的次数,//用处是:用于在迭代器遍历时,用于判断在迭代过程中是否发生了修改操作,如果发生了修改,则抛出异常private int modCount=0;//定义两个节点,首节点和尾节点private Node<T> begin;private Node<T> end;public MyLinkedList(){clear();}//初始化一个空表public void clear(){begin=new Node<T>(null, null, null);end=new Node<T>(null, begin, null);begin.next=end;theSize=0;modCount++;}public int size(){return theSize;}public boolean isEmpty(){return size()==0;}public boolean add(T value){add(size(),value);return true;}public void add(int index,T value){//在指定索引位置先找到那个索引处的节点类信息即先getNode(index).然后执行addBefore方法 addBefore(getNode(index),value);}//在指定的那个节点P的前方插入值为value的一个元素private void addBefore(Node<T> p,T value){Node<T> newNode=new Node<T>(value, p.prev, p);newNode.prev.next=newNode;p.prev=newNode;theSize++;modCount++;}//通过索引值返回对应位置的节点类信息private Node<T> getNode(int index){Node<T> p;if(index<0||index>size()){throw new IndexOutOfBoundsException();}if(index<(size()/2)){p=begin.next;for(int i=0;i<index;i++){p=p.next;}}else{p=end;for(int i=size();i>index;i--){p=p.prev;}}return p;}//返回指定索引处的元素值public T get(int index){return getNode(index).data;}//将指定所引处的元素值修改为valuepublic T set(int index,T value){Node<T> p=getNode(index);T oldData=p.data;p.data=value;return oldData;}//移除指定索引处的元素值public T remove(int index){return remove(getNode(index));}private T remove(Node<T> p){p.next.prev=p.prev;p.prev.next=p.next;theSize--;modCount++;return p.data;}@Overridepublic Iterator<T> iterator() {return new LinkedListIteraor();}private class LinkedListIteraor implements Iterator<T>{//保留第一个起始位置的节点private Node<T> current=begin.next;//记录此刻集合修改的总次数,之后会拿modCount再和此值作比较,如果不相等,说明在迭代过程中,//集合发生了修改操作,则会抛出异常private int exceptedModCount=modCount;//判断是否可以向后移动指针private boolean canMove=false;@Overridepublic boolean hasNext() {return current!=end;}@Overridepublic T next() {if(exceptedModCount!=modCount){throw new java.util.ConcurrentModificationException();}if(!hasNext()){throw new java.util.NoSuchElementException();}T nextValue=current.data;current=current.next;canMove=true;return nextValue;}@Overridepublic void remove() {if(exceptedModCount!=modCount){throw new java.util.ConcurrentModificationException();}if(!canMove){throw new IllegalStateException();}MyLinkedList.this.remove(current.prev);canMove=false;exceptedModCount++;}}private static class Node<T> {//当前节点数据public T data;//到前一个节点的链public Node<T> prev;//到后一个节点的链public Node<T> next;public Node(T data, Node<T> prev, Node<T> next) {this.data = data;this.prev = prev;this.next = next;}}}
测试类TestMyLinkedList:
package com.wang.list;import java.util.Iterator;public class TestMyLinkedList {public static void main(String[] args) {MyLinkedList<Integer> list=new MyLinkedList<>();list.add(1);list.add(5);list.add(7);list.add(6);list.add(4);list.add(2);list.add(3);for(int i=0;i<list.size();i++){System.out.print(list.get(i)+" ");}System.out.println();list.set(2, 8);list.remove(0);Iterator<Integer> it = list.iterator();while(it.hasNext()){System.out.print(it.next()+" ");}}}
打印结果:
1 5 7 6 4 2 3
5 8 6 4 2 3
遍历集合时进行修改操作为什么会抛出ConcurrentModificationException:
以前,我碰到过这样的问题,需要删除某一个元素,首先要在先遍历找到要删除的元素,然后删除它,结果会抛出一个ConcurrentModificationException()异常,错误代码大概是这样(以上面的测试类中LinkList为例):
for(Integer i:list){if(i==6){list.remove(6);} }
注意:我上面并没有实现这个remove()方法,这个remove()是根据传入的值进行删除,而我只写了一个根据传入的索引位置来删除元素的方法.这里假设我们使用的java提供的LinkedList.
现在我们知道了原因,因为增强的for循环内部使用的是iterator迭代器的方式进行遍历的,在遍历过程中,如果进行修改操作会导致exceptedModCount的值和modCount的值不相等.因此会抛出ConcurrentModificationException的异常.
正确的删除元素的方式应该是使用iterator()的方式遍历并进行删除操作,因为迭代器中的remove()方法和集合中的remove()有一点不同就是,前者删除后,会进行exceptedModCount++,因为不会抛出上面那个异常:
Iterator<Integer> it = list.iterator();while(it.hasNext()){if(it.next()==6){it.remove();}}
注:实现这两个集合的代码参考了JAVA语言描述的<数据结构和算法>一书,我自己实现了一个比较丑陋的版本,不够精致,不敢献丑,看着参考书上又修改了一番,不喜勿喷>..<
深入理解java中的ArrayList和LinkedList相关推荐
- java的foreach_深入理解java中for和foreach循环
•for循环中的循环条件中的变量只求一次值!具体看最后的图片 •foreach语句是java5新增,在遍历数组.集合的时候,foreach拥有不错的性能. •foreach是for语句的简化,但是fo ...
- 实现Java中的ArrayList
最近深受轮子哥影响,觉得造一些轮子应该会对自己的技术功底有一定的帮助,就决定先从简单的容器开始实现.废话不多说,就先实现一个Java中的ArrayList. ArrayList是我们在Java中使用非 ...
- 深入理解Java中的final关键字
深入理解Java中的final关键字 http://www.importnew.com/7553.html Java中的final关键字非常重要,它可以应用于类.方法以及变量.这篇文章中我将带你看看什 ...
- Java中使用ArrayList的10个示例–教程
Java中的ArrayList是HashMap之后最常用的集合类. Java ArrayList表示一个可自动调整大小的数组,并用于代替数组. 由于创建数组后我们无法修改数组的大小,因此我们更喜欢在J ...
- 深入理解Java中的内存泄漏
理解Java中的内存泄漏,我们首先要清楚Java中的内存区域分配问题和内存回收的问题本文将分为三大部分介绍这些内容. Java中的内存分配 Java中的内存区域主要分为线程共享的和线程私有的两大区域: ...
- 理解Java中的弱引用(Weak Reference)
理解Java中的弱引用(Weak Reference) 本篇文章尝试从What.Why.How这三个角度来探索Java中的弱引用,理解Java中弱引用的定义.基本使用场景和使用方法.由于个人水平有限, ...
- 如何理解 JAVA 中的 volatile 关键字
如何理解 JAVA 中的 volatile 关键字 最近在重新梳理多线程,同步相关的知识点.关于 volatile 关键字阅读了好多博客文章,发现质量高适合小白的不多,最终找到一篇英文的非常通俗易懂. ...
- java中demo接人_return的用法_如何理解java中return的用法?
C语言中return用法?(请熟练者进) return是返回值,这个返回值是和函数的类型有关的,函数的类型是什么,他的返回值就是什么 比方主函数intmain() {}这里就必须有一个return,只 ...
- java弱引用怎么手动释放,十分钟理解Java中的弱引用,十分钟java引用
十分钟理解Java中的弱引用,十分钟java引用 本篇文章尝试从What.Why.How这三个角度来探索Java中的弱引用,帮助大家理解Java中弱引用的定义.基本使用场景和使用方法.由于个人水平有限 ...
最新文章
- via浏览器下载路径_Via - 能够安装脚本插件的安卓浏览器
- vfast-全新的开始——荣新的第一天
- 通过MyBatis查找一张表的数据,某些字段的值为空
- decide how to invest 15 million pound in the development cost?
- 20220401 从解方程角度看什么是线性系统的能控与能观
- OpenCV立体校准stereo calib的实例(附完整代码)
- tornado 学习笔记17 HTTPServerRequest分析
- How to Fix an App that Crashes in Release but n...
- java windows 取所有任务_Win下,通过Jstack截取Java进程中的堆栈信息
- Jeecg-Boot上传及下载附件异常处理
- 【转载】Nessus安全测试插件编写教程
- java 5 线程 睡眠,Java线程之线程的调度-休眠
- Python中Unicode字符串
- 福州大学计算机报录比2019,2019-2020福州大学报录比波动分析:2020年调剂难度加大...
- Hive复杂数据类型 struct
- java form上传图片_js formData图片上传(单图上传、多图上传)后台java
- 通过Matlab或python调用ABAQUS
- IPv6与IPv4的区别 网信办等三部推进IPv6规模部署
- 再生龙给分区安装linux,用Clonezilla再生龙备份还原UBUNTU(LINUX)系统分区(可以备份MAC系统分区)...
- cocos2d-x Android游戏黑屏解决办法