Java-30 Set接口

  • Collection(接口)
    • List(接口) 元素有序,不唯一

        List相关集合有三种遍历方式:
        1、结合size()和get()方法有普通for循环
        2、迭代器遍历方式
        3、增强for循环
      --ArrayList
        底层是数组,查询快,增删慢
        线程不安全,效率高
      --Vector
        底层是数组,查询快,增删慢
        线程是安全的,效率低
      --LinkedList
        底层是链表,查询慢,增删快
        线程是不安全的,效率高
      Set(接口) 元素唯一,无序
        Set相关集合有三种遍历方式:
        1、迭代器方式遍历
        2、增强for循环
      --HashSet
        底层依赖哈希表,依赖hashCode()和equals()方法来保证元素唯一的

        线程是不安全的,效率高
      --LinkedHashSet
        底层依赖哈希表和链表,哈希表保证元素唯一,链表保证元素有序。
      --TreeSet
        底层是红黑树(自平衡二叉树)
        排序依赖自然排序和比较器排序。

Hashset:底层依赖哈希表,线程是不安全的,效率高

       依赖hashCode()和equals()方法来保证元素唯一

import java.util.HashSet;

/*
       HashSet:此类实现Set接口,由哈希表(实际为HashMap实例)支持。线程不安全,效率高
       有时候,你给的顺序可能是正好与取出遍历顺序一致,或者运行多次都一样,但是这并不代表有序
       有可能是你给的元素比较少,可以多存一点看看

       通过观察add源码发现,要想知道是否将元素插入到集合,先看元素的HashCode()值是否相同
            如果相同,继续判断equals()方法
                返回true,说明元素的内容一样,重复了,就不添加
                返回false,说明元素的内容不重复,只是哈希值一样,就会添加集合中
            如果连哈希值都不一样,就直接添加到集合中
       由于String类重写了equals()方法,所以它这里比较的是内容,发现内容一样就不添加到集合
 */
public class SetDemo1 {
    public static void main(String[] args) {
        HashSet<String> strings = new HashSet<>();
        strings.add("hello");
        strings.add("world");
        strings.add("java");
        strings.add("hello");
        strings.add("world");
        System.out.println(strings);
    }
}

 

  HashSet中add确保元素唯一性源码分析

public interface Set<E> extends Collection<E>{

}

public class HashSet<E> implements Set<E>{
    public boolean add(E e) { //"hello"
        return map.put(e, PRESENT)==null;
    }
}

public class HashMap<K,V> implements Map<K,V>{

    //计算出传进来元素的哈希值
    static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); //80121
    }

    //底层调用的是HashMap中的put()方法,最终我们发现调用的是HashMap中的putVal()方法
    //hash()方法是计算元素的哈希值,它底层是hashCode()方法
    public V put(K key, V value) {//"hello"
        return putVal(hash(key), key, value, false, true);
    }

    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {

        //哈希表存储的是一个个的结点数组
        Node<K,V>[] tab; Node<K,V> p; int n, i;

        //判断哈希表是否初始化,如果没有初始话,我们就进行初始化
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;

        //根据元素对象计算好的哈希值再进行一次相关的计算,计算出该元素的存储位置
        //如果该元素的位置是null,表示该位置没有元素,可以存储
        //就创建新的结点,存储元素
        //通过观察源码发现,HashSet的add()方法一定和HashCode()有关系
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);

        else {
            Node<K,V> e; K k;

            //存入的元素和以前该位置的元素的哈希值进行比较
            //如果哈希值不同,继续向下执行,把元素添加集合中
            //如果哈希值一,会调用对象的equals()方法进行比较
            //如果比较equals()方法返回的是false,会继续向下执行,然后将元素插入到集合中
            //如果比较equals()方法返回的是true,说明元素的哈希值和内容都一样,表示元素重复了
            //其实也就是不赋值
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            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;
    }

}

 

 

