java学习笔记 - 第14章:集合

总体内容

在这里插入图片描述

集合

集合的好处

数组只要初始化了,长度就不能变了,增删都困难
在这里插入图片描述

集合体系图

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
简单演示
在这里插入图片描述

Collection

Collection接口实现类的特点

在这里插入图片描述

Collection接口常用方法

在这里插入图片描述
方法使用说明:
接口(Collection和List)不能被实例化,所以实例化接口的实现类(ArrayList)调用它的方法

List list = new ArrayList();
//这里会自动装箱 int->包装类Integer
//相当于list.add(new Integer(10));
list.add(10);

list.remove(0);//删除第一个元素,返回被删除对象
list.remove("jack");//删除指定元素,返回boolean

list.contains("jack");//返回boolean(是否存在)

//list2是一个存放多个数据的ArrayList,
//containsAll(…)和removeAll(…)也是传入一个集合
list.addAll(list2);

迭代:1. 迭代器遍历

介绍
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
代码演示

步骤:

  1. 先得到 集合col 对应的 迭代器
  2. 使用while循环遍历
  3. 如果希望再次遍历,需要重置迭代器

快捷键:
1. 快速生成while快捷键 => itit
2. 显示所有快捷键 的快捷键 ctrl+j

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

public class Excise {
    @SuppressWarnings({"all"})
    public static void main(String[] args) {
        Collection col = new ArrayList();
        col.add("jack");
        col.add("mary");
        col.add("lina");
        //使用迭代器遍历 集合col
        //1. 先得到 集合col 对应的 迭代器
        Iterator iterator1 = col.iterator();
        Iterator iterator = iterator1;
        //2. 使用while循环遍历
        //      快速生成while快捷键 => itit
        //      显示所有快捷键 的快捷键 ctrl+j
        while (iterator.hasNext()) {
            Object next = iterator.next();
            System.out.println(next);
        }
        //3. 当退出while后,这时iterater迭代器指向最后一个元素,再使用该iterator会报NoSuchElementException
        //4. 如果希望再次遍历,需要重置迭代器
        Iterator iterator2 = col.iterator();
    }
}

迭代:2. 集合增强for

  1. 增强for也可以在数组中使用
  2. 增强for 底层仍然是 迭代器
  3. 增强for快捷键:I(大写I)

在这里插入图片描述

List(Collection)

介绍

在这里插入图片描述

List接口方法

  1. list集合支持索引
  2. subList(int form,int to) 范围是前闭后开

在这里插入图片描述
小练习
在这里插入图片描述

List的三种遍历方式

List的子类:Vector、LinkedList、ArrayList都可以用这三种遍历方法–>可以用下标
在这里插入图片描述
小练习(对集合进行排序)
在这里插入图片描述

先把需要交换的两个数取出来,然后再用set()进行交换

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

@SuppressWarnings({"all"})
public class Excise {
    public static void main(String[] args) {
        List list = new ArrayList();
        list.add(new Book("www", 331, "rrr"));
        list.add(new Book("eee", 551, "ggg"));
        list.add(new Book("ddd", 131, "vvv"));
        System.out.println("=====排序前=====");
        for (Object o : list) {
            System.out.println(o);
        }
        System.out.println("=====排序后=====");
        sortList(list);
        for (Object o : list) {
            System.out.println(o);
        }
    }

    public static void sortList(List list) {
        //为提高效率,把list.size()放在循环外计算
        int listSize = list.size();
        for (int i = 0; i < listSize; i++) {
            for (int j = 0; j < listSize - 1 - i; j++) {
                //先把需要交换的两个数取出来,然后再用set()进行交换
                Book book1 = (Book) list.get(j);
                Book book2 = (Book) list.get(j + 1);
                if (book1.getPrice() > book2.getPrice()) {
                    //交换(集合可以使用set()来指定下标对应的元素)
                    list.set(j, book2);
                    list.set(j + 1, book1);
                }
            }
        }
    }
}

@SuppressWarnings({"all"})
class Book {
    private String name;
    private double price;
    private String author;

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

    public double getPrice() {
        return price;
    }

    @Override
    public String toString() {
        String format = String.format("名称:%s\t价格:%.2f\t\t作者:%s", name, price, author);
        return format;
    }
}

ArrayList

注意事项

在这里插入图片描述

扩充机制及底层源码

