Java-集合类总结

集合的框架体系

Collection与Map的区别

Collection:单列集合
在这里插入图片描述
Map:双列集合
在这里插入图片描述

List接口基本介绍:

List接口时Collection接口的子接口

  1. List集合类中元素有序(即添加顺序和取出循序一致)、且可重复
  2. List集合中的每个元素都有其对应的顺序索引,即支持索引
  3. List容器中的元素都对应一个整数型的序号即在其在容器中的位置,可以根据序号去存容器中的元素。
  4. JDK API中List接口的实现类有:
    在这里插入图片描述
package com.NXY.gather_;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Vector;

/**
 * @author 不会写代码的16号
 */
public class List_ {
    public static void main(String[] args) {
        //List集合类中元素有序(即添加顺序和取出循序一致)、且可重复
        List list = new ArrayList();
        list.add("jack");
        list.add("tom");
        list.add("smith");
        System.out.println(list);
        //List集合中的每个元素都有其对应的索引, 索引是从0开始的
        //List容器中的元素 都对应一个整数型的序号记载其在容器中的位置
        System.out.println(list.get(0));

    }
}

List中常用的方法介绍

List集合里添加了一些根据索引来操作集合元素的方法

  1. void add(int index, Object ele): 在Index位置插入ele元素
  2. boolean addAll(int index, Collection eles):从index位置开始将eles中的所有元素添加进来
  3. Object get(int Index):获取指定Index位置的元素
  4. int indexOf(Object obj):返回obj在集合中首次出现的位置
  5. int lastIndexOf(Object obj):返回Obj在当前集合中末次出现的位置
  6. Object remove(int index): 移除指定index位置的元素,并返回此元素
  7. Object set(int index,Object ele): 设置指定Index位置的元素ele,相当于是替换
  8. List subList(int fromIndex, int toIndex): 返回从formIndex到toIndex位置的子集合
package com.NXY.gather_;

import java.util.ArrayList;
import java.util.List;

/**
 * @author 不会写代码的16号
 */
public class ListMethod_ {
    public static void main(String[] args) {
        List list = new ArrayList();
        list.add("jack");
        list.add("tom");
        //void add(int index, Object ele): 在Index位置插入ele元素
        list.add(1,"smith");//在下标为1的位置添加smith这个元素
        //boolean addAll(int index, Collection eles):从index位置开始将eles中的所有元素添加进来
        List list2 = new ArrayList();
        list.add("老子");

        list.addAll(1,list2);//从下标为1开始 添加list2全部元素进入list
        //Object get(int Index):获取指定Index位置的元素
        list.get(1);
        //int indexOf(Object obj):返回obj在集合中首次出现的位置
        list.indexOf("jack");
        //int lastIndexOf(Object obj):返回Obj在当前集合中末次出现的位置
        list.add("jack");
        list.indexOf("jack");
        //Object remove(int index): 移除指定index位置的元素,并返回此元素
        list.remove(0);
        //Object set(int index,Object ele): 设置指定Index位置的元素ele,相当于是替换
        list.set(0,"jack");
        //List subList(int fromIndex, int toIndex): 返回从formIndex到toIndex位置的子集合
        list.subList(0,3);


    }
}

List三种遍历方式

  1. 方式一:使用iterator迭代方式
    Iterator iter = col.iterator();
    while(iter.hasNext()){
    Object o = iter.next();
    }

  2. 方式二:使用增强for
    for(Object o : cal){
    }

  3. 方式三: 使用普通for
    for(int i = 0; i < list.size(); i ++){
    Object object = list.get(i);
    System.out.println(object);
    }
    重点.如果list中存入的元素是定义的类,需要向下转型

package com.NXY.gather_;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 * @author 不会写代码的16号
 */
public class LIstFor_ {
    public static void main(String[] args) {
        List list = new ArrayList();
        list.add("tom");
        list.add("jack");
        list.add("smith");
        Iterator iterator = list.iterator();
        while(iterator.hasNext()){
            Object o = iterator.next();
            System.out.println(o);
        }
    }
}

