【韩老师零基础30天学会Java 10】ArrayList和Vector源码 LinkedList源码。HashSet HashMap源码,HashCode LinkedHashSet

体系

集合框架体系图
Collection接口特点方法
Collection接口的子接口:List 实现类:ArrayList、LinkedList、Vector
Collection接口的子接口: Set 实现类:HashSet、LinkedHashSet
Map接口特点方法遍历方式Map接口的实现类:HashMap.
Hashtable等
Collections工具类的使用

Collection

  • List
    • ArrayList
    • LinkedList
    • Vector
  • Set
    • HashSet
      • Linked HashSet
    • Tree Set

Map

  • HashMap
    • Linked HashMap
  • TreeMap
  • Hashtable
    • Properties

Collections

数组缺陷

数组
1)长度开始时必须指定,而且一旦指定,不能更改

2)保存的必须为同一类型的无素

3)使用数组进行增加元素的示意代码-比较麻烦

集合好处

1)可以动态保存任意多个对象,使用比较方便!
·2)提供了一系列方便的操作对象的方法:add、remove、set、get等

3)使用集合添加,删除新元素的示意代码-
简洁了

Collection

public interface Collection<E> extends Iterable<E> { //实现迭代器方法

有些是有序的(List),有些不是有序(Set)

  1. add:添加单个元素2) remove:删除指定元素
  2. contains:查找元素是否存在4) size:获取元素个数
  3. isEmpty:判断是否为空6) clear:清空
  4. addAll:添加多个元素
  5. containsAll:查找多个元素是否都存在9) removeAll:删除多个元素
    10)说明:以ArrayList实现类来演示.
        List list=new ArrayList();
        list.add(5);
        list.add(3);
        list.add(10);

        list.remove(0); //删除0索引,int类型
        list.remove(new Integer(3));//删除 3元素,Integer类型
        List list = new ArrayList();
//        add:添加单个元素
        list.add("jack");
        list.add(10);//list.add(new Integer(10))
        list.add(true);
        System.out.println("list=" + list);
//        remove:删除指定元素
        //list.remove(0);//删除第一个元素
        list.remove(true);//指定删除某个元素
        System.out.println("list=" + list);
//        contains:查找元素是否存在
        System.out.println(list.contains("jack"));//T
//        size:获取元素个数
        System.out.println(list.size());//2
//        isEmpty:判断是否为空
        System.out.println(list.isEmpty());//F
//        clear:清空
        list.clear();
        System.out.println("list=" + list);
//        addAll:添加多个元素
        ArrayList list2 = new ArrayList();
        list2.add("红楼梦");
        list2.add("三国演义");
        list.addAll(list2);
        System.out.println("list=" + list);
//        containsAll:查找多个元素是否都存在
        System.out.println(list.containsAll(list2));//T
//        removeAll:删除多个元素
        list.add("聊斋");
        list.removeAll(list2);
        System.out.println("list=" + list);//[聊斋]

迭代器和增强for

  • 三种遍历
    • 迭代器
    • 增强for
    • 普通for

Collection接口遍历元素方式1-使用lterator(迭代器)

  1. Iterator对象称为迭代器,主要用于遍历Collection集合中的元素。2)所有实现了Collection接口的集合类都有一个iterator()方法,用以返回一个实现了lterator接口的对象,即可以返回一个迭代器。
  2. Iterator的结构.[图:]
  3. Iterator仅用于遍历集合,Iterator 本身并不存放对象。
public interface Iterable<T> {

    Iterator<T> iterator();
    
}
//迭代器模式
public interface Iterator<E> {

    boolean hasNext();

    E next();

    default void remove() {
        throw new UnsupportedOperationException("remove");
    }
lterator iterator = coll.iterator();//得到一个集合的迭代器

//hasNext():判断是否还有下一个元素
while(iterator.hasNext0){

//next():①指针下移②将下移以后集合位置上的元素返回
System.out.println(iterator.next0));
}

老韩提示:在调用iterator.next()方法之前必须要调用iterator.hasNext()·进行检测。若不调用,且下一条记录无效,直接调用it.next()会抛出
NoSuchElementException异常。

        Iterator ite = list.iterator();
        while (ite.hasNext()) {
            System.out.println("元素为:" + ite.next());
        }
//快捷键 itit ,遍历 iterator
//显示所有快捷键的 快捷键 ,ctrl+J,可惜 我的并不是

list.iterator();//再次调用,重置迭代器

增强for循环,可以代替iterator迭代器,特点:
增强for就是简化版的iterator,
本质一样。只能用于遍历集合或数组。

for(元素类型元素名:集合名或数组名){
访问元素
}

增强for可以理解成就是简化版本的达代器

//快捷键是 输入 大写的I

List

List接口是 Collection接口的子接口Li:ist_java

  1. List集合类中元素有序(即添加顺序和取出顺序一致)、且可重氡[案例]

  2. List集合中的每个元素都有其对应的顺序索引,即支持索引。[案例]

3)List容器中的元素都对应一个整数型的序号记载其在容器中的位置,可以根
据序号存取容器中的元素。

实现类:

AbstractList ,

AbstractSequentialList ,

  • ArrayList ,

AttributeList ,

  • CopyOnWriteArrayList ,

  • LinkedList

RoleList ,

RoleUnresolvedList ,

stack ,

  • Vector
        List list = new ArrayList();
        list.add("张三丰");
        list.add("贾宝玉");
//        void add(int index, Object ele):在index位置插入ele元素
        //在index = 1的位置插入一个对象
        list.add(1, "韩顺平");
        System.out.println("list=" + list);

//		get获取 某个索引的元素
        Object o = list.get(1);
        System.out.println(o);

//        boolean addAll(int index, Collection eles):从index位置开始将eles中的所有元素添加进来
        List list2 = new ArrayList();
        list2.add("jack");
        list2.add("tom");
        list.addAll(1, list2);
        System.out.println("list=" + list);

//        Object get(int index):获取指定index位置的元素
        //说过
//        int indexOf(Object obj):返回obj在集合中首次出现的位置
        System.out.println(list.indexOf("tom"));//2
//        int lastIndexOf(Object obj):返回obj在当前集合中末次出现的位置
        list.add("韩顺平");
        System.out.println("list=" + list);
        System.out.println(list.lastIndexOf("韩顺平"));

