14、集合:

14、集合:

主要包括:

  1. 集合框架体系
  2. Collection:
    1. List:
      1. ArrayList
      2. LinkedList;
      3. Vector
    2. Set:
      1. HashSet
      2. LinkedHashSet;
      3. TreeSet。
  3. Map:
    1. HashMap
    2. HashTable;
    3. LinkedHashMap;
    4. TreeMap;
    5. Properties。
  4. Collections。

使用不难,难点在于原理,源码,什么时候使用(应用场景)。

集合的理解和好处:

前面我们保存多个数据使用的是数组,但是数组也有许多不足的地方:

  1. 长度开始时必须指定,而且一旦指定,不能更改;
  2. 保存的必须为同一类型的元素;
  3. 使用数组进行增加元素的示意代码——比较麻烦(创建一个新的数组,长度为原数组的长度+1,将原数组拷贝过来,在末尾加入新数组)。

集合:

  1. 可以动态保存任意多个对象,使用比较方便;
  2. 提供了一系列方便的操作对象的方法:add、remove、set、get等;
  3. 使用集合添加,删除新元素的示意代码——简洁明了。

集合的框架体系:

主要分为两大类:

  1. 集合主要是两组(单列集合,双列集合)

Collection(单列集合):

在集合里面放的是单个对象。

有==两个重要 子接口:==List 和 Set,他们实现子类都是单列集合。

Map(双列集合):

在集合中放置的是键值对。

Map 接口实现子类 是双列集合,存放的 K-V。


Collection接口和常用方法:

Collection 接口实现类的特点:

public interface Collection<E> extends Iterable<E>

  1. Collection 实现子类可以存放多个元素,每个元素可以是 Object;
  2. 有些 Collection 的实现类,可以存放重复的元素,有些不可以;
  3. 有些 Collection 的实现类,有些是有序的(List),有些是无序的(Set);
  4. Collection 接口没有直接的实现子类,是通过它的子接口 Set 和 List 来实现的。

Collection 接口常用方法,以实现子类 ArrayList 来演示:

  1. add:添加单个元素;
  2. remove:删除指定元素;
  3. contains:查找元素是否存在;
  4. size:获取元素个数;
  5. isEmpty:判断是否为空;
  6. clear:清空;
  7. addAll:添加多个元素;
  8. containsAll:查找多个元素是否都存在;
  9. removeAll:删除多个元素。
package com.jiangxian.collection_;

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

/**
 * @author JiangXian~
 * @version 1.0
 */
public class CollectionMethod {
    @SuppressWarnings({"all"})
    public static void main(String[] args) {
        List list = new ArrayList();
        // add:添加单个元素
        list.add("jack");
        list.add(10); // 相当于List.add(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"));
        System.out.println(list.contains(true));

        // size:获取元素个数
        System.out.println(list.size());

        // isEmpty:判断是否为空
        System.out.println(list.isEmpty());

        // clear:清空
        list.clear();
        System.out.println("list是否为空:" + list.isEmpty());

        // addAll:添加多个元素
        ArrayList list2 = new ArrayList();
        list2.add("红楼梦");
        list2.add("三国演义");
        list.addAll(list2);
        System.out.println("newlist=" + list);

        // containsAll:查找多个元素是否都存在
        System.out.println(list.containsAll(list2));

        // removeAll:删除多个元素
        list.removeAll(list2);
        System.out.println("list=" + list);
        
    }
}

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

基本介绍:

接口 Iterator——是Collection的父接口

  1. Iterator 对象称为迭代器,主要是用于遍历 Collection 集合中的元素;
  2. 所有实现了 Collection 接口的集合类都有一个 iterator() 方法,用以返回一个实现了Iterator接口的对象,即可以返回一个迭代器;
  3. Iterator 的结构——看一张图;
  4. Iterator 仅用于遍历集合,Iterator 本身并不存放对象。

Iterator接口方法:

  • hasNext():若迭代器中还有元素,返回true。(即判断是否还有下一个元素)
  • next():
    • 指针下移;
    • 将下移以后集合位置上的元素返回;
    • 一开始的指向并不再迭代器内,而是在迭代器外,即一开始为空指向。
  • remove():从底层集合中删除此迭代器返回的最后一个元素(可选操作)。

注意:

​ 在调用.next()方法之前必须调用.hasNext()进行检查,若不调用,且下一条记录无效,直接调用时,会抛出异常。

package com.jiangxian.collection_;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

/**
 * @author JiangXian~
 * @version 1.0
 */
public class CollectionIterator_ {
    public static void main(String[] args) {
        Collection col = new ArrayList();
        col.add(new Book("三国演义","罗贯中",10.1));
        col.add(new Book("小李飞刀","古龙",5.1));
        col.add(new Book("红楼梦","曹雪芹",34.6));

        // System.out.println("col =" + col);
        // 我们希望能够遍历 col 集合:
        // 1. 先得到 col 对应的迭代器
        Iterator iterator = col.iterator();
        // 2. 使用 while 循环 =》快捷键itit
        while(iterator.hasNext()){
            Object obj = iterator.next(); // 向下转型,编译类型为Object,允许类型为 Book
            System.out.println(obj);
        }
        // 3. 当退出 while 循环后,这时iterator 迭代器,指向最后的元素;
        // iterator.next(); //NoSuchElementException
        // 4. 若希望再次遍历迭代器,需要重置我们的迭代器
        // 相当于我们将指针返回到未处理的状态
        iterator = col.iterator();
        while (iterator.hasNext()) {
            Object next =  iterator.next();
            System.out.println(next);

        }


    }
}

class Book{
    private String name;
    private String author;
    private double price;

    public Book(String name, String author, double price) {
        this.name = name;
        this.author = author;
        this.price = price;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAuthor() {
        return author;
    }

    public void setAuthor(String author) {
        this.author = author;
    }

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }

    @Override
    public String toString() {
        return "Book{" +
                "name='" + name + '\'' +
                ", author='" + author + '\'' +
                ", price=" + price +
                '}';
    }
}

Collection 接口遍历元素方式2-使用增强for循环:

非常适合用来遍历容器。

在底层其实仍然是一个迭代器。

可以理解成一个简化版本的迭代器。

快捷方式是输入一个:大写的i——I

package com.jiangxian.collection_;

import java.util.ArrayList;
import java.util.Collection;

/**
 * @author JiangXian~
 * @version 1.0
 */
public class CollectionFor_ {
    public static void main(String[] args) {
        Collection col = new ArrayList();
        col.add(new Book("三国演义","罗贯中",10.1));
        col.add(new Book("小李飞刀","古龙",5.1));
        col.add(new Book("红楼梦","曹雪芹",34.6));

        // 语法:for(数据类型 对象:要遍历的容器对象){}
        for(Object obj : col){ 
            System.out.println(obj);
        }
    }
}

练习:

package com.jiangxian.collection_;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

/**
 * @author JiangXian~
 * @version 1.0
 */
public class CollectionExercise {
    public static void main(String[] args) {
        Collection col = new ArrayList();
        col.add(new Dog("小白", 3));
        col.add(new Dog("小黄", 4));
        col.add(new Dog("小黑", 5));

        Iterator iterator = col.iterator();
        while(iterator.hasNext()){
            Object obj = iterator.next();
            System.out.println(obj);
        }

        System.out.println("====增强for循环====");
        for(Object obj : col){
            System.out.println(obj);
        }
    }
}

class Dog{
    private String name;
    private int age;
    public Dog(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

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

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}


List 接口(Collection子接口之一) 和 常用方法:

基本介绍:

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

  1. List 集合类中元素有序(即添加顺序和取出顺序一致),且可重复;
  2. List 集合中的每个元素都有其对应的顺序所以,即支持索引(底层是一个数组);
  3. List 容器中的元素都对应一个整数型的序号记载其在容器中的位置,可以根据序号存取容器中的元素;
  4. JDK API 中 List 接口的常用实现类:
    1. ArrayList;数组
    2. LinkedList;链表
    3. Vector。向量
package com.jiangxian.list_;

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

/**
 * @author JiangXian~
 * @version 1.0
 */
public class List_ {
    @SuppressWarnings({"all"})
    public static void main(String[] args) {
        // 1.List 集合类中元素有序(即添加顺序和取出顺序一致),且可重复
        List list = new ArrayList();
        list.add("jack");
        list.add("tom");
        list.add("marry");
        list.add("hsp");
        list.add("tom");
        System.out.println(list);

        // 2. List 集合中的每个元素都有其独赢的顺序索引,支持索引(从0开始)
        System.out.println(list.get(0));
        
    }
}

常用方法:

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):返回 List 中第一次出现 obj 的 index;
  5. int lastIndexOf(Object obj):返回 List 中最后一次出现 obj 的 index;
  6. Object remove(int index):删除指定位置的元素,并返回该元素;
  7. Object set(int index, Object ele):设置指定位置的元素为 ele ,相当于是替换;
  8. List subList(int fromIndex, int toIndex):返回从 fromIndex 到 toIndex-1 位置的子集合(左闭右开)。
