杂谈最基本数据结构--"线性表":

  表结构是一种最基本的数据结构,最常见的实现是数组,几乎在每个程序每一种开发语言中都提供了数组这个顺序存储的线性表结构实现.

 什么是线性表?

   由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;
//使用一个数组来实现这个List
private 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,如果没有该关键字,会使内部类访问不到外部的成员
@Override
public Iterator<T> iterator() {
return new ArrayListIterator();
} private class ArrayListIterator implements java.util.Iterator<T>{ private int current=0; @Override
public boolean hasNext() {
return current<size();
} @Override
public T next() {
return theItems[current++];
} @Override
public 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方式遍历List
Iterator<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;
}
//将指定所引处的元素值修改为value
public 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; } @Override
public 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; @Override
public boolean hasNext() {
return current!=end;
} @Override
public 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;
} @Override
public 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的更多相关文章

  1. JDK学习---深入理解java中的LinkedList

    本文参考资料: 1.<大话数据结构> 2.http://blog.csdn.net/jzhf2012/article/details/8540543 3.http://blog.csdn. ...

  2. C++模拟实现JDK中的ArrayList和LinkedList

    Java实现ArrayList和LinkedList的方式采用的是数组和链表.以下是用C++代码的模拟: 声明Collection接口: #ifndef COLLECTION_H_ #define C ...

  3. 深入理解Java中的不可变对象

    深入理解Java中的不可变对象 不可变对象想必大部分朋友都不陌生,大家在平时写代码的过程中100%会使用到不可变对象,比如最常见的String对象.包装器对象等,那么到底为何Java语言要这么设计,真 ...

  4. 实现Java中的ArrayList

    最近深受轮子哥影响,觉得造一些轮子应该会对自己的技术功底有一定的帮助,就决定先从简单的容器开始实现.废话不多说,就先实现一个Java中的ArrayList. ArrayList是我们在Java中使用非 ...

  5. 理解Java中的弱引用(Weak Reference)

    本篇文章尝试从What.Why.How这三个角度来探索Java中的弱引用,理解Java中弱引用的定义.基本使用场景和使用方法.由于个人水平有限,叙述中难免存在不准确或是不清晰的地方,希望大家可以指出, ...

  6. 深刻理解Java中final的作用(一):从final的作用剖析String被设计成不可变类的深层原因

    声明:本博客为原创博客,未经同意,不得转载!小伙伴们假设是在别的地方看到的话,建议还是来csdn上看吧(原文链接为http://blog.csdn.net/bettarwang/article/det ...

  7. [译]线程生命周期-理解Java中的线程状态

    线程生命周期-理解Java中的线程状态 在多线程编程环境下,理解线程生命周期和线程状态非常重要. 在上一篇教程中,我们已经学习了如何创建java线程:实现Runnable接口或者成为Thread的子类 ...

  8. 深入理解Java中的IO

    深入理解Java中的IO 引言:     对程序语言的设计者来说,创建一个好的输入/输出(I/O)系统是一项艰难的任务 < Thinking in Java >   本文的目录视图如下: ...

  9. 理解Java中的ThreadLocal

    提到ThreadLocal,有些Android或者Java程序员可能有所陌生,可能会提出种种问题,它是做什么的,是不是和线程有关,怎么使用呢?等等问题,本文将总结一下我对ThreadLocal的理解和 ...

随机推荐

  1. ASP.NET Aries 入门开发教程1:框架下载与运行

    背景: 鉴于框架的使用者越来越多,文档太少,不少用户反映框架的入门门槛太高. 好吧,再辛苦下,抽时间写教程吧! 步骤1:下载框架源码 开源地址:https://github.com/cyq1162/A ...

  2. SQL必备知识点

    经典SQL语句大全 基础 1.说明:创建数据库.说明:删除数据库drop database dbname3.说明:备份sql server--- 创建 备份数据的 device.说明:创建新表crea ...

  3. Oracle碎碎念~2

    1. 如何查看表的列名及类型 SQL> select column_name,data_type,data_length from all_tab_columns where owner='SC ...

  4. 用WebRequest +HtmlAgilityPack 从外网抓取数据到本地

    相信大家对于WebRequest 并不陌生,我们在C#中发请求的方式,就是创建一个WebRequest .那么如果我们想发一个请求到外网,比如国内上不了的一些网站,那么该怎么做呢? 其实WebRequ ...

  5. 创建ABPboilerplate模版项目

    本文是根据角落的白板报的<通过ABPboilerplate模版创建项目>一文的学习总结,感谢原文作者角落的白板报. 1 准备 开发环境: Visual Studio 2015 update ...

  6. __Block与__Weak区别

    一.__block理解: Blocks可以访问局部变量,但是不能修改, 声明block的时候实际上是把当时的临时变量又复制了一份, 在block里即使修改了这些复制的变量,也不影响外面的原始变量.即所 ...

  7. SharePoint2016安装的过程的”Microsoft.SharePoint.Upgrade.SPUpgradeException”错误解决方法

    前提 在windows server 2012的服务器上运行安装sharepoint2016出现如下错误: Could not load file or assembly ‘Microsoft.Dat ...

  8. XAMARIN.ANDROID SIGNALR 实时消息接收发送示例

    SignalR 是一个开发实时 Web 应用的 .NET 类库,使用 SignalR 可以很容易的构建基于 ASP.NET 的实时 Web 应用.SignalR 支持多种服务器和客户端,可以 Host ...

  9. Linux系统中的Device Mapper学习

    在linux系统中你使用一些命令时(例如nmon.iostat 如下截图所示),有可能会看到一些名字为dm-xx的设备,那么这些设备到底是什么设备呢,跟磁盘有什么关系呢?以前不了解的时候,我也很纳闷. ...

  10. 面向未来的友好设计:Future Friendly

    一年前翻译了本文的一部分,最近终于翻译完成.虽然此设计思想的提出已经好几年了,但是还是觉得应该在国内推广一下,让大家知道“内容策略”,“移动优先”,“响应式设计”,“原子设计”等设计思想和技术的根源. ...