//        Object remove(int index):移除指定index位置的元素,并返回此元素
        list.remove(0);
        System.out.println("list=" + list);

//        Object set(int index, Object ele):设置指定index位置的元素为ele , 相当于是替换.
        list.set(1, "玛丽");
        System.out.println("list=" + list);

//        List subList(int fromIndex, int toIndex):返回从fromIndex到toIndex位置的子集合
        // 注意返回的子集合 fromIndex <= subList < toIndex
        List returnlist = list.subList(0, 2);
        System.out.println("returnlist=" + returnlist);
冒泡排序
    //静态方法
    //价格要求是从小到大
    public static void sort(List list) {

        int listSize = list.size();
        for (int i = 0; i < listSize - 1; i++) {
            for (int j = 0; j < listSize - 1 - i; j++) {
                //取出对象Book
                Book book1 = (Book) list.get(j);
                Book book2 = (Book) list.get(j + 1);
                if (book1.getPrice() > book2.getPrice()) {//交换
                    list.set(j, book2);
                    list.set(j + 1, book1);
                }
            }
        }

    }

1)permits all elements, including null
ArrayList 可以加入nul,并且多个
2) ArrayList是由数组来实现数存储的
3) ArrayList基本等同于Vector,

除了ArrrayList是线程不安全(执行效率高)看源码.,
在多线程情况下,不建议使用ArrayList

ArrayList源码
  1. ArrayList中维护了一个Object类型的数组el
    ementData. [debug看源码]
    transient Object[] elementData;//'transient 表示瞬间,短暂的,表示该属性不会被序列化

2)当创建ArrayList对象时,如果使用的是无参构造器,则初始elementData容量为0第1
次添加,则扩容elementData为10,如需要再次扩容,则扩容elementData为1.5倍

  • 0 10 15 22 33

3)如果使用的是指定大小的构造器,则初始elem
entData容量为指定大小,如果需要扩容,
则直接扩容elementData为1.5倍。

  • 如果是 8 12 18
        //老韩解读源码
        //注意,注意,注意,Idea 默认情况下,Debug 显示的数据是简化后的,如果希望看到完整的数据
        //需要做设置.
        //使用无参构造器创建ArrayList对象
        //ArrayList list = new ArrayList();
        ArrayList list = new ArrayList(8);
        //使用for给list集合添加 1-10数据
        for (int i = 1; i <= 10; i++) {
            list.add(i);
        }
        //使用for给list集合添加 11-15数据
        for (int i = 11; i <= 15; i++) {
            list.add(i);
        }
        list.add(100);
        list.add(200);
        list.add(null);

首添加扩容

创建了一个空的elementData数组

    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
    
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

执行list.add
(1)先确定是否要扩容(2)然后在执行赋值

该方法确定minCapacity

1)第一次扩容为10

(1) modCount++记录集合被修改的次数
(2)如果elementData的大小不够,就调用grow()去扩容

    public boolean add(E e) {
        //确认 容量 够不够。size 是int,首次默认为 0,+1 后为1
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
	//minCapacity
    private void ensureCapacityInternal(int minCapacity) {
        //首次 elementData 为空 数组
        //首次 通过空构造,赋值的就是这个空数组,所以这里这样判断,如果已经是扩容后 会赋值为 新的数组。
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            // 10 和 1之间 取一个 最大的。所以:无参构造 第一次扩容,大小为 0
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }
		
        //确认 明确 扩容
        ensureExplicitCapacity(minCapacity);
    }


    private void ensureExplicitCapacity(int minCapacity) {
        //记录 当前 这个 集合 被修改 的次数,防止 多个线程 同时修改。有,就抛出异常。
        //size++ 是对 数组赋值后,就 自加1,这里是 调用add方法就 +1
        modCount++;

        // overflow-conscious code
        //如果最小的容量 - 当前数组实际的大小 > 0,说明不够了,需要扩容了
        //现在最小是10, elementData.length = 0
        //我实际需要的最小数量为 10,你数组的长度 只能提供 0个长度,不够了   。
        
        //第二次添加,是 size+1,minCapacity=2。数组的长度为10。2-10 不大于0,所以 不扩容
        //minCapacity 至少要 保证,有几个。
        
        //第11次, 数组的长度 依然为10,不够了,需要再次扩容
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

(1)真的扩容
(2)使用扩容机制来确定要扩容到多大
(3)第一次newCapacity =10

(4)第二次及其以后,按照1.5倍扩容
(5)扩容使用的是Arrays.copyof()

    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        //原先数组的大小 + 原先数组的大小 / 2 。右移1位 /2 。所以:1.5倍
        //第一次 比较奇特,都是0
        
        //第11次,minCapacity为11。oldCapacity为10,newCapacity 之后下面后,为15
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        //0 - 10
        
        //第11次为:15 - 11 > 0
        if (newCapacity - minCapacity < 0)
            //把 最小的值 ,赋值给 newCapacity。所以 第一次,并没有用 1.5被的扩容 机制。
            newCapacity = minCapacity;
        //如果新的 容量,比最大值,还要大
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);//使用这个方法处理
        // minCapacity is usually close to size, so this is a win:
        
        //现在是空数组,扩展到 10,扩充完毕: elementData 有10个空数据。
        //如果已经10个数据,扩容 会保留原来的数据
        
        //第11次,扩容的为15,扩容后 0-9索引有值,剩下5个为 空
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
  • 最最底层用的 System类的
    public static native void arraycopy(Object src,  int  srcPos,
                                        Object dest, int destPos,
                                        int length);

//源数组
//从源数组的哪个索引位置开始拷贝

//目标数组,即把源数组的数据拷贝到哪个数组
//把源数组的数据拷贝到 目标数组的哪个索引

