集合练习(2)

文章详细分析了HashSet和TreeSet的去重机制,包括HashSet基于哈希表和可能的红黑树的数据结构以及TreeSet基于Comparable或Comparator的比较策略。同时,文章讨论了Vector和ArrayList的区别,主要在于线程安全性和扩容策略:Vector是线程安全但效率较低,ArrayList非线程安全但效率更高且扩容策略更灵活。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

第一题

思路分析:

HashSet去重机制

TreeSet去重机制

第二题

思路分析:

第三题

思路分析:

Vector和ArrayList的比较


第一题

代码演示:

思路分析:

HashSet去重机制

//HashSet实现去重是因为 在底层会根据你传入的key计算一个hash值,

//会先计算一个你在table表中存放的位置 ,如果该位置还没有其他元素添加则直接加入

//如果已经有了会先进行判断,如果你的加入的对象的hash值和当前位置的hash值相同

//或者是同一个对象,或者是他们的equals相同就不进行添加

//并且底层还会判断当前是不是红黑树,如果是就按照红黑树的方法进行

//如果是一个链表 就依次循化遍历,期间如果发现相同的就直接break,如果没有发现相同的就直接加入到链表的最后一个位置

TreeSet去重机制

//TreeSet去重机制:如果我们传入了comparator匿名对象,那么底层会使用compare方法进行比较 //如果返回0就不会加入

//如果没有传入对象,直接使用默认的那么会调用

// Comparable<? super K> k = (Comparable<? super K>) key;

//就是以你添加的对象实现的Comparable接口的compareTo去重.如果返回0也加入不成功】

//举例 //如果我们没有传入对象,并且传入的类型还是一个String类型那么在执行到Comparable<? super K> k = (Comparable<? super K>) key;这句话时 //会动态绑定到String的compareTo方法因为String实现了Comparable这个接口

package idea.chapter14.homework;


public class Homework04 {
    public static void main(String[] args) {
        //HashSet实现去重是因为 在底层会根据你传入的key计算一个hash值,
        //会先计算一个你在table表中存放的位置 ,如果该位置还没有其他元素添加则直接加入
        //如果已经有了会先进行判断,如果你的加入的对象的hash值和当前位置的hash值相同
        //或者是同一个对象,或者是他们的equals相同就不进行添加
        //并且底层还会判断当前是不是红黑树,如果是就按照红黑树的方法进行
        //如果是一个链表 就依次循化遍历,期间如果发现相同的就直接break,如果没有发现相同的就直接加入到链表的最后一个位置


        //TreeSet去重机制:如果我们传入了comparator匿名对象,那么底层会使用compare方法进行比较
        //如果返回0就不会加入
        //如果没有传入对象,直接使用默认的那么会调用
        // Comparable<? super K> k = (Comparable<? super K>) key;
        //就是以你添加的对象实现的Comparable接口的compareTo去重.如果返回0也加入不成功
        //举例
        //如果我们没有传入对象,并且传入的类型还是一个String类型那么在执行到Comparable<? super K> k = (Comparable<? super K>) key;这句话时
        //会动态绑定到String的compareTo方法因为String实现了Comparable这个接口

        //看源码
        /*
            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;
                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);
                }
                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
                            这里就是添加不成功的情况,返回0,就是添加失败
                            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;
            }
         */

    }
}

第二题

代码演示:

思路分析:

因为我们的Person类没有实现Comparable接口,因此在底层的时候,会根据你传入进来的对象尝试将他转成Comparable这个类型,如果传入的对象没有实现改接口那么就报类型转换异常,

如果不想出错,让Person类实现Comparable接口,并重写compareto方法即可

package idea.chapter14.homework;

import java.util.TreeSet;

@SuppressWarnings({"all"})
public class Homework05 {
    public static void main(String[] args) {
        TreeSet treeSet = new TreeSet();
        //分析源码
        //add 方法,因为 TreeSet() 构造器没有传入Comparator接口的匿名内部类
        //所以在底层会去调用你传入对象的Comparable接口的compareTo方法 但是Person类并没有实现Comparable接口
        //因此,会尝试把一个Person转成(Comparable<? super K>)所有会报类型转换异常ClassCastException.
        // Comparable<? super K> k = (Comparable<? super K>) key;

        treeSet.add(new Person());//ClassCastException.
        treeSet.add(new Person());//ClassCastException.
        treeSet.add(new Person());//ClassCastException.
        treeSet.add(new Person());//ClassCastException.
        treeSet.add(new Person());//ClassCastException.

        System.out.println(treeSet);
        //看源码
        /*
            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;//如果没有传入匿名对象那么此时comparator为null所以会不会进入下面的if语句
                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);
                }
                else {
                    if (key == null)
                        throw new NullPointerException();
                    @SuppressWarnings("unchecked")
                        Comparable<? super K> k = (Comparable<? super K>) key;//会直接执行到这里来 会根据你传入进来的对象尝试将他转成Comparable这个类型,如果传入的对象没有实现改接口那么就报类型转换异常
                    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;
            }
         */
    }
}

