zoukankan      html  css  js  c++  java
  • java中的集合包简要分析

    1.集合包

    集合包是java中最常用的包,它主要包括Collection和Map两类接口的实现。

    对于Collection的实现类需要重点掌握以下几点:

    1)Collection用什么数据结构实现?

    2)Collection的创建、添加对象、删除对象、获取对象、遍历、判断是否存在、排序等操作的原理,及优缺点。

    1.1.Collection

    Collection存放的是多个单对象。Collection又分为两类接口,List和Set。

    1.1.1.List

    List支持放入重复的对象。List的实现类主要包括:ArrayList、LinkedList、Vector、Stack。

    ArrayList

    1)从ArrayList的构造方法可以看出,他使用数组作为底层数据结构。

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

    默认的initialCapacity为10。

    2)添加对象

    由于是定容量的数组存储对象,总有数组满的时候,此时需要我们进行扩容

     public void ensureCapacity(int minCapacity) {//minCapacity为目前需要的最小容量
    	modCount++;
    	int oldCapacity = elementData.length;//当前list的最大存储容量
    	if (minCapacity > oldCapacity) {
    	    Object oldData[] = elementData;
    	    int newCapacity = (oldCapacity * 3)/2 + 1;//扩展容量1.5倍
        	    if (newCapacity < minCapacity)
    		newCapacity = minCapacity;
                // minCapacity is usually close to size, so this is a win:
                elementData = Arrays.copyOf(elementData, newCapacity);//复制、扩容
    	}
        }
    

    其中Arrays.copyOf可能会比较陌生,他的具体实现如下:

    public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
            T[] copy = ((Object)newType == (Object)Object[].class)
                ? (T[]) new Object[newLength]
                : (T[]) Array.newInstance(newType.getComponentType(), newLength);//创建一个新数组,该数组的类型和之前ArrayList中元素的类型一致。
            System.arraycopy(original, 0, copy, 0,
                             Math.min(original.length, newLength));//System arrayCopy
            return copy;
        }
    

    添加对象还有一种方法,add(int index, E element),在指定位置插入一个对象。

    public void add(int index, E element) {
    	if (index > size || index < 0)
    	    throw new IndexOutOfBoundsException(
    		"Index: "+index+", Size: "+size);
    
    	ensureCapacity(size+1);  // Increments modCount!!
    	System.arraycopy(elementData, index, elementData, index + 1,
    			 size - index);
    	elementData[index] = element;
    	size++;
        }
    

    指定的位置必须是存在的(0到size之间),由于ArrayList是数组实现,因此需要将插入位置之后的元素进行后移一个位置,腾出空间给新元素。因此这个方法多了一次数组复制的工作

    于此同时还有一个修改对象的方法,set(int index, E element),将制定位置的对象替换掉。

    public E set(int index, E element) {
    	RangeCheck(index);
    
    	E oldValue = (E) elementData[index];
    	elementData[index] = element;
    	return oldValue;
        }
    

    好奇他的范围检索只对“上界”检查,不对“下界”检查。

    private void RangeCheck(int index) {
    	if (index >= size)
    	    throw new IndexOutOfBoundsException(
    		"Index: "+index+", Size: "+size);
        }
    

    3)删除对象

    删除指定位置的对象remove(int index)

    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; // gc回收
    
    	return oldValue;
        }
    

    remove(int index)和add(int index , E element)类似,需要通过数组的复制覆盖或腾出空间。

    删除指定对象remove(E element)

    public boolean remove(Object o) {
    	if (o == null) {
                for (int index = 0; index < size; index++)
    		if (elementData[index] == null) {
    		    fastRemove(index);
    		    return true;
    		}
    	} else {
    	    for (int index = 0; index < size; index++)
    		if (o.equals(elementData[index])) {
    		    fastRemove(index);
    		    return true;
    		}
            }
    	return false;
        }
    

    删除指定的对象,需要对删除对象是否为null区别对待。如果为null,则遍历数组中的元素,并比较是否为null(==null),如果为null则调用fastRemove删除。如果不为null,则遍历数组中的元素,并用equals比较是否相等,相等则调用fastRemove删除。

     private void fastRemove(int index) {
            modCount++;
            int numMoved = size - index - 1;
            if (numMoved > 0)
                System.arraycopy(elementData, index+1, elementData, index,
                                 numMoved);
            elementData[--size] = null; // Let gc do its work
        }
    

    fastRemove是简化的remove(int index),不需要进行范围检查。

    还有removeRange(int fromIndex, int toIndex)意思差不多,不予赘述!

    4)获取单个对象

    get(int index),传入的参数的为数组元素的位置。

    public E get(int index) {
    	RangeCheck(index);
    
    	return (E) elementData[index];
        }
    

    获取指定对象的位置,indexOf(Object o)

        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;
        }
    

    其实remove(Object o)可以改成:

    public boolean remove(Object o) {
    	int index = indexOf(o);
            if(index>=0){
                fastRemove(index);
                return true;
            }else
                return false;
        }
    

    有了indexOf,contains(Object E)就简单了。

    public boolean contains(Object o) {
    	return indexOf(o) >= 0;
        }
    

    5)遍历

    iterator有ArrayList的父类AbstractList实现,调用iterator会创建一个内部类Itr的实例(class Itr implements Iterator<E>)。主要关注hasNext、next方法。

    public boolean hasNext() {
    	return cursor != size();
    }
    

    比较当前指向数组的位置是否和数组中已有元素的个数相等。

    		public E next() {
    			checkForComodification();
    			try {
    				E next = get(cursor);
    				lastRet = cursor++;
    				return next;
    			} catch (IndexOutOfBoundsException e) {
    				checkForComodification();
    				throw new NoSuchElementException();
    			}
    		}
    

    checkForComodification

    final void checkForComodification() {
    	if (modCount != expectedModCount)
    		throw new ConcurrentModificationException();
    }
    

    调用next的时候要比较当前的modCount和创建iterator时的modCount是否相等。如果不相等,则说明对集合大小产生了影响,此时抛出ConcurrentModificationException。

    相等则调用get方法,此时有可能抛出IndexOutOfBoundsException,在捕获IndexOutOfBoundException后,检查modCount(checkForComodification),如果modCount不相等,抛出ConcurrentModificationException,

    如果相等则抛出NoSuchElementException。

    LinkedList

    LinkedList是基于双向链表机制,在LinkedList中,Entry类来代表集合中的元素。

    private static class Entry<E> {
    		E element;
    		Entry<E> next;
    		Entry<E> previous;
    
    		Entry(E element, Entry<E> next, Entry<E> previous) {
    			this.element = element;
    			this.next = next;
    			this.previous = previous;
    		}
    	}
    

    元素的值赋给element,previous指向前一个元素,next指向后一个元素,通过previous、next将多个独立的Entry串起来形成链表,因为它有两个方向的关联,所以称为双向链表。

    1)创建LinkedList

    private transient Entry<E> header = new Entry<E>(null, null, null);
    private transient int size = 0;
    
    	/**
    	 * Constructs an empty list.
    	 */
    	public LinkedList() {
    		header.next = header.previous = header;
    	}
    

    创建一个Entry对象,将其previous、nest全部指向自己(header),形成一个闭环。

    2)添加元素

    add(E e)实际调用了addBefore。(addBefore(e, header);)

    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;
    	}
    

    这个地方稍微有点绕,新建一个Entry对象,并将next指向header,previous指向header.previous,实际header.previous都是指向最后一个元素(为添加之前最后一个元素)。

    将前一元素的next指向自己,前一元素为header.previous,即为添加前最后一个元素。

    将自己的next元素,即header元素的previous指向自己,这样也始终保持了header.previous都是指向最后一个元素。

    3)删除元素

    remove(Object o)

    public boolean remove(Object o) {
    		if (o == null) {
    			for (Entry<E> e = header.next; e != header; e = e.next) {
    				if (e.element == null) {
    					remove(e);
    					return true;
    				}
    			}
    		} else {
    			for (Entry<E> e = header.next; e != header; e = e.next) {
    				if (o.equals(e.element)) {
    					remove(e);
    					return true;
    				}
    			}
    		}
    		return false;
    	}
    

    先遍历找到对应的Entry,然后在调用remove(Entry e)

    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;
    	}
    

    要删除指定的Entry e比较简单,让e的前一个元素的next指向e的next(e.previous.next = e.next),让e的后一个元素的previous指向e的previous(e.next.previous = e.previous)。

    然后将e的element、next和previous置为null,此时gc应该有机会将删除的e消灭掉。

    4)获取指定位置的元素

    get(int index)entry(int index)实现。

    private Entry<E> entry(int index) {
    		if (index < 0 || index >= size)
    			throw new IndexOutOfBoundsException("Index: " + index + ", Size: "
    					+ size);
    		Entry<E> e = header;
    		if (index < (size >> 1)) {
    			for (int i = 0; i <= index; i++)
    				e = e.next;
    		} else {
    			for (int i = size; i > index; i--)
    				e = e.previous;
    		}
    		return e;
    	}
    

    这里有个小小的优化,如果index<size/2,则从前往后遍历,否则从后往前遍历链表。

    5)遍历

    iterator会创建一个AbstractList的内部类ListItr。

    这里的类结构有必要说明一下。Iterator接口就定义了三个方法:hasNext、next、remove。

    ListIterator接口,继承Iterator接口,又定义了:add、hasPrevious、previous、set、previousIndex、nextIndex等方法。

    Ite作为AbstractList的内部类,实现了Iterator接口,主要用于ArrayList的遍历。

    ListIte作为AbstractList的内部类,实现了ListIterator接口、同时继承了Ite类,主要用于LinkedList的遍历。

    看看ListIte的previous方法:

    public E previous() {
    			checkForComodification();
    			try {
    				int i = cursor - 1;
    				E previous = get(i);
    				lastRet = cursor = i;
    				return previous;
    			} catch (IndexOutOfBoundsException e) {
    				checkForComodification();
    				throw new NoSuchElementException();
    			}
    		}
    

    因此LinkedList可以向前、向后遍历。

    6)其他一些方法

    offer、peek、poll、pop、push。

    offer和add类似(offerFirst、offerLast)

    peek和get类似(peekFirst、peekLast)

    poll和remove类似(pollFirst、pollLast)

    pop等价removeFirst

    push等价addFirst

    Vector

    Vector和ArrayList一样,也是基于数组的方式来实现的。

    Vector是基于synchronized实现的线程安全的ArrayList,因此很多方法都和ArrayList的类似,只是添加了synchronized关键字。

    除此之外,还有扩容方面稍有差别。

     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);
    	}
        }
    

    他这里直接就是翻倍,而ArrayList是1.5倍。为什么还搞个这样的区别呢?

    Stack

    Stack是继承Vector,实现了LIFO的栈操作。主要由push、pop、peek方法。

    public E push(E item) {
    	addElement(item);
    
    	return item;
        }
    
    
    public synchronized E pop() {
    		E obj;
    		int len = size();
    
    		obj = peek();
    		removeElementAt(len - 1);
    
    		return obj;
    	}
    
    public synchronized E peek() {
    	int	len = size();
    
    	if (len == 0)
    	    throw new EmptyStackException();
    	return elementAt(len - 1);
        }
    

      

    ArrayList是基于数组的,get很快,但添加、删除操作需要移动元素,效率较低。且不是线程安全的。

    LinkedList是基于双向链表的,添加、删除不需要移动元素,仅仅只要改变元素的previous、next,效率较高。get需要从前或从后开始遍历,效率较低。同样不是线程安全的。

    Vector是线程安全的ArrayList实现,Stack在继承Vector的基础上实现了栈的操作。

    通常我们会在外部对线程安全进行控制而选用ArrayList而非Vector。

    1.1.2.Set

    Set不支持放入重复的对象。Set的实现类主要包括:HashSet、TreeSet。

    (先看Map部分)

    HashSet

    HashSet是基于HashMap来实现的。

    TreeSet

    TreeSet是基于TreeMap来实现的。

    1.2.Map

    Map存放Key-Value形式的键值对。Map的实现类主要包括:HashMap、TreeMap。

    HashMap

    1)创建

    public HashMap() {
            this.loadFactor = DEFAULT_LOAD_FACTOR;
            threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);
            table = new Entry[DEFAULT_INITIAL_CAPACITY];
            init();
        }
    

    默认loadFactor为0.75,threshold为12,创建一个大小为16的Entry对象数组。(大小为2的4次方)

    public HashMap(int initialCapacity, float loadFactor) {
            if (initialCapacity < 0)
                throw new IllegalArgumentException("Illegal initial capacity: " +
                                                   initialCapacity);
            if (initialCapacity > MAXIMUM_CAPACITY)
                initialCapacity = MAXIMUM_CAPACITY;
            if (loadFactor <= 0 || Float.isNaN(loadFactor))
                throw new IllegalArgumentException("Illegal load factor: " +
                                                   loadFactor);
    
            // Find a power of 2 >= initialCapacity
            int capacity = 1;
            while (capacity < initialCapacity)
                capacity <<= 1;
    
            this.loadFactor = loadFactor;
            threshold = (int)(capacity * loadFactor);
            table = new Entry[capacity];
            init();
        }
    

    指定initialCapacity、loadFactor,capacity为大于initialCapacity的最小的2的n次方。capacity为Entry数组的大小。

    2)添加

    put(Object key , Object value)

    public V put(K key, V value) {
            if (key == null)
                return putForNullKey(value);
            int hash = hash(key.hashCode());
            int i = indexFor(hash, table.length);
            for (Entry<K,V> e = table[i]; e != null; e = e.next) {
                Object k;
                if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                    V oldValue = e.value;
                    e.value = value;
                    e.recordAccess(this);
                    return oldValue;
                }
            }
    
            modCount++;
            addEntry(hash, key, value, i);//key不存在
            return null;
        }
    

    indexFor是根据capacity做取余操作。

    static int indexFor(int h, int length) {
            return h & (length-1);
        }
    

    当key不存在的时候,也许会冲突,这个交由addEntry处理。

    void addEntry(int hash, K key, V value, int bucketIndex) {
    	Entry<K,V> e = table[bucketIndex];
            table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
            if (size++ >= threshold)
                resize(2 * table.length);
        }
    

    addEntry不对冲突进行特殊处理,都会将新加的k-v作为一个Entry加入到每个列表的头部。

    当size大于等于threshod时,需要进行扩容。扩容是一个比较繁琐的过程,需要对当前Entry对象数组中的元素重新hash,并填充数组,最后重新设置threshold值。

    void resize(int newCapacity) {
            Entry[] oldTable = table;
            int oldCapacity = oldTable.length;
            if (oldCapacity == MAXIMUM_CAPACITY) {
                threshold = Integer.MAX_VALUE;
                return;
            }
    
            Entry[] newTable = new Entry[newCapacity];
            transfer(newTable);
            table = newTable;
            threshold = (int)(newCapacity * loadFactor);
        }
    

      

    void transfer(Entry[] newTable) {
            Entry[] src = table;
            int newCapacity = newTable.length;
            for (int j = 0; j < src.length; j++) {
                Entry<K,V> e = src[j];
                if (e != null) {
                    src[j] = null;
                    do {
                        Entry<K,V> next = e.next;
                        int i = indexFor(e.hash, newCapacity);//重新hash
                        e.next = newTable[i];
                        newTable[i] = e;
                        e = next;
                    } while (e != null);
                }
            }
        }
    

    这块内容也比较复杂,有兴趣的同学最好跟下代码。

    如果我们预知需要存入很多k-v,还调用默认无参构造map,那么就会面临很多次不必要的扩容操作。因此最好选用public HashMap(int initialCapacity)构造方法。(或者:public HashMap(int initialCapacity, float loadFactor)  )

    3)获取

    get(Object key)

    public V get(Object key) {
            if (key == null)
                return getForNullKey();
            int hash = hash(key.hashCode());
            for (Entry<K,V> e = table[indexFor(hash, table.length)];
                 e != null;
                 e = e.next) {
                Object k;
                if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
                    return e.value;
            }
            return null;
        }
    

    对于key为null的情况,直接获取数组中的第一个Entry对象,并基于next属性进行遍历,寻找key为null的Entry,如果找到了则返回该Entry的value,没有找到返回null。

    如果key不为null,则对key进行hash,然后取余获取其的存储位置。然后获取该位置上的Entry,并基于next属性进行遍历,寻找key为null的Entry,如果找到了则返回该Entry的value,没有找到返回null。

    4)删除

    remove(Object key)

    具体的删除过程如下:

    final Entry<K,V> removeEntryForKey(Object key) {
            int hash = (key == null) ? 0 : hash(key.hashCode());
            int i = indexFor(hash, table.length);
            Entry<K,V> prev = table[i];
            Entry<K,V> e = prev;
    
            while (e != null) {
                Entry<K,V> next = e.next;
                Object k;
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k)))) {
                    modCount++;
                    size--;
                    if (prev == e)
                        table[i] = next;
                    else
                        prev.next = next;
                    e.recordRemoval(this);
                    return e;
                }
                prev = e;
                e = next;
            }
    
            return e;
        }
    

    remove和get类似,也是先找到key对应的存储位置,然后遍历找到key删除entry。这里使用单向链表解决冲突。

    5)包含

    containKey(Object key)

    public boolean containsKey(Object key) {
            return getEntry(key) != null;
        }
    
        /**
         * Returns the entry associated with the specified key in the
         * HashMap.  Returns null if the HashMap contains no mapping
         * for the key.
         */
        final Entry<K,V> getEntry(Object key) {
            int hash = (key == null) ? 0 : hash(key.hashCode());
            for (Entry<K,V> e = table[indexFor(hash, table.length)];
                 e != null;
                 e = e.next) {
                Object k;
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    return e;
            }
            return null;
        }
    

    getEntry和get方法类似,getEntry返回Entry对象,get返回Entry的value。

    HashMap参考:

    http://www.cnblogs.com/huangfox/archive/2012/07/06/2579614.html

    TreeMap

    TreeMap是支持排序的Map实现,可以自己指定Comparator参数。

    1)创建

    public TreeMap(Comparator<? super K> comparator) {
            this.comparator = comparator;
        }
    

      

    2)添加

    put(Object key , Object value)

    public V put(K key, V value) {
            Entry<K,V> t = root;
            if (t == null) {
    	    // TBD:
    	    // 5045147: (coll) Adding null to an empty TreeSet should
    	    // throw NullPointerException
    	    //
    	    // compare(key, key); // type check
                root = new Entry<K,V>(key, value, null);
                size = 1;
                modCount++;
                return null;
            }
            int cmp;
            Entry<K,V> parent;
            // split comparator and comparable paths
            Comparator<? super K> cpr = comparator;
            if (cpr != null) {
                do {
                    parent = t;
                    cmp = cpr.compare(key, t.key);
                    if (cmp < 0)
                        t = t.left;
                    else if (cmp > 0)
                        t = t.right;
                    else
                        return t.setValue(value);
                } while (t != null);
            }
            else {
                if (key == null)
                    throw new NullPointerException();
                Comparable<? super K> k = (Comparable<? super K>) key;
                do {
                    parent = t;
                    cmp = k.compareTo(t.key);
                    if (cmp < 0)
                        t = t.left;
                    else if (cmp > 0)
                        t = t.right;
                    else
                        return t.setValue(value);
                } while (t != null);
            }
            Entry<K,V> e = new Entry<K,V>(key, value, parent);
            if (cmp < 0)
                parent.left = e;
            else
                parent.right = e;
            fixAfterInsertion(e);
            size++;
            modCount++;
            return null;
        }
    

    先判断root是否为null,如果是则新建一个Entry对象,并赋值给root。

    如果root不为null,首先判断是否指定了Comparator,如果已经传入,则基于红黑树的方式遍历,通过比较结果选择左树或者右树(左小右大)。

    如果找到相等的key则直接替换vlaue,并返回结束put操作。

    如果遍历结束都没有找到相等的key,则根据最后一次比较结果在遍历的最后一个节点添加一个左结点或右结点,依据依然是左小右大。

    如果没有指定Comparator(Comparator == null),则需要根据key来创建一个比较器(Comparable<? super K> k = (Comparable<? super K>) key;),操作过程和上面相似。

    2)获取

    get(Object key)

    final Entry<K,V> getEntry(Object key) {
            // Offload comparator-based version for sake of performance
            if (comparator != null)
                return getEntryUsingComparator(key);
            if (key == null)
                throw new NullPointerException();
    	Comparable<? super K> k = (Comparable<? super K>) key;
            Entry<K,V> p = root;
            while (p != null) {
                int cmp = k.compareTo(p.key);
                if (cmp < 0)
                    p = p.left;
                else if (cmp > 0)
                    p = p.right;
                else
                    return p;
            }
            return null;
        }
    

      

    3)删除

    remove(Object key)

    public V remove(Object key) {
            Entry<K,V> p = getEntry(key);
            if (p == null)
                return null;
    
            V oldValue = p.value;
            deleteEntry(p);
            return oldValue;
        }
    

    首先通过getEntry获取entry对象,如果不为null将此entry从红黑树上删除,并重新调整树的相关节点。

    这个过程比较复杂,可以参考红黑树的相关知识。

  • 相关阅读:
    动态网页技术--JSP(5)
    动态网页技术--JSP(4)
    动态网页技术--JSP(3)
    动态网页技术--JSP(2)
    动态网页技术--JSP(1)
    动态网页技术--Servlet
    TomCat服务器搭建
    06_多线程
    05_进程间通信 IPC
    04_进程池
  • 原文地址:https://www.cnblogs.com/huangfox/p/2640106.html
Copyright © 2011-2022 走看看