java数据结构4--集合Set

 Set接口

 

Set接口用来表示:一个不包含“重复元素”的集合
Set接口中并没有定义特殊的方法,其方法多数都和Collection接口相同。

重复元素的理解
通常理解:拥有相同成员变量的对象称为相同的对象,如果它们出现在同一个集合中的话,称这个集合拥有重复的元素

HashSet中对重复元素的理解:和通常意义上的理解不太一样!
两个元素(对象)的hashCode返回值相同,并且equals返回值为true时(或者地址相同时),才称这两个元素是相同的。

TreeSet中对重复元素的理解:元素的compareTo方法或者集合的比较器compare方法返回值为0则认为这两个元素是相同的元素。

1 Set接口的方法

可知Set接口并没有比父类Collection接口提供更多的新方法。

2、HashSet类

线程不安全,存取速度快
它的大多数方法都和Collection相同
它不保证元素的迭代顺序;也不保证该顺序恒久不变
当HashSet中的元素超过一定数量时,会发生元素的顺序重新分配。

2.1HashSet构造方法

    public HashSet() {
        map = new HashMap<>();
    }    
    public HashSet(Collection<? extends E> c) {
        map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
        addAll(c);
    }
    public HashSet(int initialCapacity, float loadFactor) {
        map = new HashMap<>(initialCapacity, loadFactor);
    }
    public HashSet(int initialCapacity) {
        map = new HashMap<>(initialCapacity);
    }
    HashSet(int initialCapacity, float loadFactor, boolean dummy) {
        map = new LinkedHashMap<>(initialCapacity, loadFactor);
    }

看来set底层是HashMap

2.2成员变量

2.3成员方法

2.4HashSet如何保证元素唯一?

考查add(Object obj)方法的实现过程:

  1. 先调用obj的hashCode方法,计算哈希值(槽位值slot:bucket)
  2. 根据哈希值确定存放的位置
  3. 若位置上没有元素,则这个元素就是第一个元素,直接添加
  4. 若此位置上已经有元素,说明还有元素的hashCode方法返回值与它相同,则调用它的equals方法与已经存在的元素进行比较
  5. 若返回值为true,表明两个元素是“相同”的元素,不能添加
  6. 若返回值为false,表明两个元素是“不同”的元素,新元素将以链表的形式添加到集合中

    

import java.util.HashSet;

/*
 *自定义对象存储到HashSet中
 *
 *int hashCode:元素被添加时被调用,用于确认元素的槽位值
 *boolean equals:当发生碰撞时,调用被添加元素的equals方法和已经存在的元素进行比较,
 *    true:不能添加
 *    false:可以添加,多个元素占用一个槽位值.以链表形式存在.
 *
 */

class Worker{
//    static int i = 0;
    private String name;
    private int age;
    private String id;
    
    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;
    }
    public String getId() {
        return id;
    }
    public void setId(String id) {
        this.id = id;
    }
    public Worker(String name, int age, String id) {
        super();
        this.name = name;
        this.age = age;
        this.id = id;
    }
    public Worker() {
        super();
        // TODO Auto-generated constructor stub
    }
    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + age;
        result = prime * result + ((id == null) ? 0 : id.hashCode());
        result = prime * result + ((name == null) ? 0 : name.hashCode());
        return result;
    }
    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Worker other = (Worker) obj;
        if (age != other.age)
            return false;
        if (id == null) {
            if (other.id != null)
                return false;
        } else if (!id.equals(other.id))
            return false;
        if (name == null) {
            if (other.name != null)
                return false;
        } else if (!name.equals(other.name))
            return false;
        return true;
    }
    @Override
    public String toString() {
        return "Worker [name=" + name + ", age=" + age + ", id=" + id + "]";
    }
    
    //如何重写hashCode?尽量让所有的成员变量都参与到运算中.
    //name age id
    /*@Override
    public int hashCode() {
        int code1 = name.hashCode();
        int code2 = id.hashCode();
        return code1 * 3 + age + code2;
    }*/
    
    
    
}

public class HashSetDemo2 {