//从源数组拷贝多少个数据到目标数组

        int[] src={1,2,3};
        int[] dest = new int[3];// dest 当前是 {0,0,0}

		System.arraycopy(src, 0, dest, 0, src.length);
        // int[] src={1,2,3};
        System.out.println("dest=" + Arrays.toString(dest));//[1, 2, 3]
  • 对应的源码为
    public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
        //为true,会创建 new Object[newLength] ,10 长度的索引。
        @SuppressWarnings("unchecked")
        T[] copy = ((Object)newType == (Object)Object[].class)
            ? (T[]) new Object[newLength]
            : (T[]) Array.newInstance(newType.getComponentType(), newLength);
        
        //原来为空数组,0,新数组,0,最后一个参数
        //从源数组拷贝多少个数据到目标数组,求:原数组实际长度 和 新数组长度的 最小值
        System.arraycopy(original, 0, copy, 0,
                         Math.min(original.length, newLength));
        return copy;
    }
  • 返回到 elementData[size++] = e; 实际元素赋值
    public boolean add(E e) {
        //确认 容量 够不够。size 是int,首次默认为 0,+1 后为1
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        
        //注意:这是 后++,先赋值给 数组,在 ++。即:维护size 和 实际长度的大小是一样的。
        //所以:除了首次 扩容为10,之后:size+1 = minCapacity 不可能超过,size的1.5倍。
        elementData[size++] = e;//执行后,内容1,添加到 数组的 第0个元素。
        return true;
    }
  • add方法,每次都会判断 扩容,效率比较低
调试注意点
//注意,注意,注意,Idea 默认情况下,Debug 显示的数据是简化后的,如果希望看到完整的数据,需要设置
  • 设置——构建,执行,部署——调试器——数据视图——Java——启用集合类的替代视图 取消打钩
指定大小的构造器
  • 构造参数传递为 8
    public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];//创建为8的 数组长度
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }
        if (minCapacity - elementData.length > 0) //执行的时候,minCapacity 为1。长度为8。不扩容
            grow(minCapacity);

创建了一个指定大小elementData数组
this.elementData = newObject[capacity]

如果是有参数的构造器,扩容机制
()第一次扩容,就按照elementData的1.5倍扩容.

(2)整个执行的流程还是和前面讲的一样.

Vector源码
  1. Vector底层也是一个对象数组,protected Objectl elementData;
  2. Vector是线程同步的,即线程安全, Vector类的操作方法带有 synchronized
public synchronized E get(int index){
if (index >= elementCount)
	throw new ArraylndexOutOfBoundsException(index);
return elementData(index);
}

4)在开发中,需要线程同步安全时,考虑使用Vector

如果是无参,默认10满后,就按2倍扩容
如果指定大小,则每次直接按2倍扩

    public Vector() {
        this(10);
    }
    public synchronized boolean add(E e) {
        modCount++;
        ensureCapacityHelper(elementCount + 1);
        elementData[elementCount++] = e;
        return true;
    }


    private void ensureCapacityHelper(int minCapacity) {
        // overflow-conscious code
        
        //第一次 最少需要1个,现在 可以提供10个
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
  • 第11次,需要扩容
    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        
        //capacityIncrement 是 0,所以:扩容原来的2倍。
        int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
                                         capacityIncrement : oldCapacity);
        //20 - 11 > 0
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

如果需要的数组大小不够用,就扩容,扩容的算法。扩容2倍。

  • 有参构造器
    public Vector(int initialCapacity) {
        this(initialCapacity, 0);
    }

    public Vector(int initialCapacity, int capacityIncrement) {
        super();
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        this.elementData = new Object[initialCapacity];
        //capacityIncrement 又赋值为 0 了,那这个值什么时候用呢?
        this.capacityIncrement = capacityIncrement;
    }

//就是指定 每次扩容的大小的是时候用
        Vector vector = new Vector(8, 5); //每次扩容 5个长度

LinkedList

  1. LinkedList实现了双向链表和双端队列特点

2)可以添加任意元素(元素可以重复),包括null

3)线程不安全,没有实现同步

  1. LinkedList底层维护了一个双向链表.
  2. LinkedList中维护了两个属性first和last分别指向首节点和尾节点
    3)每个节点(Node对象),里面又维护了prev、next、item三个属性,其中通过prev指向前一个,通过next指向后一个节点。最终实现双向链表.
  • 删除只需要改变:Node对象里, A B C 。需:A next 改为 C,C的 prev 改为 A。B就被 删除了。

4)所以LinkedList的元素的添加和删除,不是通过数组完成的,相对来说效率较高。

模拟双向链表
public class LinkedList01 {
    public static void main(String[] args) {
        //模拟一个简单的双向链表

        Node jack = new Node("jack");
        Node tom = new Node("tom");
        Node hsp = new Node("老韩");

        //连接三个结点,形成双向链表
        //jack -> tom -> hsp
        jack.next = tom;
        tom.next = hsp;
        //hsp -> tom -> jack
        hsp.pre = tom;
        tom.pre = jack;

        Node first = jack;//让first引用指向jack,就是双向链表的头结点
        Node last = hsp; //让last引用指向hsp,就是双向链表的尾结点


        //演示,从头到尾进行遍历
        System.out.println("===从头到尾进行遍历===");
        while (true) {
            if(first == null) {
                break;
            }
            //输出first 信息
            System.out.println(first);
            first = first.next;
        }

        //演示,从尾到头的遍历
        System.out.println("====从尾到头的遍历====");
        while (true) {
            if(last == null) {
                break;
            }
            //输出last 信息
            System.out.println(last);
            last = last.pre;
        }

        //演示链表的添加对象/数据,是多么的方便
        //要求,是在 tom --------- 老韩直接,插入一个对象 smith

        //1. 先创建一个 Node 结点,name 就是 smith
        Node smith = new Node("smith");
        //下面就把 smith 加入到双向链表了
        
        //新节点 下一个 指向
        smith.next = hsp;
        //新节点 上一个 指向
        smith.pre = tom;
        //旧节点:最后一个节点,上一个指向 新节点。
        hsp.pre = smith;
        //旧节点:新节点上一个节点 的 下一个指向,指向为 新节点。
        tom.next = smith;

        //让first 再次指向jack
        first = jack;//让first引用指向jack,就是双向链表的头结点

        System.out.println("===从头到尾进行遍历===");
        while (true) {
            if(first == null) {
                break;
            }
            //输出first 信息
            System.out.println(first);
            first = first.next;
        }

        last = hsp; //让last 重新指向最后一个结点

    }
}