ArrayList集合讲解

ArrayList特性
  1. ArrayList是由数组来实现数据储存的
  2. ArrayList基本等同于Vectoe,除了ArrayList是线程不安全(执行效率高) 看源码,在多线程的情况下不建议使用ArrayList
重点.ArrayList底层结构和源码分析

结论:
3. ArrayList中维护了一个Object类型的数组elementData.
4. transient Object[] elementData;//transient 表示瞬间,短暂的,表示该属性不会被序列号
5. 当创建ArrayList对象时,如果使用的无参构造器,则初始elementData容量为0(JDK7 为10),第一次添加,则扩容elementData为10,如需要再次扩容,则扩容elementData为1.5倍。
6. 如果使用的时指定大小的构造器,则初始elementData容量为指定大小,如果需要扩容,则直接扩容elementData为1.5倍
底层源码剖析
1.无参构造器

//无参数据库
 public ArrayList() {
 //创建了一个空的 elementData数组
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
public boolean add(E e) {
        modCount++;//集合修改的次数  +1
        add(e, elementData, size);//进入e 为传入的数据 
        //elementData为前面创建的数组,size表示 数组中的数据的个数
        return true;
    }
private void add(E e, Object[] elementData, int s) {
        if (s == elementData.length)//判断是否要扩容
            elementData = grow();//如果需要扩容 则进入该函数
        elementData[s] = e;//惊醒赋值
        size = s + 1;
    }
private int newCapacity(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);//扩大1.5倍
        if (newCapacity - minCapacity <= 0) {
            if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
            //第一次扩容 容量为10  DEFAULT_CAPACITY = 10
                return Math.max(DEFAULT_CAPACITY, minCapacity);
            if (minCapacity < 0) // overflow
                throw new OutOfMemoryError();
            return minCapacity;
        }
        //下面语句为 第一次扩容之后的 扩容  将容量扩大1.5呗
        return (newCapacity - MAX_ARRAY_SIZE <= 0)
            ? newCapacity
            : hugeCapacity(minCapacity);
    }

LinkedList讲解

LinkedList特点
  1. LinkedList实现了双向链表和双端队列特点
  2. 可以添加任意元素(元素可以重复),包括null
  3. 线程不安全,没有实现同步
LinkedList底层原理讲解
  1. LinkedList底层维护了一个双向链表
  2. LinkedList中维护了两个属性first 和 last 分别指向 首节点和尾节点
  3. 每个节点(Node对象) 里面有维护了prev、next、item三个属性,其中通过prev指向前一个节点,通过next指向后一个节点,最终实现双向链表
  4. 所以LinkedList的元素的添加和删除,不是通过数组完成的,相对来说效率较高
void linkLast(E e) {
        final Node<E> l = last;
        final Node<E> newNode = new Node<>(l, e, null);//创建新的节点
        last = newNode;
        if (l == null)
            first = newNode;
        else
            l.next = newNode;
        size++;
        modCount++;
    }
Node(Node<E> prev, E element, Node<E> next) {
            this.item = element;
            this.next = next;
            this.prev = prev;
        }

Vector底层原理讲解

Vector底层特点:
  1. Vector底层也是一个对象数组,protected Object[] elementData;
  2. Vector是线程同步的,即线程安全,Vector类的操作方法带有synchronized
  3. 在开发中,需要线程同步时,考虑使用Vector
package com.NXY.gather_;

import java.util.Vector;

/**
 * @author 不会写代码的16号
 */
public class Vector_ {
    public static void main(String[] args) {
        Vector vector = new Vector(8);
        for(int i = 0; i <10; i++){
            vector.add(i);
        }
        vector.add(100);
        System.out.println(vector);
    }
}

public Vector(int initialCapacity) {
// 如果时上面的new Vetor(8) 则是使用这个函数
        this(initialCapacity, 0);
    }

    /**
     * Constructs an empty vector so that its internal data array
     * has size {@code 10} and its standard capacity increment is
     * zero.
     */
    public Vector() {
    //如果是new Vector() 则使用的是这个函数  默认第一次扩容为10
        this(10);
    }
