集合(供学习,复习)

发布于:2022-10-14 ⋅ 阅读:(466) ⋅ 点赞:(0)

JAVA集合

集合用来保存多个数据

在前面我们使用数组来保存多个数据,但是数组有一定的不足。

  1. 数组的长度必须指定,而且一旦指定,不能更改

  2. 保存的必须为同一类型的元素

  3. 使用数组进行增加/删除元素的示意代码比较麻烦

  4. 集合可以动态保存任意多个对象,使用比较方便

  5. 提供了一系列方便的操作对象的方法:add,remove,set,get等

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

集合的框架体系

在这里插入图片描述

Collection接口有两个重要子接口List Set ,他们实现的子类都是单列集合(就是单个值)

在这里插入图片描述

Map接口的实现子类 是双列集合,存放的K-V(也就是键值对)

Collection接口方法

public static void main(String[] args) {
    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("jack");//删除指定元素
    list.remove(1);//删除下表为1的元素
    System.out.println("list="+list);

    //contains查找元素是否存在
    System.out.println(list.contains("jack"));
    System.out.println(list.contains(10));

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

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

    //clear 清空
    list.clear();
    System.out.println("list="+list);

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

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

    //removeAll删除多个元素
    list.add("聊斋");
    list.removeAll(list1);
    System.out.println("list="+list);

遍历元素

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

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循环遍历
//        while (iterator.hasNext()){
            返回下一个元素,类型是Object
//            Object object = iterator.next();
//            System.out.println("obj="+object);
//        }

        //快捷键 while=> itit
        while (iterator.hasNext()) {
            Object next =  iterator.next();
            System.out.println(next);
        }

        //3.退出while循环后,这时iterator迭代器指向最后的元素
        //iterator.next();//NoSuchElementException
        //4.如果想要再次遍历,需要重置我们的迭代器
        iterator = col.iterator();
        System.out.println("====第二次遍历====");
        while (iterator.hasNext()){
            Object obj = iterator.next();
            System.out.println("obj="+obj);
        }
    }
}

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循环

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

访问元素

}

System.out.println("====第三次遍历====");
for (Object object:col){//col是被遍历的
    System.out.println(object);
}

List 接口和常用方法

list接口是Collection接口的子

  1. List集合类的元素有序(即添加顺序和取出顺序一致)且可重复
  2. List集合中的每个元素都有其对应的顺序索引,即支持索引
  3. List容器中的元素都对应一个整数型的序号记载其在容器中的位置,可以根据序号存取容器中的元素
  4. JDK API中 List接口的实现类有
 //3.List容器中的元素都对应一个整数型的序号记载其在容器中的位置
    // 可以根据序号存取容器中的元素
    List list = new ArrayList();
    list.add("张三丰");
    list.add("贾宝玉");
    //void add(int index,Object ele);在index的位置插入ele元素
    list.add(1,"吴彦祖");
    System.out.println("list="+list);

    List list1 = new ArrayList();
    list1.add("jack");
    list1.add("tom");
    list.addAll(1,list1);
    System.out.println("list="+list);

    //Object get(int index)

    //int indexOf(int index) 返回obj在当前集合中首次出现的位置
    System.out.println(list.indexOf("tom"));

    //int lastindexOf(Object obj) 返回obj在当前集合中末次出现的位置
    list.add("tom");
    System.out.println(list.lastIndexOf("tom"));

    //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位置的子集合
    //返回为左闭右开[)
    List returnlist = list.subList(0,2);
    System.out.println("returnlist="+returnlist);
}

List的三种遍历方式(ArrayList,LinkedList,Vector)

public static void main(String[] args) {
    List list = new LinkedList();

    list.add("jack");
    list.add("tom");
    list.add("鱼香肉丝");
    list.add("北京烤鸭");

    //1.迭代器
    Iterator iterator = list.iterator();
    while (iterator.hasNext()) {
        Object next =  iterator.next();
        System.out.println(next);
    }

    //2.增强for
    System.out.println("========增强for=========");
    for (Object o:list){
        System.out.println("o="+o);
    }

    //3.普通for
    System.out.println("========普通for=========");
    for (int i=0;i<list.size();i++){
        System.out.println("list="+list.get(i));
    }
}
 //List list = new LinkedList();
       // List list = new ArrayList();
        List list = new Vector();
        list.add(new Book("红楼梦","曹雪芹",100));
        list.add(new Book("西游记","吴承恩",10));
        list.add(new Book("水浒传","施耐庵",19));
        list.add(new Book("三国演义","罗贯中",80));
        list.add(new Book("西游记","吴承恩",10));

        //对集合进行排序
        for (Object object:list){
            System.out.println(object);
        }

        sort(list);

        System.out.println("========排序后==========");
        for (Object object:list){
            System.out.println(object);
        }
    }

    //冒泡排序
    //价格从小到大进行排序
    private 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);
                }
            }
        }
    }

ArrayList的源码分析

  1. ArrayList中维护了一个Object类型的数组elementData

transient Object[] elementData;

  1. 当创建ArrayList对象时,如果使用的是无参构造器,则初始elementData容量为0,第一次添加则扩容elementData为10,如需再次扩容,则扩容elementData为1.5倍
  2. 如果使用的是指定大小得到构造器,则初始elementData容量为指定大小,如需扩容,则直接扩容elememtData为1.5倍

在这里插入图片描述

有参构造与无参构造类似后面遇到elementData数组不够用时再进行扩容

//1.先调用一个有参构造器创建一个指定大小的elementData数组
public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }

Vector底层结构和源码剖析

1.继承了AbstractList,实现了List 接口

public class Vector<E>
    extends AbstractList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable

2.Vector底层也是一个对象数组,protected Object[] elementData;

3.Vector是线程同步的,即线程安全,Vector类操作方法基本都有synchronized

public synchronized E get(int index) {
    if (index >= elementCount)
        throw new ArrayIndexOutOfBoundsException(index);

    return elementData(index);
}

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

//查看vector源码的小案例
public static void main(String[] args) {
        //无参构造器
        //有参构造器
        Vector vector = new Vector();
        //在vector里增加0-9
        for (int i = 0; i < 10; i++) {
            vector.add(i);
        }
        vector.add(100);
        System.out.println("vector="+vector);
    }