LinkedHashSet:底层是哈希表和链表组成,线程是不安全的,效率高

        哈希表保证元素的唯一性
        链表保证的元素的顺序、有序(存储和取出的顺序一致)

import java.util.LinkedHashSet;

/*
LinkedHashSet:底层是哈希表和链表组成
            哈希表保证元素的唯一性
            链表保证的元素的顺序、有序(存储和取出的顺序一致)
 */
public class LinkeSerDemo {
    public static void main(String[] args) {
        LinkedHashSet<String> strings = new LinkedHashSet<>();
        strings.add("hello");
        strings.add("world");
        strings.add("bigdata");
        strings.add("java");
        strings.add("hello");
        strings.add("hello");

        for (String string : strings) {
            System.out.println(string);
        }

    }
}
/*
运行结果:
hello
world
bigdata
java
*/

 

TreeSet:底层数据结构是红黑树(红黑树是一种自平衡的二叉树),线程不安全,效率高

  • 请注意,此实现不同步 如果多个线程并发访问树,并且至少有一个线程修改该集合,则必须在外部进行同步。

  • 排序的方式有两种:

    自然排序 comparable (无参构造   传入的数据类型具备实现了Comparable接口)

    比较器排序 comparator  (在创建集合的时候,调用带参数的构造方法,这个参数是实现了Comparator接口的子类对象)

  • TreeSet的俩个特点:排序和唯一

     

      TreeSet中add源码分析:

public interface Collection<E>{
    ...
}

public interface Set<E> extends Collection<E>{
    ...
}

public abstract class AbstractSet<E> implements Set<E> {
    ...
}

public class TreeSet<E> extends AbstractSet<E> implements NavigableSet<E>{
    private transient NavigableMap<E,Object> m;

    public TreeSet() {
        this(new TreeMap<E,Object>());
    }

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


public class TreeMap<K,V> implements NavigableMap<K,V>{

    public TreeMap() {
        comparator = null;
    }

    public V put(K key, V value) {
        //造一个根
        Entry<K,V> t = root;
        //在还没有元素插入的时候,树的根是null
        //造一个实际的根出来
        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的值是null
        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 {
            //key就是想要添加到集合中的元素值
            if (key == null)
                throw new NullPointerException();
            @SuppressWarnings("unchecked")
                //String k = (String) key;
                //key本身例子是Integer,Integer类实现了Comparable < Integer >接口
                //所以可以进行向下转型
                //Comparable是自然比较
                Comparable<? super K> k = (Comparable<? super K>) key;
            do {
                parent = t;
                cmp = k.compareTo(t.key);
                if (cmp < 0)
                    t = t.left;
                else if (cmp > 0)
                    t = t.right;
                else
                    return t.setValue(value);
            } while (t != null);
        }
        Entry<K,V> e = new Entry<>(key, value, parent);
        if (cmp < 0)
            parent.left = e;
        else
            parent.right = e;
        fixAfterInsertion(e);
        size++;
        modCount++;
        return null;
    }
}  

     自然排序

/*
TreeSet: 元素唯一,且元素的顺序可以按照某种规则进行排序

        排序的方式有两种:
            自然排序     comparable   这里通过无参构造默认comparator为null
            比较器排序   comparator

        TreeSet的俩个特点:排序和唯一
 */
public class TreeSet {
    public static void main(String[] args) {
        //创建集合对象
        //构造一个新的,空的树组,根据其元素的自然排序进行排序
        //无参构造内部元素默认是自然排序,无参构造默认comparator为null
        java.util.TreeSet<Integer> integers = new java.util.TreeSet<>();
        integers.add(11);
        integers.add(23);
        integers.add(15);
        integers.add(8);
        integers.add(89);
        integers.add(11);
        integers.add(18);
        integers.add(23);

        System.out.println(integers);//[8, 11, 15, 18, 23, 89]
    }
}