public Vector(int initialCapacity, int capacityIncrement) {
        super();
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        // 创建一个容量为initialCapacity的elementData数组
        this.elementData = new Object[initialCapacity];
        this.capacityIncrement = capacityIncrement;
    }
//添加函数
public synchronized boolean add(E e) {
        modCount++;
        add(e, elementData, elementCount);
        return true;
    }
private void add(E e, Object[] elementData, int s) {
        if (s == elementData.length)//判断是否需要扩容
            elementData = grow();
        elementData[s] = e;
        elementCount = s + 1;
    }    
    //扩容
private int newCapacity(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
                                         capacityIncrement : oldCapacity);//判断是否需要扩容 如果需要扩容则扩容两倍
        if (newCapacity - minCapacity <= 0) {
            if (minCapacity < 0) // overflow
                throw new OutOfMemoryError();
            return minCapacity;
        }
        return (newCapacity - MAX_ARRAY_SIZE <= 0)
            ? newCapacity
            : hugeCapacity(minCapacity);
    }    

Set集合讲解

Set集合特点:
  1. set接口的实现类对象(Set接口对象),不能存放重复的元素,可以添加一个null

  2. set接口对象存放数据是无序的(即添加的顺序和取出的顺序不一致)

  3. 取出的顺序是固定的 不会随着运行而改变
    Set接口的遍历方式
    同Collection的遍历方式一样,因为Set接口时Collection接口的子接口

    1. 可以使用迭代器
    2. 增强for
package com.NXY.gather_;

import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

/**
 * @author 不会写代码的16号
 */
public class Set_ {
    public static void main(String[] args) {
        Set set = new HashSet();
        set.add("john");
        set.add("luck");
        set.add("john");//重复
        set.add("jack");
        set.add(null);
        set.add(null);
        System.out.println(set);//[null, luck, john, jack]
        
        //遍历
        //迭代器
        Iterator iterator = set.iterator();
        while(iterator.hasNext()){
            Object obj = iterator.next();
            System.out.println(obj);
        }
        //增强for
        for(Object obj:set){
            System.out.println(obj);
        }
    }
}

HashSet集合讲解

HashSet特点:
  1. HashSet实际上是HashMap
  2. 可以存放null值 但只是只能有一个null
  3. HashSet不保证元素是有序的,取决于hash后 在确定索引的结果
  4. 不能有重复元素\对象
  5. 在添加一个元素时,先得到hash值 会转成 索引值
  6. 找到存储数据标table 看这个索引位置是否已经存放元素
  7. 如果有,调用equals比较,如果相同,就放弃添加,如果不相同,则添加到最后面
  8. 在java8中,如果一条链表的元素个数超过TREEIFY_YHTESHOLD(默认是8)并且table的大小>=MIN_TREEIFY_CAPACITY(默认64)就会进行树化(红黑树)
    在这里插入图片描述
Set底层机制说明
  1. HashSet底层是HashMap,第一次添加时 table数组扩容到16 临界值(threshold) 是 16*加载因子(loadFactor) 是0.75 = 12
  2. 如果table数组是同到了临界值12 就会扩容到162 = 32 新的临界值 就是320.75 = 24 以此类推
public HashSet() {
        map = new HashMap<>();
    }
