集合

数组
1)长度开始时必须指定,而且一旦指定,不能更改
2)保存的必须为同一类型的元素
3)使用数组进行增加元素的示意代码 -比较麻烦

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

集合的框架体系

  1. 集合主要是两组(单列集合双列集合)
  2. Collection 接口有两个重要的子接口 List Set ,他们的实现子类都是单列集合
  3. Map 接口的实现子类 是双列集合,存放的 K-V


Collection接口和常用方法

public interface Collection<E> extends Iterable<E>
  1. collection实现子类可以存放多个元素,每个元素可以是Obiect
  2. 有些Collection的实现类,可以存放重复的元素,有些不可以
  3. 有些Collection的实现类,有些是有序的(List),有些不是有序(Set)
  4. Collection接口没有直接的实现子类,是通过它的子接口Set 和 List 来实现的

由于接口不能被实例化,所以用子类ArrayList来演示

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

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

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

Collection接口实现了Iterable接口,这个接口有个Iterator<T> iterator();方法;该方法返回一个Iterator对象;Iterator类有boolean hasNext(); ,next(); 方法,用来判断和返回迭代器里的元素。
所以想要用迭代器遍历元素,首先得获取迭代器Iterator it = 集合名.iterator(),通过it.hasNextit.next来便利元素。

Collection接口遍历对象方式2-for循环增强

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

基本语法

for (Object object : col) {  //col是集合名
  System.out.println(object)
}

List接口和常用方法

List 接口是 Collection 接口的子接口

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

List三种遍历方式

  1. 迭代器,用while循环遍历
  2. 增强for循环
  3. 普通for循环(因为List底层是数组)

ArrayList底层结构和源码分析

注意事项

  1. permits all elements, including null ,ArrayList 可以加入nul,并目多
  2. ArrayList 是由数组来实现数据存储的
  3. ArrayList 基本等同于Vector,除了 ArrayList是线程不安全(执行效率高)看源码在多线程情况下,不建议使用ArrayList
    //ArrayList 是线程不安全的,可以看源码 没有 synchronized
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

源码分析

  1. ArrayList中维护了一个Object类型的数组elementData;transient Object[] elementData;(transient 表示该属性不会被序列化)

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

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

举个栗子

//使用无参构造器创建ArrayList对象
ArrayList list = new ArrayList();
//使用for给List集合添加 1-10数据
for (int i = 1; i <= 10; i++) {
  list.add(i)
}
//使用for给List集合添加 11-15数据
for (int i = 11; i <= 15; i++) {
  list.add(i)
}
list.add(100);
list.add(500);
list.add(null);

创建ArrayList 对象时会调用ArrayList 无参构造器,会创建一个空数组elementData[]={}。
进入for循环,首先会对int i进行装箱。包装为Integer;再进入add方法(上面有源码),add方法里第一步是做一个判断,判断容量够不够ensureCapacityInternal(),然后再是赋值。返回true。
首先在创建ArrayList对象处创建断点

step into进入到ArrayList 的无参构造器

可以看到右边这玩意就是个空数组

step out + step over返回main方法,执行for循环

可以看到执行add操作前先对 i 进行装箱

后续操作不关心step out + step into进入add方法里,可以看到add方法第一条就是ensureCapacityInternal(size + 1)方法,它的作用是确保集合内部至少有size+1个容量,没有则扩容。


step into这个方法里面还有一个ensureExplicitCapacity()方法,这个方法的参数是int

step into
最小实际容量比dlementData容量还要大,则扩容

modCount是AbstractList类里受保护的成员,用来记录集合被修改的次数

num >> 1右移运算符,相当于num除以2;elementDate长度赋值给旧容量,将旧容量的1.5x赋值给新容量如果新容量比传入的最小容量还小,则将最小容量赋值给新容量;如果新容量比Integer.MAX_VALUE - 8还要大,则执行hugeCapacity()方法;最后用copyOf方法完成扩容。



第一次扩容为10

size是ArrayList的私有成员。记录集合里已经存入对象的个数。

若采用有参构造器,则

Vector

基本介绍

  1. Vector类的定义说明,继承了AbstractList,实现了List接口。
  2. Vector底层也是一个对象数组,protected Object[] elementData;
  3. Vector 是线程同步的,即线程安全,Vector类的操作方法带有synchronized
  4. 在开发中,需要线程同步安全时,考虑使用Vector

Vector和ArrayList比较


add方法


LinkedList

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

LinkedList的底层操作机制

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

add方法源码分析



