局部源码分析

1.7HashMap源码分析


    public HashMap() {
		//DEFAULT_INITIAL_CAPACITY:默认的初始化容量 16
		//DEFAULT_LOAD_FACTOR:默认加载因子 0.75F
        this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);//调用本类的其他构造器
    }
	
	
	public HashMap(int initialCapacity, float loadFactor) {
		//三个if都是检查插入的形参是否合法
        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);

		//实际的加载因子被赋值为0.75
        this.loadFactor = loadFactor;
		//阈值 = 16; 阈值初始化为16
        threshold = initialCapacity;
        init();
    }
	
	
	public V put(K key, V value) {
		//table是数组,存储键值对的数组,元素的类型Entry类型。
		//如果HashMap还没有添加过元素,table就是一个空数组
        if (table == EMPTY_TABLE) {
            inflateTable(threshold);//阈值 = 16; 阈值初始化为16
			//如果数组是空数组,长度变为16,threshold = capacity * loadFactor = 16 * 0.75 = 12
        }
		
		//HashMap允许key为null,Hashtable不允许
        if (key == null)//如果key为null,特殊处理
            return putForNullKey(value);
			
		//计算key的hash值
        int hash = hash(key);
		//计算新的映射关系的存储下标table[i]
        int i = indexFor(hash, table.length);
		
		
		//先取出table[i]的头结点
		//如果头结点不满足,就依次判断下面的结点  e = e.next
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
			//如果e.hash == hash 并且 要么是e.key 和新的映射关系的key地址相同或equls相同
			//说明e的key与新的映射关系的key相同
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
				//用新的value覆盖原来的value
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }

        modCount++;
		//添加新的映射关系到table[i]的位置,作为table[i]的头结点,原来table[i]下面的链表连接到它next中
        addEntry(hash, key, value, i);
        return null;
    }
	
	private void inflateTable(int toSize) {
        // Find a power of 2 >= toSize
		//如果数组的长度不是2的n次方,纠正为2的n次方
        int capacity = roundUpToPowerOf2(toSize);

		//重新计算阈值 = capacity * loadFactor;
        threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
		
		//table重新建新数组 ,长度为 capacity
        table = new Entry[capacity];
		
		//暂时不管它,hash种子有关
        initHashSeedAsNeeded(capacity);
    }
	
	private static int roundUpToPowerOf2(int number) {
        // assert number >= 0 : "number must be non-negative";
        return number >= MAXIMUM_CAPACITY
                ? MAXIMUM_CAPACITY
                : (number > 1) ? Integer.highestOneBit((number - 1) << 1) : 1;
				
		//Integer.highestOneBit((number - 1) << 1):这个方法的作用就是把一个非2的你次方数字变为2的n次方的数字			
    }
	
	private V putForNullKey(V value) {
		//整个for循环的作用:
		//(1)先取出数组table[0]的第一个元素e,如果e不为null
		//(2)判断e的key是否为null,如果e.key为null,就用新的value覆盖原来的value
		//(3)e=e.next,继续判断下一个结点
		//所有的操作都是在table[0]下面的
		//key为null的键值对,一定是存储在table[0]下面的。
        for (Entry<K,V> e = table[0]; e != null; e = e.next) {
            if (e.key == null) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }
        modCount++;
		
		//把新的键值对(null,value)存储到table[0]的下面
        addEntry(0, null, value, 0);//hash=0,key=null,value=value,bucketIndex=0
        return null;
    }
	
	void addEntry(int hash, K key, V value, int bucketIndex) {
		//size:HashMap中所有键值对的个数  
		//size >= threshold,达到阈值 并且 table[bucketIndex]非空
		//同时满足它俩的话,就会扩容
        if ((size >= threshold) && (null != table[bucketIndex])) {
            resize(2 * table.length);//把数组table扩大为原来的2倍
            hash = (null != key) ? hash(key) : 0; //重写计算key的hash值
            bucketIndex = indexFor(hash, table.length);//重新计算[bucketIndex]
			/*
			为什么数组扩容后,要重新计算下标?
			index = hash & table.length-1;  如果table.length变了,就需要重新计算 [index]
			*/
        }

        createEntry(hash, key, value, bucketIndex);
    }
	
	//本来我们想着直接使用key的hashCode()计算的结果的,但是很多时候用户自己实现的hashCode()不是很好
	//冲突现象比较严重,所以他在hashCode()的基础上做了一些干扰的操作,使得hash值更分散。
	//如果hash值更分散,那么存储到table中就会更均匀分布,而不是都在某个table[index]
	final int hash(Object k) {
        int h = hashSeed;
        if (0 != h && k instanceof String) {
            return sun.misc.Hashing.stringHash32((String) k);
        }

        h ^= k.hashCode();

        // This function ensures that hashCodes that differ only by
        // constant multiples at each bit position have a bounded
        // number of collisions (approximately 8 at default load factor).
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
    }
	
	void createEntry(int hash, K key, V value, int bucketIndex) {
		//取出[bucketIndex]位置的元素,即table[bucketIndex]的头结点
        Entry<K,V> e = table[bucketIndex];
		
		//table[bucketIndex]的头结点变为新结点(key,value)的Entry对象。
		//原来table[bucketIndex]下面的链表作为新结点的next
        table[bucketIndex] = new Entry<>(hash, key, value, e);
		//元素个数增加
        size++;
    }
	
	//结点类型,类似于我们在LinkedList中看到都Node,只是另一种形式的结点
	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;
        }
	}
	
	
	static int indexFor(int h, int length) {
        // assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2";
		//用key的hash值, 和数组的长度-1做运算得到下标,范围[0,table.length-1]范围内
        return h & (length-1);
    }