在这里插入图片描述无参构造器创建和使用ArrayList的源码
在这里插入图片描述
在这里插入图片描述
(minCapacity是需要的最小容量,elementData.length是数组的实际容量)
在这里插入图片描述
在这里插入图片描述
有参构造器创建和使用ArrayList的源码
在这里插入图片描述

Vector

注意事项

在这里插入图片描述

ArrayList和Vector比较

在这里插入图片描述
Vector的扩容机制可以自己用debug追一下源码,和ArrayList差不多
在这里插入图片描述

LinkedList

介绍

在这里插入图片描述

底层操作机制

在这里插入图片描述

模拟双向链表

①双向链表的形成,
②从头到尾和从尾到头遍历,
③添加数据
添加结点:四条链条的指向
在这里插入图片描述

public class Excise {
    public static void main(String[] args) {
        //模拟一个简单的双向链表 ==> ①双向链表的形成,②从头到尾和从尾到头遍历,③添加数据
        //1. 先定义链表的 三个结点
        Node mary = new Node("mary");
        Node jack = new Node("jack");
        Node tom = new Node("tom");

        //2. 把三个结点联系起来 形成双向链表
        //mary -> jack -> tom
        mary.next = jack;//底层就是:mary的next属性 指向了jack对象
        jack.next = tom;
        //mary <- jack <- tom
        tom.pre = jack;
        jack.pre = mary;

        //定义双向链表的 头节点
        Node first = mary;
        //定义双向链表的 尾结点
        Node last = tom;

//        //3. 从头到尾遍历
//        System.out.println("===从头到尾遍历===");
//        while (true) {
//            if (first == null) {
//                break;
//            }
//            try {
//                System.out.println(first);
//            } catch (Exception e) {
//                System.out.println(e.getMessage());
//            }
//            //改变指向:first指向当前结点的next结点(直到链表最后一个元素时,next为null,first指向了null,跳出循环)
//            first = first.next;
//        }
//
//        //4. 从尾到头遍历
//        System.out.println("===从尾到头遍历===");
//        while (true){
//            if (last==null){
//                break;
//            }
//            System.out.println(last);
//            last=last.pre;
//        }

        //5. 演示链表添加对象/数据
        //在mary 和 jack之间增加 smith
        //只需修改 双向链表之间的关联(修改四个链条)
        Node smith = new Node("smith");
        smith.next = jack;
        smith.pre = mary;
        mary.next = smith;
        jack.pre = smith;

        //从头到尾遍历
        System.out.println("===从头到尾遍历===");
        while (true) {
            if (first == null) {
                break;
            }
            try {
                System.out.println(first);
            } catch (Exception e) {
                System.out.println(e.getMessage());
            }
            //改变指向:first指向当前结点的next结点(直到链表最后一个元素时,next为null,first指向了null,跳出循环)
            first = first.next;
        }
    }
}

//每一个结点都是Node对象
class Node {
    public Object item;//存放数据
    public Node next;//存放下一个结点
    public Node pre;//存放上一个结点

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

    @Override
    public String toString() {
        return "item = " + item;
    }
}

增删改查方法及部分源码

在这里插入图片描述
add()的部分源码–添加在末尾
在这里插入图片描述remove()有三种(无参,下标,对象)
无参的是删除链表第一个结点
在这里插入图片描述
在这里插入图片描述

ArrayList和LinkedList的比较

在这里插入图片描述

ArrayList和LinkedList如何选择

在这里插入图片描述

Set(Collection)

介绍

在这里插入图片描述

Set接口方法和遍历

在这里插入图片描述
使用add方法给HashSet添加元素时,该方法会返回一个布尔值–>是否添加成功
无序是因为:存放数据的顺序不是按照 存放顺序来的,而是根据存放的内容来的
如果重复添加,也只能显示一个该数据

HashSet

介绍

在这里插入图片描述

添加元素例题(add)

原因看下面的 扩容机制
在这里插入图片描述
在这里插入图片描述

模拟数组链表

也就是:定义一个数组 数组中每个元素都是 一个链表(增删高效)
在这里插入图片描述

public class Excise {
    public static void main(String[] args) {
        //模拟HashSet的底层 (HashMap的底层结构)

        //1. 创建一个数组,数组的类型是 Node[]
        //2. 可以把 Node[]数组称为 表
        Node[] table = new Node[16];
        //3. 创建结点
        Node mary = new Node("mary", null);
        Node jack = new Node("jack", null);
        Node smith = new Node("smith", null);
        //4. 将mary结点 放在数组 索引为2 的位置上
        table[2] = mary;
        //5. 将其它结点挂载到该节点上,形成链条
        mary.next = jack;//将jack挂载到mary上
        jack.next = smith;
    }
}