package com.jiangxian.list_;

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

/**
 * @author JiangXian~
 * @version 1.0
 */
public class ListMethod_ {
    @SuppressWarnings({"all"})
    public static void main(String[] args) {
        List list = new ArrayList();
        list.add("jack");
        list.add("tom");
        list.add("marry");
        list.add("hsp");
        list.add("tom");
        System.out.println(list);

        // 1. void add(int index, Object ele):在 index 位置插入 ele 元素;
        System.out.println("==================void add==========");
        list.add(0,"刘备");
        System.out.println(list);

        // 2. boolean addAll(int index, Collection eles):从 index 位置开始将eles中的所有元素添加进来;
        System.out.println("=========boolean addAll==========");
        List list1 = new ArrayList();
        list1.add("刘邦");
        list1.add("项羽");
        list.addAll(list1);
        System.out.println(list);

        // 3. Object get(int index):获取指定 index 位置的元素;
        System.out.println("=========Object get==========");
        System.out.println(list.get(0));

        // 4. int indexOf(Object obj):返回 List 中第一次出现 obj 的 index;
        System.out.println("================int indexOf=================");
        System.out.println(list.indexOf("刘邦"));

        // 5. int lastIndexOf(Object obj):返回 List 中最后一次出现 obj 的 index;
        System.out.println("=========int lastIndexOf==========");
        System.out.println(list.lastIndexOf("tom"));

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

        // 7. Object set(int index, Object ele):设置指定位置的元素为 ele ,相当于是替换;
        System.out.println("=========Object set==========");
        System.out.println(list.set(0, "曹操")); // 返回的是将被修改的原始元素
        System.out.println(list.get(0));

        // 8. List subList(int fromIndex, int toIndex):返回从 fromIndex 到 toIndex-1 位置的子集合(左闭右开)。
        System.out.println("=========List subList==========");
        System.out.println(list.subList(0, 2));
    }
}

三种遍历方式:

  1. 使用迭代器 iterator;
  2. 使用增强 for 循环;
  3. 使用普通 for 循环。

ArrayList(List的实现类之一):

ArrayList 的注意事项:

  1. permits all elements, including null,即 ArrayList 可以存放所有类型的元素,也包括 null(会被转换为String 类型的"null"),并且可以多个;
  2. ArrayList 底层是由数组来实现存储的;
  3. ArrayList 基本等同于 Vector,除了 ArrayList 是线程不安全(执行效率高),多线程的情况下,不建议使用 ArrayList。

ArrayList 底层结构和源码分析(重点):

  1. ArrayList 中维护了一个 Object 类型的数组 elementData。
    1. transient Object[] elementData;
    2. transient 关键字——表示瞬间,短暂的;表示该属性不会被序列化。
  2. 当创建 ArrayList 对象时,若使用无参构造器,则初始 elementData 容量为 0,第1次添加,则扩容elementData 为10,若需要再次扩容,则扩容 elementData 为1.5倍;
  3. 若使用的是指定大小的构造器,则初始 elementData 容量为指定大小,若需要扩容,则直接扩容 elementData 为1.5倍。

add 以及 扩容机制源码解读:

package com.jiangxian.list_.arrayList_;

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

/**
 * @author JiangXian~
 * @version 1.0
 */

@SuppressWarnings({"all"})
public class ArrayListSource_ {
    public static void main(String[] args) {
        // 使用无参构造器创建 ArrayList对象
        ArrayList list = new ArrayList();
        /*
       	public ArrayList() {
        	this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    	}
    	ctrl + 鼠标左键 DEFAULTCAPACITY_EMPTY_ELEMENTDATA:
    	private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    	我们发现其就是一个空数组
    	所以我们创建了一个空elementData数组
        */
        
        
        // ArrayList list = new ArrayList(8);
        for (int i = 1; i <= 10; i++) {
            list.add(i);
            /*    
            public boolean add(E e) {
        		ensureCapacityInternal(size + 1);  // 确定容量够不够
        		elementData[size++] = e; // 然后再执行赋值操作
        		return true;
    		}
            */
            
            /*
            private void ensureCapacityInternal(int minCapacity) { // minCapacity 初始值为1
        		if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { // 判断传进来的是不是空
            		minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); 
            		// 取最大值为minCapacity
            		// private static final int DEFAULT_CAPACITY = 10;
            		所以最大值为 10,minCapacity 现在的值为10.
        		}
        		// 上面的步骤是为了确定一个真正的 minCapacity,下面才是判断是否需要扩容
        		ensureExplicitCapacity(minCapacity);
    		}
            */
            
            /*
                private void ensureExplicitCapacity(int minCapacity) {
        			modCount++; // modCount记录集合被修改的次数,一开始为0
        			// overflow-conscious code
        			if (minCapacity - elementData.length > 0) 
        			// 若需要的最小容量大于 数组的长度,调用底层的grow
            			grow(minCapacity);
    			}
            */
            
            /*
            private void grow(int minCapacity) {
        		// overflow-conscious code
        		// 将原来数组的长度,存放在OldCapacity中
        		int oldCapacity = elementData.length;
        		
        		// 新容量 = 原始长度加上原始长度/2,即原来的1.5倍
        		int newCapacity = oldCapacity + (oldCapacity >> 1);
        		
        		// 若计算出来的新容量比 minCapacity 小,就仍然使用 minCapacity
        		if (newCapacity - minCapacity < 0)
            		newCapacity = minCapacity;
            		
            	// 若新的容量比 MAX_ARRAY_SIZE 大,使用 hugeCapacity方法
            	// private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
        		if (newCapacity - MAX_ARRAY_SIZE > 0)
            		newCapacity = hugeCapacity(minCapacity);
            		
        		// minCapacity is usually close to size, so this is a win:
        		elementData = Arrays.copyOf(elementData, newCapacity);
    }
            */
           	
        }

        for (int i = 11; i <= 15; i++) {
            list.add(i);
        }

        list.add(100);
        list.add(200);

        for (Object o : list) {
            System.out.println(o);
        }

    }
}

有参构造器就是在构造器的源码有差别,其它没有差别。


Vector(List的实现类之一):

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

Vector 和 ArrayList 的比较:

底层结构版本线程安全(同步)效率扩容倍数
ArrayList可变数组jdk1.2不安全,效率高若时有参构造——1.5倍;若是无参——1.第一次设置为10;2.第二次扩容为1.5倍
Vector可变数组jdk1.0安全,效率不高若是无参,默认10,填满后,按两倍扩容;若指定大小,则每次直接按两倍扩容。

底层结构与源码分析:

package com.jiangxian.list_.vector_;

import java.util.Vector;

/**
 * @author JiangXian~
 * @version 1.0
 */

public class Vector_ {
    public static void main(String[] args) {
        Vector vector = new Vector();
        /*
        public Vector() {
        	this(10); // 使用无参构造器,容量会被设置为10
    	}
        */
        
        /*
        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];
        	this.capacityIncrement = capacityIncrement;
    	}
        */
        

        
        for(int i = 0; i < 10; i++){
            vector.add(i);
        /*
        public synchronized boolean add(E e) {
        	modCount++; // 修改次数,开始为0,在AbstractList中定义
        	ensureCapacityHelper(elementCount + 1); // 确定现在的容量是否足够
        	elementData[elementCount++] = e;
        	return true;
    	}
        */
        
        /*
        private void ensureCapacityHelper(int minCapacity) {
        	// overflow-conscious code
        	if (minCapacity - elementData.length > 0)
            	grow(minCapacity);
    	}
        */
        
        /*
        private void grow(int minCapacity) {
        	// overflow-conscious code
        	int oldCapacity = elementData.length;
        	// capacityIncrement = 0
        	int newCapacity = oldCapacity + ((capacityIncrement > 0) ?capacityIncrement : oldCapacity);
        	if (newCapacity - minCapacity < 0)
            	newCapacity = minCapacity;
        	if (newCapacity - MAX_ARRAY_SIZE > 0)
            	newCapacity = hugeCapacity(minCapacity);
        	elementData = Arrays.copyOf(elementData, newCapacity);
    	}
        */
        }

    }
}


LinkedList(List的实现类之一):

  1. LinkedList 底层实现了双向链表和双端队列特点;
  2. 可以添加任意元素(元素可以重复),包括null;
  3. 线程不安全,没有实现同步。