首先把链尾指针last赋值给l,再new一个Node类对象NewNode,newnode的prev指向last,(此时链表末尾的节点还未指向newnode,只是newnode单向指向末尾节点last)item存入数据,next指向空。然后把链表的last指向newnode。如果l是空,则表示链表之前是空表,于是把first指向newdode。否则l.next指向newnode。此时完成了双链的连接。

ArrayList和LinkedList的比较

如何选择ArrayList和LinkedList:

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

Set集合

Set集合基本介绍

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

Set接口和常用方法

和List接口一样,Set接口也是Collection的子接口,因此,常用方法和Collection接口一样
同Collection的遍历方式一样,因为Set接口是Collection接口的子接口。

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

HashSet

  1. HashSet实现了Set接口
  2. HashSet实际止是HashMap,看下源码
  3. 可以存放null值,但是只能有一个null
  4. HashSet不保证元素是有序的,取决于hash后,再确定索引的结果
  5. 不能有重复元素/对象在前面Set 接口使用已经讲过

HashSet底层机制说明

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

  1. HashSet 底层是 HashMap
  2. 添加一个元素时,先得到hash值 -会转成-> 索引值
  3. 找到存储数据表table,看这个索引位置是否已经存放的有元素
  4. 如果没有,直接加入
  5. 如果有 , 调用 equals 比较,如果相同,就放弃添加,如果不相同,则添加到最后
  6. 在Java8中,如果一条链表的元素个数超过 TREEIFY THRESHOLD(默认是 8),并且table的大小 >=MIN TREEIFY CAPACITY(默认64)就会进行树化(红黑树)
public class Collection01 {
    public static void main(String[] args) {

        HashSet hashSet = new HashSet();
        hashSet.add("java");
        hashSet .add("php");
        hashSet.add("java");
        System.out.println("set=" + hashSet);
    }
}
  1. 先执行HashSet()
  2. 执行add()

    这个PERSENT只是一个占位的,每add一个数据,key可能会变,但PERSENT一直不变
  3. 执行put(),该方法会执行 hash(key) 得到key对应的hash值

    hash算法,"^"按位异或运算符,">>>"表示无符号右移运算。简单来说:j>>>i表示将数向右位移i位,右移后左边空出的位用0补上,目的是让不同的key尽量得到不同的hash值
  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;  //定义了辅助变量
        //如果tab为空,则表示第一次扩容,默认容量16
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        //(1)根据key得到hash 去计算该key应该存放到table表的哪个索引位置
        //并把这个位置的对象,赋给 p
        //"&"是与运算,15&3854503=3
        //(2)判断p 是否为null
        //(2.1)如果为null表示还没有存放元素,就创建一个Node(key="java",value=PERSENT)
        //(2.2)
        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)指向的Node 结点的 key 的equals() 和准备加入的key比较后相同
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            //再判断 p 是不是一颗红黑树
            //如果是一颗红黑树,就调用 putTreeVal ,来进行添加
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {  //如果table对应索引位置,已经是一个链表,就使用for循环比较
                    //(1) 依次和该链表的每一个元素比较后,都不相同,则加入到该链表的最后
                    //(2) 依次和该链表的每一个元素比较过程中,如果有相同情况,就直接break
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        //注意在把元素添加到链表后,立即判断 该链表是否已经达到8个结点
                        //就调用 treeifyBin() 对当前这个链表进行树化(转成红黑树)
                        //注意,在转成红黑树时,要进行判断,如果该table数组的大小<64
                        //resize();
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

table是HashMap的成员,是Node数组,Node是它的内部类。

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)就会进行树化(红黑树),否则仍然采用数组扩容机制
/*
HashSet底层是HashMap,第一次添加时,table 数组扩容到 16.
临界值(threshold)是 16*加载因子(LoadFactor)是0.75 = 12
如果table 数组使用到了临界值 12,就会扩容到 16 * 2 = 32
新的临界值就是 32*0.75 = 24,依次类推
*/
HashSet hashSet = new HashSet();
for(int i = 1; i <= 100; i++) {
  hashset.add(i);//1,2,3,4,5...100
}
HashSet hashSet = new HashSet();
for(int i = 1; i <= 12; i++) {
  hashSet.add(new A(i));
}//


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

  @override
  public int hashCode() {return 100}
}

//执行完add(new A(8))后,该链表就有8个node了,会执行treeifyBin(tab, hash)方法,
//但是table长度不到64,所以只会扩容不会转成红黑树。
//即执行完add(new A(8))后,table长度变成32;
//执行完add(new A(9)后,table长度变成32;
//执行完add(new A(10)后,table长度变成64;
//执行完add(new A(11)后,该链表会变成红黑树;

HashSet练习