class Node {
    public Object item;//存放数据
    public Node next;//存放下一个结点

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

在这里插入图片描述

扩容机制(结论和add()源码)

结论
在这里插入图片描述
源码
在这里插入图片描述

  1. 执行HashSet()
public HashSet() {//本质是HashMap
        map = new HashMap<>();
}
  1. 执行add()
public boolean add(E e) {//此时e 为"mary"
        return map.put(e, PRESENT)==null;
        //PRESENT在HashSet定义为:   
        //private static final Object PRESENT = new Object();

}
  1. 执行put()
    该方法会执行 hash(key),得到key对应的hash值(不等价于hashCode,因为它有自己的算法,算法为:(h = key.hashCode()) ^ (h >>> 16))
public V put(K key, V value) {//key="mary" value=PRESENT共享的(固定)
    return putVal(hash(key), key, value, false, true);
}
  1. 执行putVal()–>解读写在代码注释中
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                boolean evict) {
     //定义辅助变量
     Node<K,V>[] tab; Node<K,V> p; int n, i;
     //table是HashMap定义的数组,类型是Node[]
     //if语句表示如果当前table是null,或者长度=0
     //就进行第一次扩容,到16个空间(阈值=0.75*16=12,即存储到12开始第二次扩容)
     if ((tab = table) == null || (n = tab.length) == 0)
         n = (tab = resize()).length;
     //(1)根据key得到hash,去计算该key应该存放到table表的哪个索引位置,并把这个位置的对象赋给p
     //(2)判断p是否为null
     //(2.1)若为null,表示还没存放元素,就创建一个Node(key="mary" value=PRESENT next指向null)
     if ((p = tab[i = (n - 1) & hash]) == null)
         tab[i] = newNode(hash, key, value, null);
     else {
     //开发技巧提示:需要局部变量(辅助变量)时,再定义
         Node<K,V> e; K k;
         //p指的是:准备添加的数据要添加到数组中的位置table[i]
         //如果数组的该索引位置存放的链表的第一个结点的hash值 = 准备添加的数据的hash值
         //并且满足下面两个条件之一:
         //(1)准备加入的数据key 和 链表第一个结点的key 是同一对象(地址相同)
         //(2)用equals()比较准备加入的数据的key 和 链表第一个结点的key 为true(equals方法可以重写-按地址或按内容)
         //若满足,则不能加入
         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);
         //如果数组对应索引位置,已经是一个链表,则使用for循环比较
         //(1)依次和该链表的每一个元素比较后,若都不相同(p.next == null)则将准备添加的数据 挂在该链表的最后
         /*
			注意:在把元素添加到链表末尾后,立即判断 该链表是否已经到达8个结点
			- 若到达8个结点,就调用treeifyBin方法
			在treeifyBin方法中会对数组长度进行判断
			- 若tab==null||tab.length()<64,就调用resize()对数组进行扩容
			- 若上面条件不成立,就将该链表转成一颗红黑树
		*/
         //(2)若在比较过程中,有相同情况(地址或内容),就break
         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,TREEIFY_THRESHOLD = 8
                         treeifyBin(tab, hash);
                     break;
                 }
                 //在这里判断两元素是否相等(地址或内容)
                 if (e.hash == hash &&
                     ((k = e.key) == key || (key != null && key.equals(k))))
                     break;
                 //移动p到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;
     //threshold是用容量*0.75计算出来的(防止数据突增,容量不够)
     //注意:size 就是我们每加入一个结点
     //意思说这个threshold是和加入的结点数比较,不仅是和数组第一个存放的,也包括存放在链表中的
     if (++size > threshold)
         resize();//扩容
     afterNodeInsertion(evict);
     return null;
 }

转成红黑树机制结论

结论
注意:
①threshold是临界值的意思(threshold是和加入的结点数比较)
②数组中添加的元素个数 到达临界值(0.75数组长度)时,数组就会进行扩容–>数组长度2
在这里插入图片描述

HashSet最佳实践

练习1

在这里插入图片描述

底层源码思路:

  1. 计算hash值
  2. 根据hash值 计算出key在表中的索引值
  3. 若该位置为空,则直接添加
  4. 若该位置非空,则比较equals(),若相等,则不添加,若不相等,则添加到链表的末尾