    TreeSet集合存储自定义对象并遍历

/*
 TreeSet集合存储自定义对象并保证排序和唯一

        1、如果什么都没告诉我们排序规则,是什么排序?
            自然排序,按照年龄从小到大进行排序
        2、元素什么什么情况下才算唯一呢?
            成员变量的值都相同的情况算唯一的,就是同一个元素
 */
public class TreeSet1 {
    public static void main(String[] args) {
        //创建集合对象
        TreeSet<Student> students = new TreeSet<>();

        //创建学生类对象
        Student s1 = new Student("张三", 18);
        Student s2 = new Student("李四", 19);
        Student s3 = new Student("王五", 20);
        Student s4 = new Student("赵六", 17);
        Student s5 = new Student("田七", 25);
        Student s6 = new Student("张三", 19);

        //将学生类对象存入集合中
        students.add(s1);
        students.add(s2);
        students.add(s3);
        students.add(s4);
        students.add(s5);
        students.add(s6);

        //遍历集合
        for (Student student : students) {
            System.out.println(student.getName() + "---" + student.getAge());
        }
    }
}
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 String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    @Override
    public int compareTo(Student o) {
//        return 0;
//        return 1 ;
//        return -1;
        //这里返回什么,其实应该是根据我的规则来进行排序
        //我们明确知道是按照年龄的大小,从小到大排序
        //在分析的时候,要注意什么是主要条件,什么是次要条件,缺一不可
        int i = this.age - o.age;
        //如果年龄相同,不代表是同一个人,还要继续比较姓名是否相同
        int i1 = i == 0 ? this.name.compareTo(o.name):i;
        return i1;
    }
}

 

    • 比较器排序

       在创建集合的时候,调用带参数的构造方法,这个参数是实现了Comparator接口的子类对象

/*
 TreeSet集合保证元素的排序和唯一:
        保证唯一的原理:是根据比较的结果是否是0来决定的

        排序:
            1、自然排序(传入的数据类型具备实现了Comparable接口)
            2、比较器排序
                在创建集合的时候,调用带参数的构造方法,这个参数是实现了Comparator接口的
                子类对象
 */