public class Collection01 {
    public static void main(String[] args) {

        HashSet hashSet = new HashSet();
        Employee e1 = new Employee("张三", 25);
        Employee e2 = new Employee("张三", 25);
        System.out.println(hash(e1));
        System.out.println(hash(e2));
//        hashSet.add(e1);
//        hashSet.add(e2);
    }
}

class Employee{
    private String name;
    private int age;

    public Employee(String name, int age) {
        this.name = name;
        this.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);
    }

}

写完后发现这两个内容相同的对象的hash值是不一样的,而add底层给根据hash值得到的索引值也就不一样,所以并不会判断两个key的内容是否一样,所以我们在Employee类里面重写hashCode方法,使内容相同的对象计算的hashCode值相同。

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

通过重写的hashCode方法就能让相同内容的对象返回相同的hashCode值


public class Collection01 {
    public static void main(String[] args) {

        HashSet hashSet = new HashSet();
        Employee e1 = new Employee("张三", 25,new MyDate(1998,12,14));
        Employee e2 = new Employee("张三", 26,new MyDate(1998,12,14));
//        System.out.println(hash(e1));
//        System.out.println(hash(e2));
        hashSet.add(e1);
        hashSet.add(e2);
        for (Object e:hashSet
             ) {
            System.out.println(e);
        }
    }
}

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 == null || getClass() != o.getClass()) return false;
        MyDate myDate = (MyDate) o;
        return year == myDate.year && month == myDate.month && day == myDate.day;
    }

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

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

class Employee{
    private String name;
    private int age;

    private MyDate date;


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

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

    @Override
    public int hashCode() {
        return hash(name, date);
    }

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

LinkedHashSet

public class Collection01 {
    public static void main(String[] args) {

        Set set = new LinkedHashSet();
        set.add(new String("AAA"));
        set.add(456);
        set.add(456);
        set.add(new Customer("王",1001));
        set.add(123);
        set.add("WZW");
        //LinkedHashMap
        //LinkedHashSet 加入顺序和取出元素/数据的顺序一致
        //LinkedHashSet 底层维护的是一个LinkedHashMap(是HashMap的子类)
        //LinkedHashSet 底层结构 (数组+双向链表)
        // 添加第一次时,直接将 数组table 扩容到 16,存放的结点类型是 LinkedHashMap$Entry
        //数组是 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);
                }
            }
            之前的HashSet内部就是个map,也就是node,只有next,而Entry有before和after
         */
    }
}
class Customer{
    private String name;

    private int n;

    public Customer(String name, int n) {
        this.name = name;
        this.n = n;
    }

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

Mep

Map接口常用方法

  1. Map与Collection并列存在。用于保存具有映射关系的数据:Key-Value
  2. Map 中的 key 和 value 可以是任何引用类型的数据,会封装到HashMapsNode对象中
  3. Map 中的 key 不允许重复,原因和HashSet 一样,前面分析过源码
  4. Map 中的 value 可以重复
  5. Map 的key 可以为 null, value 也可以为null ,注意 key 为null,只能有一一个;value的null可以有多个
  6. 常用String类作为Map的 key
  7. key 和 value 之间存在单向一对一关系,即通过指定的 key总能找到对应的 value
  8. Map存放数据的key-value示意图,一对 k-v 是放在一个Node中的,有因为Node 实现了 Entry 接口,有些书上也说 一对k-v就是一个Entry

//1. k-v 最后是HashMap$Node node = newNode(hash, key, value, null)
//2. k-v 为了方便程序员的遍历,还会 创建 EntrySet 集合 ,该集合存放的元素的类型 Entry,而一个Entry
//  对象就有k,v EntrySet<Entry<K,V>>

HashMap里的成员,它是一个set集合,里面存放的时Entry<k,v>对象。所以叫EntrySet。