已知:new出的对象,计算出来的hash值一定不等(即存放的索引值也不等),所以默认会存放在表中不同位置。
在本题中:要求name和age相同时,不添加
所以就要让name和age相同的对象返回相同的hash值,再让它们的equals()相等 -->用快捷键重写这两个方法
在这里插入图片描述
在这里插入图片描述

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

public class Excise {
    @SuppressWarnings({"all"})
    public static void main(String[] args) {
        //如果name和age相同,则不能添加到hashset中
        HashSet hashSet = new HashSet();
        hashSet.add(new Employee("王胖子", 21));
        hashSet.add(new Employee("高瘦子", 22));
        hashSet.add(new Employee("王胖子", 21));
        System.out.println(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;
    }
    //name和age相同的对象返回相同的hash值,再让它们的equals()相等 -->用快捷键重写这两个方法

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

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

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

结果:
在这里插入图片描述

练习2(hash()源码)

在这里插入图片描述
hash()源码–>分析在代码中

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

public static int hashCode(Object a[]) {
if (a == null)
    return 0;

int result = 1;

for (Object element : a)
    //重点是这句: element.hashCode()
    //计算hash值会调用传进参数的hashCode()
    //name是String类型的 String重写了hashCode,所以内容相同则hash值相等
    //但是因为birthday是new出来的,默认情况下调用hashCode()计算出的每一个birthday的hash值都不相等
    //所以要重写birthday的hashCode(),当三个元素都相等时,hash值相等
    result = 31 * result + (element == null ? 0 : element.hashCode());

return result;
}

总体思路:

  1. 若name和birthday相同,则返回相同的hash值
  2. hash值相等时,调用Employee类的equals()

细节(可查看源码):

  1. Objects.hash(name, birthday)会调用birthday的hashCode()
  2. Objects.equals(birthday, employee.birthday)会调用birthday的equals()
import java.util.HashSet;
import java.util.Objects;

public class Excise {
    @SuppressWarnings({"all"})
    public static void main(String[] args) {
        HashSet hashSet = new HashSet();
        hashSet.add(new Employee("王胖子", 1000, new MyDate(2001, 1, 1)));
        hashSet.add(new Employee("高瘦子", 2000, new MyDate(1999, 2, 2)));
        hashSet.add(new Employee("王胖子", 3000, new MyDate(2001, 1, 1)));
        System.out.println(hashSet);
    }
}

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

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

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

class Employee {
    private String name;
    private double sal;
    private MyDate birthday;

    public Employee(String name, double sal, MyDate birthday) {
        this.name = name;
        this.sal = sal;
        this.birthday = birthday;
    }

    //hash+equals
    //总体思路:
    // 1. 若name和birthday相同,则返回相同的hash值
    // 2. hash值相等时,调用Employee类的equals()
    //这里会调用birthday的equals()-->将Employee类中equals()重写,当三属性相等时,equals()返回true
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Employee)) return false;
        Employee employee = (Employee) o;
        return Objects.equals(name, employee.name) &&
                Objects.equals(birthday, employee.birthday);
    }

    //这里会调用birthday的hashCode()-->看源码
    @Override
    public int hashCode() {
        return Objects.hash(name, birthday);
    }

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

结果:
在这里插入图片描述

LinkedHashSet(HashSet子类)

介绍

HashSet维护的是单向链表
LinkedHashSet维护的是双向链表,有头有尾所以存储有序
在这里插入图片描述

底层机制+源码解读

在这里插入图片描述

//按照这段代码进行debug
import java.util.LinkedHashSet;
import java.util.Set;

@SuppressWarnings({"all"})
public class Excise {
    public static void main(String[] args) {
        Set set = new LinkedHashSet();
        set.add(new java.lang.String("王胖子"));
        set.add(456);
        set.add(456);
        System.out.println(set);
    }
}
  1. LinkedHashSet 加入顺序和取出顺序一致
    在这里插入图片描述
  2. LinkedHashSet 底层维护的是LinkedHashMap(是HashMap的子类)
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
   map = new LinkedHashMap<>(initialCapacity, loadFactor);
}
  1. LinkedHashSet 底层结构是:数组(table)+双向链表
  2. 第一次添加时:直接将数组table扩容到16,存放的数据结点类型是 LinkedHashMap$Entry
  3. 数组是 HashMap $Node[],存放的数据是 LinkedHashMap $Entry(LinkedHashMap里定义的内部类Entry)
    5.1 因为LinkedHashMap中的内部类Entry继承了HashMap中的Node,所以Node数组中可以存放Entry
    5.2 这个entry就是双向链表中的结点,包含了before, after属性