1.8HashMap源码分析

    public HashMap() {
	   //把加载因子,初始化为0.75
        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
    }
	
	public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }
	
	//虽然是JDK1.7的算法不同,但是仍然是为了干扰key对象的hashCode值,得到一个更加分散的hash值
	static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }
	
	final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {
        Node<K,V>[] tab; 
		Node<K,V> p; 
		int n, i;
		
		//tab就是table
		//如果table是null或者是一个长度为0的数组
		//用n记录了table的长度
        if ((tab = table) == null || (n = tab.length) == 0)
			//对table重新调整了一下大小,长度变为16,threshold = 12
            n = (tab = resize()).length;
		
		/*
		i = (n-1) & hash  = (table.length-1) & hash;
		p = table[i]的头结点
		如果p为null,说明table[i]还没存储过其他元素
		*/
        if ((p = tab[i = (n - 1) & hash]) == null)
			//直接创建一个Node结点放到table[i]中,新结点的next是null
            tab[i] = newNode(hash, key, value, null);
        else {//如果p不为空,说明table[i]下面有其他结点
            Node<K,V> e; K k;
			//第一个if,是判断 table[i]的头结点是否是和新的映射关系的key重复
            if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;//如果重复,用e记录
            else if (p instanceof TreeNode)
				//如果p是树结点,就在树中查找是否有重复的key,如果有重复的用e记录哪个重复的结点
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {//如果p下面是链表,就在链表中查找是否有重复的key,如果有用e记录哪个重复的结点
				//一边找,一边记录当前链表的结点的个数
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
						//当链表的结点的个数 >= TREEIFY_THRESHOLD(树化阈值) - 1
						//因为新结点还未加入,如果加入,就称为8个了
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
							//考虑树化
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
			
			//如果e不为null,说明找到了重复的,就用新的value覆盖原来的value
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
		
		//如果++size > threshold,说明要扩容
        if (++size > threshold)
            resize();
		
        afterNodeInsertion(evict);
        return null;
    }
	
	
	final Node<K,V>[] resize() {
        Node<K,V>[] oldTab = table;
		//用oldCap记录原来的table的长度
        int oldCap = (oldTab == null) ? 0 : oldTab.length;//如果原来的table是空的,原来的容量就是0,否则就是取原来table的长度
       
	   int oldThr = threshold;//用oldThr记录原来的阈值
	   
        int newCap, newThr = 0;
		//原来的Table的容量非0,相当于是对原来table的一个扩容操作
        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
        }
        else if (oldThr > 0) // initial capacity was placed in threshold
            newCap = oldThr;
        else {               // zero initial threshold signifies using defaults
            newCap = DEFAULT_INITIAL_CAPACITY; //新数组的容量为默认初始化容量16
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);//新的阈值是 16 * 0.75 = 12
        }
        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];
			//创建了一个新的数组,长度为newCap
			
		//让table指向新的数组
        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;
    }