LinkedList 的底层操作机制:

  1. LinkedList 底层维护了一个双向链表;
  2. LinkedList 中维护了两个属性 first 和 last 分别指向 首节点尾节点
  3. 每个节点(Node对象),里面又维护了 prev、next、item 三个属性,其中通过 prev 指向前一个,通过 next 指向下一个。最终实现双向队列。
  4. 所以 LinkedList 的元素的添加和删除,不是通过数组完成的,相对来说效率较高。

Node 演示:

package com.jiangxian.list_.LinkedList_;

/**
 * @author JiangXian~
 * @version 1.0
 */

public class LinkedList01 {
    public static void main(String[] args) {
        // 模拟一个简单的双向链表
        Node jack = new Node("jack");
        Node queen = new Node("queen");
        Node tom = new Node("tom");

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

        Node first = jack; // 让 first 引用指向 jack,就是双向链表的首节点
        Node last = tom; // 让 last 引用指向 tom,就是双向链表的尾节点

        // 演示,从头到尾的遍历
        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;
            }
            System.out.println(last);
            last = last.pre;
        }

        // 演示链表的添加对象/数据,是多么的方便
        // 1. 先创建一个要添加的 Node
        Node smith = new Node("smith");
        queen.next = smith;
        smith.pre = queen;
        smith.next = tom;
        tom.pre = smith;

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

        last = tom;
        System.out.println("=====从尾到头遍历=====");
        while(true){
            if(last == null){
                break;
            }
            System.out.println(last);
            last = last.pre;
        }

    }
}


// 定义一个Node类,Node对象,表示双向链表的一个点
class Node{
    public Object item; // 真正存放数据
    public Node next; // 指向下一个节点
    public Node pre; // 指向前一个节点
    public Node(Object name){
        this.item = name;
    }

    @Override
    public String toString() {
        return item.toString();
    }
}

LinkedList的增删改查:

package com.jiangxian.list_.LinkedList_;

import java.util.LinkedList;

/**
 * @author JiangXian~
 * @version 1.0
 */
@SuppressWarnings({"all"})
public class LinkedListCRUD {
    public static void main(String[] args) {
        LinkedList linkedList = new LinkedList();
        linkedList.add(1);
        /*
        public boolean add(E e) {
        	linkLast(e);
        	return true;
    	}
        */
        
        /*
        将传入的元素添加进双向列表:
        void linkLast(E e) {
        	final Node<E> l = last; // 将原来的last 传递给final修饰的 l
        	final Node<E> newNode = new Node<>(l, e, null); 
        	// 创建一个新的节点,前节点是 last(尾插法),值是传入的e,尾节点是 null
        	last = newNode; // 插入在末尾的元素自然是最后一个元素
        	if (l == null) // 若以前的双向链表中没有元素,即开始的last为空
            	first = newNode; // 那么现在里面只有一个元素,所以头节点和尾节点,都指向新添加的节点
        	else
            	l.next = newNode; // 否则,将l即原始的last的下一个指向修改为新添加的节点
        	size++; // 链表中元素的个数+1
        	modCount++; // 修改次数加一
    	}
        */
        
        linkedList.add(2);
        /*
        public boolean add(E e) {
        	linkLast(e);
        	return true;
    	}
        */
        
        /*
        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++;
    	}
        */
        linkedList.add(3);
        System.out.println("linkedList=" + linkedList);
        
        // 演示删除最后的节点(注意有多种删除,仅演示一种删除first指向的节点)
        linkedList.remove();
        System.out.println("linkedList=" + linkedList);
        /*
        public E remove() {
        	return removeFirst();
    	}
        */
        
        /*
        public E removeFirst() {
     	   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; // 将首节点的值提取出来,作为返回值
        	final Node<E> next = f.next; // 将首节点指向的下一个节点提取出来
        	f.item = null; // 将原始首节点的值设置为空
        	f.next = null; // help GC,将原始首节点的下一个指向设置为空
        	first = next; // 将首节点设置为next
        	if (next == null)
            	last = null;
       	 	else
            	next.prev = null; // 将现在首节点的前指针指向设置为空,不然会指向原来的first
        	size--; // 双向链表的长度减一
        	modCount++; // 修改次数加一
        	return element; // 返回原来节点中的值
    	}
        */
        
        // 修改:
        linkedList.set(1, "abc");
        /*
        public E set(int index, E element) {
            checkElementIndex(index); // 检验是否是合法的index
            Node<E> x = node(index);
            E oldVal = x.item;
            x.item = element;
            return oldVal;
        }
        */
        
        /*
        private void checkElementIndex(int index) {
            if (!isElementIndex(index))
                throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
        }
        */
        
        /*
        private boolean isElementIndex(int index) {
            return index >= 0 && index < size;
        }
        */
        
        /*
        Node<E> node(int index) {
        // assert isElementIndex(index);
            if (index < (size >> 1)) { // 当下标比链表长度的一半小时
                Node<E> x = first; // 设置x为 首节点
                for (int i = 0; i < index; i++) // 遍历要查询的 index 次
                    x = x.next; // 一直更新 x 为 后一个节点
                return x; // 直到x的下标为index位置,返回(链表不是顺序存储所以查找麻烦)
            } else {
            // 否则,反向遍历即可
                Node<E> x = last;
                for (int i = size - 1; i > index; i--)
                    x = x.prev;
                return x;
            }
        }
        */
        System.out.println("linkedList=" + linkedList);
        
        // 查找
        linkedList.get(1);
        /*
        public E get(int index) {
            checkElementIndex(index);
            return node(index).item;
        }
        // 和上面的修改是一样的,自己领悟下。
        */
    }
}

这里的源码涉及到数据结构,没学过数据结构的理解起来可能比较困难。

ArrayList 和 LinkedList 的比较:

底层结构增删的效率改查的效率
ArrayList可变数组较低,数组扩容较高
LinkedList双向链表较高,通过链表节点追加较低
  1. 若改查操作较多,使用ArrayList;
  2. 若增删操作较多,使用LinkedList;
  3. 都是线程不安全。

Set 接口(Collection子接口之一)和常用方法:

Set基本介绍:

  1. 无序(添加和取出元素的顺序不一样,但是取出的顺序是固定的),没有索引
  2. 不允许重复元素,所以最多包含一个null;
  3. JDK API 中 Set 接口的实现类常用的有:
    1. HashSet;
    2. TreeSet。

Set接口的常用方法:

和 List 接口一样,Set 接口也是 Collection 的子接口,所以常用方法和 Collection 接口一样。

Set接口的常用遍历方法:

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

  1. 可以使用迭代器iterator;
  2. 可以使用增强for循环;
  3. 不能使用索引方式来获取
package com.jiangxian.set_;

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

/**
 * @author JiangXian~
 * @version 1.0
 */

@SuppressWarnings({"all"})
public class SetMethod {
    public static void main(String[] args) {
        // 1. 以Set接口的实现类 HashSet 来讲解 Set 接口的方法;
        // 2. Set接口的实现类的对象(Set 接口对象),不能存放重复的元素,可以添加一个 null
        // 3. Set接口对象存放数据是无序(即添加的顺序和取出的顺序不一致)
        // 4. 注意:取出的顺序虽然不是添加的顺序,但是顺序是固定的
        Set set = new HashSet();
        set.add("jack");
        set.add("john");
        set.add("jack"); // 重复
        set.add("john");
        set.add("hsp");
        set.add("mary");
        set.add(null);
        set.add(null);
        System.out.println("set=" + set);
        // set=[null, hsp, mary, john, jack]

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

        // 方式2 :增强for循环:
        System.out.println("========增强for循环=======");
        for(Object o : set){
            System.out.println("o=" + o);
        }

    }
}


HashSet(Set的实现类之一):

HashSet的全面说明:

  1. HashSet 实现了 Set 接口;

  2. HashSet 实际上是 HashMap,源码如下:

        public HashSet() {
            map = new HashMap<>();
        }
    
        public HashMap() {
            this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
        }
    
  3. 可以存放 null 值,但是只能有一个 null;

  4. HashSet 不保证元素是有序的,取决于 hash 后,再确定索引的结果。(即不能保证存放元素的顺序和取出顺序一致);

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

底层机制说明:

模拟数组链表:

对结构的了解。

package com.jiangxian.set_;

/**
 * @author JiangXian~
 * @version 1.0
 */

@SuppressWarnings({"all"})
public class HashSetStructure {
    public static void main(String[] args) {
        // 模拟一个 HashSet 底层——实际上就是 HashMap 底层
        // 1. 创建一个数组,数组的类型是 Node[]
        // 2. 有些人直接将 Node[] 数组称为表(Table)
        Node[] table = new Node[16];
        System.out.println("table = " + table);
        // 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;
        System.out.println("table = " + table);
    }
}