//定义一个Node 类,Node 对象 表示双向链表的一个结点
class Node {
    public Object item; //真正存放数据
    public Node next; //指向后一个结点
    public Node pre; //指向前一个结点
    public Node(Object name) {
        this.item = name;
    }
    public String toString() {
        return "Node name=" + item;
    }
}
源码分析实例
        LinkedList linkedList = new LinkedList();
        linkedList.add(1);
        linkedList.add(2);
        linkedList.add(3);
        System.out.println("linkedList=" + linkedList);

        //演示一个删除结点的
        linkedList.remove(); // 这里默认删除的是第一个结点
        //linkedList.remove(2);

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

        //修改某个结点对象
        linkedList.set(1, 999);
        System.out.println("linkedList=" + linkedList);

        //得到某个结点对象
        //get(1) 是得到双向链表的第二个对象
        Object o = linkedList.get(1);
        System.out.println(o);//999

        //因为LinkedList 是 实现了List接口, 遍历方式
        System.out.println("===LinkeList遍历迭代器====");
        Iterator iterator = linkedList.iterator();
        while (iterator.hasNext()) {
            Object next =  iterator.next();
            System.out.println("next=" + next);

        }

        System.out.println("===LinkeList遍历增强for====");
        for (Object o1 : linkedList) {
            System.out.println("o1=" + o1);
        }
        System.out.println("===LinkeList遍历普通for====");
        for (int i = 0; i < linkedList.size(); i++) {
            System.out.println(linkedList.get(i));
        }

*abbr.*增查改删(create, read (retrieve), update, delete)

retrieve
v.
找回,收回;检索(储存于计算机的信息);(狗等)衔回(物品、猎物);挽救,挽回;回忆,追忆;收绕钓鱼线
n.
(尤指中枪猎物的)找回;收绕钓鱼线;<古> 恢复的可能性;检索
增加的源码
    public LinkedList() {
    }

    public boolean add(E e) {
        linkLast(e);
        return true;
    }
    void linkLast(E e) {
        //l 为 null ,last 为 null
        final Node<E> l = last;
        //创建一个 Node ,前后指向 都是 null
        final Node<E> newNode = new Node<>(l, e, null);
        
        //最后的节点 指向新节点
        last = newNode; //将新的结点,加入到双向链表的最后

        //l 肯定为 null
        if (l == null)
            //首个节点, 也指向 新节点
            first = newNode;
        else//否则就把 新节点 值到 最后一个(原来 last节点的.next)。
            l.next = newNode;
        size++;
        modCount++;
    }
  • 添加第二个元素 2 的时候。四步如下:
    • l = last,last为 原来的 最后一个。
    • 新节点的 pre 指向了 l
    • last = newNode,last的指向 变成了 新节点。
    • 原来节点的 最后一个 l.next = new Node; 的 next 指向 新节点。
void linkLast(E e) {
        //l 指向 最后一个 节点 (就是 上次添加 元素1)
        final Node<E> l = last;  //1
        //创建一个 Node ,前面的指向 为 l。相当于下面这个新节点的 pre ,指向了 上个内容1 的节点。
        final Node<E> newNode = new Node<>(l, e, null);  //2
        
        //最后的节点 指向新节点。原来 last 的指向 断掉。
        last = newNode; //将新的结点,加入到双向链表的最后		3

        //l 肯定 不为null,l是 上个内容1
        if (l == null)
            first = newNode;
        else
            //l 的 下一个节点,指向了 新节点。 
            l.next = newNode;		//4 。到此完成了 链表操作的 4补。
        size++;
        modCount++;
    }
  • 操作后:
    • first 依然指向 第一个节点。
    • last 指向 最后一个节点(最新的节点)
    • 第一个节点的 next ,指向了最新的节点
    • 最新的节点的 pre,指向了 第一个节点。
删除
  • 删除首节点 (默认)

    • 调用的是:removeFirst()
  • 根据节点索引删除 麻烦

  • 根据节点的内容删除 玛法

        linkedList.remove(); // 这里默认删除的是第一个结点
    public E remove() {
        return removeFirst();
    }

    public E removeFirst() {
        //f节点 指向头
        final Node<E> f = first;
        if (f == null)
            throw new NoSuchElementException();
        return unlinkFirst(f);
    }
    private E unlinkFirst(Node<E> f) {
        // assert f == first && f != null;
        final E element = f.item;//内容 取出。
        
        //next 就是 第二个节点 了 (f是头)
        final Node<E> next = f.next;	//1
        
        //内容置空
        f.item = null;
        //首节点的 下一个指向 置空,帮助 GC
        f.next = null; // help GC	//2
        //首节点 直接指向 第二个节点。  //3
        first = next;
        
        //next 是 原来的 第二个节点(现在的首节点),肯定不为空。
        if (next == null)
            last = null;
        else
            //把 现在的首节点,前一个指向 ,置空  //4
            next.prev = null;
        size--;
        modCount++;
        return element;
    }
  1. next = f.next ,取出 原来的 第二个节点(现在作为 第一个节点)。

  2. 原来的 首节点 的 next 指向 断开。 f.next = null; 重要

  3. first 首节点指针,指向第二个节点 first = next; 重要

  4. 原来的第二个 节点的 prev = null (现在变为 首节点了) 重要

比较和选择

ArrayList

  • 可变数组
  • 较低,数组扩容 增删的效率
  • 较高 改查的概率

LinkedList

  • 双向链表
  • 较高,通过链表追加. 增删的效率
  • 较低 改查的概率

1)如果我们改查的操作多,选择ArrayList

2)如果我们增删的操作多,选择LinkedList

3)一般来说,在程序中,80%-90%都是查询,
因此大部分情况下会选择ArrayList

4)在一个项目中,根据业务灵活选择,也可能这样,一个模块使用的是ArrayList,另
外一个模块是LinkedList.

Set

1)无序(添加和取出的顺序不一致),没有索引[后面演示]

2)不允许重复元素,所以最多包含一个null

  1. JDK API中Set接口的实现类有:

AbstractSet ,

ConcurrentHashMap.KeySetView ,

ConcurrentSkipListSet ,

CopyOnWriteArraySet ,

EnumSet

Hashset ,

JobstateReasons ,

LinkedHashset ,

TreeSet

同Collection的遍历方式一样,因为Set接口是Collection接口的子接口。

1.可以使用迭代器
2.增强for
3.不能使用索引的方式 遍历 (没有提供get(x)方法)。