//1.无参构造时先分配10个大小空间的数组
public Vector() {
    this(10);
}
//如果是有参构造则分配 initialCapacity个大小空间的数组
public Vector(int initialCapacity) {
        this(initialCapacity, 0);
    }

//2.vector.add()
//2.1调用该方法添加数据到vector集合
 public synchronized boolean add(E e) {
        modCount++;//判断添加元素的次数
        ensureCapacityHelper(elementCount + 1);//确认数组的大小是否足够
        elementData[elementCount++] = e;
        return true;
    }
//上面的add调用该ensureCapacityHelper(int minCapacity)方法
 private void ensureCapacityHelper(int minCapacity) {
        // overflow-conscious code
        if (minCapacity - elementData.length > 0)//当数组容量不足时需要扩容
            grow(minCapacity);
    }
//grow()扩容机制
private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
                                         capacityIncrement : oldCapacity);
    //当创建 Vector vector = new Vector(8,3);时可以设置capacityIncrement为3既增加时可不按默认的2倍增加
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
  • Vector和ArrayList的比较
底层结构 版本 线程安全(同步)效率 扩容倍数
ArrayList 可变数组 Object[] jdk1.2 不安全,效率高 如果有参构造1.5倍,
如果是无参
第一次是10,以后是1.5倍
Vector 可变数组 Object[] jdk1.0 安全,效率不高 如果是无参,就默认10
满后就按2倍扩容
如果指定大小,则每次按2倍扩容

LinkedList底层结构和源码剖析

  1. LinkedList底层实现了双向链表和双端队列的特点
  2. 可以添加任意元素(元素可以重复),包括null
  3. 线程不安全,没有实现同步
  • LinkedList的底层操作机制
  1. LinkedList底层维护了一个双向链表
  2. LinkedList中维护了两个属性first和last分别指向首节点和尾结点
  3. 每个节点(Node对象),里面又维护了prev、next、item三个属性,其中通过prev指向前一个结点,通过next指向后一个结点。最终实现双向链表
  4. 所以LinkedList的元素的添加和删除,不是通过数组完成的,相对来说效率较高

在这里插入图片描述

我们先看一个小案例,了解链表进行增删的方便

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

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

        johny.pre = tom;
        tom.pre = jack;

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

        //从头到尾进行遍历
        while (true){
            if (first==null){
                break;
            }
            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结点,name是smith
        Node smith = new Node("smith");
        //把smith加到双向链表
        smith.next = johny;
        smith.pre = tom;
        johny.pre = smith;
        tom.next = smith;

        //让first再次指向jack
       first = jack;
       last = johny;
        //从头到尾进行遍历
        System.out.println("================");
        while (true){
            if (first==null){
                break;
            }
            System.out.println(first);
            first=first.next;
        }
        //从后往前遍历
        System.out.println("==============");
        while (true){
            if(last==null){
                break;
            }
            System.out.println(last);
            last=last.pre;
        }
    }

}
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实现crud

public static void main(String[] args) {
    LinkedList linkedList = new LinkedList();

    //添加add()
    linkedList.add(1);
    linkedList.add(2);
    linkedList.add(3);
    System.out.println("linkedlist="+linkedList);

    //删除 remove()
    //linkedList.remove();//默认删除第一个结点
    linkedList.remove(2);//删除索引为2的结点
    System.out.println("linkedlist="+linkedList);

    //修改set()
    linkedList.set(1, 9999);
    System.out.println("linkedlist="+linkedList);

    //得到某个节点的对象get()
    Object o = linkedList.get(1);
    System.out.println("o="+o);

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

    System.out.println("LinkedList遍历增强for");
    for (Object o1:linkedList){
        System.out.println("o1="+o1);
    }

    System.out.println("LinkedList遍历普通for");
    for (int i = 0; i < linkedList.size(); i++) {
        System.out.println(linkedList.get(i));
    }

}

源码解读:

//1.LinkedList linkedList = new LinkedList()进入 public LinkedList()
//Constructs an empty list.构造一个空列表
public LinkedList() {
    }
//这时LinkedList的属性first=null last=null

//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);//三个参数分别表示pre,item和next
        last = newNode;
        if (l == null)
            first = newNode;
        else
            l.next = newNode;
        size++;
        modCount++;
    }


//3.执行删除 linkedList.remove();
//无参数时
public E remove() {
        return removeFirst();
    }
 public E removeFirst() {
        final Node<E> f = first;
        if (f == null)
            throw new NoSuchElementException();
        return unlinkFirst(f);
    }
//执行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;
        if (next == null)
            last = null;
        else
            next.prev = null;
        size--;
        modCount++;
        return element;
    }
底层结构 增删的效率 改查的效率
ArrayList 可变数组 较低
涉及到数组扩容
较高
LinkedList 双向链表 较高,通过链表追加 较低

那么我们该如何选择ArrayList和LinkedList 呢 ❓

  1. 如果我们改查操作多,就选择ArrayList
  2. 如果我们增删操作多,选择LinkedList
  3. 一般来说,在程序中,80%~90%都是查询,因此大部分情况下会选择ArrayList
  4. 在一个项目中,根据业务灵活选择,也可能是一个模块使用ArrayList,另一个模块使用LinkedList

Set接口和常用方法

  1. Set接口是无序的(添加和取出的顺序不一致),没有索引
  2. 不允许重复元素,所以最多包含一个null
  3. JDK API中set接口的实现类有

在这里插入图片描述

Set接口的常用方法:

和List接口一样,set接口也是Collection的子接口,因此,常用方法和Collection接口一样

Set接口的遍历方式:

  1. 可以使用迭代器
  2. 增强for
  3. 不能使用索引的方式来获取

Set接口的常用方法举例

