通过自己实现java中的ArrayList和LinkedList 深入理解数据结构---"表"

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

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

 什么是线性表?

   由0个或多个数据元素组成的有限序列.如果没有元素,称为空表,如果存在多个元素,则第一个元素无前驱,最后一个元素无后继,其他元素元素都有且只有一个前驱和后继.

 ArrayList和LinkedList  

  ArrayList和LinkedList是顺序存储结构和链式存储结构的表在java语言中的实现.

  ArrayList提供了一种可增长数组的实现,使用ArrayList,因为内部使用数组实现,所以,它的优点是,对于get和set操作调用花费常数时间.缺点是插入元素和删除元素会付出昂贵的代价.因为这个操作会导致后面的元素都要发生变动,除非操作发生在集合的末端.

  鉴于这个缺点,如果需要对表结构的前端频繁进行插入,删除操作,那么数组就不是一个好的实现,为此就需要使用另一种结构,链表,而LinkedList就是基于一种双链表的实现,使用它的优点就是,对于元素的插入,删除操作开销比较小,无论是在表的前端还是后端.但是缺点也显而易见,对于get操作,它需要从表的一端一个一个的进行查找,因此对get的调用花费的代价也是很昂贵的.

实现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语言描述的<数据结构和算法>一书,我自己实现了一个比较丑陋的版本,不够精致,不敢献丑,因为参考书上修改了一番,不喜勿喷>..<

时间: 2024-12-24 08:59:23

通过自己实现java中的ArrayList和LinkedList 深入理解数据结构---"表"的相关文章

Java中针对 ArrayList和LinkedList 的区别

一般大家都知道ArrayList和LinkedList的大致区别:      1.ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构.      2.对于随机访问get和set,ArrayList觉得优于LinkedList,因为LinkedList要移动指针.      3.对于新增和删除操作add和remove,LinedList比较占优势,因为ArrayList要移动数据. ArrayList和LinkedList是两个集合类,用于存储一系列的对象引用

实现Java中的ArrayList

最近深受轮子哥影响,觉得造一些轮子应该会对自己的技术功底有一定的帮助,就决定先从简单的容器开始实现.废话不多说,就先实现一个Java中的ArrayList. ArrayList是我们在Java中使用非常多的一个类,它是顺序表的数组实现,LinkedList是顺序表的链式实现(自己编的名字,懂就好哈),还有个Vector,它与ArrayList比较像,区别是它是线程安全的. 顺序表应该都有相同的操作,所以我先定义一个接口,描述好顺序表需要哪些操作.代码如下: public interface KI

Java中的ArrayList的初始容量和容量分配

List接口的大小可变数组的实现.实现了所有可选列表操作,并允许包括 null 在内的所有元素.ArrayList继承于List接口,除继承过来的方法外,还提供一些方法来操作内部用来存储列表的数组的大小.每个ArrayList实例都有一个容量.该容量是指用来存储列表元素的数组的大小.它总是至少等于列表的大小.随着向ArrayList中不断添加元素,其容量也自动增长.并未指定增长策略的细节,因为这不只是添加元素会带来分摊固定时间开销那样简单. ArrayList是经常会被用到的,一般情况下,使用的

JAVA集合类之ArrayList和LinkedList性能比较

关于ArrayList和LinkedList这两个集合类的性能,网上很多文章表示:ArrayList的插入性能要比LinkedList差.今天突然想测试下,这个结论是否准确. 编写了如下代码: import java.util.ArrayList; import java.util.LinkedList; import java.util.List; public class Demo { public static void main(String[] args) { int count = 

java集合(ArrayList,Vector,LinkedList,HashSet,TreeSet的功能详解)

说起集合,我们会潜意识里想到另外一个与之相近的名词——数组,OK!两者确实有相似之处,但也正是这点才是我们应该注意的地方,下面简单列出了两者的区别(具体功能的不同学习这篇文章后就会明白了): 数组 长度固定 既可以存储基本数据类型,也能存储引用数据类型 一个数组中的元素类型必一致 集合 长度可变 只能存储引用数据类型 一个集合中的元素类型可以是任意的引用类型 一.集合概述 Collection<E> 父接口 List<E> 子接口 ArrayList<E>类 Vecto

Java中关于WeakReference和WeakHashMap的理解

新美大的10月11日的笔试中有一道选择题,让选择函数返回结果,代码如下: 1 private static String test(){ 2 String a = new String("a"); 3 WeakReference<String> b = new WeakReference<String>(a); 4 WeakHashMap<String, Integer> weakMap = new WeakHashMap<String, In

java中的泛型的使用与理解

什么是泛型? 泛型是程序设计语言的一种特性.允许程序员在强类型程序设计语言中编写 体验泛型代码时定义一些可变部份,那些部份在使用前必须作出指明.各种程序设计语言和其编译器.运行环境对泛型的支持均不一样.将类型参数化以达到代码复用提高软件开发工作效率的一种数据类型.泛型类是引用类型,是堆对象,主要是引入了类型参数这个概念. 定义: 泛型是Java SE 1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数.这种参数类型可以用在类.接口和方法的创建中,分别称为泛型类.泛型

java中String、StringBuilder和StringBuffer理解

String.StringBuilder和StringBuffer理解 1>String java.lang.String 类 public final class String implements java.io.Serializable, Comparable<String>, CharSequence { /** The value is used for character storage. */ private final char value[]; /** Cache th

Java集合之ArrayList和LinkedList的实现原理

ArrayList实现可变数组的原理: 当元素超出数组内容,会产生一个新数组,将原来数组的数据复制到新数组中,再将新的元素添加到新数组中. ArrayList:是按照原数组的50%来延长,构造一个初始容量为10的空列表 用ArrayList模拟数组: 1 package iterater.patten.design; 2 3 //探索ArrayList实现的可变数组的原理,用ArrayList实现一个容器存储对象 4 public class ArrayList { 5 Object[] obj