    public static void main(String[] args) {
        Map map = new HashMap();
        map.put("no1","韩顺平");
        map.put("no2","张无忌");
        //1. k-v 最后是HashMap$Node node = newNode(hash, key, value, null)
        //2. k-v 为了方便程序员的遍历,还会 创建 EntrySet 集合 ,该集合存放的元素的类型 Entry,而一个Entry
        //  对象就有k,v EntrySet<Entry<K,V>> 即    transient Set<Map.Entry<K,V>> entrySet;
        //3. entrySet 中,定义的类型是 Map.Entry ,但是实际上存放的还是 HashMap$Node
        //  这是因为static class Node<K,V> implements Map.Entry<K,V>
        //4. 当把 HashMapSNode 对象 存放到 entrySet 就方便我们的遍历,因为 Map.Enty提供了重要方法
        //  K getKey(); V getValue() ;
        Set set = map.entrySet();
        System.out.println(set.getClass());//HashMap$EntrySet
        for (Object obj: set) {

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

HashMap小结

  1. Map接口的常用实现类: HashMap、Hashtable和Properties
  2. HashMap是 Map 接口使用频率最高的实现类
  3. HashMap 是以 key-val 对的方式来存储数据(HashMap$Node类型)[案例 Entry]
  4. key 不能重复,但是是值可以重复,允许使用null键和null值.
  5. 如果添加相同的key,则会覆盖原来的key-val,等同于修改(key不会替换,val会替换)
  6. 与HashSet一样,不保证映射的顺序,因为底层是以hash表的方式来存储的.
  7. HashMap没有实现同步,因此是线程不安全的

HashMap底层机制,源码

扩容机制

  1. HashMap底层维护了Node类型的数组table,默认为null
  2. 当创建对象时,将加载因子(loadfactor)初始化为0.75
  3. 当添加kev-val时,通过kev的哈希值得到在table的索引。然后判断该索引处是否有元素
    如果没有元素直接添加。如果该索引处有元素,继续判断该元素的key是否和准备加入的key相等,如果相等,则直接替换val; 如果不相等需要判断是树结构还是链表结构,做出相应处理。如果添加时发现容量不够,则需要扩容。
  4. 第1次添加,则需要扩容table容量为16,临界值(threshold)为12
  5. 以后再扩容,则需要扩容table容量为原来的2倍,临界值为原来的2倍,即24,依次类推.
  6. 在Java8中,如果一条链表的元素个数超过 TREEIFY THRESHOLD(默认是 8),并且table的大小 >= MIN TREEIFY CAPACITY(默认64),就会进行树化(红黑树)

HashTable

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

HashTable底层

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

HashMap和HashTable对比

Map接口实现类-Pgoperties

基本介绍

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

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

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

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

TreeSet

TreeSet排序底层

TreeSet的构造函数可以传入一个实现了Comparator接口的类(即匿名内部类);
实际上时new了一个TreeMap,然后把comparator传了进去,赋给了TreeMap的comparator属性


当TreeSet执行add方法时,实际上是在执行TreeMap的put方法,会判断comparator属性是否为空,不为空则按照重写的compare方法进行比较
这是TreeSet的属性,一个map,add方法就会跳转到m.put

具体比较代码:

          if (cpr != null) {//cpr 就是我们的匿名内部类(对象)
            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);
public class MapSource_ {
    public static void main(String[] args) {

        //1. 当我们使用无参构造器,创建TreeSet时,仍然是无序的
        //2. 老师希望添加的元素,按照字符串大小来排序
        //3. 使用TreeSet 提供的一个构造器,可以传入一个比较器(匿名内部类)
        //  并指定排序规则
//        TreeSet treeSet = new TreeSet();
        TreeSet treeSet = new TreeSet(new Comparator() {//实现了Comparator接口的匿名对象
            @Override
            public int compare(Object o1, Object o2) {
                //下面 调用String的 compareTo方法进行字符串大小比较
                return ((String) o2).compareTo((String) o1);
            }
        });
        treeSet.add("jack");
        treeSet.add("tom");
        treeSet.add("sp");
        treeSet.add("a");
        System.out.println(treeSet);

        //1. 构造器把传入的比较器对象赋给了 TreeSet的底层的 TreeMap的属性this.comparator
        /*
            public TreeMap(Comparator<? super K> comparator) {
                this.comparator = comparator;
            }
        * */
        //把Comparator传给了TreeSet底层的TreeMap的comparator属性
        //2. 在 调用 treeSet.add("tom"),在底层会执行到
        /*
        * if (cpr != null) {//cpr 就是我们的匿名内部类(对象)
            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);
        }*/
    }
}

TreeMap

前面TreeSet底层是TreeMap时讲了

练习


如果没有传入comparator,则会默认把传入的key转成Comparable类型

只需要让Person类实现Comparable接口就行

set集合存放的是对象的引用
把p1.name修改后,p1的hashCode变了,而remove函数是根据传入对象的hashCode来删除的,所以删不了。
第一个输出语句是有2条。{1001,“CC”}和{1002,“BB”}
执行add时计算hashCode确定索引值,实际上table里该索引位置是空的,能成功添加。
所以第二条输出语句能输出3个对象
第二次添加,计算的索引值和p1{1001,"CC"}相等,此时他俩会比较key和equals,实际上是不一样的,能存放。
所以最后一条输出语句能输出4个对象。

posted @ 2024-02-25 22:54  不会des  阅读(2)  评论(0编辑  收藏  举报