常用方法

        //1. 以Set 接口的实现类 HashSet 来讲解Set 接口的方法
        //2. set 接口的实现类的对象(Set接口对象), 不能存放重复的元素, 可以添加一个null
        //3. set 接口对象存放数据是无序(即添加的顺序和取出的顺序不一致)
        //4. 注意:取出的顺序的顺序虽然不是添加的顺序,但是他的固定.
        Set set = new HashSet();
        set.add("john");
        set.add("lucy");
        set.add("john");//重复
        set.add("jack");
        set.add("hsp");
        set.add("mary");
        set.add(null);//
        set.add(null);//再次添加null
        for(int i = 0; i <10;i ++) {
            System.out.println("set=" + set);
        }

        //遍历
        //方式1: 使用迭代器
        System.out.println("=====使用迭代器====");
        Iterator iterator = set.iterator();
        while (iterator.hasNext()) {
            Object obj =  iterator.next();
            System.out.println("obj=" + obj);

        }

        set.remove(null);

        //方式2: 增强for
        System.out.println("=====增强for====");

        for (Object o : set) {
            System.out.println("o=" + o);
        }

        //set 接口对象,不能通过索引来获取
HashSet
  • 实际是 hashMap。是数组 + 链表+红黑树
    public HashSet() {
        map = new HashMap<>();
    }

3)可以存放null值,但是只能有一个null
4) HashSet不保证元素是有序的,取决于hash后,再确定索引的结果.

5)不能有重复元素/对象.在前面Set 接口使用已经讲过

        HashSet set = new HashSet();

        //说明
        //1. 在执行add方法后,会返回一个boolean值
        //2. 如果添加成功,返回 true, 否则返回false
        //3. 可以通过 remove 指定删除哪个对象
        System.out.println(set.add("john"));//T
        System.out.println(set.add("lucy"));//T
        System.out.println(set.add("john"));//F

        set.remove("john");//根据对象 移除


        set.add("lucy");//添加成功
        set.add("lucy");//加入不了
        set.add(new Dog("tom"));//OK
        set.add(new Dog("tom"));//Ok


        //去看他的源码,即 add 到底发生了什么?=> 底层机制.
        set.add(new String("hsp"));//ok
        set.add(new String("hsp"));//加入不了. //先计算hashCode 后:或者对象相同 或者 equals。String 是直接 用常量池的串 "" 传递的参数。这里是 对象相同的
        System.out.println("set=" + set);
数组链表 模拟

分析HashSet底层是HashMap, HashMap底层是(数组+链表+红黑网)

  • 链表,变成红黑树
  • 链表的数据 到了8个(不是超过),数组(也叫table)的大小,>= 64
    • 如果 table表 没到,table 表 会扩容(2倍)
@SuppressWarnings({"all"})
public class HashSetStructure {
    public static void main(String[] args) {
        //模拟一个HashSet的底层 (HashMap 的底层结构)

        //1. 创建一个数组,数组的类型是 Node[]
        //2. 有些人,直接把 Node[] 数组称为 表
        Node[] table = new Node[16];

        //3. 创建结点
        Node john = new Node("john", null);

        table[2] = john;
        Node jack = new Node("jack", null);
        john.next = jack;// 将jack 结点挂载到john
        Node rose = new Node("Rose", null);
        jack.next = rose;// 将rose 结点挂载到jack

        Node lucy = new Node("lucy", null);
        table[3] = lucy; // 把lucy 放到 table表的索引为3的位置.
        System.out.println("table=" + table);


    }
}
class Node { //结点, 存储数据, 可以指向下一个结点,从而形成链表
    Object item; //存放数据
    Node next; // 指向下一个结点

    public Node(Object item, Node next) {
        this.item = item;
        this.next = next;
    }
}
HashSet源码

