数据结构之List实现类

目录

1.Arraylist

2.LinkedList

3.Vector

4.Stack


1.Arraylist

Arraylist作为常用的数据容器,还是有必要知道一些内部的细节。从线程安全方面来看,Arraylist是非线程安全,假设10个线程同时运行,往Arraylist添加100条数据,有可能出现Arraylist最终的数据总和会小于1000,所以开发中要注意,可以通过锁去解决。而Vector就是线程安全的,不过Vector也有自身的缺点,如果存储大量的数据,Vector会消耗的资源较大,或者做插入、删除、resize等操作都有可能导致Vector的迭代器失效,总之各有优点。

Arraylist内部,就先从构造方法看起:

//内部存储数据,数量变化到一定程度去操作的数组
transient Object[] elementData;

//数据个数
private int size;

//默认初始化容量为10
private static final int DEFAULT_CAPACITY = 10;

//默认空数组
private static final Object[] EMPTY_ELEMENTDATA = {};

public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            //指定大小的初始化
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            //使用默认空数组
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            //容量大小 < 0时候抛出的异常
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }

   
public ArrayList() {
       //无参构造函数,初始化一个空的数组
     this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

当我们在初始化Arraylist时,如未指定大小,存储数据的数组会设置默认大小为10。后面当存储数据个数大于10时,Arraylist的存储数组会指向新数组,且新数组的大小为旧数组大小的1.5倍。后面大小如接着超过,会重复这个操作,想想数据量大的时候,使用Arraylist确实会影响性能。下面看看扩容的方法:

private void grow(int minCapacity) {
        // 旧数组数量和扩容后的数量
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);

        //新容量比期望的最小容量小,将期望的最小容量作为当前容量
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;

        //新容量大于规定最大值,调用大的“扩容”方法hugeCapacity
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);

        //旧的数量容量变大,原数据存入,索引不变
        elementData = Arrays.copyOf(elementData, newCapacity);
    }


 private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) 
            throw new OutOfMemoryError();

        //最小期望大小如大于MAX_ARRAY_SIZE,设置新容量为(2^31-1)
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

因为内部是数组,所以保留了索引的概念,因为add和remove和get等方法能很好理解,主要就是add函数多次调用到一定数量会触发"扩容"。

2.LinkedList

LinkedList是一种链表的结构,看看其内部的一个私有类Node,正式这些内部的node节点类组成了链表的形式

public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
  
/**
     * Pointer to first node.
     * 指向第一个node
     */
    transient Node<E> first;

    /**
     * Pointer to last node.
     * 指向最后一个lode
     */
    transient Node<E> last;  

private static class Node<E> {
        E item;
        Node<E> next;
        Node<E> prev;

        Node(Node<E> prev, E element, Node<E> next) {
            this.item = element;
            this.next = next;
            this.prev = prev;
        }
  }

......
}

单看一个node类节点。item变量就是本节点值,prev就是前一个节点值,next即为下一个节点值。这样就形成了一条节点关联起来的链式结构。

LinkedList有一些特殊的函数,比如:

public void addFirst(E e) {
        linkFirst(e);
    }

  
public void addLast(E e) {
        linkLast(e);
}

public E removeFirst() {
        final Node<E> f = first;
        if (f == null)
            throw new NoSuchElementException();
        return unlinkFirst(f);
}

public E removeLast() {
        final Node<E> l = last;
        if (l == null)
            throw new NoSuchElementException();
        return unlinkLast(l);
}

看名字估计也能知道,就是直接在链表头、尾添加或者删除数据。

3.Vector

Vector为矢量队列,内部存储数据也是数组,采用的存储方式类似于LinkedList的"扩容",先看看它的4个构造方法:

//capacity是Vector的默认容量大小,capacityIncrement是每次Vector容量增加时的增量值。
public Vector(int initialCapacity, int capacityIncrement) {
        super();
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        this.elementData = new Object[initialCapacity];
        this.capacityIncrement = capacityIncrement;
}


//指定Vector的容量大小
public Vector(int initialCapacity) {
        this(initialCapacity, 0);
}


//构造一个空的向量,内部数据数组大小为10
public Vector() {
    this(10);
}