@SuppressWarnings({"all"})
public static void main(String[] args) {
    Set set = new HashSet();
    set.add("jack");
    set.add("mike");
    set.add("jack");//重复
    set.add("john");
    set.add("mary");
    set.add(null);
    set.add(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 next = iterator.next();
        System.out.println("next="+next);
    }

    set.remove(null);
    //2.使用增强for
    System.out.println("==========使用增强for============");
    for (Object obj:set){
        System.out.println("obj="+obj);
    }

    //set不能通过索引来访问所以不能通过普通for来遍历

    
}
public static void main(String[] args) {
    Set set = new HashSet();
    //add()会返回TRUE 或FALSE
    System.out.println(set.add("jackson"));
    System.out.println(set.add("mike"));
    System.out.println(set.add("jackson"));
    System.out.println(set.add("Rose"));

    set.remove("mike");
    System.out.println("set="+set);

     set = new HashSet();
    System.out.println("set="+set);

    //HashSet不能添加相同的元素
    set.add("lucky");
    set.add("lucky");//不能添加上
    set.add(new Dog("tom"));
    set.add(new Dog("tom"));//可以添加上

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

    //经典面试题
    set.add(new String("jony"));
    set.add(new String("jony"));//加入不了
    System.out.println("set="+set);
}

HashSet底层机制说明

HashSet的底层是HashMap,HashMap的底层是(数组+链表+红黑树)

源码分析:

//1.Constructs a new, empty set; 
public HashSet() {
    map = new HashMap<>();
}
//2.执行add()
public boolean add(E e) {//e="java"
        return map.put(e, PRESENT)==null; //private static final Object PRESENT = new Object(); value的值不会变且共享
    }
//3.执行put(),该方法会执行 hash(key)得到key对应的hash值 算法 
/*static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);  >>>表示不带符号右移 前面补0即可
    }*/

 public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }
//4.执行putval
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;//定义了辅助变量
    //table就是HashMap的一个数组,类型是Node[]
    //下面表示如果当前的table是null或者大小为0则
    //进行第一次扩容,扩到16个空间
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
    //1.根据key得到hash去计算该key应存放到table的哪个索引位置
    //并且把该位置赋值给p
    //2.判断p是否为null
    //如果p为null,表示还没有创建元素,就创建一个
    //放在该位置 tab[i] = newNode(hash, key, value, null);
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            //一个开发小技巧
            //在需要局部变量(辅助变量)的时候,在创建下面的局部变量时
            Node<K,V> e; K k;
            //如果当前索引位置对应的链表的第一个元素和准备添加key的hash值一样
            //并且满足下面两个条件之一:
            
            //1.准备加入的key和 p指向的Node节点的key 是同一个对象
            //2.p指向的Node节点的key的equals()和准备加入的key比较后相同
            
            //就不能加入
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            
            //再判断p是不是一颗红黑树
            //如果是一颗红黑树就调用putTreeVal,来进行添加  
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                //如果table对应的索引位置已经是一个链表,就用for循环来进行比较
                //依次和链表的每一个元素比较后,都不相同,则加入到链表的最后
                //在把元素添加到链表后应该立即判断,该链表是否已经达到8个结点
                //如果达到8个结点,就调用treeifyBin(tab, hash);对当前这个链表进行树化(转化成红黑树)
                //注意:!!!!!!在转化成红黑树之前要进行判断,判断条件
                //if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY(64))
                   // resize();
                //如果上面条件成立,先对table扩容
                //只有上述条件不成立,才转化成红黑树
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
    //size就是我们加入的结点数
    //如果当前结点数大于缓冲数量(12),则进行扩容
        if (++size > threshold)
// 上面的resize()里面
//  newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); threshold = newThr;
        resize();//扩容
        afterNodeInsertion(evict);//HashMap留给它的子类比如LinkedHashMap去实现对于HashMap来说是一个空方法。
        return null;
    }

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

  1. HashSet的底层是HashMap,第一次添加时,table数组扩容到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),就会对该链表进行树化(红黑树),==否则仍然采用数组扩容机制(2倍扩容)==直到数组容量到达64
public static void main(String[] args) {
        HashSet hashSet = new HashSet();
//        for (int i = 0; i <=100; i++) {
//            hashSet.add(i);//1,2,3...,100
//        }

        for (int i = 0; i <=6; i++) {
            hashSet.add(new A(i));
        }
        for (int i = 0; i <=11; i++) {
            hashSet.add(new A(i));
        }
        for (int i = 0; i <=6; i++) {
            hashSet.add(new B(i));
        }
        System.out.println("hashSet="+hashSet);
    }
}
class A{
    private int n;

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

    @Override
    public int hashCode(){
        return 100;
    }

    @Override
    public String toString() {
        return "A{" +
                "n=" + n +
                '}';
    }
}

class B{
    private int n;

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

    @Override
    public String toString() {
        return "B{" +
                "n=" + n +
                '}';
    }
}

可以通过重写equals和hashCode方法来判断两个对象是否相同来判断是否可以添加到集合中

 public static void main(String[] args) {
        HashSet hashSet = new HashSet();
        hashSet.add(new Employee("xiaoming",18));
        hashSet.add(new Employee("xiaogang",18));
        hashSet.add(new Employee("xiaoming",18));

        System.out.println("HashSet="+hashSet);
    }
}
class Employee{
    private  String name;
    private int age;

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

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

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

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

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

在这里插入图片描述

//Employe和Mydate的euqals和hashcode都需要重写
public class HashSetExercise02 {
    @SuppressWarnings({"all"})
    public static void main(String[] args) {
        HashSet hashSet = new HashSet();
        hashSet.add(new Employe("xiaoming", 12345, new Mydate(2002,01,02)));
        hashSet.add(new Employe("xiaoming", 12345, new Mydate(2002,01,02)));
//        hashSet.add(new Employe("xiaoming", 12345, new Mydate(2002,01,03)));
//        hashSet.add(new Employe("xiaobing", 12345, new Mydate(2002,01,02)));

        System.out.println("hashset="+hashSet);
    }
}
class Employe{
    private String name;
    private int sal;
    private Mydate birthday;

    public Employe(String name,int sal,Mydate birthday){
        this.name=name;
        this.sal=sal;
        this.birthday=birthday;
    }

    public String getName() {
        return name;
    }

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

    public int getSal() {
        return sal;
    }

    public void setSal(int sal) {
        this.sal = sal;
    }

    public Mydate getBirthday() {
        return birthday;
    }

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

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

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Employe)) return false;
        Employe employe = (Employe) o;
        return Objects.equals(name, employe.name) && Objects.equals(birthday, employe.birthday);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, birthday);
    }
}
class Mydate{
    private int year;
    private int month;
    private int day;