//继承关系是在内部类中完成
//Node类是HashMap中的静态内部类,才能用类名去访问
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);
   }
}
  1. LinkedHashSet中的add()还是用了HashSet中的add(),HashSet中的add()调用了HashMap中的put()和putVal()

  2. before中存放前一个结点,after存放后一个结点,形成数组+双向链表结构

TreeSet

源码解读

在这里插入图片描述
debug用的源码

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

@SuppressWarnings({"all"})
public class Excise {
    public static void main(String[] args) {
        TreeSet treeSet = new TreeSet(new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                //① 调用String的compareTo方法进行字符串大小比较
//                return ((String)o1).compareTo((String)o2);
                //② 按照长度大小排序 --> 因为长度相等的返回值为0,所以添加不进去
                //这里可以理解为:如果o1长度>o2长度,则交换,所以前面的数据长度小,为从小到大排序
                return ((String)o1).length() - ((String)o2).length();
            }
        });
        treeSet.add("ani");
        treeSet.add("clo");
        treeSet.add("bmi");
        System.out.println(treeSet);
    }
}

输出结果:
按字符串大小排序输出
在这里插入图片描述
按字符串长度大小输出–>因为长度都为3,所以只加入了一个数据
在这里插入图片描述

解读源码
① TreeSet构造器把传入的匿名内部类对象,赋给了TreeMap的属性this.Comparator(TreeSet本质是TreeMap)

public TreeMap(Comparator<? super K> comparator) {
   this.comparator = comparator;
}

② 执行TreeSet的add()方法

public boolean add(E e) {
        return m.put(e, PRESENT)==null;
    }

③ 执行TreeMap的put()方法

int cmp;
Entry<K,V> parent;
// split comparator and comparable paths
Comparator<? super K> cpr = comparator;
if (cpr != null) {//cpr就是我们的匿名内部类对象
    do {
        parent = t;
        //动态绑定到匿名内部类的compare()
        cmp = cpr.compare(key, t.key);
        if (cmp < 0)
            t = t.left;
        else if (cmp > 0)
            t = t.right;
        //如果compare()的返回值是0,
        //那么这个数据就加入不了
        else
            return t.setValue(value);
    } while (t != null);
}

Map

Map接口实现类的特点

在这里插入图片描述
在这里插入图片描述

解读细节8(源码)

① k-v本质是存放在HashMap$ Node中的,但是为了更方便的获取k-v,让Node实现了Map.Entry接口,并把k-v包装成Node 封装到Entry中,再把这个Entry放在集合entrySet中,也就是创建entrySet集合,集合中存放的元素是Entry,但元素中实际上存放的内容是Node(集合中的k-v指向了Node中的k-v)
② 当要通过该集合获取k-v时,首先要向下转型为Entry,再调用Entry的getKey()或getValue()
在这里插入图片描述
-源码为:transient Set<Map.Entry<K,V>> entrySet;

  • 在entrySet中,定义的类型是Map.Entry,但实际上,存放的内容还是 HashMap$Node,这是因为HashMap $Node 实现了 Map.Entry
  • 当把 HashMap $Node 对象 存放到 entrySet 就方便我们遍历,因为Map.Entry 提供了重要方法 K getkey(); V getValue();

用法示例:

Map map = new HashMap();
map.put("no1","王胖子");
map.put("no2","高瘦子");

在这里插入图片描述

Map接口方法

在这里插入图片描述
在这里插入图片描述

Map四大遍历方式

  1. 取出所有的key,通过key 取出对应的value(用get()方法)
  2. 取出所有value
  3. 通过EntrySet来获取 k-v -->用到向下转型
import java.util.*;

