Java集合之ArrayList,LinkedList,HashMap,HashSet

有人说Java很简单都是api调用而已,没啥技术.其实光是Jdk提供的api中很多思想和算法都用在里面,比如集合中的List,Set,Queue,Map,多线程,排序,noi等等.这里我从JDK源码开始简单分析这些项目中常使用的类是如何实现的.下面先简单介绍一些集合类型.

一.ArrayList

这就是地址上连续的数组.里面存储数据的是一个Object数组. size是当前数组装的对象的数量.默认初始化Object大小是10.

       private static final int DEFAULT_CAPACITY = 10;
    private transient Object[] elementData;
        private int size;

    在add一个元素的时候先判断Object数组有没有创建,没有的话先创建数组,然后判断数组size+1 > 数组的长度之后会进行扩容,   int newCapacity = oldCapacity + (oldCapacity >> 1);  扩容的大小是原来大小的1.5倍.

public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
    
     private void ensureCapacityInternal(int minCapacity) {
        if (elementData == EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        
   private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

        ensureExplicitCapacity(minCapacity);
    }
    
    
       private void grow(int minCapacity) {
        // overflow-conscious code
        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);
    }

取得时候就比较简单了,先判断边界,然后用下标去取.

    public E get(int index) {
        rangeCheck(index);

        return elementData(index);
    }

二.LinkedList

LinkedList内部是由双链表实现的,有一个静态内部类实现了Node节点,可以看到里面有next和prex两个指针.

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

    size表示当前链表长度. 有两个变量保存链表头部,和链表结尾.这样可以从头插入(取出,删除)节点或从尾插入节点.  普通的add方法只是在尾节点插入节点.

    transient int size = 0;
    transient Node<E> first;
   transient Node<E> last;
   
   public void addFirst(E e) 
   public void addLast(E e) 
   public E removeFirst()
   public E removeLast() 
   
     public boolean add(E e) {
        linkLast(e);
        return true;
    }

三.HashMap

214626_ZOCp_2250599.png

HashMap的核心Entry是一个节点,有一个指针可以指向下一个Entry.

static class Entry<K,V> implements Map.Entry<K,V> {
        final K key;
        V value;
        Entry<K,V> next;
        int hash;

        /**
         * Creates new entry.
         */
        Entry(int h, K k, V v, Entry<K,V> n) {
            value = v;
            next = n;
            key = k;
            hash = h;
        }

 HashMap有一个Entry数组,默认的加载因子为0.75,默认初始Entry数组长度为16.

     transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;
     static final float DEFAULT_LOAD_FACTOR = 0.75f;
     static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

HashMap在put的时候,先判断数组是否为空,是空进行初始化,数组默认长度为16.根据元素的hashCode方法去计算hash值然后找到对应的下标.当前Entry数组下标的元素,然后从这个元素开始遍历,如果找到hash相等且equals也相等那么就把Entry的value值替换掉.若没有找到hash相等且equals也相等的话,就生成一个新的Entry且把这个Entry放在table下标下,指针指向原来的下标元素.在生成新的Entry之前会去判断是否需要rehash进行扩容.当数组元素个数size>原始数组长度大小乘以load_fact 第一次扩容的时候在size > 16*0.75的时候.

public V put(K key, V value) {
        if (table == EMPTY_TABLE) {
            inflateTable(threshold);
        }
        if (key == null)
            return putForNullKey(value);
        int hash = hash(key);
        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);
        return null;
    }

HashMap  get()方法就比较简单了,根据key的hashCode方法找到key在数组中的下标.然后从下标位置开始遍历Entry.从而找到Entry.


  public V get(Object key) {
        if (key == null)
            return getForNullKey();
        Entry<K,V> entry = getEntry(key);

        return null == entry ? null : entry.getValue();
    }
    
    
    
     final Entry<K,V> getEntry(Object key) {
        if (size == 0) {
            return null;
        }

        int hash = (key == null) ? 0 : hash(key);
        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;
    }

四.HashSet

    其实HashSet就是利用HashMap去实现的,value值是一个固定的Object对象

 private static final Object PRESENT = new Object();
  private transient HashMap<E,Object> map;
  public HashSet() {
        map = new HashMap<>();
    }

HashSet add 方法就是好调用的HashMap的put方法value就是Object对象.contains方法也是调用的HashMap的contains方法

public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }
    
public boolean contains(Object o) {
        return map.containsKey(o);
    }
    
public boolean remove(Object o) {
        return map.remove(o)==PRESENT;
    }

五.Hashtable

HashMap是存在并发问题的,Hashtable解决了并发问题,解决的方法也很简单,在几乎所有的方法前面加上sychronized,导致同一个Hashtable对象同一时刻只能有一个线程使用其中的一个方法,大大的降低了效率

public synchronized V put(K key, V value);
public synchronized V get(Object key) ;
public synchronized V remove(Object key) ;
public synchronized int size() ;

六.ConcurrentHashMap


215149_ok6y_2250599.png

ConcurrentHashMap里面有一个Segment数组,每一个Segment相当于一个Hashtable,这样就把锁分成了多了(默认是16),不同的Segment之间是没有并发问题的.

    /**
     * The segments, each of which is a specialized hash table.
     */
    final Segment<K,V>[] segments;


    static final class Segment<K,V> extends ReentrantLock implements Serializable {
            transient volatile HashEntry<K,V>[] table;
            
            
    static final class HashEntry<K,V> {
        final int hash;
        final K key;
        volatile V value;
        volatile HashEntry<K,V> next;

        HashEntry(int hash, K key, V value, HashEntry<K,V> next) {
            this.hash = hash;
            this.key = key;
            this.value = value;
            this.next = next;
        }

        /**
         * Sets next field with volatile write semantics.  (See above
         * about use of putOrderedObject.)
         */
        final void setNext(HashEntry<K,V> n) {
            UNSAFE.putOrderedObject(this, nextOffset, n);
        }

        // Unsafe mechanics
        static final sun.misc.Unsafe UNSAFE;
        static final long nextOffset;
        static {
            try {
                UNSAFE = sun.misc.Unsafe.getUnsafe();
                Class k = HashEntry.class;
                nextOffset = UNSAFE.objectFieldOffset
                    (k.getDeclaredField("next"));
            } catch (Exception e) {
                throw new Error(e);
            }
        }
    }
    
    }

get的时候先根据hash值找到segment下标,然后找到HashEntry.再遍历HashEntry链表,找到HashEntry.

 public V get(Object key) {
        Segment<K,V> s; // manually integrate access methods to reduce overhead
        HashEntry<K,V>[] tab;
        int h = hash(key);
        long u = (((h >>> segmentShift) & segmentMask) << SSHIFT) + SBASE;
        if ((s = (Segment<K,V>)UNSAFE.getObjectVolatile(segments, u)) != null &&
            (tab = s.table) != null) {
            for (HashEntry<K,V> e = (HashEntry<K,V>) UNSAFE.getObjectVolatile
                     (tab, ((long)(((tab.length - 1) & h)) << TSHIFT) + TBASE);
                 e != null; e = e.next) {
                K k;
                if ((k = e.key) == key || (e.hash == h && key.equals(k)))
                    return e.value;
            }
        }
        return null;
    }

ConcurrentHashMap有一部分代码没看懂,回头有时间再来整理.

转载于:https://my.oschina.net/u/2250599/blog/401537

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值