    public Mydate(int year, int month, int day) {
        this.year = year;
        this.month = month;
        this.day = day;
    }

    public int getYear() {
        return year;
    }

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

    public int getMonth() {
        return month;
    }

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

    public int getDay() {
        return day;
    }

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

    @Override
    public String toString() {
        return "Mydate{" +
                year +
                "-" + month +
                "-" + day +
                '}';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Mydate)) return false;
        Mydate mydate = (Mydate) o;
        return year == mydate.year && month == mydate.month && day == mydate.day;
    }

    @Override
    public int hashCode() {
        return Objects.hash(year, month, day);
    }
}

Set接口实现类LinkedHashSet

  1. LinkedHashSet是HashSet的子类
  2. LinkedHashSet的底层是一个LinkedHashMap,底层维护了一个数组+双向链表
  3. LinkedHashSet根据元素的hashCode值来确定元素的存储位置,同时使用链表维护元素的次序,使元素看起来是以插入顺序保存的
  4. LinkedHashSet不允许添加重复元素
  public static void main(String[] args) {
        Set set = new java.util.LinkedHashSet();
        set.add(new String("AA"));
        set.add(456);
        set.add(456);
        set.add(new Custom("刘",1001));
        set.add(123);
        set.add("HSP");

        System.out.println("set="+set);
    }
//1.LinkedHashSet 添加第一次时直接将数组table扩容到16,存放的结点类型是LinkedHashMap&Entry
//2.数组是 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);
            }
        }*/
}
class Custom{
    private String name;
    private int num;

    public Custom(String name,int num){
        this.name=name;
        this.num=num;
    }

    public String getName() {
        return name;
    }

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

    public int getNum() {
        return num;
    }

    public void setNum(int num) {
        this.num = num;
    }

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

Map接口和常用方法

Map接口实现类的特点

  1. Map与Collection并列存在,用于保存具有映射关系的数据 Key-Value
  2. Map中的key和value可以是任何引用类型的数据,会封装到HashMap$Node对象中
  3. Map中的key不允许重复,原因和HashSet一样
  4. Map中的value值可以重复
  5. Map中的key可以为null,value也可以为nulll,key为null只可以有一个,而value为null可以有多个
  6. 常用String类作为Map的key
  7. key和value之间存在单向一对一的关系,即通过指定的key总能找到对应的value
public static void main(String[] args) {
    Map map = new HashMap();
    map.put("no1", "小明");
    map.put("no1", "小明");
    map.put("no2", "小红");
    map.put("no3", "小刚");
    map.put("no1", "小强");//具有相同的value就相当于替换
    map.put("no4", "小明");
    map.put(null, null);
    map.put(null,"小绿" );
    map.put("no5", "小兰");
    map.put(1, "小紫");

    //通过get方法传入key可以返回对应的value
    System.out.println(map.get("no2"));

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

    Set set = map.entrySet();
    System.out.println(set.getClass());
}

8.Map存放数据的key-value示意图,一对k-v是放在一个HashMap$Node中的因为Node实现了Entry接口,有些书上也说一对k-v就是一个Entry

在这里插入图片描述

public class MapMethod {
    @SuppressWarnings({"all"})
    public static void main(String[] args) {
        //演示map接口常用方法

        Map map = new HashMap();
        map.put("邓超",new Book("",100));
        map.put("邓超","孙俪");
        map.put("王宝强","马蓉");
        map.put("宋喆","马蓉");
        map.put("彭于晏",null);
        map.put(null,"刘亦菲");
        map.put("鹿晗","关晓彤");
        map.put("hsp","hsp老婆");

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

        //remove根据键删除映射关系
        map.remove(null);
        System.out.println("map="+map);

        //get根据键获取值
        Object val = map.get("鹿晗");
        System.out.println("val="+val);

        //size获取元素个数
        System.out.println("k-v"+map.size());

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

        //clear:清除k-v
        map.clear();

        //containsKey 查找键是否存在
        System.out.println("结果="+map.containsKey("hsp"));
    }
}
class Book{
    private String name;
    private int num;

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

Map接口的遍历方法

1.containsKey:查找键是否存在

2.keySet:获取所有的键

3.entrySet:获取所有的k-v

4.values:获取所有的值

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

        Map map = new HashMap();
        map.put("邓超","孙俪");
        map.put("王宝强","马蓉");
        map.put("宋喆","马蓉");
        map.put("彭于晏",null);
        map.put(null,"刘亦菲");
        map.put("鹿晗","关晓彤");
        map.put("hsp","hsp老婆");

        //第一组 先取出所有的key ,通过key取出对应的value
        Set keyset = map.keySet();

        //1.增强for
        System.out.println("=========第一种方式===========");
        for (Object key:keyset){
            System.out.println(key+"-"+map.get(key));
        }
       //2.迭代器
        System.out.println("=========第二种方式===========");
        Iterator iterator = keyset.iterator();
        while (iterator.hasNext()) {
            Object next =  iterator.next();
            System.out.println(next+"-"+map.get(next));
        }

        //第二组 把所有的values取出
        Collection values = map.values();
        //这里可以使用Collections使用的遍历方法
        //1.增强for
        System.out.println("======取出所有value的增强for========");
        for (Object value:values){
            System.out.println(value);
        }
        //2.迭代器
        System.out.println("======取出所有value的迭代器========");
        Iterator iterator1 =values.iterator();
        while (iterator1.hasNext()) {
            Object next =  iterator1.next();
            System.out.println(next+"-"+map.get(next));
        }

        //第三组:通过EntrySet来获取k-v
        Set entryset=map.entrySet();//    Set<Map.Entry<K, V>> entrySet();
        //1.增强for
        System.out.println("====使用entryset的增强for===");
        for (Object entry:entryset){
            //将entry转成Map.Entry
            Map.Entry m = (Map.Entry) entry;
            System.out.println(m.getKey()+"-"+m.getValue());
        }
        //2.迭代器
        System.out.println("====使用entryset的迭代器===");
        Iterator iterator2 = entryset.iterator();
        while (iterator2.hasNext()) {
            Object entry = iterator2.next();
            //System.out.println(entry.getClass());class java.util.HashMap$Node
            //向下转型Map.Entry
            Map.Entry m = (Map.Entry) entry;
            System.out.println(m.getKey()+"-"+m.getValue());
        }
    }
}

