Set、Map琐碎

集合框架能适应数据变化的需求,长度可以改变,集合只能是引用类型,常用的集合类:

Collection
        |--List
            |--ArrayList
            |--LinkedList
        |--Set
            |--HashSet
            |--LinkedHashSet
            |--TreeSet

HashSet

在List的集合中可以存储重复的值,Set集合正好相反,不能存储相同的值。
HashSet的add()方法依赖HashMap的put()方法。
在put()方法中会依赖 hashCode()和equals()这两个方法。
所以add()在添加元素时会走的逻辑是:

先看hashCode()值是否相同
    相同:继续走equals()方法
         返回true:说明元素重复,就不添加
         返回false:说明元素不重复,就添加到集合
     不同:就直接把元素添加到集合

add()方法的底层操作第一步比较hash值的时候是相同的,那下面每新添加一个元素时,都要和目前集合中的元素进行equals()的比较,这样效率低下。另外,返回hash值一样,这些添加的元素会在哈希表的同一个位置,保存方式类似一个链表的结构。

如果类没有重写这两个方法,默认使用的Object的。
String类重写了hashCode()和equals()方法,Set集合在添加String类型的元素时,它就可以把内容相同的字符串去掉,只留下一个。

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

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

   // 保证对象的hashCode()值不一样
    @Override
    public int hashCode() {  //自动生成
        final int prime = 31;
        int result = 1;
        result = prime * result + age; //基本类型 +值
        // 引用类型, 加哈希值
        result = prime * result + ((name == null) ? 0 : name.hashCode());
        return result;
    }
    
     @Override
     public boolean equals(Object obj) {
         if (this == obj) {
            return true;
         }
         if (!(obj instanceof Student)) {
            return false;
         }
         Student s = (Student) obj;
         return this.name.equals(s.name) && this.age == s.age;
     }
    
     @Override
     public String toString() {
        return "Student [name=" + name + ", age=" + age + "]";
     }
    

}

上面的Student类重写了equals()和hashCode()方法。
在使用Set时:

    HashSet hs = new HashSet();
    Student s1 = new Student("jiaobuchong", 22);
    hs.add(s1);

保证不会添加进去重复的Student类。

LinkedHashSet

底层数据结构由哈希表和链表组成。
哈希表保证元素的唯一, 链表保证元素有序, 存储和取出一致.

TreeSet

元素会排好序,并唯一。
TreeSet的add()方法依赖于TreeMap()的put()方法。底层数据结构是红黑树(是一个自平衡的二叉树)。根据比较的返回值是否是0来决定元素的唯一性。

  • 排序方法
  • 自然排序(按自然数从小到大的排序)
  • 自定义排序
自定义排序
  • 实现Comparable接口
public class Student implements Comparable{
    private String name;
    private int age;

    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;   只加入了一个元素,程序认为加入的都是同一个元素,所以只add了一个元素
        // return 1;   表示后面添加的元素比前面的大
        // return -1;  表示后面添加的元素比前面的小

        // 按照年龄排序,主要条件
        int num = this.age - s.age;
        // 次要条件
        // 年龄相同的时候,还得去看姓名是否也相同
        // 如果年龄和姓名都相同,才是同一个元素
        int num1 = (num == 0) ? this.name.compareTo(s.name) : num;
        return num1;
    }
}

//--------------------------------------------------
   TreeSet ts = new TreeSet<>();
   Student s1 = new Student("jiaobuchong", 22);
   Student s2 = new Student("jack", 23);
   ts.add(s1);
   ts.add(s2);

ts中的Student元素按照年龄排序,通过compareTo()方法比较返回值是0的元素不会添加到TreeSet中。

  • 自定义比较器 Comparator

      // 匿名内部内灵活性较高
          TreeSet ts = new TreeSet(new Comparator() {    //new 后面是类名或接口名,使用一次,这种实现较好
              @Override
              public int compare(Student s1, Student s2) {
                  // 姓名长度
                  int num = s1.getName().length() - s2.getName().length();
                  // 姓名内容
                  int num2 = num == 0 ? s1.getName().compareTo(s2.getName())
                          : num;
                  // 年龄
                  int num3 = num2 == 0 ? s1.getAge() - s2.getAge() : num2;
                  return num3;
              }
          });
          
          // 使用lambda表达式
            TreeSet ts = new TreeSet<>((o1, o2) -> {
              int num = o1.getName().length() - o2.getName().length();
              // 姓名的长度相同,不代表姓名的内容相同
              int num2 = num == 0 ? o1.getName().compareTo(o2.getName()) : num;
              // 姓名的长度和内容相同,不代表年龄相同,所以还得继续判断年龄
              int num3 = num2 == 0 ? o1.getAge() - o2.getAge() : num2;
              return num3;
          });
          
    

使用操作集合的工具类Collections排序:

// list是一个保存Student对象的List,传一个比较器Comparator
// 可以使用lambda表达式
Collections.sort(list, new Comparator() {
            @Override
            public int compare(Student s1, Student s2) {
                int num = s2.getAge() - s1.getAge();
                int num2 = num == 0 ? s1.getName().compareTo(s2.getName())
                        : num;
                return num2;
            }
        });

Map

Map常用的类:

    Map
     |--HashMap
     |--TreeMap
     |--LinkedHashMap

Map集合的数据结构仅仅针对键有效,与值无关。
存储的是键值对形式的元素,键唯一,值可重复。

HashMap

底层数据结构是哈希表,线程不安全的,效率高
哈希表依赖两个方法:HashCode()和equals()方法
执行顺序和(前面叙述的Set集合是依赖于Map的):

首先判断hashCode()值是否相同
    是:继续执行equals(),看其返回值
        true:说明元素重复,不添加
        false:就直接添加到集合
    否:直接添加到集合中
    
    // 一般做法:自动生成这两个方法即可
LinkedHashMap
  • 底层数据结构是链表和哈希表组成。
  • 链表保证存储有序
  • 哈希表保证元素唯一性
TreeMap

底层数据结构是红黑树(是一种自平衡的二叉树)

  • 如何保证元素唯一性?
  • 根据比较的返回值是否是0来决定
  • 如何保证元素排序?
    • 两种方式
    • 自然排序(元素局部比较性)
      • 让元素所属的类实现Comparable接口
    • 比较器排序(集合具备比较性)
      * 让集合接收一个Comparator比较器对象

你可能感兴趣的