class Node{
    public Object item;
    public Node next;
    public Node(Object item, Node next) {
        this.item = item;
        this.next = next;
    }
}

源码:

分析HashSet 的添加元素底层是如何实现的。

  1. HashSet 的底层是 HashMap;
  2. 添加一个元素时,先得到 hash 值,会转换成索引值。
  3. 找到存储数据表 table,看这个索引位置是否已经存放有元素;
  4. 若没有,直接加入;
  5. 若有,调用 equals 进行比较,若相同,放弃添加,若不同,则添加到最后(说的是链表的最后,参照上面的数组lian’bi);
  6. 再Java8中,若一条链表的元素个数超过 TREEIFY_THRESHOLD(默认时8),并且table 的大小 >= MIN_TREEIFY_CAPACITY(默认64),就会进化成红黑树
第一次添加与第二次添加源码解读:
package com.jiangxian.set_;

import java.util.HashSet;

/**
 * @author JiangXian~
 * @version 1.0
 */

@SuppressWarnings({"all"})
public class HashSetSource {
    public static void main(String[] args) {
        HashSet hashSet = new HashSet();
        // 1. 执行HashSet()
        /*
        public HashSet() { // 构造函数,底层是HashMap
            map = new HashMap<>();
        }
        */
        
        hashSet.add("java"); // 到此处,第一次 add 添加完成。
        // 2. 执行 add()
        /*
        public boolean add(E e) { // e就是我们传入的对象,此处为java,E表示泛型。
            return map.put(e, PRESENT)==null; 
            // private static final Object PRESENT = new Object();
        }
        */
        
        /*
        public V put(K key, V value) {
            return putVal(hash(key), key, value, false, true);
        }
        */
        
        // 使用 force step into 强行进入hash(key),这个是用来计算hash值的
        /*
        static final int hash(Object key) {
            int h;
            // 可以看到,hash并不完全是hashCode,而是经过算法优化的结果
            return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
        }
        */
        
        /*
        // 是最重要的,需要仔细体会
        final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {
        	// 这一行,是我们定义的辅助变量,tab为table的复制,p为要对应索引位置处的空间
            // n为tab的容量
            Node<K,V>[] tab; Node<K,V> p; int n, i;
            
            // table是HashMap的一个属性,放Node节点的数据
            //transient Node<K,V>[] table;
            // 由源码可值,一开始为空
            if ((tab = table) == null || (n = tab.length) == 0)
            // 将table赋值给tab,判断是否为空,或者长度是否为0
            	// 这个代码块较长,放置在紧接的代码块中解释。
                n = (tab = resize()).length;
            // 将tab[i = (n - 1) & hash]存储的节点赋值给p,判断是否为空
            if ((p = tab[i = (n - 1) & hash]) == null)
            	// 为空,就直接将新节点填入
                tab[i] = newNode(hash, key, value, null);
            else {
            	// 一个开发技巧提示:在需要局部变量(辅助变量)时候,在需要的时候创建
                Node<K,V> e; K k;
                // p实际上是指向我们当前的索引位置对应的链表的第一个元素
                // 1.若p的hash与我们想要添加的key的hash值相等
                // 2.同时满足 key 和 p的key属性一样或者是key不为空且内容与p的key属性一样
                //   所以我们需要看下对象的equals是否被重写
                // 就不能加入,跳转到if (e != null),return oldValue;
                if (p.hash == hash &&
                    ((k = p.key) == key || (key != null && key.equals(k))))
                    // 那么将 p 赋值给 辅助变量 e
                    e = p;
                
                // 判断 p 是不是一棵红黑树
                // 若是一颗红黑树,就调用 putTreeVal(暂时不展开),来进行添加
                else if (p instanceof TreeNode)
                    e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
                
                // 只剩下一种情况了,就是 p 为一条链表
                // 1.依次去和链表中的各个节点进行比较,若都不相同,遍历到null,将null 赋值为空,
                // 	添加新节点到末尾,若bigCount达到了TREEIFY_THRESHOLD,转换为红黑树
                //  在添加新节点后,立即判断 该链表是否已经达到8个节点,若满足,要对当前链表进行树化
                //	但是在进行树化的时候,还需要一个对table 长度的判断,若小于 64 也不会马上树化
                //  此时会先进行扩容
                // 2.若与链表中的一个节点相同,那么跳出循环
                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为p.next,e中存储的是p.next
                        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();
            // 这个方法现在是空,在LinkedHashSet中实现
            afterNodeInsertion(evict);
            // 添加成功就返回空
            return null;
        }
        */
        hashSet.add("hsp");
    }
}

对resize()的源码解读

final Node<K,V>[] resize() {
    	// oldTab 用于存放原始的 table
        Node<K,V>[] oldTab = table;
    	// oldCap 用于存放原始的通量
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
    	// oldThr 用于存放阈值,用于防止多线程出现拥堵
        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;
    	// 由于是第一次,所以 oldCap = 0,执行这段代码
        else {               // zero initial threshold signifies using defaults
            // static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; 为16
            newCap = DEFAULT_INITIAL_CAPACITY;
            // static final float DEFAULT_LOAD_FACTOR = 0.75f; 默认的缓冲系数
            // 现在 newThr 为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"})
            Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
    	// 将新的 newTab 存放在属性table中
        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;
                        }
                    }
                }
            }
        }
    	// 返回新的newTab
        return newTab;
    }
扩容和转换成红黑树:
  1. HashSet 底层是 HashMap,第一次添加时,table 数组从0扩容到了 16,临界值(threshold)是16*加载因子(loadFactor)——0.75 = 12;
  2. 若 table 数组使用到了临界值 12,就会执行扩容,16 * 2 = 32,那么新的临界值就是 32 * 0.75 = 24
  3. 在Java8中,若一条链表的元素个数达到了 (TREEIFY_THRESHOLD)默认为8,并且 table的大小 >= MIN_TREEIFY_CAPACITY(默认为64),就会进行树化为红黑树,否则仍然采用数组
扩容:

扩容发生在哪里呢?当我们每次加入一个节点,不管节点是加在 table表的空位置,还是在链表上,都会使得++size。

package com.jiangxian.set_;

import java.util.HashSet;

/**
 * @author JiangXian~
 * @version 1.0
 */

@SuppressWarnings({"all"})
public class HashSetIncrement {
    public static void main(String[] args) {
        /*
        1. HashSet 底层是 HashMap,第一次添加时,table 数组从0扩容到了 16,
            临界值(threshold)是16*加载因子(loadFactor)——0.75 = 12;
        2. 若 table 数组使用到了临界值 12,就会执行扩容,16 * 2 = 32,
            那么新的临界值就是 32 * 0.75 = 24
         */

        HashSet hashSet = new HashSet();
        for (int i = 0; i < 100; i++) {
            hashSet.add(i);
        }
    }
}


final Node<K,V>[] resize() {
    	// oldTab 用于存放原始的 table
        Node<K,V>[] oldTab = table;
    	// oldCap 用于存放原始的通量
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
    	// oldThr 用于存放阈值,用于防止多线程出现拥堵
        int oldThr = threshold;
        int newCap, newThr = 0;
    	// oldCap>0说明非第一次执行resize
        if (oldCap > 0) {
            if (oldCap >= MAXIMUM_CAPACITY) { // 一般是不会超过最大容量的,所以我们看下一个else-if
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
            // 若两倍 oldCap 比最大值小,且oldCap 大于等于默认值16
            // 那么新的容量为 oldCap的两倍,代码块中执行 newThr = 2倍的oldThr
            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;
    	// 由于是第一次,所以 oldCap = 0,执行这段代码
        else {               // zero initial threshold signifies using defaults
            // static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; 为16
            newCap = DEFAULT_INITIAL_CAPACITY;
            // static final float DEFAULT_LOAD_FACTOR = 0.75f; 默认的缓冲系数
            // 现在 newThr 为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"})
            Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
    	// 将新的 newTab 存放在属性table中
        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;
                        }
                    }
                }
            }
        }
    	// 返回新的newTab
        return newTab;
    }
转换成红黑树:
package com.jiangxian.set_;

import java.util.HashSet;

/**
 * @author JiangXian~
 * @version 1.0
 */