小案例:

使用HashMap添加3个员工对象

要求:键:员工id 值:员工对象

遍历显示工资>18000的员工(遍历方式最少两种)

public class MapExercise01 {
    @SuppressWarnings({"all"})
    public static void main(String[] args) {
        Map map = new HashMap();
        map.put(1,new Emp("小明",12323,1));
        map.put(2,new Emp("小红",18001,2));
        map.put(3,new Emp("小兰",156354,3));

        //通过值 使用增强for
        Set keyset = map.keySet();

        System.out.println("=====增强for=======");
        for (Object key:keyset){
            Emp emp = (Emp)map.get(key);
           if (emp.getSalary()>18000){
               System.out.println(emp);
           }
        }
        
        //通过值 使用迭代器
        Set keySet = map.keySet();
        System.out.println("===========迭代器=========");
        Iterator iterator = keySet.iterator();
        while (iterator.hasNext()){
//            Map.Entry entry = (Map.Entry)iterator.next();
            //通过entry和value得到key和value
           Object next = iterator.next();
            Emp emp = (Emp) map.get(next);
            if (emp.getSalary()>18000){
                System.out.println(emp);
            }
        }
        
        //通过entrySet 使用迭代器
        Set entrySet = map.entrySet();
        System.out.println("===========迭代器=========");
        Iterator iterator = entrySet.iterator();
        while (iterator.hasNext()){
            Map.Entry entry = (Map.Entry)iterator.next();
            //通过entry和value得到key和value
            Emp emp = (Emp) entry.getValue();
            if (emp.getSalary()>18000){
                System.out.println(emp);
            }
        }
    }
}
class Emp{
    private String name;
    private int salary;
     public int id;

    public Emp(String name, int salary, int id) {
        this.name = name;
        this.salary = salary;
        this.id = id;
    }

    public String getName() {
        return name;
    }

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

    public int getSalary() {
        return salary;
    }

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

    public int getId() {
        return id;
    }

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

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

HashMap底层机制和源码剖析

  1. k-v是一个Node 实现了Map.Entry<K,V>,查看HashMap源码可以看到
  2. jdk7.0的hashmap底层实现是[数组+链表],jdk8.0底层实现是[数组+链表+红黑树]

在这里插入图片描述

  • 扩容机制(与HashMap相同)
  1. HashMap底层维护了Node类型的数组table,默认为null

  2. 当创建对象时,将加载因子(loadfactor)初始化为0.75

  3. 当添加key-va时,通过key的哈希值得到在table的索引,然后判断该所索引处是否有元素

    如果没有元素则直接添加,如果该所引处有元素,继续判断该元素的key和准备加入的key是否相等

    如果相等则直接替换val;如果不相等需要判断是树结构还是链表结构做出相应处理,如果添加时发现容量不足需要扩容

  4. 第一次添加,需要扩容table容量为16,临界值(threshold)为12(16*0.75)

  5. 以后在扩容,则需扩容table容量为原来的2倍,临界值为原来的2倍,依次类推

  6. 在java8中,如果一条链表的元素个数超过TREEIFY_THRESHOLD(默认是8),并且table的大小>=MIN_TREEIFY_CAPICITY(默认是64),就会树化(红黑树)

public class HashMapSource {
    @SuppressWarnings({"all"})
    public static void main(String[] args) {
        HashMap hashMap = new HashMap();
        hashMap.put("java",10);
        hashMap.put("php",10);
        hashMap.put("java",20);
        System.out.println("map="+hashMap);
    }
    //hashmap的源码解读:
    //1.先进入hashmap的构造器,初始化加载因子的的值DEFAULT_LOAD_FACTOR=0.75
    public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
    }
    
    //2.调用put函数
    //调用hash函数:
    /* static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }*/
    