ArrayList源码分析

private class ArrayList<E>{	
	Object[] elementData;//数组用来存储ArrayList的元素
	private int size;//记录实际存的元素的个数
	private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};//空数组,长度为0的数组
	
	/*
	这里的空数组,不能存储元素。它的作用是,当我们new ArrayList(),并没有存储元素时,
	并不需要申请额外的空间。因为你此时还没有存储元素,也可能不存。
	例如:当你某个方法  public ArrayList select(String goodsName){根据商品名称,查询符合的商品信息
							//....
							//结果有可能没有找到,我们又不希望返回null,调用者有可能粗心没有检查null,会报空指针异常。
							//所以我这里给它new ArrayList(),但是里面确实没有元素,所以就不要new Object[10];浪费了
						}
	*/
	private static final int DEFAULT_CAPACITY = 10; //默认容量
	 private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
	
	public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
	
	public boolean add(E e) {
		//看是否需要扩容
        ensureCapacityInternal(size + 1);  // Increments modCount!!
		
		//将新元素存储到elementData[size]位置,并且size加1
        elementData[size++] = e;
        return true;
    }
	
	private void ensureCapacityInternal(int minCapacity) {
		//判断elementData是否是空数组,如果是,意味着我还未存过元素
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
			//minCapacity:最小需要容量
			//Math.max(DEFAULT_CAPACITY, minCapacity)返回DEFAULT_CAPACITY和minCapacity中最大值
			//DEFAULT_CAPACITY:10
			//minCapacity是形参,调用这个方法时传入的实参值是多少它就是多少。
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);
    }
	
	private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code
		//minCapacity:最小需要容量
		//elementData.length:当前数组的长度
		//if (minCapacity - elementData.length > 0) 意味着当前数组不够用了
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
	
	private void grow(int minCapacity) {
        // overflow-conscious code
		//oldCapacity:原来的数组的长度
        int oldCapacity = elementData.length;
		//newCapacity:新数组的长度 = 原来数组的长度 + 原来数组的长度 /2; 即1.5倍
        int newCapacity = oldCapacity + (oldCapacity >> 1);
		
		//minCapacity:最小需要容量
		//如果1.5倍还不够
        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复制元素到新数组中,新数组的长度为newCapacity
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
	
	public void add(int index, E element) {
        rangeCheckForAdd(index);

		//看是否需要扩容
        ensureCapacityInternal(size + 1);  // Increments modCount!!
		
		//将[index]以及后面的元素往右移动
        System.arraycopy(elementData, index, elementData, index + 1,size - index);
		
		//将新元素添加到 elementData[index]
        elementData[index] = element;
		//元素个数增加
        size++;
    }
	
	private void rangeCheckForAdd(int index) {
		//elementData实际已经存储[0,size-1]
		//可插入的位置[0,size]
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }
	
	
	public E remove(int index) {
        rangeCheck(index);//删除时的下标检查

        modCount++;
		
		//用oldValue记录要被删除的[index]位置的元素,因为之后要返回被删除的元素
        E oldValue = elementData(index);

		//要移动的元素的个数
        int numMoved = size - index - 1;
		//如果要移动的元素的个数>0,再调用System.arraycopy()方法移动,如果是0就不调用了
		//如果我们要删除的是当前数组的[size-1]的元素,意味着不需要移动,那么就不用调用System.arraycopy方法,不需要入栈,出栈,浪费时间
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index, numMoved);
		
		//让GC垃圾回收器,回收elementData[size-1]位置的无用元素
        elementData[--size] = null; // clear to let GC do its work

        return oldValue;
    }
	private void rangeCheck(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }
	
	public boolean remove(Object o) {
        if (o == null) {
            for (int index = 0; index < size; index++)
                if (elementData[index] == null) {//如果o是空,那么我们看elementData中谁是null
                    fastRemove(index);
                    return true;
                }
        } else {
            for (int index = 0; index < size; index++)
                if (o.equals(elementData[index])) {//如果o非空,那么用equals比较,看哪个满足,而且用o.equals(xx)
                    fastRemove(index);
                    return true;
                }
        }
        return false;
    }
	
	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; // clear to let GC do its work
    }
	
	
	public E set(int index, E element) {
        rangeCheck(index);//检查下标

		//先记录要被替换的[index]位置的元素		
        E oldValue = elementData(index);
        elementData[index] = element;
        return oldValue;
    }
	
	public E get(int index) {
        rangeCheck(index);//检查下标

        return elementData(index);
    }
	
	E elementData(int index) {
        return (E) elementData[index];
    }
	
	
	public Iterator<E> iterator() {
        return new Itr();
    }
	
	//实现了Iterator接口
	private class Itr implements Iterator<E> {
		//游标,当前迭代器遍历到动态数组哪个位置了
        int cursor;       // index of next element to return
		//上一个迭代的位置
        int lastRet = -1; // index of last element returned; -1 if no such
		//后面单独讲
        int expectedModCount = modCount;

        public boolean hasNext() {
			//有效元素的范围[0,size-1]
            return cursor != size;
        }

        @SuppressWarnings("unchecked")
        public E next() {
            checkForComodification();
			
			//用i记录当前迭代器遍历到动态数组哪个位置了
            int i = cursor;
			
			//加这个判断是以防用户在next()方法之前没有调用hasNext()方法
            if (i >= size)
                throw new NoSuchElementException();
			
			//用一个变量记录了当前动态数组的elementData
            Object[] elementData = ArrayList.this.elementData;
			
			//和并发有关
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
			
			
            cursor = i + 1;//下次访问的元素的下标
            return (E) elementData[lastRet = i];//i是本次遍历的数组的下标位置,用lastRet记录,对应下一次调用next方法,lastRet就是上次的下标
			//变量是在remove方法中用到了
		}

        public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
				//删除的是你刚刚调用next()方法取走的位置的元素
                ArrayList.this.remove(lastRet);
                cursor = lastRet;//因为删除元素,[cursor]会被往前移动,所以这里要cursor = lastRet
                lastRet = -1;//因为[lastRet]位置被删除了,然后不存在了,如果连续调用remove()就会报错
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }		
}	