HashSet添加元素底层是如何实现(hash()+equals()

1.HashSet底层是HashMap

2。添加一个元素时,先得到hash值-会转成->索引值

3.找到存储数据表table,看这个索引位置是否已经存放的有元素

4。如果没有,直接加入

5.如果有,调用equals比较,如果相同,就放弃添加,如果不相同,则添加到最后

6.在Java8中,如果一条链表的元素个数
到达TREEIFY_THRESHOLD(默认是8),并且table的大小>=
MIN’TREEIFY CAPACITY(默认64),就会进行树化(红黑树)

        HashSet hashSet = new HashSet();
        hashSet.add("java");//到此位置,第1次add分析完毕.
        hashSet.add("php");//到此位置,第2次add分析完毕
        hashSet.add("java");
        System.out.println("set=" + hashSet);
    public HashSet() {
        map = new HashMap<>();
    }

    private static final Object PRESENT = new Object();

    public boolean add(E e) {
        //回看这个 hashMap返回的是 null,hashSet 判断了,返回 true
        return map.put(e, PRESENT)==null;
    }
    public V put(K key, V value) { //key为 "java"
        return putVal(hash(key), key, value, false, true);
    }


    static final int hash(Object key) {
        int h;
        //key如果为 null,就是 0,所以:所有的null值,都放在第一个。
        //key 取 hashCode() ^ h无符号又移动16位。 
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

//hash("java") = 3254803,并不 完全等于 hashCode ,因为无符号右移 16位,得到了一个很小的,又和这个 很小的值 异或了。
//比如下面:774889 ^ 11 = 774882 ,小了 7
//总之 就是为了 避免碰撞。

//注意:这个值,不是 hashCode
符号右移 和 异或 hashCode
  • 2进制 100,为4,右移1为 就是2
  1. 算术右移 >>:低位溢出,符号位不变,并用符号位补溢出的高位
  • 第一位为1,表示 负数
  1. 算术左移 <<: 符号位不变,低位补 0
  2. 逻辑右移也叫无符号右移,运算规则是: 低位溢出,高位补 0

  3. 特别说明:没有 <<< 符号

按位异或^

1代表 真, 0代表假

  • 两位一个为0,一个为1,结果为1,否则为0 (不同为1,相同为0)
  • 111 ^ 101 结果为:010
  • 当 a 和 b 不同时,则结果为 true
// hash源码:   return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);

        Integer i = 1;
        int hashCode = i.hashCode();//1
        int yiHuo = hashCode >>> 16;//0
        int r = hashCode ^ yiHuo;//1 ^ 0 得 1
		//所以:添加1,肯定放在 null后面,各种值得前面。 1 放在了第一个。但是:null放在了 第0个(数组 表)。
        //添加到set后:null 第一个,1 第二个:hashSet.add(1);
        String i = "A";
        int hashCode = i.hashCode();//65
		//就是 把 A 转为了 char 取的 Unicode 表。小a是97,"1" 为 49

    private final char value[];

	//hashCode源码
    public int hashCode() {
        int h = hash;
        if (h == 0 && value.length > 0) {
            //value 就是 [a]
            char val[] = value;

            //h是0,31*0 + 97
            for (int i = 0; i < value.length; i++) {
                h = 31 * h + val[i];
            }
            hash = h;
        }
        return h;
    }

//如果是 张三,张 的 码表,是:24352,h 第一次为24352 (原来为0)。
//再次循环到 三, 31 * 24352 + 三的码表(假如为19977)
//那 hash就是为:754912+19977= 774889

//774889 >>> 16 = 11
//774889 ^ 11 = 774882

1111
8421=8+4+2+1=15

0000001111 这个是15
1111110011 这个是hash,是个很大的数
& 运算,相同才为0,
最大的值,也就是15,如上 例子 为3

HashMap的putVal
evict
v.
驱逐,逐出

Absent
adj.
缺勤的,缺席的;不存在的,缺乏的;心不在焉的,出神的;不与子女一起住的
prep.
<美,正式>缺乏,没有
v.
缺席,离开
putVal
//onlyIfAbsent = false
//evict = true

    transient Node<K,V>[] table;//table 就是HashMap 放Node 节点的 数组 (就是模拟的那个数组)。

	putVal(hash(key), key, value, false, true);


    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[],默认为 null
    	//if 语句表示如果当前table是null,或者大小=0//就是第一次扩容,到16个空间.
        if ((tab = table) == null || (n = tab.length) == 0)
            //交给了 resize 方法
            //tab首次 赋值为 16个长度的数组,所以:n为 16
            n = (tab = resize()).length;
    
    	//15 & hash,赋值给了 i,上面说了:15 & 很大的数,最大为 15。就是最大索引
    			//这里 15 & hash(110969)得出结果为9,所以i=9
    			//p = tab[9] ,因为是 首次,肯定为 null。
    
    	//就是 key 应该 存放在 哪一个 索引位置。
    		//java,计算出 i 就是 table数组的索引位置为3
   			//重复解释:根据key,得到hash去计算该key应该存放到table表的哪个索引位置,
        	//并把这个位置的对象,赋给p
    	
        //(2)判断p是否为null,首次那必然为null啊。
		//(2.1)如果p为null,表示还没有存放元素,
        //就创建一个Node (key="java" , value=PRESENT)

        if ((p = tab[i = (n - 1) & hash]) == null)
            
            //hash也存进去,将来会 比较。 同一个 表(数组)里面的 同一个索引 的 hash 相等否。就是:真实 算出的位置一样,hash也一样,才是 相同的,不会再次存储。 (老师说的是 向后面怼,应该错了)
            //就 放入该 位置
            tab[i] = newNode(hash, key, value, null);//null就是后面还没有节点。
        else {
            Node<K,V> e; K k;
           。。。。。 看下面的分析即可
        }
    	//修改的数量 ++
        ++modCount;
    	//看看 大小是不是超过了 12
        if (++size > threshold)
            resize();
    	//留给子类的,如 LinkedHash。就是留的 钩子方法
        afterNodeInsertion(evict);
    	//返回空,代表成功
        return null;
    }

    void afterNodeInsertion(boolean evict) { }
resize() 扩容
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16 即:2*2*2*2=16 左移
    static final float DEFAULT_LOAD_FACTOR = 0.75f; //加载因子

final Node<K,V>[] resize() {
    	//第一次 table 为 null
        Node<K,V>[] oldTab = table;
    	//oldTab 也为null,赋值给 oldCap = 0
    	//capacity 能力,才能;容积
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
    
        int oldThr = threshold;//也是0
        int newCap, newThr = 0;
    	
    	//oldCap 为 0 ,进入 else
        if (oldCap > 0) {
            if (oldCap >= MAXIMUM_CAPACITY) {
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY)
                //每次扩大 2倍
                newThr = oldThr << 1; // double threshold
        }
        else if (oldThr > 0) // initial capacity was placed in threshold
            newCap = oldThr;
        else {               // zero initial threshold signifies using defaults
            
            //就是进入到 这里
            //默认为 16
            newCap = DEFAULT_INITIAL_CAPACITY;
            // 0.75 * 16 ,取整。 = 12
           	//12 个 空间,就是 临界值。 就是到了 12 就扩容 (担心 有大量的线程 同时添加)
            //就是 16个座位,老师 看到用了 12个座位,就加 凳子了。
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
        }
        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"})
    		//new 了个 一个 16 的数组
            Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
    	//赋值给 talbe
        table = newTab;
    
    	//oldTab 是 为 null 的直接到 下面 返回的 代码了。
        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;
    }
    public boolean add(E e) {
        //回看这个 hashMap返回的是 null,hashSet 判断了,返回 true
        return map.put(e, PRESENT)==null;
    }
PultVal 多次添加
    
    static final int TREEIFY_THRESHOLD = 8;

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已经存在了,不进入这个
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        
        //添加的是 PHP,所以 i的位置 不存在,会走到这里。
        //第三次,添加的依然是 java,这里 tab[i] 不为 null
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            //tab[i] 不为 null,进入这里
            Node<K,V> e; K k;
            
            //p在上面已经赋过值,p = tab[假如为3],就是这个索引对象。也就是链表的 第一个对象。
            ///如果当前索引位置对应的链表的第一个元素和准备添加的key的hash值一样
            //并且满足 下面的 两个条件的 其一:
            //(1)key 也是相同的,赋值给 k。就是p中的值,和 要放入的值,是 同一个对象。
            	//准备加入的key和 p指向的Node 结点的key 是同一个对象
            //(2)或者 key 不为null,并且 内容是相同的。
            	//注意:这里用到了 equals 方法 (对象 重写equals 两个new 对象,可以是 相同的)。				//两个dog对象,调equals方法之后,相同。
            
            	//p指向的Node 结点的 key 的equals()和准备加入的key比较后相同
            
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                //就不能加入了。对应下面 e!=null 替换成 最新的值
                e = p;
            else if (p instanceof TreeNode)
                //如果 tab[i] 有值了,并且 上面的if没通过,就是 hash 可能不相同 或 下面没满足
                //在判断 p 是不是 红黑树。p就是 tab[i]
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                //p是一个链表,所以就 一个一个的比较。有一个是相同的,就不插入了。都没有 就加入到最后。
                
                //如果table对应索引位置,已经是一个链表,就使用for循环比较
				//(1)依次和该链表的每一个元素比较后,都不相同,则加入到该链表的最后
				//(2)依次和该链表的每一个元素比较过程中,如果有相同情况,就直接break
                
                //注意在把元素添加到链表后,立即判断该链表是否已经达到8个结点
				//就调用treeifyBin()对当前这个链表进行树化(转成红黑树),具体是不是真的 转为输,还会判断数组(表)的长度 >= 64。如果是 < 64 的,依然扩容这个 数组(表)

                
                for (int binCount = 0; ; ++binCount) {//死循环,推出的情况,只有下面 break 两种情况。
                    //e = p.next,指针 移动异步。
                    //这里就 不和 p 比较了,因为 最上面 已经 比较过了 
                    	//这里:if (p.hash == hash &&...。直接 和 p.next 比较
                    if ((e = p.next) == null) {
                        //走到最后了,就 挂到最后一个。
                        //p.next为空,空指向 新节点
                        p.next = newNode(hash, key, value, null);
                        
                        //添加后,就判断 链表查找的次数是不是 >=7 
                        //(如果当前是7,当前 添加的是 链表的 第8个值)
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            //是否 树化的方法
                            treeifyBin(tab, hash);
                        break;
                    }
                    
                    //e是 下一个节点。 让 e 和 要插入的新的key(结点) 比较
                    //有相同了,就 break
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    
                    //p 指向了 e(下一个节点),就是下次 循环 p是新的了(是 上次的 p.next) 
                    //指针 下移动,每次都指向一个 新的节点。
                    //这两句,相当于 p=p.next
                    p = e;
                }
            }
            
            //e = p;就 不能加入了。
            //这里 处理,返回 oldVlue
            if (e != null) { // existing mapping for key
                //oldValue赋值
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                //不返回空,代表 本次添加失败
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }
    static final int MIN_TREEIFY_CAPACITY = 64;    