@SuppressWarnings({"all"})
public class HashSetIncrement {
    public static void main(String[] args) {
        /*
        1. HashSet 底层是 HashMap,第一次添加时,table 数组从0扩容到了 16,
            临界值(threshold)是16*加载因子(loadFactor)——0.75 = 12;
        2. 若 table 数组使用到了临界值 12,就会执行扩容,16 * 2 = 32,
            那么新的临界值就是 32 * 0.75 = 24
         */

        HashSet hashSet = new HashSet();
//        for (int i = 0; i < 100; i++) {
//            hashSet.add(i);
//        }

        /*
        在Java8中,若一条链表的元素个数达到了 (TREEIFY_THRESHOLD)默认为8,
        并且 table的大小 >= MIN_TREEIFY_CAPACITY(默认为64),
        就会进行树化为红黑树,否则仍然采用数组扩容机制,在扩容后需要重写计算hash。
         */
        for (int i = 1; i <= 12; i++) {
            hashSet.add(new A(i));
            // 什么意思呢?
            // 在执行到第9次时,会执行一次resize,从16扩容到32
            // 执行到第10次时,会再执行一次resize,从32扩容到64
            // 执行到11次时,会进行树化(红黑树,在数据结构中再深入)
        }
    }
}

class A{
    private int n;
    public A(int n) {
        this.n = n;
    }

    // 为了确保hashCode一样
    @Override
    public int hashCode() {
        return 100;
    }
}

第一次扩容:索引在4,节点类型为Node,还是链表,此时容量为16:

第二次扩容:索引在4,节点类型为Node,还是链表,此时容量为32:

第三次扩容:索引在36,节点类型为Node,还是链表,此时容量为64:

然后便不再执行扩容,而是将该链表转换为红黑树,节点类型为TreeNode,table的容量仍然时64:

关于红黑树,此处不展开,另外扩容后的hash更改,应该在resize处,但是resize第一次后的源码暂时我也没看懂,所以不写,以免误导。

Exercise:

Exercise1:

定义一个Employee 类,该类包含:private成员属性——name,age;

  1. 创建3个Employee 对象放入 HashSet;
  2. 当 name 和 age 的值相同时,认为是相同员工,不能添加到 HashSet 集合中。
package com.jiangxian.set_;

import java.util.Arrays;
import java.util.HashSet;
import java.util.Objects;

/**
 * @author JiangXian~
 * @version 1.0
 */

@SuppressWarnings({"all"})
public class HashSetExercise01 {
    public static void main(String[] args) {
        HashSet hashSet = new HashSet();
        hashSet.add(new Employee("milan",18,10000));
        hashSet.add(new Employee("smith",18,10000));
        hashSet.add(new Employee("milan",18,20000));

        System.out.println(hashSet);
    }
}

class Employee{
    private String name;
    private int age;
    private double salary;
    public Employee(String name, int age, double salary) {
        this.name = name;
        this.age = age;
        this.salary = salary;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public double getSalary() {
        return salary;
    }

    public void setSalary(double salary) {
        this.salary = salary;
    }

    @Override
    public boolean equals(Object o) {
        // 判断是否相等,首先,判断是否是同一个对象
        if (this == o) return true;
        // 若传入的数据为空,或者类型不一致返回false
        if (o == null || getClass() != o.getClass()) return false;
        // 向下转型
        Employee employee = (Employee) o;
        return age == employee.age  && Objects.equals(name, employee.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);

        // 下面为计算hash的源码
//        public static int hash(Object... values) { 传入的是一个可变参数
//            return Arrays.hashCode(values);
//        }

//        public static int hashCode(Object a[]) { 计算的是一个数组的hash
//            if (a == null)
//                return 0;
//
//            int result = 1;
//
//            for (Object element : a) // 使用增强for循环,将内部的值遍历,并计算到result中
//                result = 31 * result + (element == null ? 0 : element.hashCode());
//
//            return result;
//        }
    }

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

Exercise02:

定义一个Employee类,该类包含:private成员属性——name,sal,birthday(MyData类型——包含属性:year,month,day)

package com.jiangxian.set_;

import java.util.HashSet;
import java.util.Objects;

/**
 * @author JiangXian~
 * @version 1.0
 */

@SuppressWarnings({"all"})
public class HashSetExercise02 {
    public static void main(String[] args) {
        HashSet hashSet = new HashSet();
        hashSet.add(new Employee_("Smith",21,new MyData("12","11","1")));
        hashSet.add(new Employee_("Smith",21,new MyData("12","11","1")));
        hashSet.add(new Employee_("Smith",21,new MyData("12","11","1")));
        System.out.println(hashSet);
    }
}

class Employee_{
    private String name;
    private int age;
    MyData birthday;
    public Employee_(String name, int age, MyData birthday) {
        this.name = name;
        this.age = age;
        this.birthday = birthday;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public MyData getBirthday() {
        return birthday;
    }

    public void setBirthday(MyData birthday) {
        this.birthday = birthday;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Employee_ employee = (Employee_) o;
        return age == employee.age && Objects.equals(name, employee.name) && Objects.equals(birthday, employee.birthday);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age, birthday);
    }

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

class MyData{
    private String year;
    private String month;
    private String day;
    public MyData(String year, String month, String day) {
        this.year = year;
        this.month = month;
        this.day = day;
    }

    public String getYear() {
        return year;
    }

    public void setYear(String year) {
        this.year = year;
    }

    public String getMonth() {
        return month;
    }

    public void setMonth(String month) {
        this.month = month;
    }

    public String getDay() {
        return day;
    }

    public void setDay(String day) {
        this.day = day;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        MyData myData = (MyData) o;
        return Objects.equals(year, myData.year) && Objects.equals(month, myData.month) && Objects.equals(day, myData.day);
    }

    @Override
    public int hashCode() {
        return Objects.hash(year, month, day);
    }
    //[Employee_{name='Smith', age=21, birthday=com.jiangxian.set_.MyData@4554617c}, Employee_{name='Smith', age=21, birthday=com.jiangxian.set_.MyData@74a14482}, Employee_{name='Smith', age=21, birthday=com.jiangxian.set_.MyData@1540e19d}]
    // 这是不重写MyData的hashCode方法的结果,我们发现由于birthday的hashCode不同,即使重写了equals也没有意义


    @Override
    public String toString() {
        return "MyData{" +
                "year='" + year + '\'' +
                ", month='" + month + '\'' +
                ", day='" + day + '\'' +
                '}';
    }
}

若不重写MyData的hashCode(),会导致计算Employee的hash值都是不同的,这是为什么呢?

其实我们上面贴出过源码能很好的解释这一点:

此时我们传进a[]的参数有:name,age 和 MyData;

name是String,age是int类型的hashCode已经被重写过了,所以特定的值返回的hashCode都是一样的;

但是由于我们MyData的hashCode()没有被重写,所以,其每个对象的hashCode的计算按默认来计算,导致不同对象的hashCode一定不同;

所以我们在add时,由于hash值都不同,就直接添加了。

//        public static int hashCode(Object a[]) { 计算的是一个数组的hash
//            if (a == null)
//                return 0;
//
//            int result = 1;
//
//            for (Object element : a) // 使用增强for循环,将内部的值遍历,并计算到result中
//                result = 31 * result + (element == null ? 0 : element.hashCode());
//
//            return result;
//        }

LinkedHashSet(Set的实现类之一):

LinkedHashSet的全面说明:

  1. LinkedHashSet 是 HashSet 的子类;

  2. LinkedHashSet 底层是一个 LinkedHashMap,底层维护了一个 数组 + 链表 + 双链表(怎么理解呢?就是在HashMap的Node基础上,又多了两个指针,用来指向按顺序添加的节点);

  3. LinkedHashSet 根据元素的 hashCode 值来决定元素的存储位置(和HashSet一样),同时使用双向链表维护元素的次序,这使得元素看起来是以插入顺序保存的,实际不然,只是逻辑有序,不是物理有序,物理上的存储和HashSet一摸一样,逻辑的有序是通过多的双链表实现的;

  4. LinkedHashSet 不允许重复元素。

  5. table 为 Node[]的类型,但是内部存放的元素为 Entry(LinkedHashSet的,与后文map中的Entry不是一样的!!!!那个里面的Entry是接口),其为Node的子类,添加了before 和 after 两个指针,用来实现双向链表。

        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);
            }
        }
    
package com.jiangxian.set_;

import java.util.LinkedHashSet;
import java.util.Set;

/**
 * @author JiangXian~
 * @version 1.0
 */

@SuppressWarnings({"all"})
public class LinkedHashSetSource {
    public static void main(String[] args) {
        Set set = new LinkedHashSet();
        /*
        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.Node(hash, key, value, next)
            }
        }
        */
        
        /*
        private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
        // 将新添加的节点置为尾节点tail,然后将双向链表形成
        	// 将tail赋值给last
            LinkedHashMap.Entry<K,V> last = tail;
            // 将传入的节点设置为尾节点 tail
            tail = p;
            if (last == null)
                head = p;
            else { // 形成双向链表
                p.before = last;
                last.after = p;
            }
        }
        */
        set.add(new String("AA"));
        set.add(456);
        set.add(456);
        set.add(123);
        System.out.println("set=" + set);
    }
}