LinkedList的源码分析

public class LinkedList<E>{
	int size = 0;
	Node<E> first;//头结点
	Node<E> last;//尾结点
	
	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;
        }
    }
	
	public boolean add(E e) {
        linkLast(e);//连接到最后
        return true;
    }
	
	void linkLast(E e) {
		//last是当前链表的最后一个结点
        final Node<E> l = last;
		
		//创建新结点
		//Node(Node<E> prev, E element, Node<E> next) :
		//新结点的prev是 原来链表的最后一个结点l,即让新结点的prev指向原来的最后一个结点
		//新结点的next是 null
		//新结点的数据项是:e
        final Node<E> newNode = new Node<>(l, e, null);
		
		//新结点变成了链表的最后一个结点
        last = newNode;
		
		//l是原来链表的最后一个结点
		//如果l==null,说明原来链表是一个空链表
        if (l == null)
			//first:链表的第一个结点,
			//现在这个新结点即是第一个结点,又是最后一个结点
            first = newNode;
        else//l==null不成立,说明原来的链表非空
			//l是原来链表的最后一个结点,原来的最后一个结点指向了新结点
            l.next = newNode;

		//结点个数增加
		size++;
        modCount++;
    }
	
	
	public void add(int index, E element) {
        checkPositionIndex(index);//检查index是否合法,合法的位置[0,size]

        if (index == size)//连接到最后
            linkLast(element);
        else //否则,就是插入到中间
            linkBefore(element, node(index));
    }
	
	private void checkPositionIndex(int index) {
        if (!isPositionIndex(index))
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }
	private boolean isPositionIndex(int index) {
        return index >= 0 && index <= size;
    }
	
	//返回了链表的[index]位置的结点
	Node<E> node(int index) {
        // assert isElementIndex(index);
		//index < (size >>1) 即  index < size/2,说明位置在链表的前半段
        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;
        }
    }
	
	//e:是要新增的结点的数据
	//succ是[index]位置的结点
	void linkBefore(E e, Node<E> succ) {
        // assert succ != null;
		//pred是[index]位置的前一个结点
        final Node<E> pred = succ.prev;
		//创建新结点,Node(Node<E> prev, E element, Node<E> next) 
		//新结点的prev指向[index]位置的前一个结点
		//新结点的next指向原来[index]的结点
        final Node<E> newNode = new Node<>(pred, e, succ);
		
		//原来[index]位置的结点  的prev指向新结点
        succ.prev = newNode;
		
		//原来[index]位置的前一个结点是null,说明要插入的位置是头结点的位置
        if (pred == null)
			//新结点就是新的头结点
            first = newNode;
        else
			//否则原来[index]位置的前一个结点的next指向新结点
            pred.next = newNode;
			
        size++;
        modCount++;
    }
	
	
	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) {
        // assert x != null;
        final E element = x.item;//被删除结点的数据
        final Node<E> next = x.next;//被删除结点的下一个结点
        final Node<E> prev = x.prev;//被删除结点的上一个结点

		//被删除结点的上一个结点是null,说明被删除结点是第一个结点
        if (prev == null) {
			//第一个结点变为被删除结点的下一个结点
            first = next;
        } else {
			//否则被删除结点的上一个结点 的next 指向 被删除结点的下一个结点
            prev.next = next;
			//被删除结点与它之前的上一个结点断开
            x.prev = null;
        }

		//被删除结点的下一个结点是null,说明被删除结点是最后一个结点
        if (next == null) {
			//链表的最后一个结点,变成被删除结点的上一个结点
            last = prev;
        } else {
			//否则,被删除结点的下一个结点的prev = 被删除结点的上一个结点
            next.prev = prev;
			//被删除结点与它之前的下一个结点断开
            x.next = null;
        }

		//把被删除结点的数据置空
        x.item = null;//经过x.prev = null ,x.next == null, x.item==null,相当于x与其结点断开关系,数据也置空,可以彻底被回收
        
		//元素个数减少
		size--;
        modCount++;
        return element;
    }
	
	public E remove(int index) {
        checkElementIndex(index);//检查index是否合法,合法的位置[0,size-1]
        return unlink(node(index));
    }
	
	private void checkElementIndex(int index) {
        if (!isElementIndex(index))
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

    private void checkPositionIndex(int index) {
        if (!isPositionIndex(index))
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }
	
	public E get(int index) {
        checkElementIndex(index);
		//node()返回了链表的[index]位置的结点
        return node(index).item;
    }
}