public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }//hash(key) 先获取元素的哈希值(hashCode方法) 对哈希值进行运算,得出一个索引值 即为要存放在哈希表中的元素
 static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
        // 判断该元素是否存在其他元素  如果没有则直接存放
        // 如果该位置已经有其他元素 则需要进行equals判断,如果相等则不再添加
    }    
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K,V> e; K k;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        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;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }


    final Node<K,V>[] resize() {
        Node<K,V>[] oldTab = table;
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        int oldThr = threshold;
        int newCap, newThr = 0;
        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;//第一次扩容 DEAULT_INITIAL_CAPACITY = 16
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
            // 临界值12 0.75 * 16
        }
        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 = newTab; //令table = 创建的数组
        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;
    }

     /*
        源码解读:
        1.构造器:
        public HashSet() {
        map = new HashMap<>();
         }
         
         
        2.执行add():
        public boolean add(E e) {
        return map.put(e, PRESENT)==null;
        // PRESENT是 private static final Object PRESENT = new Object();
         }
         
         
         3.执行put(): 该方法会执行hash(key) 得到key的hash值
          // k是 java 字符串  value 是 PRESENT
         public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
        }
        
        
        4.执行 putVal():
        final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i; //定义辅助变量
        //table 就是HashMap的一个数组  类型是Node[]
        //if语句 是 如果table==null 或者大小 ==0时
        //进行第一次扩容  扩容到大小为16
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
         //(1)根据key传入的Hash值 计算得到 key应该放入table表哪个索引位置
         //并把该位置的对象 赋给p
         //(2)判断p 是否为null
         //(2.1)如果p为空 表示还没存储元素 就创建一个Node(key="java",value=PRESENT)
         //(2.2)就放到该位置 tab[i] = newNode(hash,key,value,null)
         //
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
        //在需要局部变量时候的再创建
            Node<K,V> e; K k;//辅助变量
            //如果当索引位置对应的链表的第一个元素和准备添加的key的hash值一样
            //并且满足 以下两个条件:
            //(1) 准备加入的key 和 p 指向的Node 结点的key 是同一个对象
            //(2) p 指向的Node结点 的 key 的equals() 和准备加入的key比较后相同
            // 所以就不能加入
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
             //在判断 p 是不是一颗红黑树
             //如果是一颗红黑树 就调用 putTreeVal,来进行添加
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
            // 如果table 对应的索引位置 已经是个链表时 就使用for循环比较
            //(1) 依次和该链表的每一个元素比较后,都不相同 则加入该链表的最后
            //    注意在把元素添加到链表后 立即判断,该链表是否已经达到8个结点
            //    若达到 就调用 treeifyBin() 对当前这个链表进行树化(转成红黑树)
            //    注意,在转成红黑树时,要进行判断 判断条件
            //    if(tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY(64))
                             resize();
                    //如果上面条件成立 先将table扩容
                    //如果条件不成立 才进行转成红黑树
            //(2) 依次和该链表的每个元素比较过程中 如果有相同情况 就直接break
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        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;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            resize();//扩容  在扩容机制上  会算上链表上的个数
        afterNodeInsertion(evict);
        return null;
    }
         */
    

TreeSet集合讲解

源码讲解

package com.Set_;

import java.util.Comparator;
import java.util.TreeSet;

/**
 * @author 不会写代码的16号
 */
public class TreeSet_ {
    public static void main(String[] args) {

        
        //1. 当我们使用无参构造器,创建TreeSet时,仍然是无序的
        //2. 希望添加的元素,按照字符串大小来排序
        //3. 使用TreeSet 提供的一个构造器,可以传入一个比较器(匿名内部类)
        //   并指定排序规则
        //4. 简单看看源码
        
        /*
        1. 构造器把传入的比较器对象,赋给了 TreeSet的底层的 TreeMap的属性this.comparator

         public TreeMap(Comparator<? super K> comparator) {
                this.comparator = comparator;
            }
         2. 在 调用 treeSet.add("tom"), 在底层会执行到

             if (cpr != null) {//cpr 就是我们的匿名内部类(对象)
                do {
                    parent = t;
                    //动态绑定到我们的匿名内部类(对象)compare
                    cmp = cpr.compare(key, t.key);
                    if (cmp < 0)
                        t = t.left;
                    else if (cmp > 0)
                        t = t.right;
                    else //如果相等,即返回0,这个Key就没有加入
                        return t.setValue(value);
                } while (t != null);
            }
         */

//        TreeSet treeSet = new TreeSet();
        TreeSet treeSet = new TreeSet(new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                //下面 调用String的 compareTo方法进行字符串大小比较
                //如果要求加入的元素,按照长度大小排序
                //return ((String) o2).compareTo((String) o1);
                return ((String) o1).length() - ((String) o2).length();
            }
        });
        //添加数据.
        treeSet.add("jack");
        treeSet.add("tom");//3
        treeSet.add("sp");
        treeSet.add("a");
        treeSet.add("abc");//3


        System.out.println("treeSet=" + treeSet);




    }
}