除此以外,大部分的操作都和HashSet一摸一样,不再赘述。


TreeSet(Set的实现类之一):

其可以实现排序,因为内置有一个Comparator 类型的属性。在调用无参构造器时,直接使用的是compareTo,在调用有参构造器时,我们可以重写Comparator中的 compare方法,实现按我们想要的方式排序:

package com.jiangxian.set_;

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

/**
 * @author JiangXian~
 * @version 1.0
 */

@SuppressWarnings({"all"})
public class TreeSet_ {
    public static void main(String[] args) {
        // 1.当我们使用无参构造器,创建TreeSet时,仍然是无序的
        // 2.使用TreeSet 提供的一个构造器,可以传入一个比较器(匿名内部类)
        //      并可以指定排序规则
        // 浅浅看看个源码:
        /*
        1. 构造器将传入的比较器对象,赋给了 TreeSet 的底层的 TreeMap属性 comparator
        public TreeSet(Comparator<? super E> comparator) {
                this(new TreeMap<>(comparator));
            }
        public TreeMap(Comparator<? super K> comparator) {
                this.comparator = comparator;
            }

        2. 执行add操作:
        public boolean add(E e) {
                return m.put(e, PRESENT)==null;
            }

            private transient Entry<K,V> root;

        public V put(K key, V value) {
                Entry<K,V> t = root; // 创建一个t,将root赋值给他,一开始root为null
                if (t == null) {// 当t为空时,即第一次
                    compare(key, key); // type (and possibly null) check
                    // 这个比较只是为了防止key为空,且没有返回值

                    root = new Entry<>(key, value, null);
                    // 将新的Entry存入root(),且此处的Entry不是map.Entry这个接口了,而是一个内部类(与Node类似)
                    size = 1; // 将内部实际存储的数据个数记录为size
                    modCount++; // 修改次数自增
                    return null; // 返回null
                }
                int cmp;
                Entry<K,V> parent;
                // split comparator and comparable paths
                // cpr是我们传入的 comparator对象,若没有,就是空
                Comparator<? super K> cpr = comparator;
                if (cpr != null) { // 当其不为空,即我们使用的是有参构造器
                    do {
                        parent = t;
                        cmp = cpr.compare(key, t.key);
                        if (cmp < 0)
                            t = t.left;
                        else if (cmp > 0)
                            t = t.right;
                        else // 若相等,则添加不进去了,更改value(在set中没有意义)
                            return t.setValue(value);
                    } while (t != null);
                }
                else { // 若使用的是无参构造器,使用compareTo方法
                    if (key == null)
                        throw new NullPointerException();
                    @SuppressWarnings("unchecked")
                        Comparable<? super K> k = (Comparable<? super K>) key;
                    do {
                        parent = t;
                        cmp = k.compareTo(t.key);
                        if (cmp < 0)
                            t = t.left;
                        else if (cmp > 0)
                            t = t.right;
                        else
                            return t.setValue(value);
                    } while (t != null);
                }
                Entry<K,V> e = new Entry<>(key, value, parent);
                if (cmp < 0)
                    parent.left = e;
                else
                    parent.right = e;
                fixAfterInsertion(e);
                size++;
                modCount++;
                return null;
            }
         */
        
        TreeSet treeSet = new TreeSet(new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                return ((String)o1).compareTo((String)o2);
            }
        });

        // 添加元素:
        treeSet.add("jack");
        treeSet.add("tom");
        treeSet.add("smith");
        treeSet.add("a");
        System.out.println(treeSet);



    }
}


Map 接口和常用方法:

Map 接口实现类的特点(很实用):

存放双列k-v;

Set也用k-v,k也是存放我们传入的对象,但是v的值确是确定的,是Present(Object,仅仅用来占位)。

此处仅讲JDK8的Map接口的特点:

  1. Map 和 Collection 是并列存在的,用于保存具有映射关系的数据:Key-Value(键值对);
  2. Map 中的 key 和 value 可以是任何引用类型的数据,其会被封装到 HashMap$Node 对象中,也就是说Map中的键值对(Key-Value)实际上就是存放在 Node 类型中的;
  3. Map 中的 key 不允许重复,所以只能最多有一个空;
  4. Map 中的 value 可以重复,所以可以有多个空;
  5. 我们常用 String 类作为 Map 的 key;
  6. key 和 value 之间存在单向一对一关系,即通过指定的 key 总能找到对应的 value。
  7. Map 存放数据的key-value示意图,一对k-v 是放在 HashMap$Node 中的,又因为 Node 实现了 Entry 接口,有些书上也说一对k-v就是一个Map.Entry。

源码:

Example:
package com.jiangxian.map_.mapsource_;

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

/**
 * @author JiangXian~
 * @version 1.0
 */

@SuppressWarnings({"all"})
public class MapSource {
    public static void main(String[] args) {
        // 1. Map 与 Collection 并列存在,用于保存具有映射关系的数据;
        // 2. Map 中的 key 和 value 可以是任何引用类型的数据,会封装到 HashMap$Node 对象中
        // 3. Map 中的 key 不允许重复,所以只能有一个 null;
        // 4. Map 中的 value 可以冲覅,所以可以有多个 null;
        // 5. 经常使用 String 类作为 Map 的 key
        // 6. key 和 value 之间存在单向一对一关系,即通过指定的 key 总能找到指定的 key 总能找到对应的 value

        Map map = new HashMap();
        map.put("no1","韩顺平"); // 源码在HashSet中看过了。
        map.put("no2","张无忌");
        map.put("no1","张三丰"); // 当有相同的k,就等价于替换,用新的value去替换oldValue。
        System.out.println(map.get("no1"));
        System.out.println("map=" + map);
    }
}

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;
        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;
                }
            }
            
            // 此处会和HashSet的结果不同,为什么呢?我们来分析下:
            // 当e不为空时,说明key有相同的,即有重复的元素
            // HashSet 中的 value 都是一样的,key 也都是一样的,所以没有什么影响,就是不添加
            // 但是Map中的value是有实际意义的,不再是HashSet中起到占位作用的了
            // 它会将新的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;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }
get(key):
public V get(Object key) {
        Node<K,V> e;
        return (e = getNode(hash(key), key)) == null ? null : e.value;
    }
Key-Value的存储:

一对k-v就是放在 HashMap$Node 中的,我们怎么得到这个结论的呢,从putval的源码中有这样一段代码:

if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);

newNode的返回类型是啥呢?

Node<K,V> newNode(int hash, K key, V value, Node<K,V> next) {
        return new Node<>(hash, key, value, next);
    }

可以看到我们存放到tab中的是一个Node类型的节点,那么自然,一对k-v是存放在Node中的啦,那么Node这个内部类是什么样的呢?我们可以看到,Node中有一个接口,Map.Entry。怎么去理解下面的这种图呢,我们会把指向实际存放k-v的Node引用存放进一个Map.Entry类型的Entry中,Entry组成的Set叫做EntrySet,在

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

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

        public final K getKey()        { return key; }
        public final V getValue()      { return value; }
        public final String toString() { return key + "=" + value; }

        public final int hashCode() {
            return Objects.hashCode(key) ^ Objects.hashCode(value);
        }

        public final V setValue(V newValue) {
            V oldValue = value;
            value = newValue;
            return oldValue;
        }

        public final boolean equals(Object o) {
            if (o == this)
                return true;
            if (o instanceof Map.Entry) {
                Map.Entry<?,?> e = (Map.Entry<?,?>)o;
                if (Objects.equals(key, e.getKey()) &&
                    Objects.equals(value, e.getValue()))
                    return true;
            }
            return false;
        }
    }
// 1. k-v 最后是 HashMap$Node node = newNode(hash, key, value, null);
// 2. k-v 为了方便程序员遍历,还会创建 EntrySet(是一个集合)
// 		该集合存放的元素类型是Map.Entry,而一个Entry对象就包含key和value
// 		源码——transient Set<Map.Entry<K,V>> entrySet;
// 		在这段代码中,我们只是让K指向了Node中的key,V指向了Node中的value,
//		只是一个简单的指向,没有存放新的东西,
//		所以当我们迭代EntrySet时,实际上是在迭代HashMap中所有的Node对象
// 3. entrySet 中,定义的类型是 Map.Entry,但实际存放的是 HashMap$Node 的类型,为什么呢,
//	这是因为Node实现了Map.Entry 这个接口,所以运行类型是 Node(编译类型是Map.Entry,运行类型是Node)
// 4. 这样,当把 HashMap$Node 对象的引用存放到 entrySet 就方便我们进行遍历,其提供了两个非常重要的方法
//    getKey和getValue