    public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }
    //3.调用putval函数
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)//当tab为null或tab的长度为0时,即初始情况
            n = (tab = resize()).length;//调用resize函数(见下方)n初值被赋为16
        if ((p = tab[i = (n - 1) & hash]) == null)//如果此下标处没有node结点则将该结点放在此处并赋给p
            tab[i] = newNode(hash, key, value, null);
        else {//当下标处有node节点时
            Node<K,V> e; K k;
            //当哈希值相等  并且 满足现有的结点的key和准备添加的key是同一个对象 或 equals为真
            //就认为不能加入新的value
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            //如果p是TreeNode类型,就调用puttreeval方法插入到红黑树中
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            //如果是链表则挂到链表后面
            else {
                for (int binCount = 0; ; ++binCount) {
                    //如果p的next为空就直接将其插到p后面
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        //如果binCount大于8个则进行树化
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            //如果e不为空则将新的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;//每次增加一个node结点都++这个表示对table进行操作的次数
     //size就是我们加入的结点数
    //如果当前结点数大于缓冲数量(12),则进行扩容
        if (++size > threshold)
            resize();
        // 上面的resize()里面
//  newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); threshold = newThr;
        afterNodeInsertion(evict);
        return null;
    }
}

//
    final Node<K,V>[] resize() {
        Node<K,V>[] oldTab = table;//将table赋值给oldtab
        int oldCap = (oldTab == null) ? 0 : oldTab.length;//如果oldtab为空则oldcap为0
        int oldThr = threshold;//将threshold(临界值)赋值给oldthr
        int newCap, newThr = 0;
        if (oldCap > 0) {//如果oldcap>0
            if (oldCap >= MAXIMUM_CAPACITY) {
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY)
                newThr = oldThr << 1; // double threshold
        }
        else if (oldThr > 0) // initial capacity was placed in threshold
            newCap = oldThr;
        else {               // zero initial threshold signifies using defaults
            //如果oldcap=0就信生成一个newcap大小为16,newthr大小为12的数组
            newCap = DEFAULT_INITIAL_CAPACITY;
            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;//将newthr(12)赋值给threshold
        @SuppressWarnings({"rawtypes","unchecked"})
        Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];//new一个Node容量为16
        table = newTab;
        if (oldTab != null) {
            for (int j = 0; j < oldCap; ++j) {
                Node<K,V> e;
                if ((e = oldTab[j]) != null) {
                    oldTab[j] = null;
                    if (e.next == null)
                        newTab[e.hash & (newCap - 1)] = e;
                    else if (e instanceof TreeNode)
                        ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                    else { // preserve order
                        Node<K,V> loHead = null, loTail = null;
                        Node<K,V> hiHead = null, hiTail = null;
                        Node<K,V> next;
                        do {
                            next = e.next;
                            if ((e.hash & oldCap) == 0) {
                                if (loTail == null)
                                    loHead = e;
                                else
                                    loTail.next = e;
                                loTail = e;
                            }
                            else {
                                if (hiTail == null)
                                    hiHead = e;
                                else
                                    hiTail.next = e;
                                hiTail = e;
                            }
                        } while ((e = next) != null);
                        if (loTail != null) {
                            loTail.next = null;
                            newTab[j] = loHead;
                        }
                        if (hiTail != null) {
                            hiTail.next = null;
                            newTab[j + oldCap] = hiHead;
                        }
                    }
                }
            }
        }
        return newTab;
    }

Map接口的实现类HashTable

  1. 存放的元素是键值对:k-v
  2. hashtable的键和值都不能为null,否则会抛出NullPointerException
  3. hashtable使用方法基本上和HashMap一样
  4. hashTable是线程安全的(synchronize),hashMap是线程不安全的

HashTable扩容机制为 <<1+1即*2+1

HashTable源码分析

public static void main(String[] args) {
    Hashtable hashtable = new Hashtable();
    hashtable.put("hello1",1);
    hashtable.put("hello2",1);
    hashtable.put("hello3",1);
    hashtable.put("hello4",1);
    hashtable.put("hello5",1);
    hashtable.put("hello6",1);
    hashtable.put("hello7",1);
    hashtable.put("hello8",1);
    hashtable.put("hello9",1);
    hashtable.put("hello10",1);
    hashtable.put("hello11",1);
    hashtable.put("hello12",1);
    hashtable.put("hello13",1);
}
//1.initialCapacity赋值为11,loadFactor为0.75  
//threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);临界因子为8
public Hashtable() {
    this(11, 0.75f);
}
//进入put方法
    public synchronized V put(K key, V value) {
        // Make sure the value is not null
        if (value == null) {
            throw new NullPointerException();
        }

        // Makes sure the key is not already in the hashtable.
        Entry<?,?> tab[] = table;
        int hash = key.hashCode();
        int index = (hash & 0x7FFFFFFF) % tab.length;
        @SuppressWarnings("unchecked")
        Entry<K,V> entry = (Entry<K,V>)tab[index];
        //如果在下标为index处已有元素并且equals为true则新值替代旧值
        for(; entry != null ; entry = entry.next) {
            if ((entry.hash == hash) && entry.key.equals(key)) {
                V old = entry.value;
                entry.value = value;
                return old;
            }
        }
//如果下标处为空或hash值不相等则调用addEntry方法将其添加到该处
        addEntry(hash, key, value, index);
        return null;
    }

    private void addEntry(int hash, K key, V value, int index) {
        modCount++;

        Entry<?,?> tab[] = table;
        if (count >= threshold) {
            // Rehash the table if the threshold is exceeded
            //当count的值大于threshold的值时调用rehash方法进行扩容
            rehash();

            tab = table;
            hash = key.hashCode();
            index = (hash & 0x7FFFFFFF) % tab.length;
        }

        // Creates the new entry.
        @SuppressWarnings("unchecked")
        Entry<K,V> e = (Entry<K,V>) tab[index];
        tab[index] = new Entry<>(hash, key, value, e);
        //计算当前结点数
        count++;
    }


protected void rehash() {
        int oldCapacity = table.length;
        Entry<?,?>[] oldMap = table;

        // overflow-conscious code
    //新的容量为旧的*2+1
        int newCapacity = (oldCapacity << 1) + 1;
        if (newCapacity - MAX_ARRAY_SIZE > 0) {
            if (oldCapacity == MAX_ARRAY_SIZE)
                // Keep running with MAX_ARRAY_SIZE buckets
                return;
            newCapacity = MAX_ARRAY_SIZE;
        }
        Entry<?,?>[] newMap = new Entry<?,?>[newCapacity];

        modCount++;
    //threshold在newCapacity * loadFactor, MAX_ARRAY_SIZE + 1选取最小值
        threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
        table = newMap;

        for (int i = oldCapacity ; i-- > 0 ;) {
            for (Entry<K,V> old = (Entry<K,V>)oldMap[i] ; old != null ; ) {
                Entry<K,V> e = old;
                old = old.next;

                int index = (e.hash & 0x7FFFFFFF) % newCapacity;
                e.next = (Entry<K,V>)newMap[index];
                newMap[index] = e;
            }
        }
    }

版本 线程安全(同步) 效率 允许null键null值
HashMap 1.2 不安全 可以
Hashtable 1.0 安全 较低 不可以

Map的接口实现类-properties

  1. properties类继承自Hashtable并且实现了Map接口,也是一种键值对的形式来保存数据
  2. 他的使用特点和Hashtable类似
  3. Properties还可以用于xxx.properties类对象并进行读取和修改
  4. 说明:工作后 xxx.properties文件通常作为配置文件,这个知识点在io流有举例
public class Properties_ {
    @SuppressWarnings({"all"})
    public static void main(String[] args) {
        Properties properties = new Properties();
        //1.Properties 继承hashtable
        //2.通过 k-v存放数据 k和v也不能为空
        //增加
        properties.put("john",100);
        properties.put("lucy",100);
        properties.put("lic",100);
        properties.put("lic",88);//值相同,替换

        System.out.println("properties"+properties);

        //通过key获取对应的值
        System.out.println(properties.get("lic"));

        //删除
        properties.remove("lic");
        System.out.println("properties"+properties);
        
        //修改:键相同,值不相同
    }

小结 开发中如何选择集合实现类

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

  1. 先判断存储的类型(一组对象[单列]或一组键值对[双列])

  2. 一组对象[单列]:collection接口

    允许重复:List

    ​ 增删多:LinkedList[底层维护了一个双向链表]

    ​ 改查多:ArrayList[底层维护了一个Object类型的可变数组]

    不允许重复:Set

    ​ 无序:HashSet[底层是HashMap,维护了一个哈希表 即(数组+链表+红黑树)]

    ​ 排序:TreeSet

    ​ 插入顺序和取出顺序一致:LinkedHashSet,维护数组+双向链表

  3. 一组键值对[双列]:Map

    键无序:HashMap[底层是:哈希表 jdk7:数组+链表 ,jdk8:数组+链表+红黑树]

    键排序:TreeMap

    键插入和取出顺序一致:LinkedHashMap

    读取文件 Properties

TreeSet和TreeMap的常用方法

TreeSet的使用

  1. 当使用无参构造器时,创建TreeSet时采用默认排序从小到大
  2. 当使用TreeSet(Comparator comparator):即根据指定的比较器进行排序
public class TreeSet_ {
    @SuppressWarnings({"all"})
    public static void main(String[] args) {
//        TreeSet treeSet = new TreeSet();
       TreeSet treeSet =new TreeSet<>(new Comparator<Object>() {
           @Override
           public int compare(Object o1, Object o2) {
               return ((String)o2).compareTo((String)o1);
           }
       });
        treeSet.add("jack");
        treeSet.add("tom");
        treeSet.add("sp");
        treeSet.add("a");
        System.out.println("treeset="+treeSet);
    }
}
//默认  treeset=[a, jack, sp, tom]
treeset=[tom, sp, jack, a]

自然排序Comaprable的使用

  • 存储学生对象并遍历,创建TreeSet集合使用无参构造方法
  • 要求:按照年龄从小到大排序,年龄相同时,按照姓名的字母顺序排序

学生类 实现Comparable接口,改写compareTo()方法

public class TreeSet_ {
    @SuppressWarnings({"all"})
   public static void main(String[] args) {
     TreeSet<Student> ts = new TreeSet<Student>();
        Student s1 = new Student("tom",20);
        Student s2 = new Student("lucy",21);
        Student s3 = new Student("luna",22);
        Student s4 = new Student("som",20);

        ts.add(s1);
        ts.add(s2);
        ts.add(s3);
        ts.add(s4);

        System.out.println("ts="+ts);
    }
}
public class Student implements Comparable<Student> {
    private String name;
    private int age;

    public Student(){
    }

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

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

    @Override
    public int compareTo(Student s) {
        //return 0 每次比较返回0只添加第一个元素,其余元素会被认为是重复元素不添加
        //return 1 返回正数按照升序存储
        //return -1 返回负数按照降序存储

        //按照年龄从小到大排序
        //当前年龄和传入对象年龄比较
        int num = this.age-s.age;
        //若年龄相同比较姓名
        int res=num==0?this.name.compareTo(s.name):num;
        return res;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
ts=[Student{name='som', age=20}, Student{name='tom', age=20}, Student{name='lucy', age=21}, Student{name='luna', age=22}]

比较器(匿名内部类)排序Comparator的使用

  • 存储学生对象并遍历,创建TreeSet集合使用带参构造方法
  • 要求:按照年龄从小到大排序,年龄相同时,按照姓名的字母顺序排序

学生类-没有实现Comparable接口

public class TreeSet_ {
    @SuppressWarnings({"all"})
   public static void main(String[] args) {
      TreeSet<Student> ts = new TreeSet<Student>(new Comparator<Student>() {
            @Override
            public int compare(Student o1, Student o2) {
                int num = o1.getAge()-o2.getAge();
                //若年龄相同比较姓名
                int res=num==0?o1.getName().compareTo(o2.getName()):num;
                return res;
            }
        });
        Student s1 = new Student("tom",20);
        Student s2 = new Student("lucy",21);
        Student s3 = new Student("luna",22);
        Student s4 = new Student("som",20);

        ts.add(s1);
        ts.add(s2);
        ts.add(s3);
        ts.add(s4);

        System.out.println("ts="+ts);
    }
}

public class Student {
    private String name;
    private int age;

    public Student(){
    }

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

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


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

TreeSet源码

public class TreeSet_ {
    @SuppressWarnings({"all"})
   public static void main(String[] args) {
//      TreeSet treeSet = new TreeSet();
       TreeSet treeSet =new TreeSet<>(new Comparator<Object>() {
           @Override
           public int compare(Object o1, Object o2) {
               return ((String)o2).compareTo((String)o1);
           }
       });
        treeSet.add("jack");
        treeSet.add("tom");
        treeSet.add("sp");
        treeSet.add("a");
        System.out.println("treeset="+treeSet);

    }
}


//1.构造器吧传入的比较器对象赋给了 TreeSet的底层的TreeMap的属性this.comparator
 public TreeSet(Comparator<? super E> comparator) {
        this(new TreeMap<>(comparator));
    }

//2.调用add方法(在TreeSet中)
public boolean add(E e) {
        return m.put(e, PRESENT)==null;
    }
//3.调用put方法
    public V put(K key, V value) {
        Entry<K,V> t = root;
        if (t == null) {
            compare(key, key); // type (and possibly null) check

            root = new Entry<>(key, value, null);
            size = 1;
            modCount++;
            return null;
        }
        int cmp;
        Entry<K,V> parent;
        // split comparator and comparable paths
        Comparator<? super K> cpr = comparator;//将我们的匿名内部类(对象)传给cpr
        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
                    return t.setValue(value);//这里如果cmp为0会改变值但他的值为 static final类型即不改变
            } while (t != null);
        }
        else {
            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;
    }

TreeMap源码

public class TreeMap_ {
    @SuppressWarnings({"all"})
    public static void main(String[] args) {
//        TreeMap treeMap = new TreeMap();
        TreeMap treeMap = new TreeMap(new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                return ((String)o2).compareTo((String)o1);
            }
        });
        treeMap.put("jack","杰克");
        treeMap.put("tom","汤姆");
        treeMap.put("kristina","克里斯提那");
        treeMap.put("smith","史密斯");

        System.out.println("treemap="+treeMap);
    }
}
//1.构造器把实现了Comparator接口的匿名内部类(对象)传给了TreeMap的comparator
public TreeMap(Comparator<? super K> comparator) {
        this.comparator = comparator;
    }
//2.调用put方法
public V put(K key, V value) {
        Entry<K,V> t = root;
        if (t == null) {//第一次添加t为空,把k-v封装到Entry对象,放入root
            compare(key, key); // type (and possibly null) check

            root = new Entry<>(key, value, null);
            size = 1;
            modCount++;
            return null;
        }
        int cmp;
        Entry<K,V> parent;
        // split comparator and comparable paths
        Comparator<? super K> cpr = comparator;
        if (cpr != null) {
            do {//遍历所有的key
                parent = t;
                cmp = cpr.compare(key, t.key);//动态绑定到匿名内部类的位置
                if (cmp < 0)
                    t = t.left;
                else if (cmp > 0)
                    t = t.right;
                else//如果遍历过程中发现准备添加的key和当前已有的key相等则将值替换
                    return t.setValue(value);
            } while (t != null);
        }
        else {
            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;
    }

Collections工具类

  • Collections工具类介绍
    1. Collections是一个操作Set、List和Map等集合的工具类
    2. Collections中提供了一系列静态的方法对集合元素进行排序、查询和修改等操作
  • 排序操作:(均为static方法)
    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处元素进行交换
public class Collections_ {
    @SuppressWarnings({"all"})
    public static void main(String[] args) {
        List list = new ArrayList();
        list.add("tom");
        list.add("smith");
        list.add("king");
        list.add("milan");
//reverse(List):反转List中元素的顺序
        Collections.reverse(list);

        System.out.println("list="+list);
//shuffle(List):对List集合元素进行随机排序
        Collections.shuffle(list);
        System.out.println("list="+list);
//sort(List):根据元素的自然顺序对指定的List集合按升序排列
        Collections.sort(list);
        System.out.println("自然排序后="+list);
//sort(List,Comparator):根据指定的Comparator产生顺序对List集合元素进行排序
        Collections.sort(list, new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                return ((String)o1).length()-((String)o2).length();
            }
        });
        System.out.println("按照字符串长度大小排序="+list);
//swap(List,int,int):将指定list集合中的i处元素和j处元素进行交换
        Collections.swap(list,0,1);
        System.out.println("交换后的情况="+list);
        
    }
}
  • 查找、替换

    1. Object max(Collextion):根据元素的自然顺序,返回给定集合中的最大元素
    2. Object max(Collection,Comparator):根据Comparator指定的顺序,返回集合中的最大元素
    3. Object min(Collection)
    4. Object min(Collection,Comparator)
    5. int frequency(Collection,Object):返回指定集合中指定元素的出现次数
    6. void copy(List dest,List src):将src中的内容复制到dest
    7. bollean replaceAll(List list,Object oldVal,Object newVal):使用新值替换List对象的所有旧值
    //1.Object max(Collextion):根据元素的自然顺序,返回给定集合中的最大元素
            System.out.println("自然顺序的最大元素="+Collections.max(list));
    
    //2.Object max(Collection,Comparator):根据Comparator指定的顺序,返回集合中的最大元素
            //比如返回长度最大的元素
            Object maxObject=Collections.max(list, new Comparator() {
                @Override
                public int compare(Object o1, Object o2) {
                    return ((String)o1).length()-((String)o2).length()>=0?1:-1;
                }
            });
            System.out.println("长度最大的元素="+maxObject);
    
    //3.int frequency(Collection,Object):返回指定集合中指定元素的出现次数
            System.out.println("tom出现的次数="+Collections.frequency(list,"tom"));
    
    //4.void copy(List dest,List src):将src中的内容复制到dest
            for (int i=0;i<list.size();i++){
                        dest.add("");
                    }
            //拷贝
            Collections.copy(dest,list);
            System.out.println("dest="+dest);
    
    //5.bollean replaceAll(List list,Object oldVal,Object newVal):使用新值替换List对象的所有旧值
    //如果list中,有tom就替换成汤姆
     		Collections.replaceAll(list,"tom","汤姆");
            System.out.println("list替换后="+list);
    

元素进行随机排序
Collections.shuffle(list);
System.out.println(“list=”+list);
//sort(List):根据元素的自然顺序对指定的List集合按升序排列
Collections.sort(list);
System.out.println(“自然排序后=”+list);
//sort(List,Comparator):根据指定的Comparator产生顺序对List集合元素进行排序
Collections.sort(list, new Comparator() {
@Override
public int compare(Object o1, Object o2) {
return ((String)o1).length()-((String)o2).length();
}
});
System.out.println(“按照字符串长度大小排序=”+list);
//swap(List,int,int):将指定list集合中的i处元素和j处元素进行交换
Collections.swap(list,0,1);
System.out.println(“交换后的情况=”+list);

}

}