//是否 树化 。变成 TreeNode 对象
final void treeifyBin(Node<K,V>[] tab, int hash) {
        int n, index; Node<K,V> e;
    	//表为空,或 小于 < 64,不会进行树 化,继续掉 扩容
        if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
            resize();
        else if ((e = tab[index = (n - 1) & hash]) != null) {
            TreeNode<K,V> hd = null, tl = null;
            do {
                TreeNode<K,V> p = replacementTreeNode(e, null);
                if (tl == null)
                    hd = p;
                else {
                    p.prev = tl;
                    tl.next = p;
                }
                tl = p;
            } while ((e = e.next) != null);
            if ((tab[index] = hd) != null)
                hd.treeify(tab);
        }
    }
HashSet和Map整理

1.先获取元素的哈希值( hashCode方法)

2.对哈希值进行运算,得出一个索引值即为要存放在哈希表中的位置号

3.如果该位置上没有其他元素,则直接存放
如果该位置上已经有其他元素,则需要进行equals判断

  • 如果相等,则不再添加。如果不相等,
  • 则以链表的方式添加。

HashSet的扩容和转成红黑树机制

1.HashSet底层是HashMap,第一次添加
时,table数组扩容到16,临界值(threshold)是16加载因子
(loadFactor)是0.75 = 12

2.如果table数组使用到了临界值12 (超过12),就
会扩容到162= 32,新的临界值就是320.75 = 24,依次类推

3.在Java8中,如果一条链表的元素个数到
达 TREEIFY_THRESHOLD(默认是8),并且table的大小>=
MIN TREEIFY CAPACITY(默认64),就会进行树化(红黑树),否则仍然采用数组扩容机制

放在同一个节点上
@SuppressWarnings({"all"})
public class HashSetIncrement {
    public static void main(String[] args) {
        /*
        HashSet底层是HashMap, 第一次添加时,table 数组扩容到 16,
        临界值(threshold)是 16*加载因子(loadFactor)是0.75 = 12
        如果table 数组使用到了临界值 12,就会扩容到 16 * 2 = 32,
        新的临界值就是 32*0.75 = 24, 依次类推

         */
        HashSet hashSet = new HashSet();
//        for(int i = 1; i <= 100; i++) {
//            hashSet.add(i);//1,2,3,4,5...100
//        }
        /*
        在Java8中, 如果一条链表的元素个数到达 TREEIFY_THRESHOLD(默认是 8 ),
        并且table的大小 >= MIN_TREEIFY_CAPACITY(默认64),就会进行树化(红黑树),
        否则仍然采用数组扩容机制

         */

//        for(int i = 1; i <= 12; i++) {
//            hashSet.add(new A(i));//
//        }


        /*
            当我们向hashset增加一个元素,-> Node -> 加入table , 就算是增加了一个size++

         */

        for(int i = 1; i <= 7; i++) {//在table的某一条链表上添加了 7个A对象
            hashSet.add(new A(i));//
        }

        for(int i = 1; i <= 7; i++) {//在table的另外一条链表上添加了 7个B对象
            hashSet.add(new B(i));//
        }



    }
}

class B {
    private int n;

    public B(int n) {
        this.n = n;
    }
    @Override
    public int hashCode() {
        return 200;
    }
}

class A {
    private int n;

    public A(int n) {
        this.n = n;
    }
    @Override
    public int hashCode() {
        return 100;
    }
}
        if (++size > threshold) //就是我们每加入一个结点Node, 不管加到 table表的 第一个位置,还是 加到 链表上(table表上已经有值了),都会扩容
            resize();

HashSet练习题

重写 hashCode 和 equals

@SuppressWarnings({"all"})
public class HashSetExercise {
    public static void main(String[] args) {


        /**
         定义一个Employee类,该类包含:private成员属性name,age 要求:
         创建3个Employee 对象放入 HashSet中
         当 name和age的值相同时,认为是相同员工, 不能添加到HashSet集合中

         */
        HashSet hashSet = new HashSet();
        hashSet.add(new Employee("milan", 18));//ok
        hashSet.add(new Employee("smith", 28));//ok
        hashSet.add(new Employee("milan", 18));//加入不成功.

        //回答,加入了几个? 3个
        System.out.println("hashSet=" + hashSet);
    }
}