Map接口讲解

Map接口实现类的特点

  1. Map与Collection并列存在。用于保存具有映射关系的数据:key—Value
  2. Map中的key和value可以是任何引用类型的数据,会封到HashMap$Node对象中
  3. Map中的key不允许重复,原因和HashSet一样,前面分析过源码
  4. Map中的value可以重复
  5. Map的key可以为null,value也可以为null,注意key为null,只能一个,value为null,可以为多个
  6. 常用String类作为Map的key
  7. key和value之间存在单向一对一关系,即通过指定key中能找到对应的value
package com.Map_;

import java.util.HashMap;
import java.util.Map;

/**
 * @author 不会写代码的16号
 */
@SuppressWarnings({"all"})
public class Map_ {
        public static void main(String[] args) {
            //解读Map 接口实现类的特点, 使用实现类HashMap
            //1. Map与Collection并列存在。用于保存具有映射关系的数据:Key-Value(双列元素)
            //2. Map 中的 key 和  value 可以是任何引用类型的数据,会封装到HashMap$Node 对象中
            //3. Map 中的 key 不允许重复,原因和HashSet 一样,前面分析过源码.
            //4. Map 中的 value 可以重复
            //5. Map 的key 可以为 null, value 也可以为null ,注意 key 为null,
            //   只能有一个,value 为null ,可以多个
            //6. 常用String类作为Map的 key
            //7. key 和 value 之间存在单向一对一关系,即通过指定的 key 总能找到对应的 value
            Map map = new HashMap();
            map.put("no1", "jack");//k-v  输出 no1=jack
            map.put("no2", "smith");//k-v
            map.put("no1", "tom");//当有相同的k , 就等价于替换.
            map.put("no3", "tom");//k-v
            map.put(null, null); //k-v
            map.put(null, "abc"); //等价替换
            map.put("no4", null); //k-v
            map.put("no5", null); //k-v
            map.put(1, "john");//k-v
            map.put(new Object(), "luck");//k-v
            // 通过get 方法,传入 key ,会返回对应的value
            System.out.println(map.get("no2"));

            System.out.println("map=" + map);



        }
    }
package com.Map_;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
 * @author 不会写代码的16号
 */
@SuppressWarnings({"all"})
public class MapSource_ {
    public static void main(String[] args) {
        Map map = new HashMap();
        map.put("no1", "jack");//k-v  输出 no1=jack
        map.put("no2", "张无忌");//k-v
        //解读
        //1. k-v 最后是 HashMap$Node node = newNode(hash, key, value, null)
        //2. k-v 为了方便程序员的遍历,还会 创建 EntrySet 集合 ,该集合存放的元素的类型 Entry, 而一个Entry
        //   对象就有k,v EntrySet<Entry<K,V>> 即: transient Set<Map.Entry<K,V>> entrySet;
        //3. entrySet 中, 定义的类型是 Map.Entry ,但是实际上存放的还是 HashMap$Node
        //   这时因为 static class Node<K,V> implements Map.Entry<K,V>
        //4. 当把 HashMap$Node 对象 存放到 entrySet 就方便我们的遍历, 因为 Map.Entry 提供了重要方法
        //   K getKey(); V getValue();

        Set set = map.entrySet();
        System.out.println(set.getClass());// HashMap$EntrySet
        for (Object obj : set) {

            //System.out.println(obj.getClass()); //HashMap$Node
            //为了从 HashMap$Node 取出k-v
            //1. 先做一个向下转型 之前是object类 引用类型
            Map.Entry entry = (Map.Entry) obj;
            System.out.println(entry.getKey() + "-" + entry.getValue() );
        }

        Set set1 = map.keySet();
        System.out.println(set1.getClass());
        Collection values = map.values();
        System.out.println(values.getClass());


    }
}
@SuppressWarnings({"all"})
class Car {

}
@SuppressWarnings({"all"})
class Person{

}
Map六种遍历方法
package com.NXY.gather_;