关于HashMap的面试问题

1、HashMap的底层实现

答:JDK1.7及其之前的版本是数组+链表,JDK1.8是数组+链表/红黑树

2、HashMap的数组的元素类型

答:java.util.Map$Entry接口类型。

JDK1.7的HashMap中有内部类Entry实现Entry接口

JDK1.8的HashMap中有内部类Node和TreeNode类型实现Entry接口,并且TreeNode是Node的子类。

3、为什么要使用数组?

答:因为数组的访问的效率高或者说,根据[下标]操作效率高

4、为什么数组还需要链表?或问如何解决hash或[index]冲突问题?

答:为了解决hash和[index]冲突问题

(1)两个不相同的key的hashCode值本身可能相同

(2)两个不相同的key的hashCode值不同,但是经过hash()运算,结果相同

(3)两个hashCode不相同的key,经过hash()运算,结果也不相同,但是通过 hash & table.length-1运算得到的[index]可能相同

那么意味着table[index]下可能需要存储多个Entry的映射关系对象,所以需要链表

5、HashMap的数组的初始化长度

答:默认的初始容量值是16,也可以手动指定

6、HashMap的映射关系的存储索引index如何计算

答:hash & table.length-1

7、为什么要使用hashCode()? 空间换时间

答:因为hashCode()是一个整数值,可以用来直接计算index,效率比较高,用数组这种结构虽然会浪费一些空间,但是可以提高查询效率。

8、hash()函数的作用是什么

答:在计算index之前,会对key的hashCode()值,做一个hash(key)再次哈希的运算,这样可以使得Entry对象更加散列的存储到table中

JDK1.8关于hash(key)方法的实现比JDK1.7要简洁。 key.hashCode() ^ key.Code()>>>16; 因为这样可以使得hashCode的高16位信息也能参与到运算中来

9、HashMap的数组长度为什么一定要是2的幂次方