    public static void main(String[] args) {
        HashSet<Worker> set = new HashSet<>();
        
        Worker w1 = new Worker("tom1", 20, "001");
        Worker w2 = new Worker("tom1", 20, "001");
        Worker w3 = new Worker("tom2", 22, "003");
        Worker w4 = new Worker("tom2", 22, "003");
        Worker w5 = new Worker("tom3", 22, "003");//
        
        set.add(w1);
        set.add(w2);
        set.add(w3);
        set.add(w4);
        set.add(w5);
        
        for (Worker worker : set) {
            System.out.println(worker);
        }
        
    }

}
重写HashCode和equals方法

HashSet注意事项:
1.想要往HashSet中添加的对象,需要在定义类时,重写hashCode和equals方法
2.由于HashSet使用的是散列算法,所以,轻易不要在迭代集合元素的时候改变集合中的元素

2.5并发修改异常

import java.util.HashSet;
import java.util.Iterator;

/*
 * 演示HashSet并发修改异常
 */
public class HashSetDemo3 {

    public static void main(String[] args) {
        HashSet<String> set = new HashSet<String>();
        
        set.add("hello");
        set.add("hello2");
        set.add("world");
        set.add("world");
        
        Iterator<String> it = set.iterator();
        while(it.hasNext()){
            String str = it.next();
            if(str.equals("world")){
//                set.remove("world");//ConcurrentModificationException
                it.remove();
            }
        }
        
        for (String s : set) {
            System.out.println(s);
        }
        
        
        
    }

}
并发修改异常

 补充技能:

3、LinkedHashSet类

从后缀可以看出:其本质是HashSet,只不过在内部维护了一个链表,可以记住元素放入的顺序,这样就保证了存取的顺序,

但是正是由于多了链表,所以它的效率低些.

  • 如何保证元素唯一性:hashCode, equals方法
  • 如何保证存取顺序性:链表

没有特殊成员方法全部都是继承而来的。

4、TreeSet类

从图中可以看出:
TreeSet继承于AbstractSet,并且实现了NavigableSet接口。
TreeSet的本质是一个"有序的,并且没有重复元素"的集合,它是通过TreeMap实现的(见构造方法)。TreeSet中含有一个"NavigableMap类型的成员变量"m,而m实际上是"TreeMap的实例"。

4.1成员变量

4.2构造方法

public TreeSet() {
    this(new TreeMap<E,Object>());
}
public TreeSet(Comparator<? super E> comparator) {
    this(new TreeMap<>(comparator));
}
public TreeSet(Collection<? extends E> c) {
    this();
    addAll(c);
}
public TreeSet(SortedSet<E> s) {
    this(s.comparator());
    addAll(s);
}

4.3TreeSet排序

public static void demoOne() {
        TreeSet<Person> ts = new TreeSet<>();
        ts.add(new Person("张三", 11));
        ts.add(new Person("李四", 12));
        ts.add(new Person("王五", 15));
        ts.add(new Person("赵六", 21));
        
        System.out.println(ts);
    }
为什么会报错

 

  

案例String类:

 

匿名内部类改进:

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

/*
 * TreeSet对元素排序的原理:
 * 1.让元素具有比较性
 * 2.集合本身具有比较性
 * 
 * 取决于创建集合对象时使用的构造方法.
 * 
 * 
 */

class Student /* implements Comparable<Student> */ {

    private String name;
    private int 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;
    }

    public Student(String name, int age) {
        super();
        this.name = name;
        this.age = age;
    }

    public Student() {
        super();
        // TODO Auto-generated constructor stub
    }
    /*
     * @Override public int compareTo(Student o) { // 首要条件:按照年龄比较 int r1 = -(age
     * - o.getAge()); //次要条件:名字,它已经实现了Comparable接口 int r2 = (r1 == 0)?
     * -(name.compareTo(o.getName())) : r1; return r2; }
     */
}
/*
class MyComparator implements Comparator<Student> {

    @Override
    public int compare(Student o1, Student o2) {
        // o1--> this o2 --> other

        int r1 = o1.getAge() - o2.getAge();
        int r2 = (r1 == 0) ? o1.getName().compareTo(o2.getName()) : r1;
        return r2;
    }

}
*/

public class TreeSetDemo1 {