@SuppressWarnings({"all"})
public class Excise {
    public static void main(String[] args) {
        Map map = new HashMap();
        map.put("no1", "王胖子");
        map.put("no2", "高瘦子");
        map.put("no3", "小艺艺");
        //第一组:先取出所有的key,通过key 取出对应的value(用get()方法)
        //① 增强for
        System.out.println("=====①由key取出value-->增强for=====");
        Set keyset = map.keySet();
        for (Object key : keyset) {
            System.out.println(key + " - " + map.get(key));
        }
        //② 迭代器-->获取到keySet的迭代器
        System.out.println("=====②由key取出value-->迭代器=====");
        Iterator iterator = keyset.iterator();
        while (iterator.hasNext()) {//快捷键itit
            Object key = iterator.next();
            System.out.println(key + " - " + map.get(key));
        }
        //第二组:取出所有value
        Collection values = map.values();
        //使用Collection的遍历方式
        //增强for
        System.out.println("=====遍历值-->增强for=====");
        for (Object value : values) {
            System.out.println(value);
        }
        //迭代器
        System.out.println("=====遍历值-->迭代器=====");
        Iterator iterator1 = values.iterator();
        while (iterator1.hasNext()) {
            Object value = iterator1.next();
            System.out.println(value);
        }
        //第三组:通过EntrySet来获取 k-v
        Set entrySet = map.entrySet();
        //① 增强for
        System.out.println("=====③使用EntrySet-->增强for");
        for (Object entry : entrySet) {
            //将Object向下转型为Entry
            Map.Entry m = (Map.Entry) entry;
            System.out.println(m.getKey() + " - " + m.getValue());
        }
        //② 迭代器
        System.out.println("=====④使用EntrySet-->迭代器");
        Iterator iterator2 = entrySet.iterator();
        while (iterator2.hasNext()) {
            //这里需要说明:HashMap$Node实现了Map$Entry,
            //Entry接口默认是public,但是实现类Node是default
            //所以HashMap包外的方法不能访问到Node类(即这里没办法转换成Node,只能转换成Entry)
            Object node = iterator2.next();
            Map.Entry m = (Map.Entry) node;
            System.out.println(m.getKey() + " - " + m.getValue());
        }
    }
}

Map课堂练习

在这里插入图片描述

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

@SuppressWarnings({"all"})
public class Excise {
    public static void main(String[] args) {
        HashMap map = new HashMap();
        map.put("2019001111", new Employee("王胖子", 10000, "2019001111"));
        map.put("2019002222", new Employee("高瘦子", 20000, "2019002222"));
        map.put("2019003333", new Employee("小艺子", 19000, "2019003333"));
        //遍历工资大于18000的员工
        //方法1:
        System.out.println("=====方法一:entrySet=====");
        Set entrySet = map.entrySet();
        Iterator iterator = entrySet.iterator();
        while (iterator.hasNext()) {
            Map.Entry entry = (Map.Entry) iterator.next();
            //取出值->Employee对象
            //因为下面还要用这个变量,所以就不简写了
            //注意要在最外面写括号! ((Employee) entry.getValue()).getSal()
            Employee employee = (Employee) entry.getValue();
            if (employee.getSal() > 18000) {
                System.out.println(employee);
            }
        }
        //方法2:
        System.out.println("=====方法二:keySet=====");
        Set keySet = map.keySet();
        for (Object key : keySet) {
            //获取value
            Employee employee = (Employee) map.get(key);
            if (employee.getSal() > 18000) {
                System.out.println(employee);
            }
        }

    }
}

class Employee {
    private String name;
    private double sal;
    private String id;

    public Employee(String name, double sal, String id) {
        this.name = name;
        this.sal = sal;
        this.id = id;
    }

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

    public String getName() {
        return name;
    }

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

    public double getSal() {
        return sal;
    }

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

    public String getId() {
        return id;
    }

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

HashMap

HashMap底层机制结论

在这里插入图片描述

HashMap扩容机制结论

hashSet底层就是hashMap
要求明确put()方法的流程 --> 可以看一下前面hashSet的源码解读
在这里插入图片描述

hashMap源码解读

根据这段代码debug
在这里插入图片描述
在这里插入图片描述

  1. debug中解读put方法 请看hashSet的源码解读 和 hashMap小结中解读细节5(如何替换)
    表的扩容:
    在这里插入图片描述

