• Java基础知识陷阱(七)


    本文发表于本人博客

        上次说了下HashSet和HashMap之间的关系,其中HashMap这个内部有这么一句:

    static final float DEFAULT_LOAD_FACTOR = 0.75f;

    这一句表示一个常量,作用是当容器数量大小达到0.75%的时候就进行重新在构建一个2倍大小的数组。竟然这2个是集合,那我们今天看看其他的集合类,比如:ArrayList、Vector、LinkedList,开始咯。

        首先查看下ArrayList的源码,这几个集合类都是在java.util包下;看其构造:

        private transient Object[] elementData;
        public ArrayList(int initialCapacity) {
            super();
            if (initialCapacity < 0)
                throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);
            this.elementData = new Object[initialCapacity];
        }
        public ArrayList() {
            this(10);
        }

    可以看到当我们new ArrayList()的时候,其实质在内部是默认初始化一个长度为10的Object对象数组。明白了内部是用数组来实现的;继续看下那如果我们add的时候又是怎么样的呢?看看其代码:

        public boolean add(E e) {
            ensureCapacity(size + 1);  // Increments modCount!!
            elementData[size++] = e;
            return true;
        }
        public void ensureCapacity(int minCapacity) {
            modCount++;
            int oldCapacity = elementData.length;
            if (minCapacity > oldCapacity) {
                Object oldData[] = elementData;
                int newCapacity = (oldCapacity * 3)/2 + 1;
                if (newCapacity < minCapacity)
                    newCapacity = minCapacity;
                elementData = Arrays.copyOf(elementData, newCapacity);
            }
        }

    这里可以看出,add对象的时候先要去判断插入的位置小标是否大于数组的长度,是的话就重新实例化一个数组赋值给elementData,重新实例化数组的时候长度时按照当前长度*3 / 2 +1来计算,那就是每次add的时候都会判断,如果一次性插入很多(几百几千上万)元素对象的话,那我们可以想象一下,10个元素的时候就会重新实例化一个数组,在16个时候又一次,在25的时候又一次,以此算下去这样的性能应该是太差了,所以我们在知道数组长度的情况可以直接调用带参数构造来提升一下性能如:

    ArrayList list = new ArrayList(10000);

    那接下来我们看看其查询方法:

        public boolean contains(Object o) {
            return indexOf(o) >= 0;
        }
        public int indexOf(Object o) {
            if (o == null) {
                for (int i = 0; i < size; i++)
                if (elementData[i]==null)
                    return i;
            } else {
                for (int i = 0; i < size; i++)
                if (o.equals(elementData[i]))
                    return i;
            }
            return -1;
        }

    可以看出查询的时候是遍历整个数组的!在看下移除代码:

        public E remove(int index) {
            RangeCheck(index);
            modCount++;
            E oldValue = (E) elementData[index];
            int numMoved = size - index - 1;
            if (numMoved > 0)
                System.arraycopy(elementData, index+1, elementData, index,numMoved);
            elementData[--size] = null;
            return oldValue;
        }
        private void RangeCheck(int index) {
            if (index >= size)
            throw new IndexOutOfBoundsException("Index: "+index+", Size: "+size);
        }

    这移除的代码也是先要判断小标是否超出了,不然就截取数组。从这写方面我们可以看出:ArrayList在查找的时候直接使用的是下标遍历,增加删除的时候是需要重新构造一个新的数组性能消耗比较大!


    现在来看下Vector这个类,先看其构造:

        protected Object[] elementData;
        public Vector(int initialCapacity) {
            this(initialCapacity, 0);
        }
        public Vector() {
            this(10);
        }
        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内部也是使用的是Object数组来实现的,initialCapacity我们可以看出是设置的数组长度,而capacityIncrement是什么呢,我们看看其注释:

        /**
         * The amount by which the capacity of the vector is automatically
         * incremented when its size becomes greater than its capacity.  If
         * the capacity increment is less than or equal to zero, the capacity
         * of the vector is doubled each time it needs to grow.
         *
         * @serial
         */

    我靠英文不好,奇葩了,有点难我们看使用的地方:

        private void ensureCapacityHelper(int minCapacity) {
        int oldCapacity = elementData.length;
            if (minCapacity > oldCapacity) {
                Object[] oldData = elementData;
                int newCapacity = (capacityIncrement > 0) ?(oldCapacity + capacityIncrement) : (oldCapacity * 2);
                if (newCapacity < minCapacity) {
                newCapacity = minCapacity;
                }
                elementData = Arrays.copyOf(elementData, newCapacity);
            }
        }

    从这个代码中可以看到当设置了capacityIncrement>0时,增加对象的时候会在重新构造新数组的时候作为新数组的长度,否则就是原来数组长度的2倍。OK!兄弟英文不好,苦逼啊!

    下面来看看其的增加删除是怎么实现的。看代码:

        public synchronized boolean add(E e) {
            modCount++;
            ensureCapacityHelper(elementCount + 1);
            elementData[elementCount++] = e;
            return true;
        }
        public synchronized E remove(int index) {
            modCount++;
            if (index >= elementCount)
                throw new ArrayIndexOutOfBoundsException(index);
            Object oldValue = elementData[index];
            int numMoved = elementCount - index - 1;
            if (numMoved > 0)
                System.arraycopy(elementData, index+1, elementData, index, numMoved);
            elementData[--elementCount] = null;
            return (E)oldValue;
        }

    呀,这里出现了synchronized关键字了,是同步管理哦。增加Add的时候先去判断是否需要重新构造新的数组,然后赋值;删除的时候先判断下标是否超出,否则直接截取数组,这个跟ArrayList一样!到此可以看出Vector内部也是使用的是数组来实现,但是在增加删除有些方法的时候使用同步即使在多线程下也能确保正确。这个的性能会比ArrayList稍点,但是如果考虑到多线程环境下应该使用这个确保查询正确执行。


    接下来我们看看LinkedList这个,看下其代码:

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

    从这个可以看到,这个LinkedList竟然实现了Deque哦,可以看看这个的注释,是双向循环的链表。那竟然是链表大家应该知道了,链表在插入数据删除数据的时候会不数组的要快,原因是不用去移除数据直接来弄再修改指针就可以了,但是也有不好的地方就是查询啊,是要先从当前节点的位置从下或网上依次查找下次找个比较费时!

    public boolean add(E e) {
            addBefore(e, header);
            return true;
        }
        private Entry<E> addBefore(E e, Entry<E> entry) {
            Entry<E> newEntry = new Entry<E>(e, entry, entry.previous);
            newEntry.previous.next = newEntry;
            newEntry.next.previous = newEntry;
            size++;
            modCount++;
            return newEntry;
        }
        private E remove(Entry<E> e) {
            if (e == header)
                throw new NoSuchElementException();
            E result = e.element;
            e.previous.next = e.next;
            e.next.previous = e.previous;
            e.next = e.previous = null;
            e.element = null;
            size--;
            modCount++;
            return result;
        }

    从上面代码可以看出,增加删除都只是对header的节点进行操作就可以了,这样大大提高性能。

        private transient Entry<E> header = new Entry<E>(null, null, null);
        private transient int size = 0;
        public LinkedList() {
            header.next = header.previous = header;
        }
        
        private static class Entry<E> {
            E element;
            Entry<E> next;
            Entry<E> previous;
            ......
            ......
        }

    从这部分代码可以看出使用了header来做链接节点,找个header有previous以及next节点,一前一后构成了整个链表循环。

    这次先到这里。坚持记录点点滴滴!


  • 相关阅读:
    kvm虚拟迁移(5)
    kvm虚拟化网络管理(4)
    计算系数
    排列组合
    错排
    加分二叉树
    皇宫看守
    战略游戏
    数字转换
    JDK8 HashMap源码分析
  • 原文地址:https://www.cnblogs.com/luoliang/p/4160182.html
Copyright © 2020-2023  润新知