    public static void main(String[] args) {
        /*
         * // 没有传参,意味着使用元素本身的比较性. TreeSet<Student> ts = new TreeSet<Student>();
         * 
         * Student s1 = new Student("tom2", 12); Student s2 = new
         * Student("tom3", 13); Student s3 = new Student("tom2", 13); Student s4
         * = new Student("tom1", 12);
         * 
         * ts.add(s1); ts.add(s2); ts.add(s3); ts.add(s4);
         * 
         * // for (Student student : ts) { System.out.println(student.getName()
         * + "--" + student.getAge()); }
         * 
         */

        // 让集合具有比较性
//        TreeSet<Student> ts = new TreeSet<>(new MyComparator());
        TreeSet<Student> ts = new TreeSet<>(new Comparator<Student>() {
            @Override
            public int compare(Student o1, Student o2) {
                // o1--> this o2 --> other

                int r1 = o1.getAge() - o2.getAge();
                int r2 = (r1 == 0) ? o1.getName().compareTo(o2.getName()) : r1;
                return r2;
            }
            
        });
        

        Student s1 = new Student("tom2", 12);
        Student s2 = new Student("tom3", 13);
        Student s3 = new Student("tom2", 13);
        Student s4 = new Student("tom1", 12);

        ts.add(s1);
        ts.add(s2);
        ts.add(s3);
        ts.add(s4);

        for (Student s : ts) {
            System.out.println(s.getName() + "--" + s.getAge());
        }

    }

}
上面两个案例的代码

 练习:

 从键盘上录入3个学生的信息,包括语文,数学,英语的成绩三个成员变量,并根据总成绩进行排序

public class Student implements Comparable<Student>{
    private String name;
    private int ch;
    private int math;
    private int en;
    
    public Student(String name, int ch, int math, int en) {
        super();
        this.name = name;
        this.ch = ch;
        this.math = math;
        this.en = en;
    }
    public Student() {
        super();
        // TODO Auto-generated constructor stub
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getCh() {
        return ch;
    }
    public void setCh(int ch) {
        this.ch = ch;
    }
    public int getMath() {
        return math;
    }
    public void setMath(int math) {
        this.math = math;
    }
    public int getEn() {
        return en;
    }
    public void setEn(int en) {
        this.en = en;
    }
    
    public int getSum() {
        return ch + math + en;
    }
    
    @Override
    public int compareTo(Student s) {
        //首要条件
        int r1 = getSum() - s.getSum();
        //次要条件:语文成绩:
        int r2 = (r1 == 0)?getCh()-s.getCh():r1;
        //次次要条件:数学成绩:
        int r3 = (r2 == 0)?getMath() - s.getMath():r2;
        //次要条件:名字
        int r4 = (r3 == 0)?getName().compareTo(s.getName()):r3;
        return r4;
    }
    
    
}
Student 类
import java.util.Scanner;
import java.util.TreeSet;
public class Test{

    public static void main(String[] args) {
        TreeSet<Student> set = new TreeSet<Student>();
        
        Scanner sc = new Scanner(System.in);
        for(int i = 1;i<4;i++){
            System.out.print("输入第" + i + "个学生姓名 : ");
            String name = sc.next();
            System.out.print("输入第" + i + "个学生语文成绩 : ");
            int ch = sc.nextInt();
            System.out.print("输入第" + i + "个学生数学成绩 : ");
            int math = sc.nextInt();
            System.out.print("输入第" + i + "个学生英语成绩 : ");
            int en = sc.nextInt();
            
            Student stu = new Student(name, ch, math, en);
            
            set.add(stu);
        }
        
        //
        for (Student s : set) {
            System.out.println(s.getSum() +","+ s.getName() +","+ s.getCh() +","+ s.getMath() +","+ s.getEn());
        }

    }

}
Test

4.4顺序遍历

Iterator顺序遍历

for(Iterator iter = set.iterator(); iter.hasNext(); ) { 
    iter.next();
}   

Iterator顺序遍历

// 假设set是TreeSet对象
for(Iterator iter = set.descendingIterator(); iter.hasNext(); ) { 
    iter.next();
}

for-each遍历HashSet

// 假设set是TreeSet对象,并且set中元素是String类型
String[] arr = (String[])set.toArray(new String[0]);
for (String str:arr)
    System.out.printf("for each : %s
", str);

TreeSet不支持快速随机遍历,只能通过迭代器进行遍历!

4.5 方法的源码研究

以后写吧

原文地址:https://www.cnblogs.com/wqbin/p/11192280.html