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

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

 什么是线性表?

   由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. 利用snowfall.jquery.js实现爱心满屏飞

    小颖在上一篇一步一步教你用CSS画爱心中已经分享一种画爱心的方法,这次再分享一种方法用css画爱心,并利用snowfall.jquery.js实现爱心满屏飞的效果. 第一步: 利用伪元素before和 ...

  2. CMS模板应用调研问卷

    截止目前,已经有数十家网站与我们合作,进行了MIP化改造,在搜索结果页也能看到"闪电标"的出现.除了改造方面的问题,MIP项目组被问到最多的就是:我用了wordpress,我用了织 ...

  3. .NET 基础 一步步 一幕幕[面向对象之方法、方法的重载、方法的重写、方法的递归]

    方法.方法的重载.方法的重写.方法的递归 方法: 将一堆代码进行重用的一种机制. 语法: [访问修饰符] 返回类型 <方法名>(参数列表){ 方法主体: } 返回值类型:如果不需要写返回值 ...

  4. MySQL数据库和InnoDB存储引擎文件

    参数文件 当MySQL示例启动时,数据库会先去读一个配置参数文件,用来寻找数据库的各种文件所在位置以及指定某些初始化参数,这些参数通常定义了某种内存结构有多大等.在默认情况下,MySQL实例会按照一定 ...

  5. 初学者看过来之JSON入门

    1. 什么是JSON JSON---Javascript Object Notation,前两个单词大家应该都认识,最后一个notation,是"记号.标记法"的意思,连在一起,便 ...

  6. nodejs项目mysql使用sequelize支持存储emoji

    nodejs项目mysql使用sequelize支持存储emoji 本篇主要记录nodejs项目阿里云mysql如何支持存储emoji表情. 因由 最近项目遇到用户在文本输入emoji进行存储的时候导 ...

  7. 读python源码--对象模型

    学python的人都知道,python中一切皆是对象,如class生成的对象是对象,class本身也是对象,int是对象,str是对象,dict是对象....所以,我很好奇,python是怎样实现这些 ...

  8. 移动端访问PC站点时自动跳转至移动站点

    方法一: 百度Site APP的uaredirect.js 实现手机访问,自动跳转 <script src="http://siteapp.baidu.com/static/webap ...

  9. 七牛云:ckeditor JS SDK 结合 C#实现多图片上传。

    成功了,搞了2天.分享一下经验. 首先是把官方的那个例子下载下来,然后照如下的方式修改. 其中tempValue是一个全局变量. function savetoqiniu() { var upload ...

  10. scala练习题1 基础知识

    1, 在scala REPL中输入3. 然后按下tab键,有哪些方法可以被调用? 24个方法可以被调用, 8个基本类型: 基本的操作符, 等:     2,在scala REPL中,计算3的平方根,然 ...