import java.util.Comparator;
import java.util.TreeSet;
public class TreeSet3 {
    public static void main(String[] args) {
        //TreeSet(Comparator<? super E> comparator)
        //构造一个新的,空的树集,根据指定的比较器进行排序。
//        TreeSet<Student3> students = new TreeSet<>(new Mycomparator());


        //匿名内部类的形式实现Comparator接口 省去创建一个实现接口类MyComparator
        TreeSet<Student3> students = new TreeSet<>(new Comparator<Student3>() {
            @Override
            public int compare(Student3 o1, Student3 o2) {
//               return 0;
                int i = o1.getName().length() - o2.getName().length();
                //姓名的长度相同不代表内容也相同
                int i1 = i == 0 ? o1.getName().compareTo(o2.getName()) : i;
                //姓名的长度和内容都一样,但是年龄可能不一样
                //只有当姓名和年龄都一样的时候才判断为同一个人,才算重复
                int i2 = i1 == 0 ? o1.getAge() - o2.getAge() : i1;
                return i2;
            }
        });

        //创建学生类对象
        Student3 s1 = new Student3("张老三", 18);
        Student3 s2 = new Student3("李四", 19);
        Student3 s3 = new Student3("王五六七", 20);
        Student3 s4 = new Student3("赵六", 17);
        Student3 s5 = new Student3("田", 25);
        Student3 s6 = new Student3("张三", 19);

        //将学生类对象存入集合中
        students.add(s1);
        students.add(s2);
        students.add(s3);
        students.add(s4);
        students.add(s5);
        students.add(s6);

        //根据姓名的长度来排序
        for (Student3 student : students) {
            System.out.println(student);
        }
    }
}

 

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

    public Student3() {
    }

    public Student3(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 "Student2{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
import java.util.Comparator;

public class Mycomparator implements Comparator<Student3> {
    @Override
    public int compare(Student3 o1, Student3 o2) {
//        return 0;
        int i = o1.getName().length()-o2.getName().length();
        //姓名的长度相同不代表内容也相同
        int i1 = i == 0 ? o1.getName().compareTo(o2.getName()) : i;
        //姓名的长度和内容都一样,但是年龄可能不一样
        //只有当姓名和年龄都一样的时候才判断为同一个人,才算重复
        int i2 = i1 == 0 ? o1.getAge() - o2.getAge() : i1;
        return i2;
    }

}

 

题目:

键盘录入5个学生信息(姓名,语文成绩,数学成绩,英语成绩),
按照总分从高到低输出到控制台

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

/*
 键盘录入5个学生信息(姓名,语文成绩,数学成绩,英语成绩),
        按照总分从高到低输出到控制台

      1.定义学生类
      2.创建集合
 */
public class TreeSetText {
    public static void main(String[] args) {
        //创建一个TreeSet集合对象
        TreeSet<Student4> students = new TreeSet<>(new Comparator<Student4>() {
            @Override
            public int compare(Student4 o1, Student4 o2) {
                //总分从高到低
                int i = o2.getNum() - o1.getNum();
                //总分一样不一定语文成绩一样
                int i2 = i == 0 ? o1.getChinese() - o2.getChinese() : i;

                //总分一样不一定数学成绩一样
                int i3 = i2 == 0 ? o1.getMath() - o2.getMath() : i2;

                //总分一样不一定英语成绩一样
                int i4 = i3 == 0 ? o1.getEnglish() - o2.getEnglish() : i3;

                //各科成绩都一样的话,姓名不一定一样
                int i5 = i4 == 0 ? o1.getName().compareTo(o2.getName()) : i4;

                return i5;

            }
        });

        //创建Scanner对象
        Scanner sc = new Scanner(System.in);

        System.out.println("开始录入学生成绩");
        for (int i = 1; i < 6; i++) {
            System.out.println("请输入第 "+ i +" 个学生的姓名:");
            String name = sc.next();
            System.out.println("请输入学生 "+ name +" 的语文成绩:");
            int chinese = sc.nextInt();
            System.out.println("请输入学生 "+ name +" 的数学成绩:");
            int math = sc.nextInt();
            System.out.println("请输入学生 "+ name +" 的英语成绩:");
            int english = sc.nextInt();

            //创建学生对象,将输入值赋值给成员变量
            Student4 s = new Student4();
            s.setName(name);
            s.setChinese(chinese);
            s.setMath(math);
            s.setEnglish(english);

            //将学生对象添加到集合中
            students.add(s);

            if(i == 5){
                System.out.println("学生信息录入完毕!");
            }
        }

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

        System.out.println("学生信息如下:");
        System.out.println("姓名\t语文成绩\t数学成绩\t英语成绩\t总分");

        //遍历学生信息
        for (Student4 student : students) {
            System.out.println(student.getName()+"\t"+student.getChinese()+"\t"
                    +student.getMath()+"\t"+student.getEnglish()+"\t"+student.getNum());
        }


    }

}
public class Student4 {
    private String name;
    private int chinese;
    private int math;
    private int english;

    public Student4() {
    }

    public Student4(String name, int chinese, int math, int english) {
        this.name = name;
        this.chinese = chinese;
        this.math = math;
        this.english = english;
    }

    public String getName() {
        return name;
    }

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

    public int getChinese() {
        return chinese;
    }

    public void setChinese(int chinese) {
        this.chinese = chinese;
    }

    public int getMath() {
        return math;
    }

    public void setMath(int math) {
        this.math = math;
    }

    public int getEnglish() {
        return english;
    }

    public void setEnglish(int english) {
        this.english = english;
    }

    public int getNum() {
        return chinese + math + english;
    }

    @Override
    public String toString() {
        return "Student4{" +
                "name='" + name + '\'' +
                ", chinese=" + chinese +
                ", math=" + math +
                ", english=" + english +", 总分="+getNum()+
                '}';
    }
}

 

posted @ 2021-10-15 21:48  艺术派大星  阅读(67)  评论(0)    收藏  举报
levels of contents