欸,那我们为什么要这么写呢,Node明明实现类Entry接口,那么肯定有getKey 和 getValue方法呀?

看下源码,我们发现了什么?Node类型是一个默认类型的内部类,所以我们在外部是不能够调用的!

static class HashMap. Node<K, V> implements Map. Entry<K, V>

而Map.Entry呢?

public static interface Map. Entry<K, V>

我们来看一下代码:

package com.jiangxian.map_.mapsource_;

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

/**
 * @author JiangXian~
 * @version 1.0
 */

@SuppressWarnings({"all"})
public class MapSource {
    public static void main(String[] args) {
        // 1. Map 与 Collection 并列存在,用于保存具有映射关系的数据;
        // 2. Map 中的 key 和 value 可以是任何引用类型的数据,会封装到 HashMap$Node 对象中
        // 3. Map 中的 key 不允许重复,所以只能有一个 null;
        // 4. Map 中的 value 可以冲覅,所以可以有多个 null;
        // 5. 经常使用 String 类作为 Map 的 key
        // 6. key 和 value 之间存在单向一对一关系,即通过指定的 key 总能找到指定的 key 总能找到对应的 value

        Map map = new HashMap();
        map.put("no1","韩顺平"); // 源码在HashSet中看过了。
        map.put("no2","张无忌");
        map.put("no1","张三丰"); // 当有相同的k,就等价于替换,用新的value去替换oldValue。
        System.out.println(map.get("no1"));
        System.out.println("map=" + map);

        Set set = map.entrySet();
        for (Object object : set) {
            Map.Entry entry = (Map.Entry) object;
            System.out.println(entry.getKey() + "-" + entry.getValue());
        }

        // keySet就是我们图上的keySet
        Set set1 = map.keySet();
        for (Object object : set1) {
            System.out.println(object );
        }
        // values就是我们图上的values
        Collection collection = map.values();
        for (Object object : collection) {
            System.out.println(object );
        }
    }
}

我们发现了一个小问题,为什么一个接口可以有实例化的对象呢?

这其实是一个理解误区,这不是实例化,只是类型转换(向下转换)

Map.Entry entry = (Map.Entry) object;

所以这段代码仅仅是类型转换,而不是实例化,实例化需要new,是一个创建对象的过程。

Map常用方法:

  1. map.put(Object key,Object value):添加k-v;
  2. map.remove(Object key):根据键删除映射关系;
  3. map.get(Object key):根据键返回值;
  4. map.size():获取元素的个数;
  5. map.clear():清除k-v;
  6. map.containsKey():查找键是否存在;
package com.jiangxian.map_.mapmethod_;

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

/**
 * @author JiangXian~
 * @version 1.0
 */

@SuppressWarnings({"all"})
public class MapMethod {
    public static void main(String[] args) {
        // 演示Map的常用方法:
        System.out.println("========put==========");
        Map map = new HashMap();
        map.put("邓超",new Book("",100));
        map.put("邓超","孙俪");
        map.put("JiangXian",null);
        map.put(null,"JiangXian");
        System.out.println("map=" + map);

        // remove
        System.out.println("========remove========");
        map.remove(null);
        System.out.println("map=" + map);

        // get
        System.out.println("=======get==========");
        System.out.println(map.get("JiangXian"));

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

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

        // containsKey
        System.out.println("=======contains==========");
        System.out.println("contains=" + map.containsKey("邓超"));

        // clear
        System.out.println("========clear=======");
        map.clear();
        System.out.println("map=" + map);
        System.out.println(map.isEmpty());
    }
}

class Book{
    private String name;
    private int num;
    public Book(String name, int num) {
        this.name = name;
        this.num = num;
    }
}

Map接口遍历方式:

  1. 使用keySet——获得key的set后,用get去访问值;
  2. 使用values——直接获得Collection形式的值,遍历即可;
  3. 使用entrySet。
package com.jiangxian.map_.mapmethod_;

import java.util.*;

/**
 * @author JiangXian~
 * @version 1.0
 */

@SuppressWarnings({"all"})
public class MapFor_ {
    public static void main(String[] args) {
        Map map = new HashMap();
        map.put("邓超","孙俪");
        map.put("JiangXian",null);
        map.put(null,"JiangXian");
        map.put("hsp","hsp's wife");

        // 使用HashMap的keySet,获取所有键
        Set set = map.keySet();
        // 1. 使用迭代器:
        System.out.println("======keySet's iterator======");
        Iterator iterator = set.iterator();
        while(iterator.hasNext()){
            Object key = iterator.next();
            System.out.println(key + "-" + map.get(key));
        }
        // 2. 增强for
        System.out.println("======keySet's for======");
        for(Object key : set){
            System.out.println(key + "-" + map.get(key));
        }

        // 使用HashMap的values:
        Collection value = map.values();
        // iterator
        System.out.println("======values's iterator======");
        Iterator iterator1 = value.iterator();
        while(iterator1.hasNext()){
            Object object = iterator1.next();
            System.out.println(object);
        }
        // for
        System.out.println("======values's for======");
        for(Object o : value){
            System.out.println(o);
        }

        // 使用entrySet获取所有关系的k-v
        Set set1 = map.entrySet();
        // iterator
        System.out.println("======entrySet's iterator======");
        Iterator iterator2 = set1.iterator();
        while(iterator2.hasNext()){
            Map.Entry entry = (Map.Entry)iterator2.next();
            System.out.println(entry.getKey() + "-" + entry.getValue());
        }
        // for
        System.out.println("======entrySet's for======");
        for(Object o : set1){
            Map.Entry entry = (Map.Entry)o;
            System.out.println(entry.getKey() + "-" + entry.getValue());
        }
    }
}

Exercise:

package com.jiangxian.map_.exercise;

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

/**
 * @author JiangXian~
 * @version 1.0
 */
@SuppressWarnings({"all"})
public class Exercise {
    public static void main(String[] args) {
        Map map = new HashMap();
        Employee jiangxian = new Employee("Jiangxian", 20000);
        Employee Hsp = new Employee("Hsp", 30000);
        Employee king = new Employee("King", 1000000000);

        map.put(jiangxian.getId(), jiangxian);
        map.put(Hsp.getId(), Hsp);
        map.put(king.getId(), king);
        System.out.println(map);

        Set set = map.keySet();
        // 输出薪水大于20000的,遍历方式1
        for (Object key : set) {
            Employee employee = (Employee) map.get(key);
            if(employee.getSalary() > 20000){
                System.out.println(employee);
            }
        }
        // 遍历方式2
        System.out.println("=======value========");
        Collection value = map.values();
        for(Object o : value){
            Employee employee = (Employee) o;
            if(employee.getSalary() > 20000){
                System.out.println(employee);
            }
        }
        // 遍历方式3:
        Set set1 = map.entrySet();
        System.out.println("=====entrySet======");
        for(Object o : set1){
            Map.Entry entry = (Map.Entry) o;
            Employee employee = (Employee) entry.getValue();
            if(employee.getSalary() > 20000){
                System.out.println(employee);
            }

        }
    }
}