答:因为2的n次方-1的二进制值是前面都0,后面几位都是1,这样的话,与hash进行&运算的结果就能保证在[0,table.length-1]范围内,而且是均匀的。

10、HashMap 为什么使用 &按位与运算代替%模运算?

答:因为&基于二进制补码的位运算符,效率高,

11、HashMap的数组什么时候扩容?

答:JDK1.7版:当要添加新Entry对象时发现(1)size达到threshold(2)table[index]!=null时,两个条件同时满足会扩容

JDK1.8版:当要添加新Entry对象时发现(1)size达到threshold(2)当table[index]下的结点个数达到8个但是table.length又没有达到64。两种情况满足其一都会导致数组扩容

而且数组一旦扩容,不管哪个版本,都会导致所有映射关系重新调整存储位置。

12、如何计算扩容阈值(临界值)?

答:threshold = capacity * loadfactor

13、loadFactor为什么是0.75,如果是1或者0.1呢有什么不同?

答:1的话,会导致某个table[index]下面的结点个数可能很长

0.1的话,会导致数组扩容的频率太高

14、JDK1.8的HashMap什么时候树化?

答:当table[index]下的结点个数达到8个但是table.length已经达到64

15、JDK1.8的HashMap什么时候反树化?

答:当table[index]下的树结点个数少于等于6个

(1)当继续删除table[index]下的树结点,最后这个根结点的左右结点有null,会反树化

(2)当重新添加新的映射关系到map中,导致了map重新扩容了,这个时候如果table[index]下面还是小于等于6的个数,那么会反树化

16、JDK1.8的HashMap为什么要树化?

答:因为当table[index]下的结点个数超过8个后,查询效率就低下了,修改为红黑树的话,可以提高查询效率

17、JDK1.8的HashMap为什么要反树化?

答:因为因为当table[index]下树的结点个数少于6个后,使用红黑树反而过于复杂了,此时使用链表既简洁又效率也不错

18、作为HashMap的key类型重写equals和hashCode方法有什么要求

(1)equals与hashCode一起重写

(2)重写equals()方法,但是有一些注意事项;

  • 自反性:x.equals(x)必须返回true。 对称性:x.equals(y)与y.equals(x)的返回值必须相等。 传递性:x.equals(y)为true,y.equals(z)也为true,那么x.equals(z)必须为true。 一致性:如果对象x和y在equals()中使用的信息都没有改变,那么x.equals(y)值始终不变。 非null:x不是null,y为null,则x.equals(y)必须为false。

(3)重写hashCode()的注意事项

  • 如果equals返回true的两个对象,那么hashCode值一定相同,并且只要参与equals判断属性没有修改,hashCode值也不能修改; 如果equals返回false的两个对象,那么hashCode值可以相同也可以不同; 如果hashCode值不同的,equals一定要返回false; hashCode不宜过简单,太简单会导致冲突严重,hashCode也不宜过于复杂,会导致性能低下;

19、为什么大部分 hashcode 方法使用 31?

答:因为31是一个不大不小的素数,而且是一个2的n次方-1的一个素数。用这样的一个数来计算,底层使用二进制计算效率会更高,hashCode冲突概率比较低。

20、请问已经存储到HashMap中的key的对象属性是否可以修改?为什么?

答:如果该属性参与hashCode的计算,那么不要修改。因为一旦修改hashCode()已经不是原来的值。 而存储到HashMap中时,key的hashCode()-->hash()-->hash已经确定了,不会重新计算。用新的hashCode值再查询get(key)/删除remove(key)时,算的hash值与原来不一样就不找不到原来的映射关系了。

21、所以为什么,我们实际开发中,key的类型一般用String和Integer

答:因为他们不可变。

22、为什么HashMap中的Node或Entry类型的hash变量与key变量加final声明?

答:因为不希望你修改hash和key值

23、为什么HashMap中的Node或Entry类型要单独存储hash?

答:为了在添加、删除、查找过程中,比较hash效率更高,不用每次重新计算key的hash值

24、请问已经存储到HashMap中的value的对象属性是否可以修改?为什么?

答:可以。因为我们存储、删除等都是根据key,和value无关。

25、如果key是null是如何存储的?

答:会存在table[0]中

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值