class Person implements Comparable{
    //如果想不报错那么就需要让Person类去实现Comparable接口
    //因为我们写的时return 0 因此只能加入一个,如果加入多个会加入不进去,因为返回0就是不加入

    @Override
    public int compareTo(Object o) {
        return 0;
    }
}

第三题

代码演示:

思路分析:

/*

因为Person类重写了equals方法和hashCode方法,HashSet底层时HashMap在添加元素时
会先计算你的hash值,在转换成索引值,也就是在table表中的位置加入,如果当前位置为空
那么就直接加入,如果不为空则进行后续的比较
p1 和 p2虽然重写了hashcode方法和equals方法,但是他们的内容不相同,因此计算出来的
索引值也不会相同,所以能够成功的添加进去
在执行p1.name = "CC";时会把p1里的AA改成CC  这时set.remove(p1);想再去删除p1时
是根据当前的id 1001和当前的name CC来计算 ,计算过后等到的索引值会在table数组的其他位置
因此删除不成功
在执行set.add(new Person(1001,"CC"));这句话时,表中已经有了一个 id 为1001  name 为CC
虽然他们的id和name都相同,但是最后添加的那个对象都是重写new出来的,他们的hash值不同 也不是同一个对象
所以可以 添加成功   可以通过Debug查看
set.add(new Person(1001,"AA"));这句话和上面相同
 */
package idea.chapter14.homework;



import java.util.HashSet;
import java.util.Objects;
@SuppressWarnings({"all"})
public class Homework06 {
    public static void main(String[] args) {
        HashSet set = new HashSet();//ok
        Person p1 = new Person(1001,"AA");//ok
        Person p2 = new Person(1002,"BB");//ok
        set.add(p1);//ok
        set.add(p2);//ok
        p1.name = "CC";
        //这里会删除失败,因为删除是根据你当前的id 和 name 来计算的 而你在删除之前把name的值修改了,那么在删除的时候,需要重新计算hash的值,得到的位置就不再是之前的位置了
        set.remove(p1);
        System.out.println(set);//2
        //这里可以加入成功,因为是根据当前的id1001 和 name CC 来计算的
        set.add(new Person(1001,"CC"));
        System.out.println(set);//3
        //这里也可以加入,和我们第一个加入的元素p1一样,他们的id 都是1001 name也是AA 所以计算出来的hash值肯定是相同的,因为我们之前把p1的值修改了,修改成了CC 所以这里会把值直接挂在p1的后面
        set.add(new Person(1001,"AA"));
        System.out.println(set);//4
        /*

        因为Person类重写了equals方法和hashCode方法,HashSet底层时HashMap在添加元素时
        会先计算你的hash值,在转换成索引值,也就是在table表中的位置加入,如果当前位置为空
        那么就直接加入,如果不为空则进行后续的比较
        p1 和 p2虽然重写了hashcode方法和equals方法,但是他们的内容不相同,因此计算出来的
        索引值也不会相同,所以能够成功的添加进去
        在执行p1.name = "CC";时会把p1里的AA改成CC  这时set.remove(p1);想再去删除p1时
        是根据当前的id 1001和当前的name CC来计算 ,计算过后等到的索引值会在table数组的其他位置
        因此删除不成功
        在执行set.add(new Person(1001,"CC"));这句话时,表中已经有了一个 id 为1001  name 为CC
        虽然他们的id和name都相同,但是最后添加的那个对象都是重写new出来的,他们的hash值不同 也不是同一个对象
        所以可以 添加成功   可以通过Debug查看
        set.add(new Person(1001,"AA"));这句话和上面相同
         */
    }
}

class Person {
    public String name;
    public int id;

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

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

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

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

Vector和ArrayList的比较

底层结构版本线程安全(同步)效率扩容倍数
ArrayList可变数组jdk1.2不安全,效率高如果使用有参构造器按照1.5倍扩容,如果是无参构造器。第一次扩容10.第二次开始按照1.5倍
Vector可变数组Object[]jdk1.0安全,效率不高如果是无参,默认是10,满后,按照2倍扩容。如果指定大小创建Vector,则每次按照2倍扩容
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值