  2. 还有扩容树化前面也有源码解读
    ① 当链表长度>8且数组长度<64时 在链表中每添加一个node都会对数组进行扩容(调用resize()),直到数组长度为64
    ② 当数组扩容到64时 再往链表中添加一个node,就会把该链表转成一棵红黑树,结点变为TreeNode

HashMap小结

在这里插入图片描述

解读细节5:
将原先key的结点赋值给p,当添加相同的key时,将p赋值给e,e.value是数组中该索引位置的value,将新添加的value赋给这个索引位置的value,就相当于替换了
在这里插入图片描述

HashTable(Dictionary的子类)

介绍

在这里插入图片描述

HashTable扩容机制

① 初始化table数组Entry[] 大小为11,临界值 大小为8
② put()方法添加结点时,调用addEntry,把结点的K-V封装到Entry中,满足条件的Entry(K-V不为null)放到Entry数组table中(若key重复,则替换value)
③ 当结点数>=临界值(Threshold),则对数组进行扩容
④ 扩容:数组大小*2+1,临界值 *0.75

在这里插入图片描述

HashTable和HashMap对比

在这里插入图片描述

Properties(HashTable的子类)

介绍

在这里插入图片描述
配置文件
在这里插入图片描述
常用方法也是增(put)删(remove)改(put相同key)查(get(“key”))

TreeMap

TreeSet本质是TreeMap,只是前者是单例的,只有key,后者是双例的,K-V,前者调用TreeMap方法时,value的值是固定的Present,后者是变化的

源码解读

debug的源码

import java.util.Comparator;
import java.util.TreeMap;

@SuppressWarnings({"all"})
public class Excise {
    public static void main(String[] args) {
        TreeMap treeMap = new TreeMap(new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                //字符串大小
                return ((String) o1).compareTo((String) o2);
                //字符串长度
                //return ((String)o1).length() - ((String)o2).length();

            }
        });
        treeMap.put("wpz", "王胖子");
        treeMap.put("gsz", "高瘦子");
        System.out.println(treeMap);
    }
}

输出结果
按字符大小
在这里插入图片描述

按字符串长度
在这里插入图片描述

① 构造器
在这里插入图片描述
② put()方法
第一次添加时:把k-v封装到Entry中,放入root

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

第二次添加时:遍历所有key,动态绑定匿名内部类的compare方法

int cmp;
Entry<K,V> parent;
// split comparator and comparable paths
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
            return t.setValue(value);
    } while (t != null);
}

集合选型规则!

在这里插入图片描述

Collection工具类

介绍

在这里插入图片描述

排序操作

在这里插入图片描述

查找替换操作

在这里插入图片描述

本章练习

练习1:截取和遍历

在这里插入图片描述

用到的方法:
ArrayList中:

  • 增:add()
  • 获取集合中元素: get() --> 可以使用下标获取
  • 长度: size()

String中:

  • 截取子字符串:subString()
import java.util.ArrayList;

@SuppressWarnings({"all"})
public class Excise {
    public static void main(String[] args) {
        ArrayList arrayList = new ArrayList();
        //将新闻对象添加到集合中,直接在参数中new,因为只使用一次
        arrayList.add(new News("新冠确诊病例超千万,数百万印度教信徒赴恒河“圣浴”引民众担忧"));
        arrayList.add(new News("男子突然想起2个月前钓的鱼还在网兜里,捞起一看赶紧放生"));
        //1. 倒序遍历->注意:这里不能使用Collections的reverse方法,因为reverse是翻转(会改变集合本身)
        //2. 标题超过15个字只保留前15个,然后在后边加"…"
        int size = arrayList.size();
        for (int i = size - 1; i >= 0; i--) {
            //处理前输出
//            System.out.println(arrayList.get(i));
            //处理标题-->另外写一个方法去处理
            News temp = (News) arrayList.get(i);
            System.out.println(processTitle(temp.getTitle()));
        }
    }

    //写一个方法来截取标题
    public static String processTitle(String title) {
        //判断标题有效性
        if (title == null || title.equals("")) {
            return "";
        }
        if (title.length() <= 15) {
            return title;
        }
        //若上面的都不满足,则对title进行处理
        //title.substring(0,15)会返回截取的字符串
        return title.substring(0, 15) + "...";//[0,15)
    }
}

class News {
    private String title;//标题
    private String content;//内容