import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

/**
 * @author 不会写代码的16号
 */
public class MapFor {
    public static void main(String[] args) {
        Map map = new HashMap();
        map.put("jack","jack1");
        map.put("smith","smith1");
        map.put("john","john1");
        map.put("luck","luck1");
        // 第一组:选取出 所有key  通过key 取出对应的values;
        Set set = map.keySet();//得到一个key 的set集合
        //方法一: 增强for循环
        for(Object key:set){
            System.out.println(key + "-" + map.get(key));
        }
        //方法二:迭代器 keySet是Set类型 可以用迭代器
        Iterator iterator = set.iterator();
        while(iterator.hasNext()){
            Object next = iterator.next();
            System.out.println(next + "-" + map.get(next));
        }
        //第二组:把所以的values 取出
        Collection values = map.values();
        //方法一:增强for
        for (Object value :values) {
            System.out.println(value);
        }
//        (2).迭代器
        Iterator iterator1 = values.iterator();
        while (iterator1.hasNext()) {
            Object next =  iterator1.next();
            System.out.println(next);
        }
        //第三组:通过EntrySet 来获取 k-v
        Set set1 = map.entrySet();//EntrySet<Map.Entry<k,v>
        for(Object entry:set1){
            //将entry 转成 Map.Entry
            Map.Entry m = (Map.Entry) entry;//向下转型从引用类型 转成Map.Entry
            System.out.println(m.getKey() + "-" + m.getValue());
        }
        Iterator iterator2 = set1.iterator();
        while(iterator2.hasNext()){
            Map.Entry m = (Map.Entry) iterator2.next();//向下转型
            System.out.println(m.getKey() + "-" + m.getValue());
        }
    }
}

HashMap底层原理剖析.重点

  1. (k,v)是一个Node实现了Map.Entry<k,v>,查看HashMap的源码可以看到
  2. jdk7.0的hashMap底层实现了[数组+链表],jdk8.0底层[数组+链表+红黑树]
HashMap扩容机制
  1. HashMap底层维护了Node类型的数组table,默认为null
  2. 当创建对象时,将加载因子(lodafactor) 初始化为0.75
  3. 当添加key-val时,通过key的哈希值得到在table的索引。然后判断该索引是否有元素,如果没有元素直接添加,如果该索引处有元素,继续判断该元素的key是否和准备加入的key相等,如果相等,则直接替换val,如果不相等需要判断树结构还是链表结构,做出相应的处理,如果添加时发现容量不够,则需要扩容
  4. 第一次添加,则需要扩容table容量为16,临界值(threshold)为12
  5. 之后再次扩容,则需要扩容table容量为原来的2倍,临界值为原来的2倍
  6. 在java8中,如果一条链表的元素个数超过TREEIFY_THRESHOLD(默认是8) 并且table的大小>=MIN_TREEIFY_CAPACITY(默认64)就会进行树化(红黑树)
package com.Map_;

import java.util.HashMap;

/**
 1. @author 不会写代码的16号
 */
