精华内容
下载资源
问答
  • Java集合源码解析

    2018-05-23 17:58:01
    看到了一个很不错的 Java 集合源码解析博客,记录一下: https://blog.csdn.net/u011240877/article/category/6447444

    看到了一个很不错的 Java 集合源码解析博客,记录一下:
    https://blog.csdn.net/u011240877/article/category/6447444

    展开全文
  • java集合源码解析

    JAVA集合的框架图:


    从图中可以看出集合分为collection 和 map 两大类, 其中collection内部主要以数组或者链表的形式存放一系列集合对象,map则是以系列键值对的集合


    collection主要包含list 和 set 两个部分,是list和set 高度抽象出来的接口,主要包含add() remove() contains()等集合基本方法,还包含一个iterator() 方法,

    依赖iterator接口,可以用来对集合进行遍历


    AbstractCollection是一个抽象类, 实现了collection中的部分方法,比如:

        public boolean isEmpty() {
    	return size() == 0;
        }
        public boolean contains(Object o) {
    	Iterator<E> e = iterator();
    	if (o==null) {
    	    while (e.hasNext())
    		if (e.next()==null)
    		    return true;
    	} else {
    	    while (e.hasNext())
    		if (o.equals(e.next()))
    		    return true;
    	}
    	return false;
        }
    其中用到的siez()方法,以及iterator()等方法,则由具体的子类实现,比如list或者set

    AbstractList 和 AbstractSet 则分别实现了List 和 Set接口,并都继承自AbstractCollection


    List中我们用的最多的是ArrayList和LinkedList , ArrayList内部使用数组实现,而LinkedList则使用链表的方式实现, 

    先看看ArrayList的部分源码:

    private static final int DEFAULT_CAPACITY = 10;
    transient Object[] elementData;
    private int size;
    
        public ArrayList(int initialCapacity) {
            if (initialCapacity > 0) {
                this.elementData = new Object[initialCapacity];
            } else if (initialCapacity == 0) {
                this.elementData = EMPTY_ELEMENTDATA;
            } else {
                throw new IllegalArgumentException("Illegal Capacity: "+
                                                   initialCapacity);
            }
        }
    如果初始化时不指定大小,则默认初始化一个无内容的数组,指定了大小,则根据指定大小进行初始化, 其中size用来表示数组实际长度

    我们先看看add方法

        public boolean add(E e) {
            ensureCapacityInternal(size + 1);  // Increments modCount!!
            elementData[size++] = e;
            return true;
        }
    
        public void add(int index, E element) {
            rangeCheckForAdd(index);
    
            ensureCapacityInternal(size + 1);  // Increments modCount!!
            System.arraycopy(elementData, index, elementData, index + 1,
                             size - index);
            elementData[index] = element;
            size++;
        }

     第一个是直接加在数组后面,第二个是加在指定下标的位置,arraylist可以根据下标直接定位,所以查询效率很高,但是在新增和删除的时候,效率非常低

    原因是因为新增和删除的时候,如果不是从最后一个下标开始操作,那么需要将指定下标后面所有的元素都进行移动

    		List list = new ArrayList(100000);
    		//List list = new LinkedList();
    		long s1 = System.currentTimeMillis();
    		for(int i=0; i<100000; i++) {
    			list.add(0,i);
    		}
    		long s2 = System.currentTimeMillis();
    		System.out.println(s2 - s1);
    我用这段代码进行测试,时间是1123多毫秒,但是我如果把list.add(0,i) 这段代码直接改成list.add(i),那么直需要5毫秒左右

    并且随着数组容量增加,所需数量会呈指数级增长,所以我们如果需要对list从中间进行新增,删除操作,那么尽量使用linkedlist

    上面这段代码如果把new Arraylist() 直接用new LinkedList()来代替, 两种add方法需要的时间都在几毫秒左右


    list每次在新增时,必须先对其容量进行计算, ensureCapacityInternal(size + 1) ,如果超出容量,则需进行扩容

        private void ensureCapacityInternal(int minCapacity) {
            if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
                minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);//默认初始容量为10,取所需容量和初始容量的最大值
            }
    
            ensureExplicitCapacity(minCapacity);
        }
        private void ensureExplicitCapacity(int minCapacity) {
            modCount++;
    
            // 当所需容量大于数组初始的长度时,需要进行扩容操作
            if (minCapacity - elementData.length > 0)
                grow(minCapacity);
        }
        private void grow(int minCapacity) {
            // 扩容一般为原来的1.5倍,如果还不够,那么直接取所需容量进行操作,进行扩容操作需要对整个数组进行移动,性能较差,所以尽可能确定初始容量
            int oldCapacity = elementData.length;
            int newCapacity = oldCapacity + (oldCapacity >> 1);
            if (newCapacity - minCapacity < 0)
                newCapacity = minCapacity;
            if (newCapacity - MAX_ARRAY_SIZE > 0)
                newCapacity = hugeCapacity(minCapacity);
            // minCapacity is usually close to size, so this is a win:
            elementData = Arrays.copyOf(elementData, newCapacity);
        }
    arraylist的get可以直接定位下标,从数组中取值,所以效率很高:public E get(int index) {rangeCheck(index);return elementData(index);}




    再看看linkedlist的源码:

    //首先定义了头尾2个节点,这里源码来自jdk1.8,1.6的源码里面是直接定义了一个head节点并且 头尾都指向自己,作为一个双向链表
    transient Node<E> first;
    transient Node<E> last;
    
    //node节点的定义,其实就是一个指定的对象,再加上前后指针
        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;
            }
        }
    linkedlist的add比较简单,先判断 头节点是否为空,如果头节点为空,那么直接将新增的节点作为头节点,如果不为空,则将头节点的next指向这个节点,这个节点的next指向原来 头结点的next,prev也按类似的逻辑进行操作.

    linkedlist由于是基于链表的,没有arraylist里面的扩容操作,新增和删除都只需要修改几个指针的指向就行,所以新增和删除效率高

    但是查询的时候,由于没有下标,那么只能通过遍历来进行比较,所以效率非常低


    list中的另一个实现:vector,其基本实现原理和arraylist差不多,但是他在很多方法中加入了synchronized来保证线程安全,所以效率低

    而且很多情况,这个synchronized其实没法真正保证线程安全(比如size()方法和add(E e)都单独加了同步,但是我们如果需要先判断size()然后再add,那么在多线程的情况下,这2个方法执行的间隙,就有可能被其他线程修改了数据), 所以vector现在基本被弃用了, 如果需要使用线程安全的集合,应该首选java.util.Concurrent下面的相关集合


    展开全文
  • java集合源码解析:map

    2017-02-23 16:06:51
    java集合源码解析:map

    map里面用的最多的就是HashMap了, 如果需要对key进行排序的话,会用到 TreeMap

    先看看HashMap的源码

    HashMap内部还是用数组的方式实现的

    transient Node<K,V>[] table;
    //Node的定义,除了key,value外,hash用来确定在数组中的位置. Node数组中每个元素其实是个链表(链表长度超过8则转为红黑树)结构,当有hash冲突时,这个链表中就会存放有相同hash值的节点,
    //next用来表示其在链表(红黑树)中的下一个节点
        static class Node<K,V> implements Map.Entry<K,V> {
            final int hash;
            final K key;
            V value;
            Node<K,V> next;
    
            Node(int hash, K key, V value, Node<K,V> next) {
                this.hash = hash;
                this.key = key;
                this.value = value;
                this.next = next;
            }
    
            public final K getKey()        { return key; }
            public final V getValue()      { return value; }
            public final String toString() { return key + "=" + value; }
    
            public final int hashCode() {
                return Objects.hashCode(key) ^ Objects.hashCode(value);
            }
    
            public final V setValue(V newValue) {
                V oldValue = value;
                value = newValue;
                return oldValue;
            }
    
            public final boolean equals(Object o) {
                if (o == this)
                    return true;
                if (o instanceof Map.Entry) {
                    Map.Entry<?,?> e = (Map.Entry<?,?>)o;
                    if (Objects.equals(key, e.getKey()) &&
                        Objects.equals(value, e.getValue()))
                        return true;
                }
                return false;
            }
        }



    当使用put方法添加元素时,需要先根据hashcode确定在数组中的位置,然后找出此位置的节点,然后插入这个节点对应的链表(红黑树)中

        final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                       boolean evict) {
            Node<K,V>[] tab; Node<K,V> p; int n, i;
    	//在put时需要先判断map是否为空,如果为空则需要用resize()进行扩容,jdk1.6中会在new HashMap() 时对数组进行初始化,但是jdk1.8中则不会,直接将初始化的操作放在了put方法里面
            if ((tab = table) == null || (n = tab.length) == 0)
                n = (tab = resize()).length;
    	//这里先根据Node的hash值来计算在数组中的索引"i",如果tab[i]为空则直接插入这个位置
            if ((p = tab[i = (n - 1) & hash]) == null)
                tab[i] = newNode(hash, key, value, null);
    	//如果不为空,则说明有了相同的hash值,如果key也相同,那么直接覆盖现有元素,如果没有相同的key,说明hash值有了冲突,需要将这个Node放在此处对应的链表(红黑树)中
            else {
                Node<K,V> e; K k;
    	//有对应的key,直接覆盖
                if (p.hash == hash &&
                    ((k = p.key) == key || (key != null && key.equals(k))))
                    e = p;
    	//这个节点已经是红黑树了,直接插入红黑树中
                else if (p instanceof TreeNode)
                    e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
                else {//如果还不是红黑树,那么则是一个链表,遍历链表中的数据,如果有相同的key则替换,没有则放到链表结尾
                    for (int binCount = 0; ; ++binCount) {
                        if ((e = p.next) == null) {
                            p.next = newNode(hash, key, value, null);
                            if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st 链表长度超过8,链表转换为红黑树
                                treeifyBin(tab, hash);
                            break;
                        }
                        if (e.hash == hash &&
                            ((k = e.key) == key || (key != null && key.equals(k))))
                            break;
                        p = e;
                    }
                }
                if (e != null) { // existing mapping for key
                    V oldValue = e.value;
                    if (!onlyIfAbsent || oldValue == null)
                        e.value = value;
                    afterNodeAccess(e);
                    return oldValue;
                }
            }
            ++modCount;
            if (++size > threshold)
                resize();//超过容量是需要进行扩容
            afterNodeInsertion(evict);
            return null;
        }
    hashmap默认容量为16,默认加载因子为0.75, 当map里面的内容超过容量*加载因子时,就需要对map进行扩容操作,避免产生过多的hash冲突

    使用resize()扩容时,会扩容为原来长度的2倍,jdk1.8之前进行扩容需要重新计算整个map的hash值,jdk1.8进行了优化

    在resize()的注释中我们可以看到 must either stay at same index, or move with a power of two offset in the new table, 一部分保持原来位置,另一部分则会根据原位置再移动2次幂(比如原来下标是15,总长度是16,现在扩容长度变为32,那么原来15位置的节点,部分不变,另一部分会移动到15+16=31的位置),具体的可以看resize()的代码:

        final Node<K,V>[] resize() {
            Node<K,V>[] oldTab = table;
            int oldCap = (oldTab == null) ? 0 : oldTab.length;
            int oldThr = threshold;
            int newCap, newThr = 0;
            if (oldCap > 0) {
                if (oldCap >= MAXIMUM_CAPACITY) {
                    threshold = Integer.MAX_VALUE;
                    return oldTab;
                }
                else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                         oldCap >= DEFAULT_INITIAL_CAPACITY)
                    newThr = oldThr << 1; // double threshold 计算新的容量,左移一位,变为原来长度的2倍
            }
            else if (oldThr > 0) // initial capacity was placed in threshold
                newCap = oldThr;
            else {               // zero initial threshold signifies using defaults
                newCap = DEFAULT_INITIAL_CAPACITY;
                newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
            }
            if (newThr == 0) {
                float ft = (float)newCap * loadFactor;
                newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                          (int)ft : Integer.MAX_VALUE);
            }
            threshold = newThr;
            @SuppressWarnings({"rawtypes","unchecked"})
                Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
            table = newTab;
            if (oldTab != null) {
                for (int j = 0; j < oldCap; ++j) {
                    Node<K,V> e;
                    if ((e = oldTab[j]) != null) {
                        oldTab[j] = null;
                        if (e.next == null)
                            newTab[e.hash & (newCap - 1)] = e;
                        else if (e instanceof TreeNode)
                            ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                        else { // preserve order
                            Node<K,V> loHead = null, loTail = null;
                            Node<K,V> hiHead = null, hiTail = null;
                            Node<K,V> next;
                            do {
                                next = e.next;
                                if ((e.hash & oldCap) == 0) {
                                    if (loTail == null)
                                        loHead = e;
                                    else
                                        loTail.next = e;
                                    loTail = e;
                                }
                                else {
                                    if (hiTail == null)
                                        hiHead = e;
                                    else
                                        hiTail.next = e;
                                    hiTail = e;
                                }
                            } while ((e = next) != null);
                            if (loTail != null) {
                                loTail.next = null;
                                newTab[j] = loHead;
                            }
                            if (hiTail != null) {
                                hiTail.next = null;
                                newTab[j + oldCap] = hiHead;//下标移动至 原位置下标+原容量(扩容) 所处的位置
                            }
                        }
                    }
                }
            }
            return newTab;
        }

    hashmap在get时,只需要先根据hash值获取对应的下标,然后遍历对应的链表或者红黑树,找到对应的value即可

        public V get(Object key) {
            Node<K,V> e;
            return (e = getNode(hash(key), key)) == null ? null : e.value;
        }


    TreeMap则可以根据key进行排序,每个节点都是一个红黑树,红黑树比一般的二叉搜索树要复杂的多,以后有空再研究下吧
    Hashtable跟HashMap的实现基本类似,Hashtable不允许key 为null,而HashMap则可以, Hashtable也是线程安全的,在方法上都加了synchronized

    而collection里面的HashSet 和 TreeSet则是直接依赖了HashMap和TreeMap,将map里面的可以作为集合元素(与value无关,value = new Object())

    展开全文
  • Java集合源码解析之LinkedList LinkedList 同时实现了 List 接口和 Deque 接口,所以既可以将 LinkedList 当做一个有序容器,也可以将之看作一个队列(Queue),同时又可以看作一个栈(Stack)。虽然 LinkedList 和 ...

    Java集合源码解析之LinkedList

    LinkedList 同时实现了 List 接口和 Deque 接口,所以既可以将 LinkedList 当做一个有序容器,也可以将之看作一个队列(Queue),同时又可以看作一个栈(Stack)。虽然 LinkedList 和 ArrayList 一样都实现了 List 接口,但其底层是通过双向链表来实现的,所以插入和删除元素的效率都要比 ArrayList 高,但也因此随机访问的效率要比 ArrayList 低

    一、LinkedList 的类声明

    public class LinkedList<E>
            extends AbstractSequentialList<E>
            implements List<E>, Deque<E>, Cloneable, java.io.Serializable
    

    从其实现的几个接口可以看出来,LinkedList 是支持快速访问,可克隆,可序列化的,而且可以将之看成一个支持有序访问的 队列/栈

    上面说过,LinkedList 内部是通过双向链表的数据结构来实现的,对于链表中的结点来说,除了首尾两个结点外,其余每个结点除了存储本结点的数据元素外,还分别有两个指针用于指向其上下两个相邻结点,这个结点类就是 LinkedList 中的静态类 Node

    	//结点类
        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;
            }
        }
    

    二、包含的成员变量

     	//双向链表包含的结点总数
        transient int size = 0;
    
        //双向链表的头结点
        transient Node<E> first;
    
        //双向链表的尾结点
        transient Node<E> last;
    
        //序列化ID
        private static final long serialVersionUID = 876323262645176354L;
    

    当中的成员变量 first 和 last 分别用于指向链表的头部和尾部结点,因此 LinkedList 的数据结构图是类似于这样的

    在这里插入图片描述

    三、构造函数

    不同于 ArrayList,因为 LinkedList 使用的是链表结构,所以 LinkedList 不需要去请求一片连续的内存空间来存储数据,而是在每次有新的元素需要添加进来时,再来动态请求内存空间。因此 LinkedList 的两个构造函数要简单得多

    	public LinkedList() {
        }
    
        //传入初始数据
        public LinkedList(Collection<? extends E> c) {
            this();
            addAll(c);
        }
    

    四、添加元素

    add(E e) 方法用于向链表的尾部添加结点,因为有 last 指向链表的尾结点,因此向尾部添加新元素只需要修改几个引用即可,效率较高

        //将元素 e 作为尾结点添加
        //因为 LinkedList 允许添加相同元素,所以此方法固定返回 true
        public boolean add(E e) {
            linkLast(e);
            return true;
        }
        
        //将元素 e 置为尾结点
        void linkLast(E e) {
            //先保存原尾结点
            final Node<E> l = last;
            //构建新的尾结点,并指向原尾结点
            final Node<E> newNode = new Node<>(l, e, null);
            last = newNode;
            //如果原尾结点为 null,说明原链表包含的元素个数为 0,则此时插入的尾结点同时即为头结点
            //如果原尾结点不为 null,则将 next 指向新的尾结点
            if (l == null)
                first = newNode;
            else
                l.next = newNode;
            //元素个数加1
            size++;
            modCount++;
        }
    

    在这里插入图片描述

    add(int index, E element) 方法用于向指定索引处添加元素,需要先通过索引 index 获取相应位置的结点,并在该位置开辟一个新的结点来存储元素 element,最后还需要修改相邻结点间的引用

     	//在索引 index 处插入元素 element
        public void add(int index, E element) {
            //判断索引大小是否合法,不合法则抛出 IndexOutOfBoundsException
            checkPositionIndex(index);
            //如果 index == size,则将 element 作为尾结点来添加
            //否则则在索引 index 前开辟一个新结点
            if (index == size)
                linkLast(element);
            else
                linkBefore(element, node(index));
        }
    
    	//将元素 e 置为 succ 结点的上一个结点
        void linkBefore(E e, Node<E> succ) {
            //保存 succ 的上一个结点信息
            final Node<E> pred = succ.prev;
            //构建元素 e 对应的结点
            final Node<E> newNode = new Node<>(pred, e, succ);
            //将结点 succ 的上一个结点指向 newNode
            succ.prev = newNode;
            //如果 pred 为 null,说明 succ 是头结点,则将 newNode 置为新的头结点
            if (pred == null)
                first = newNode;
            else
                pred.next = newNode;
            //元素个数加1
            size++;
            modCount++;
        }
    

    在这里插入图片描述

    五、移除元素

    remove() 方法有两种重载形式,其内部都是通过调用 unlink(Node<E> x) 方法来移除指定结点在链表中的引用,不同于 ArrayList 在移除元素时可能导致的大量数据移动,LinkedList 只需要通过移除引用即可将指定元素从链表中移除

        //移除索引 index 处的结点
        public E remove(int index) {
            //判断索引大小是否合法,不合法则抛出 IndexOutOfBoundsException
            checkElementIndex(index);
            return unlink(node(index));
        }
    
    	//对链表进行正向遍历,移除第一个元素值为 o 的结点
        //如果移除成功则返回 true,否则返回 false
        public boolean remove(Object o) {
            if (o == null) {
                for (Node<E> x = first; x != null; x = x.next) {
                    if (x.item == null) {
                        //移除结点 x
                        unlink(x);
                        return true;
                    }
                }
            } else {
                for (Node<E> x = first; x != null; x = x.next) {
                    if (o.equals(x.item)) {
                        //移除结点 x
                        unlink(x);
                        return true;
                    }
                }
            }
            return false;
        }
    
    	//移除结点 x 并返回其包含的元素值
        E unlink(Node<E> x) {
            final E element = x.item;
            final Node<E> next = x.next;
            final Node<E> prev = x.prev;
            //如果 prev == null,说明结点 x 为头结点,则将头结点置为原先的第二个结点
            //如果 prev != null,则移除对结点 x 的引用
            if (prev == null) {
                first = next;
            } else {
                prev.next = next;
                x.prev = null;
            }
            //如果 next == null,则说明结点 x 为尾结点,则将尾结点置为原先的倒数第二个结点
            //如果 next != null,则移除对结点 x 的引用
            if (next == null) {
                last = prev;
            } else {
                next.prev = prev;
                x.next = null;
            }
            //帮助GC回收
            x.item = null;
            //元素个数减1
            size--;
            modCount++;
            return element;
        }
    

    六、获取/修改 元素

    在获取或修改指定索引的元素前,都需要先通过正向遍历或者反向遍历获取到该结点,如果集合包含的数据量很大,那么要相应的代价也会很大,因此说 LinkedList 的查找效率并不高

        //获取索引 index 处的结点元素
        public E get(int index) {
            //判断索引大小是否合法,不合法则抛出 IndexOutOfBoundsException
            checkElementIndex(index);
            return node(index).item;
        }
    
        //将索引 index 处的结点包含的元素修改为 element,并返回旧元素
        public E set(int index, E element) {
            //判断索引大小是否合法,不合法则抛出 IndexOutOfBoundsException
            checkElementIndex(index);
            Node<E> x = node(index);
            E oldVal = x.item;
            x.item = element;
            return oldVal;
        }
    
        //获取索引 index 处的结点
        Node<E> node(int index) {
            //size >> 1 的含义即为:将 size 值除以 2
            //如果 index 靠近链表的头部,则从头部向尾部正向遍历查找结点
            //如果 index 靠近链表的尾部,则从尾部向头部反向遍历查找结点 
            if (index < (size >> 1)) {
                Node<E> x = first;
                for (int i = 0; i < index; i++)
                    x = x.next;
                return x;
            } else {
                Node<E> x = last;
                for (int i = size - 1; i > index; i--)
                    x = x.prev;
                return x;
            }
        }
    

    七、几个常用的方法

    	//判断是否包含元素 o
        public boolean contains(Object o) {
            return indexOf(o) != -1;
        }
    
        //获取元素个数
        public int size() {
            return size;
        }
        
        //清空链表元素
        //这里将各个结点之间的引用都切断了,这并不是必须的,但这样有助于GC回收
        public void clear() {
            for (Node<E> x = first; x != null; ) {
                Node<E> next = x.next;
                x.item = null;
                x.next = null;
                x.prev = null;
                x = next;
            }
            first = last = null;
            size = 0;
            modCount++;
        }
        
        //返回第一个元素值为 o 的结点所在的索引值
        //如果查找不到,则返回 -1
        public int indexOf(Object o) {
            int index = 0;
            if (o == null) {
                for (Node<E> x = first; x != null; x = x.next) {
                    if (x.item == null)
                        return index;
                    index++;
                }
            } else {
                for (Node<E> x = first; x != null; x = x.next) {
                    if (o.equals(x.item))
                        return index;
                    index++;
                }
            }
            return -1;
        }
    
        //返回最后一个元素值为 o 的结点所在的索引值
        //如果查找不到,则返回 -1
        public int lastIndexOf(Object o) {
            int index = size;
            if (o == null) {
                for (Node<E> x = last; x != null; x = x.prev) {
                    index--;
                    if (x.item == null)
                        return index;
                }
            } else {
                for (Node<E> x = last; x != null; x = x.prev) {
                    index--;
                    if (o.equals(x.item))
                        return index;
                }
            }
            return -1;
        }
    

    八、Deque接口

    以上介绍的几个方法都是 List 接口中所声明的,接下来看下 Deque 接口中的方法

    其实 Deque 接口中很多方法的含义都是类似的,且一些方法都是相互调用的,并不算复杂

    	//将元素 e 置为头结点
        public void addFirst(E e) {
            linkFirst(e);
        }
    
        //将元素 e 置为尾结点
        public void addLast(E e) {
            linkLast(e);
        }
        
        //将元素 e 作为尾结点添加
        public boolean offer(E e) {
            return add(e);
        }
    
        //将元素 e 作为头结点添加
        public boolean offerFirst(E e) {
            addFirst(e);
            return true;
        }
    
        //将元素 e 作为尾结点添加
        public boolean offerLast(E e) {
            addLast(e);
            return true;
        }
    
        //获取头部结点的元素值
        public E peekFirst() {
            final Node<E> f = first;
            return (f == null) ? null : f.item;
        }
    
        //获取尾部结点的元素值
        public E peekLast() {
            final Node<E> l = last;
            return (l == null) ? null : l.item;
        }
    
        //获取头部结点的元素值,并将之从链表中移除
        public E pollFirst() {
            final Node<E> f = first;
            return (f == null) ? null : unlinkFirst(f);
        }
    
        //获取尾部结点的元素值,并将之从链表中移除
        public E pollLast() {
            final Node<E> l = last;
            return (l == null) ? null : unlinkLast(l);
        }
    
        //将元素 e 作为头结点添加
        public void push(E e) {
            addFirst(e);
        }
    
        //获取头部结点的元素值,并将之从链表中移除
        public E pop() {
            return removeFirst();
        }
    
        //从链表头部向尾部正向遍历,移除第一个元素值为 o 的结点
        //如果移除成功则返回 true,否则返回 false
        public boolean removeFirstOccurrence(Object o) {
            return remove(o);
        }
    
        //从链表尾部向头部反向遍历,移除第一个元素值为 o 的结点
        //如果移除成功则返回 true,否则返回 false
        public boolean removeLastOccurrence(Object o) {
            if (o == null) {
                for (Node<E> x = last; x != null; x = x.prev) {
                    if (x.item == null) {
                        unlink(x);
                        return true;
                    }
                }
            } else {
                for (Node<E> x = last; x != null; x = x.prev) {
                    if (o.equals(x.item)) {
                        unlink(x);
                        return true;
                    }
                }
            }
            return false;
        }
    

    九、效率比较

    上面说过,LinkedList 相比 ArrayList 添加和移除元素的效率会高些,但随机访问元素的效率要比 ArrayList 低,这里我也来做个测试,看下两者之间的差距

    分别向 ArrayList 和 LinkedList 存入同等数据量的数据,然后各自移除 100 个元素以及遍历 10000 个元素,观察两者所用的时间

    public static void main(String[] args) {
            List<String> stringArrayList = new ArrayList<>();
            for (int i = 0; i < 300000; i++) {
                stringArrayList.add("leavesC " + i);
            }
            //开始时间
            long startTime = System.currentTimeMillis();
            for (int i = 0; i < 100; i++) {
                stringArrayList.remove(100 + i);
            }
            //结束时间
            long endTime = System.currentTimeMillis();
            System.out.println("移除 ArrayList 中的100个元素,用时:" + (endTime - startTime) + "毫秒");
    
            //开始时间
            startTime = System.currentTimeMillis();
            for (int i = 0; i < 10000; i++) {
                stringArrayList.get(i);
            }
            //结束时间
            endTime = System.currentTimeMillis();
            System.out.println("遍历 ArrayList 中的10000个元素,用时:" + (endTime - startTime) + "毫秒");
    
    
            List<String> stringLinkedList = new LinkedList<>();
            for (int i = 0; i < 300000; i++) {
                stringLinkedList.add("leavesC " + i);
            }
            //开始时间
            startTime = System.currentTimeMillis();
            for (int i = 0; i < 100; i++) {
                stringLinkedList.remove(100 + i);
            }
            //结束时间
            endTime = System.currentTimeMillis();
            System.out.println("移除 LinkedList 中的100个元素,用时:" + (endTime - startTime) + "毫秒");
            //开始时间
            startTime = System.currentTimeMillis();
            for (int i = 0; i < 10000; i++) {
                stringLinkedList.get(i);
            }
            //结束时间
            endTime = System.currentTimeMillis();
            System.out.println("遍历 LinkedList 中的10000个元素,用时:" + (endTime - startTime) + "毫秒");
        }
    

    可以看出来,两者之间的差距还是非常大的,因此,在使用集合时需要根据实际情况来判断到底哪一种数据结构才更加适合

    移除 ArrayList 中的100个元素,用时:11毫秒
    遍历 ArrayList 中的10000个元素,用时:1毫秒
    移除 LinkedList 中的100个元素,用时:0毫秒
    遍历 LinkedList 中的10000个元素,用时:246毫秒
    
    展开全文
  • 从今天开始,会用一段时间对Java集合框架中的一些常用数据结构进行源码解析。首先入手的是ArrayList,部分的源码解析会以注释的方式出现。 public class ArrayList extends AbstractList implements List,
  • 概述 ArrayList是一个以动态数组(支持扩展)实现的List UML类图 ArrayList实现了List接口,提供了增删改查的基础操作...源码解析 属性 /** * 默认容量为10 */ private static final int DEFAULT_CAPACITY = 10; /**
  • 概述 LinkedList是一个以双向链表实现的List,同时可作为队列(FIFO)与栈(LIFO)使用(非线程安全) UML类图 LinkedList实现了List接口,提供...源码解析 属性 //节点个数 transient int size = 0; //链表首节点 transient
  • 概述 Vector是一个以动态数组(支持扩展)实现的List(线程安全) UML类图 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tVFOFWLx-1596794659370)(Vector.png)] ...源码解析 属性 /** 数组
  • 学习路线: ... 1 总体框架 2 Collection架构 3 ArrayList详细介绍(源码解析)和使用示例 4 fail-fast总结(通过ArrayList来说明fail-fast的原理、解决办法) ...6 Vector详细介绍(源码解析)和使用...
  • 概述 CopyOnWriteArrayList是...CopyOnWriteArrayList实现了List, RandomAccess, Cloneable, java.io.Serializable等接口; CopyOnWriteArrayList实现了List,提供了基础的添加、删除、遍历等操作; CopyOnWriteArrayL
  • HashMap的源码解析 HashMap概述 在官方文档中是这样描述的: Hash table based implementation of the Map interface. This implementation provides all of the optional map operations, and permits nul...
  • Java 集合源码解析(1):Iterator

    万次阅读 多人点赞 2016-10-06 14:19:28
    Java, Android 开发也有段时间了,当初为了早点学 Android,Java 匆匆了解个大概... 这段时间就开始 Java 集合源码学习。 Java 提供的 集合类都在 Java.utils 包下,其中包含了很多 List, Set, Map, Queue… 它们的关
  • 这是基于jdk1.8分析的,主要是对java集合的实现源码分析。 Java集合框架: 注:上图参考百度结果。 除了上面的集合类型。我们还会将Stack(栈)、Node(树)、Quene(队列)、HashTable的源码实现和优化点。 1 List集合 ...
  • implements List, RandomAccess, Cloneable, java.io.Serializable { private static final long serialVersionUID = 8683452581122892189L; //默认的数组大小 private static final int DEFAULT_CAPACITY = 10...
  • 在每次添加新的元素时,ArrayList都会检查是否需要进行扩容操作,扩容是十分消耗性能的操作,所以知道集合组要多大的容量,再初始化的时候就进行赋值。 二、源码阅读 先来看看继承关系 ArrayList实现了List,提供了...
  • 今天来介绍下ArrayList,在集合框架整体框架一章中,我们介绍了List接口,ArrayList继承了AbstractList,实现了List。ArrayList在工作中经常用到,所以要弄懂这个类是极其重要的。 构造图如下: 蓝色线条:继承 绿色...
  • LinkedList 与 ArrayList 一样实现 List 接口,只是 ArrayList 是 List 接口的大小可变数组的实现...本文主要通过源码分析 LinkedList。 LinkedList 的类结构 public class LinkedList<E> extends AbstractSequ
  • 1.Q群【Java开发技术交流】:jq.qq.com/?_wv=1027&a… 2.简书博客:www.shishusheng.com 3.知乎:www.zhihu.com/people/shi-… 4.微信公众号:JavaEdge 5.Github:github.com/Wasabi1234 1 概述 HashMap是基于哈希表...
  • Java集合是java提供的工具包,包含了常用的数据结构:集合、链表、队列、栈、数组、映射等。Java集合工具包位置是java.util.* Java集合主要可以划分为4个部分:List列表、Set集合、Map映射、工具类(Iterator迭代器、...
  • 在分析要理解HashMap源码前有必要对hashcode进行说明。 以下是关于HashCode的官方文档定义: hashcode方法返回该对象的哈希码值。支持该方法是为哈希表提供一些优点,例如,java.util.Hashtable 提供的哈希...
  • TreeMap源码解析(基于JDK1.6.0_45) 红黑树的添加原理及TreeMap的put实现 将一个节点添加到红黑树中,通常需要下面几个步骤: 将红黑树当成一颗二叉查找树,将节点插入. 这一步比较简单,就上开始我们...
  • HashSet源码解析(基于JDK1.6.0_45) 增加和删除 /** * 利用HashMap的put方法实现add方法 */ public boolean add (E e) { return map .put(e, PRESENT)== null ; } /** * 利用HashMap的...
  • 在分析要理解HashMap源码前有必要对hashcode进行说明。 以下是关于HashCode的官方文档定义: hashcode方法返回该对象的哈希码值。支持该方法是为哈希表提供一些优点,例如,java.util.Hashtable 提供的哈希...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 2,952
精华内容 1,180
关键字:

java集合源码解析

java 订阅