    public News(String title) {
        this.title = title;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

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

在这里插入图片描述

练习2:ArrayList常用方法

在这里插入图片描述

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

@SuppressWarnings({"all"})
public class Excise {
    public static void main(String[] args) {
        //add,remove,contains
        //addAll,constainsAll,removeAll
        //clear,size,isEmpty
        //使用增强for和迭代器来遍历所有的car

        Car car1 = new Car("宝马", 400000);
        Car car2 = new Car("宾利", 5000000);
        ArrayList arrayList1 = new ArrayList();
        ArrayList arrayList2 = new ArrayList();

        //1. add()和addAll()
        arrayList2.add(car1);
        arrayList2.add(car2);
        arrayList1.addAll(arrayList2);
        //2. remove()和removeAll()
        arrayList1.remove(0);
        arrayList1.remove(car2);
        arrayList1.removeAll(arrayList2);
        //3. contains()和containsAll()
        System.out.println(arrayList1.contains(car1));
        System.out.println(arrayList1.containsAll(arrayList2));
        //4. clear
        arrayList1.clear();
        //5. size()
        System.out.println(arrayList1.size());
        //6. isEmpty()
        System.out.println(arrayList1.isEmpty());
        //7. 增强for
        for (Object o : arrayList1) {
            System.out.println(o);
        }
        //8. 迭代器
        Iterator iterator = arrayList1.iterator();
        while (iterator.hasNext()) {
            Object next = iterator.next();
            System.out.println(next);
        }
    }
}

class Car {
    private String name;
    private double price;

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

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

练习3:HashMap

在这里插入图片描述

HashMap中:

  • 增加/修改:put() --> 相同key,不同value,会对value进行替换
  • 获取key对应的value:get(key) --> 不能用下标,只能传key值,且get只能获取,不能修改

比如:所有员工工资加100元

  • 获取到key对应的value,然后对value进行修改
  • 相同key,传入不同的value,新value会替换掉旧value
    map.put(key, (Integer) map.get(key) + 100);
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

@SuppressWarnings({"all"})
public class Excise {
    public static void main(String[] args) {
        HashMap map = new HashMap();
        map.put("jack", 650);//int-->Integer
        map.put("tom", 1200);
        map.put("smith", 2900);
        System.out.println(map);
        //1. 将jack工资更改为2600
        map.put("jack", 2600);//替换
        System.out.println(map);
        //2. 为所有员工工资加100元
        //   - 思路:要更改工资,那么使用put()替换就可以(用key获取value,然后用put对key的value值进行替换)
        Set keySet = map.keySet();
        for (Object key : keySet) {
            //map.get(key)返回的是Object,所以向下转型为Integer
            //用key来获取value值,再对value值进行替换
            map.put(key, (Integer) map.get(key) + 100);
        }
        System.out.println(map);
        //3. 遍历集合中所有员工
        Set set = map.entrySet();
        for (Object o : set) {
            Map.Entry entry = (Map.Entry) o;
            System.out.println(entry.getKey() + " - " + entry.getValue());
        }
        //4. 遍历集合中所有工资
        Collection values = map.values();
        for (Object o : values) {
            System.out.println(o);
        }
    }
}

练习4:TreeSet去重

TreeSet去重

  • add()最终调用put(),put()中调用compare(),若已经传入了一个comparator匿名对象,则调用此匿名对象里面的compare()。如果没有传入匿名对象,则会调用 准备添加的对象 实现的Comparable接口的compareTo()去重
  • 若传入的是String,String实现了Compareable接口,compareTo方法是按照字符串是否相同做的比较
  • 若传入的是自定义的类,且在add()方法中,没有写Comparator匿名内部类,则必须实现Comparable接口,并实现compareTo()方法

在这里插入图片描述
在这里插入图片描述

练习5:HashSet的add()易错

用到的知识:

  • 先计算添加的数据的hash值,
  • 然后计算出该hash值应该存放在数组的哪个位置上,
  • 若该位置为空,则直接添加,若不为空,比较和数据该位置第一个数据的equals是否true,若true则不添加,若F,则看该链表是否为红黑树,若F,则用equals循环比较链表上的结点,若有一个相同则不添加,若都不相同,则添加到链表末尾

该题:

  • 若不重写hashCode(),则若每次new对象都返回不同的hash值(也就是对象存放的地址决定了hash值),若该对象内部属性修改了,hash值还是和原来相等
  • 若重写hashCode(),则对象中属性的内容决定了hash值,若name和id相等则hash值相等,若name和id修改,hash值也会跟着修改
  • 本题的坑就在于上面说的两句话,当修改了对象的某个属性,hash值会随之改变

在这里插入图片描述

解读本题:

  • p1修改name为CC,hash值随之发生改变
  • set.remove(p1),会根据修改后p1的hash值计算出p1在数组中的位置,这时的位置不是p1开始时存放的位置了,所以删除失败
  • 此时输出,会输出p1和p2
    再次添加1001和CC,计算出一个地址,这个地址是空的,所以可以添加
    此时输出,会输出3个数据,p1,p2和第三次添加的对象
    再次添加1001和AA,计算出一个地址,该地址存放着p1,但是p1已经发生了变化(1001和CC),所以equals为F,新添加的数据存到了链表的末尾
  • 此时输出,会输出4个数据,p1,p2,第三次添加的对象和第四次添加的对象

练习6:ArrayList和BVector区别

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值