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节点,一前一后构成了整个链表循环。

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

时间: 2024-08-04 18:28:48

Java基础知识陷阱(七)的相关文章

Java基础知识陷阱系列

Java基础知识陷阱系列 今天抽空把Java基础知识陷阱有关的文章汇总于此,便于大家查看. Java基础知识陷阱(一) Java基础知识陷阱(二) Java基础知识陷阱(三) Java基础知识陷阱(四) Java基础知识陷阱(五) Java基础知识陷阱(六) Java基础知识陷阱(七) Java基础知识陷阱(八)

Java基础知识陷阱(九)

本文发表于本人博客. 今天我来说说关于JAVA多线程知识,有错误请指出.大家都知道JAVA在服务端上处理也有很大优势,很多公司也有在服务器跑JAVA进程,这说明JAVA在处理这个多线程以及并发下也有一定有优点的(这说法有点坑了).下面来看看 那java中,不具备直接操作像操作系统一样的PV信号,然而它提供了synchronized来实现同步机制,可能这样说不够严谨.JAVA的基类Object中有以下几个方法: public final native void notify(); public f

Java基础知识陷阱(十)

上个星期由于时间比较紧所以未能继续写下去,今天再接再厉,专心 + 坚持这样离目标就越来越近了!废话少说说正题,今天我们还是来说说java中比较基础的知识,大家知道编写java程序中很多时候都用到了xml文件,有 些是框架本身支持配置的,有些是自定义配置的,这样就要求我们要对这个xml原理要比较了解,其中加载xml文件转换节点元素时有个核心:递归调用转换. 我们可以通过下面方法来查查这个实现类有关的源码: 1 2 3 4 DocumentBuilderFactory factory = Docum

Java基础知识陷阱(三)

本文发表于本人博客. 之前都讲了有关字符串的陷阱,那今天来说下关于静态这个东西,这分为静态变量.静态方法,先看下面的代码请问结果输出是什么?: class Person01{ private static Person01 person = new Person01(); public static int age; public static int hight = 0; private Person01(){ age++; hight++; } public static Person01

Java基础知识陷阱(四)

本文发表于本人博客. 今天我们来说说关于java继承以及反射有关的问题,大家先看下下面代码,试问可以编译通过不,为什么具体说说原因? public class Test{ public static void main(String[] args){ Person p = new Man(); p.talk(); } } class Person{ public void talk() throw NullPointerException{ System.out.println("Person

Java基础知识陷阱(六)

本文发表于本人博客. 上次说了下equals跟==的问题,今天再来认识一下这个equals()跟hasCode().上次的代码如下: class Person{ public String name; public Person(String name){ this.name = name; } public String getName(){ return this.name; } @Override public boolean equals(Object anObject) { if (t

Java基础知识陷阱(一)

本文发表于本人博客. 事隔好多年了,重新拿起来Java这门语言,看似熟悉其实还很陌生,想想应该梳理下顺便提高下自己.这次先来看看Java里面的String这个对象. 经典的先看下面一段代码,请问最终创建几个对象,分别在哪里? String s0 = new String("luoliang.me"); String s1 = "luoliang.me"; String s2 = new String("luoliang.me"); 想要回答这个问

Java基础知识陷阱(五)

本文发表于本人博客. 今天我来说说关于静态变量初始化.数组.==与equals的问题,看下面代码: public class Test{ private final int age; private String name; public Test(){ age = 30; } public Test(String name){ this.name = name; } } 我想上面的代码好多人都知道了会编译报错的,原因是在代餐构造函数中未对age进行复制操作. 对于终态变量final程序中是可以

Java基础知识陷阱(八)

本文发表于本人博客. 这次我来说说关于&跟&&的区别,大家都知道&是位运算符,而&&是逻辑运算符,看下面代码: public static void main(String[] args) throws Exception { int a = 1; int b = 2; int c = a & b; if(a >= 1 && b >= 1){ System.out.println("&&条件1&q