public class HashMapSource1 {
    public static void main(String[] args) {
        HashMap map = new HashMap();
        map.put("java", 10);//ok
        map.put("php", 10);//ok
        map.put("java", 20);//替换value

        System.out.println("map=" + map);//
// table 一开始为HashNode 数组 里面储存的是 Node内部类的结点
        /*解读HashMap的源码+图解
        1. 执行构造器 new HashMap()
           初始化加载因子 loadfactor = 0.75
           HashMap$Node[] table = null
        2. 执行put 调用 hash方法,计算 key的 hash值 (h = key.hashCode()) ^ (h >>> 16)
            public V put(K key, V value) {//K = "java" value = 10
                return putVal(hash(key), key, value, false, true);
            }
         3. 执行 putVal
         final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
                Node<K,V>[] tab; Node<K,V> p; int n, i;//辅助变量
                //如果底层的table 数组为null, 或者 length =0 , 就扩容到16
                if ((tab = table) == null || (n = tab.length) == 0)
                    n = (tab = resize()).length;
                //取出hash值对应的table的索引位置的Node, 如果为null, 就直接把加入的k-v
                //, 创建成一个 Node ,加入该位置即可
                if ((p = tab[i = (n - 1) & hash]) == null)
                    tab[i] = newNode(hash, key, value, null); //内部类Node 创建一个			新的结点 包含了 kv
                else {
                    Node<K,V> e; K k;//辅助变量
                // 如果table的索引位置的key的hash相同和新的key的hash值相同,
                 // 并 满足(table现有的结点的key和准备添加的key是同一个对象  || equals返			回真)
                 // 就认为不能加入新的k-v
                    if (p.hash == hash &&
                        ((k = p.key) == key || (key != null && key.equals(k))))
                        e = p;
                    else if (p instanceof TreeNode)//如果当前的table的已有的Node 是红黑树,就按照红黑树的方式处理
                        e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
                    else {
                        //如果找到的结点,后面是链表,就循环比较
                        for (int binCount = 0; ; ++binCount) {//死循环
                            if ((e = p.next) == null) {//如果整个链表,没有和他相同,就加到该链表的最后
                                p.next = newNode(hash, key, value, null);
                                //加入后,判断当前链表的个数,是否已经到8个,到8个,后
                                //就调用 treeifyBin 方法进行红黑树的转换
                                if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                                    treeifyBin(tab, hash);
                                break;
                            }
                            if (e.hash == hash && //如果在循环比较过程中,发现有相同,就break,就只是替换value
                                ((k = e.key) == key || (key != null && key.equals(k))))
                                break;
                            p = e;
                        }
                    }
                    if (e != null) { // existing mapping for key
                        V oldValue = e.value;
                        if (!onlyIfAbsent || oldValue == null)
                            e.value = value; //替换,key对应value
                        afterNodeAccess(e);
                        return oldValue;
                    }
                }
                ++modCount;//每增加一个Node ,就size++
                if (++size > threshold[12-24-48])//如size > 临界值,就扩容
                    resize();
                afterNodeInsertion(evict);
                return null;
            }

              5. 关于树化(转成红黑树)
              //如果table 为null ,或者大小还没有到 64,暂时不树化,而是进行扩容.
              //否则才会真正的树化
              final void treeifyBin(Node<K,V>[] tab, int hash) {
                int n, index; Node<K,V> e;
                if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
                    resize();

            }
         */


    }
}

HashTable集合讲解

HashTable特点讲解:

  1. 存放的元素是键值对:即k-v
  2. HashTable的键和值都不能为null
  3. hashTable使用方法基本上和HashMap一样
  4. HashTable是线程安全,HashMap是线程不安全的
package com.Map_;

import java.util.Hashtable;

/**
 * @author 不会写代码的16号
 */
@SuppressWarnings({"all"})
public class HashTable {
    public static void main(String[] args) {
        Hashtable hashTable = new Hashtable();
        hashTable.put("john",100);
        //键 和 值 不能为空 即 k-v不能为null
//        hashTable.put(null,100);
//        hashTable.put("john",null);
        hashTable.put("lucy",100);
        hashTable.put("lic",100);
        hashTable.put("lic",88);
        hashTable.put("hello1", 1);
        hashTable.put("hello2", 1);
        hashTable.put("hello3", 1);
        hashTable.put("hello4", 1);
        hashTable.put("hello5", 1);
        hashTable.put("hello6", 1);
        System.out.println(hashTable);

        //简单说明一下Hashtable的底层
        //1. 底层有数组 Hashtable$Entry[] 初始化大小为 11
        //2. 临界值 threshold 8 = 11 * 0.75
        //3. 扩容: 按照自己的扩容机制来进行即可.
        //4. 执行 方法 addEntry(hash, key, value, index); 添加K-V 封装到Entry
        //5. 当 if (count >= threshold) 满足时,就进行扩容
        //5. 按照 int newCapacity = (oldCapacity << 1) + 1; 的大小扩容.

    }
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值