class Employee{
    public static int num;
    private int id;
    private String name;
    private double salary;
    public Employee(String name, double salary){
        this.name = name;
        this.salary = salary;
        this.id = ++num;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public double getSalary() {
        return salary;
    }

    public void setSalary(double salary) {
        this.salary = salary;
    }

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


HashMap(Map接口实现类之一):

HashMap小结:

  1. HashMap 是 Map 接口使用频率最高的实现类;
  2. HashMap 是以 key-val 对的方式来存储数据(实际存放在HashMap$Node中)
  3. key 不能重复,但是value可以重复,允许使用null键和null值;
  4. 若添加相同的key,会用新的value覆盖掉原先旧的value;
  5. 与 HashSet 一样,不能保证映射的顺序,因为底层是 hash表的方式存储的(JDK8的hashMap 底层为数组+链表(+红黑树));
  6. HashMap 没有实现同步,因此是线程不安全的,方法没有做同步互斥的操作,没有synchronized。

底层机制和源码剖析:

扩容机制:

和HashSet相同(因为HashSet实际上创建的就是HashMap):

  1. HashMap 底层维护了Node类型的数组 table,默认为 null;
  2. 当创建对象时,将加载因子(loadFactor)初始化为0.75;
  3. 当添加k-v时,通过key的hash值,得到在 table 的索引位置。然后判断索引位置是否有元素:
    1. 若没有元素,直接添加;
    2. 若有元素,判断该元素的key和我们准备加入的key是否相等:
      1. 若相等,替换原始的value为新的value
      2. 若不相等,需要判断是树结构还是链表结构,做出相应的处理。(若容量不够还需要进行扩容)。
  4. 第一次添加后,table 的 capacity会被设置为默认的16,临界值 threshold 为 12;
  5. 以后每次扩容,都是将原始的 capacity * 2,临界值 threshold 也会变为原来的两倍;
  6. 在 JDK8 中,要满足以下两个条件,那么链表就会进化为树:
    1. 一条链表的长度超过了8;
    2. table 的大小 >= MIN_TREEIFY_CAPACITY(默认64)。
  7. 要是一条链表的长度>=8且还要往上添加节点,但是table的大小没有超过64,那么会执行的是扩容操作,哪怕没有达到阈值。

源码:

已经在 HashSet 和 Map 中分析过了,就不再啰嗦了。


HashTable(Map接口实现类之一):

基本介绍:

  1. 存放的是键值对k-v;
  2. HashTable 的键和值都不能是null,否则会抛出 NullPointerException的异常;
  3. HashTable 使用方法基本和 HashMap一样;
  4. HashTable 是线程安全的(synchronized),HashMap是线程不安全的。

与HashMap的对比:

版本线程安全(同步)效率允许null键和null值
HashMap1.2不安全较高
HashTable1.0安全较低

Properties(Map接口实现类之一):

基本介绍:

  1. Properties类继承自HashTable类并且实现了Map接口,也是一种键值对的形式来保存数据;
  2. 他的使用特点和Hashtable类似;
  3. Properties 还可以用于 从 xxx.properties文件中,加载数据到 Properties类对象,并进行读取和修改;
  4. 说明:工作后,常使用 xxx.properties 文件为配置文件,这个知识点会在IO流举例。

基本使用:

package com.jiangxian.map_;

import java.util.Properties;

/**
 * @author JiangXian~
 * @version 1.0
 */

@SuppressWarnings({"all"})
public class Properties_ {
    public static void main(String[] args) {
        // 1. Properties 继承 Hashtable
        // 2. 可以通过 k-v 存放数据,当然 key 和 value 不能为 null
        Properties properties = new Properties();
//        properties.put("abc",null);
//        properties.put(null,"def");
        properties.put("lucy",100);
        System.out.println("properties = " + properties);
    }
}

基本用法外,在IO流中介绍。


总结——开发中如何选择集合实现类:

在开发中,选择什么集合实现类,主要取决于业务操作特点,然后根据集合实现类特性进行选择,分析如下:

  1. 先判断存储的类型(一组对象[单列] 或 一组键值对[双列])
  2. 一组对象[单列]:Collection
    1. 允许重复:List
      1. 增删多:LinkedList [底层维护了一个双向链表];
      2. 改查多:ArrayList [底层维护 Object 类型的可变数组]。
    2. 不允许重复:Set
      1. 无序:HashSet——数组+链表(+红黑树);
      2. 排序:TreeSet;
      3. 插入和取出顺序一致:LinkedHashSet——数组+链表+双向链表;
  3. 一组键值对[双列]:Map
    1. 键无序:HashMap
    2. 键排序:TreeMap
    3. 键插入和取出顺序一致:LinkedHashMap;
    4. 读取文件:Properties。

Collections 工具类:

Collections 工具类介绍:

  1. 是一个操作 Set、List 和 Map 等集合的工具类;
  2. 提供了一系列静态的方法对集合元素进行排序、查询和修改等操作。

排序操作:

  1. reverse(List):反转 List 中元素的顺序;
  2. shuffle(List):对 List 集合元素进行随机排序;
  3. sort(List):根据元素的自然顺序对指定 List 集合元素按升序排列;
  4. sort(List, Comparator):根据指定 Comparator 产生的顺序对List集合元素进行排序
  5. swap(List, int, int):将指定 List 集合中的 i 处元素和 j 处元素交换。

查找和替换:

  1. Object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素
  2. Object max(Collection, Comparator):根据 Comparator 指定的顺序,返回给定集合中的最大元素
  3. Object min(Collection):根据元素的自然顺序,返回给定集合中的最小元素;
  4. Object min(Collection, Comparator):根据 Comparator 指定的顺序,返回给定集合中的最小元素;
  5. int frequency(Collection, Object):返回指定集合中指定元素出现的次数;
  6. void copy(List dest, List src):将 src 中的内容复制到 dest 中;
  7. boolean replaceAll(List list, Object oldVal, Object newVal):使用新值替换List对象中的所有旧值。
    ity * 2,临界值 threshold 也会变为原来的两倍;
  8. 在 JDK8 中,要满足以下两个条件,那么链表就会进化为树:
    1. 一条链表的长度超过了8;
    2. table 的大小 >= MIN_TREEIFY_CAPACITY(默认64)。
  9. 要是一条链表的长度>=8且还要往上添加节点,但是table的大小没有超过64,那么会执行的是扩容操作,哪怕没有达到阈值。

源码:

已经在 HashSet 和 Map 中分析过了,就不再啰嗦了。


HashTable(Map接口实现类之一):

基本介绍:

  1. 存放的是键值对k-v;
  2. HashTable 的键和值都不能是null,否则会抛出 NullPointerException的异常;
  3. HashTable 使用方法基本和 HashMap一样;
  4. HashTable 是线程安全的(synchronized),HashMap是线程不安全的。

与HashMap的对比:

版本线程安全(同步)效率允许null键和null值
HashMap1.2不安全较高
HashTable1.0安全较低

Properties(Map接口实现类之一):

基本介绍:

  1. Properties类继承自HashTable类并且实现了Map接口,也是一种键值对的形式来保存数据;
  2. 他的使用特点和Hashtable类似;
  3. Properties 还可以用于 从 xxx.properties文件中,加载数据到 Properties类对象,并进行读取和修改;
  4. 说明:工作后,常使用 xxx.properties 文件为配置文件,这个知识点会在IO流举例。

基本使用:

package com.jiangxian.map_;

import java.util.Properties;

/**
 * @author JiangXian~
 * @version 1.0
 */

@SuppressWarnings({"all"})
public class Properties_ {
    public static void main(String[] args) {
        // 1. Properties 继承 Hashtable
        // 2. 可以通过 k-v 存放数据,当然 key 和 value 不能为 null
        Properties properties = new Properties();
//        properties.put("abc",null);
//        properties.put(null,"def");
        properties.put("lucy",100);
        System.out.println("properties = " + properties);
    }
}

基本用法外,在IO流中介绍。


总结——开发中如何选择集合实现类:

在开发中,选择什么集合实现类,主要取决于业务操作特点,然后根据集合实现类特性进行选择,分析如下:

  1. 先判断存储的类型(一组对象[单列] 或 一组键值对[双列])
  2. 一组对象[单列]:Collection
    1. 允许重复:List
      1. 增删多:LinkedList [底层维护了一个双向链表];
      2. 改查多:ArrayList [底层维护 Object 类型的可变数组]。
    2. 不允许重复:Set
      1. 无序:HashSet——数组+链表(+红黑树);
      2. 排序:TreeSet;
      3. 插入和取出顺序一致:LinkedHashSet——数组+链表+双向链表;
  3. 一组键值对[双列]:Map
    1. 键无序:HashMap
    2. 键排序:TreeMap
    3. 键插入和取出顺序一致:LinkedHashMap;
    4. 读取文件:Properties。

Collections 工具类:

Collections 工具类介绍:

  1. 是一个操作 Set、List 和 Map 等集合的工具类;
  2. 提供了一系列静态的方法对集合元素进行排序、查询和修改等操作。

排序操作:

  1. reverse(List):反转 List 中元素的顺序;
  2. shuffle(List):对 List 集合元素进行随机排序;
  3. sort(List):根据元素的自然顺序对指定 List 集合元素按升序排列;
  4. sort(List, Comparator):根据指定 Comparator 产生的顺序对List集合元素进行排序
  5. swap(List, int, int):将指定 List 集合中的 i 处元素和 j 处元素交换。

查找和替换:

  1. Object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素
  2. Object max(Collection, Comparator):根据 Comparator 指定的顺序,返回给定集合中的最大元素
  3. Object min(Collection):根据元素的自然顺序,返回给定集合中的最小元素;
  4. Object min(Collection, Comparator):根据 Comparator 指定的顺序,返回给定集合中的最小元素;
  5. int frequency(Collection, Object):返回指定集合中指定元素出现的次数;
  6. void copy(List dest, List src):将 src 中的内容复制到 dest 中;
  7. boolean replaceAll(List list, Object oldVal, Object newVal):使用新值替换List对象中的所有旧值。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

江弦凤歌

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值