//创建Employee
class Employee {
    private String name;
    private int age;

    public Employee(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Employee{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    //如果name 和 age 值相同,则返回相同的hash值
    @Override
    public boolean equals(Object o) {
        //如果是 同一个对象,返回true
        if (this == o) return true;
        //参数为空,或者 getClass(类型不相同)方法 不相同,返回 true
        if (o == null || getClass() != o.getClass()) return false;
        
        //转为 原来的对象
        Employee employee = (Employee) o;
        
        //age 和 name 相同的 时候,返回 true
        return age == employee.age &&
                Objects.equals(name, employee.name);
    }

    @Override
    public int hashCode() {
        //Object 的 hash方法
        return Objects.hash(name, age);
    }
}


    public final native Class<?> getClass();


    public static int hash(Object... values) {
        return Arrays.hashCode(values);
    }


    public static int hashCode(Object a[]) {
        //null,放在 第0个
        if (a == null)
            return 0;
		
        //默认放在 第一个
        int result = 1;

        //循环 遍历 数组
        for (Object element : a)
            //数组里 为 0,就是0,否则 就是 对象的 HashCode
            //上次结果 * 31 + 这个对象的 hashCode(这里就是 Unicode编码)
            result = 31 * result + (element == null ? 0 : element.hashCode());

        return result;
    }
	
    public native int hashCode();

        Employee9 e = new Employee9("张三", 1000, new MyDate2(2000, 1, 1));
        Employee9 e3 = new Employee9("张三", 1000, new MyDate2(2000, 1, 1));

//如果这样的两个对象, new MyDate2 也要重写 hashCode 和 equals 才能算作 同一个对象。HashSet才不会 重复插入

LinkedHashSet

基本说明

1)LinkedHashSet是 HashSet的子类

  1. LinkedHashSet底层是一个LinkedHashMap,底层维护了一个数组+双
    向链表
    3)LinkedHashSet根据元素的hashCode值来决定元素的存储位置,同时使用链表维护元素的次序(图),这使得元素看起来是以插入顺序保存的。
    4)LinkedHashSet不允许添重复元素
public class LinkedHashSet<E>
    extends HashSet<E>
    implements Set<E>, Cloneable, java.io.Serializable { }

说明
1)在LinkedHastSet中维护了一个hash表和双向链表
(LinkedHashSet有head和tail)

  • 就是 hash表 的每一项 组成的链表,链表的 第一个元素是 头 head,最后一个元素是 tail尾
  • 这是 LinkedHashSet 里面 LinkedHashMap中的 两个属性

123 HSP 假如HSP是最后一个添加的 tail就是 HSP

456

AA 假如AA是第一个添加的 head 就是AA

  • 问题:123 如何找到 HSP呢? LinkedHashSet 里面 LinkedHashMap 里面 table是 HashMap$Node
    • 里面是子类:LinkedHashMap$Entry,每个链表 都有 next 找到下一个 (找到 HSP)。
      • 同理:before 和 after 都是 元素的 上一个下一个连接。

2)每一个节点有before和after属性,这样可以形成双向链表

  • 就是:表里的 每一个项 是一个链表,链表里的 每一个元素,
  • 都有指向 前一个节点 和 后一个节点。这个指向,很可能跨链表。
  • 这是 LinkedHashSet 里面 LinkedHashMap中的 table属性(HashMap$Node)中的 某个元素的 两个属性

3)在添加一个元素时,先求hash值,在求索引.确定该元素在
hashtable的位置,然后将添加的元素加入到双向链表(如果已经存在,不添加[原则和hashset一样])

tail.next = newElement //结尾的下一个 指向新节点
newElement.pre = tail //新节点的前一个,指向结尾
tail = newEelment; //新节点 指向 结尾

4)这样的话,我们遍历LinkedHashSet 也能确保插入顺序和遍
历顺序一致

源码分析

        //分析一下LinkedHashSet的底层机制
        Set set = new LinkedHashSet();
        set.add(new String("AA"));
        set.add(456);
        set.add(456);
        set.add(new Customer("刘", 1001));
        set.add(123);
        set.add("HSP");

        System.out.println("set=" + set);
        //老韩解读
        //1. LinkedHashSet 加入顺序和取出元素/数据的顺序一致
        //2. LinkedHashSet 底层维护的是一个LinkedHashMap(是HashMap的子类)
        //3. LinkedHashSet 底层结构 (数组table+双向链表)

        //4. 添加第一次时,直接将 数组table 扩容到 16 ,存放的结点类型是 LinkedHashMap$Entry
        //5. 数组是 HashMap$Node[] 存放的元素/数据是 LinkedHashMap$Entry类型
        /*
                //继承关系是在内部类完成.
                //静态内部类
                static class Entry<K,V> extends HashMap.Node<K,V> {
                    Entry<K,V> before, after; //就是 这个元素的 上一个 和 下一个 指向
                    Entry(int hash, K key, V value, Node<K,V> next) {
                        super(hash, key, value, next);
                    }
                }

         */

    }
    //hashMap的静态内部类
	static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        V value;
        Node<K,V> next;
 	}
案例
    @Override
    public boolean equals(Object o) {
        //相同返回true
        if (this == o) return true;
        //为null || class不同,返回false
        if (o == null || getClass() != o.getClass()) return false;
        Car car = (Car) o;
        //使用 包装类 的 compare 和 Object的 equals
        return Double.compare(car.price, price) == 0 &&
                Objects.equals(name, car.name);
    }

    @Override
    public int hashCode() {
        //调用 Object的 hash。底层:Arrays.hashCode 就是 每个char的 unicode每次 * 31
        // result = 31 * result + element.hashCode();
        //    public native int hashCode();
        return Objects.hash(name, price);
    }
        LinkedHashSet linkedHashSet = new LinkedHashSet();
        linkedHashSet.add(new Car("奥迪", 300000));//OK
        linkedHashSet.add(new Car("奥迪", 300000));//加入不了
        linkedHashSet.add(new Car("奥迪", 300000));//加入不了
//如果只 重新 hashCode 会放在 table表 的 同一个 Linked上,一直往后 增加元素,都会增加成功。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值