- 查找、替换

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

  ```java
  //1.Object max(Collextion):根据元素的自然顺序,返回给定集合中的最大元素
          System.out.println("自然顺序的最大元素="+Collections.max(list));
  
  //2.Object max(Collection,Comparator):根据Comparator指定的顺序,返回集合中的最大元素
          //比如返回长度最大的元素
          Object maxObject=Collections.max(list, new Comparator() {
              @Override
              public int compare(Object o1, Object o2) {
                  return ((String)o1).length()-((String)o2).length()>=0?1:-1;
              }
          });
          System.out.println("长度最大的元素="+maxObject);
  
  //3.int frequency(Collection,Object):返回指定集合中指定元素的出现次数
          System.out.println("tom出现的次数="+Collections.frequency(list,"tom"));
  
  //4.void copy(List dest,List src):将src中的内容复制到dest
          for (int i=0;i<list.size();i++){
                      dest.add("");
                  }
          //拷贝
          Collections.copy(dest,list);
          System.out.println("dest="+dest);
  
  //5.bollean replaceAll(List list,Object oldVal,Object newVal):使用新值替换List对象的所有旧值
  //如果list中,有tom就替换成汤姆
   		Collections.replaceAll(list,"tom","汤姆");
          System.out.println("list替换后="+list);
本文含有隐藏内容,请 开通VIP 后查看