//构造一个包含指定元素元素的矢量集合,按照集合的顺序返回
public Vector(Collection<? extends E> c) {
        elementData = c.toArray();
        elementCount = elementData.length;
        // c.toArray might (incorrectly) not return Object[] (see 6260652)
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, elementCount, Object[].class);
}

其空参构造方法,默认创建容量为10的数组。同时也可以指定大小,主要看看capacityIncrement这个参数,解释为"扩容增量",可以先看看下面的代码:

private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;

        //扩容增量的设置与否影响扩容后的大小
        int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
                                         capacityIncrement : oldCapacity);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        elementData = Arrays.copyOf(elementData, newCapacity);
}

可以看到如无设置capacityIncrement,容器数量个数会增长为当前容器数量的一倍。

内部常见函数有:

转载这个老哥的方法整理,有点多:https://www.cnblogs.com/zedosu/p/6662231.html

synchronized boolean        add(E object)
             void           add(int location, E object)
synchronized boolean        addAll(Collection<? extends E> collection)
synchronized boolean        addAll(int location, Collection<? extends E> collection)
synchronized void           addElement(E object)
synchronized int            capacity()
             void           clear()
synchronized Object         clone()
             boolean        contains(Object object)
synchronized boolean        containsAll(Collection<?> collection)
synchronized void           copyInto(Object[] elements)
synchronized E              elementAt(int location)
             Enumeration<E> elements()
synchronized void           ensureCapacity(int minimumCapacity)
synchronized boolean        equals(Object object)
synchronized E              firstElement()
             E              get(int location)
synchronized int            hashCode()
synchronized int            indexOf(Object object, int location)
             int            indexOf(Object object)
synchronized void           insertElementAt(E object, int location)
synchronized boolean        isEmpty()
synchronized E              lastElement()
synchronized int            lastIndexOf(Object object, int location)
synchronized int            lastIndexOf(Object object)
synchronized E              remove(int location)
             boolean        remove(Object object)
synchronized boolean        removeAll(Collection<?> collection)
synchronized void           removeAllElements()
synchronized boolean        removeElement(Object object)
synchronized void           removeElementAt(int location)
synchronized boolean        retainAll(Collection<?> collection)
synchronized E              set(int location, E object)
synchronized void           setElementAt(E object, int location)
synchronized void           setSize(int length)
synchronized int            size()
synchronized List<E>        subList(int start, int end)
synchronized <T> T[]        toArray(T[] contents)
synchronized Object[]       toArray()
synchronized String         toString()
synchronized void           trimToSize()

4.Stack

称之为栈,本身继承自Vector,有着数据先进后出的特性。

内部存储也是由数组实现

protected Object[] elementData;

常用的几个方法:

//push:将元素添加到数组的末尾
public E push(E item) {
        addElement(item);
        return item;
}


//加了锁,可以看出push添加数据也线程安全的
public synchronized void addElement(E obj) {
        //数量+1,下个索引数据指向参数传递的数据
        modCount++;
        ensureCapacityHelper(elementCount + 1);
        elementData[elementCount++] = obj;
}
//peek:取出栈顶元素,不执行删除
public synchronized E peek() {
        int     len = size();
        if (len == 0)
            throw new EmptyStackException();
        //返回指定索引数据
        return elementAt(len - 1);
}




//pop时:取出栈顶元素,并将该元素从栈中删除
  public synchronized E pop() {
        E       obj;
        int     len = size();
        //拿到最后一个数据
        obj = peek();
        //删除最后一个数据
        removeElementAt(len - 1);
        return obj;
}

5.hashmap

hashmap内部是16位的数组,数组中存储的每一个元素又是链表的表头,表头这个节点是个Entry类型。

final K key;
V value;
final int hash;
HashMapEntry<K, V> next;

上图看出Entry中包含键对值中的key和value,next指向的是下一个节点Entry。hash作用主要用来计算值存储的位置,公式为:

hash(key)%len。将hash(key)值对len,也就是16取余。

转:https://www.cnblogs.com/huozhong/p/5896077.html

猜你喜欢

转载自blog.csdn.net/qq_37321098/article/details/82777005