Java_集合09_集合(Set)

集合(Set)

1.    Set集合的特点

Set:不保存元素加入的顺序(存储顺序和取出顺序不一定一致),不能保存重复值

|--    HashSet:底层数据结构是哈希表(是一个元素为链表的数组)。集合中的元素根据哈希值进行排序(好像不是)。创建集合时可以指定集合的长度,长度不够时默认以75%的比例增加长度。

|--    TreeSet类:底层数据结构是红黑树(是一个自平衡的二叉树)。集合中的元素按照某种规则进行排序。创建集合不能指定集合的长度。

public class SetDemo {

    public static void main(String[] args) {

        // 创建集合对象

        Set<String> set = new HashSet<String>();

 

        // 创建并添加元素

        set.add("hello");

        set.add("java");

        set.add("world");

        set.add("java");

        set.add("world");

 

        // 增强for

        for (String s : set) {

            System.out.println(s);// java \n world \n hello \n

        }

    }

}

2.    HashSet集合:它不保证 set 的迭代顺序;特别是它不保证该顺序恒久不变。此类允许使用 null 元素。

A:底层数据结构是哈希表(是一个元素为链表的数组)

B:哈希表底层依赖两个方法:hashCode()和equals()

执行顺序:

首先比较哈希值是否相同

相同:继续执行equals()方法

返回true:元素重复了,不添加

返回false:直接把元素添加到集合

不同:就直接把元素添加到集合

C:如何保证元素唯一性的呢?

由hashCode()和equals()保证的

D:开发的时候,代码非常的简单,自动生成即可。

E:HashSet存储字符串并遍历

F:HashSet存储自定义对象并遍历(对象的成员变量值相同即为同一个元素)

/*****************************************************************************************************************************************/

/*

* HashSet:存储字符串并遍历

* 问题:为什么存储字符串的时候,字符串内容相同的只存储了一个呢?

* 通过查看add方法的源码,我们知道这个方法底层依赖 两个方法:hashCode()equals()

* 步骤:

*         首先比较哈希值

*         如果相同,继续走,比较地址值或者走equals()

*         如果不同,就直接添加到集合中    

* 按照方法的步骤来说:    

*         先看hashCode()值是否相同

*             相同:继续走equals()方法

*                 返回true    说明元素重复,就不添加

*                 返回false:说明元素不重复,就添加到集合

*             不同:就直接把元素添加到集合

* 如果类没有重写这两个方法,默认使用的Object()。一般来说不会相同。

* String类重写了hashCode()equals()方法,所以,它就可以把内容相同的字符串去掉。只留下一个。

*/

public class HashSetDemo {

    public static void main(String[] args) {

        // 创建集合对象

        HashSet<String> hs = new HashSet<String>();

 

        // 创建并添加元素

        hs.add("hello");

        hs.add("world");

        hs.add("java");

        hs.add("world");

 

        // 遍历集合

        for (String s : hs) {

            System.out.println(s);// world \n java \n hello \n

        }

    }

}

public class HashCodeDemo {

    public static void main(String[] args) {

        System.out.println("hello".hashCode());// 99162322

        System.out.println("hello".hashCode());// 99162322

        System.out.println("world".hashCode());// 113318802

    }

}

 

HashSet集合的add()方法的源码解析:

interface Collection {

    ...

}

 

interface Set extends Collection {

    ...

}

 

class HashSet implements Set {

    private static final Object PRESENT = new Object();

    private transient HashMap<E,Object> map;

    

    public HashSet() {

        map = new HashMap<>();

    }

    

    public boolean add(E e) { //e=hello,world

return map.put(e, PRESENT)==null;

}

}

 

class HashMap implements Map {

    public V put(K key, V value) { //key=e=hello,world

      

        //看哈希表是否为空,如果空,就开辟空间

if (table == EMPTY_TABLE) {

inflateTable(threshold);

}

 

//判断对象是否为null

if (key == null)

return putForNullKey(value);

 

int hash = hash(key); //和对象的hashCode()方法相关

 

//在哈希表中查找hash

int i = indexFor(hash, table.length);

for (Entry<K,V> e = table[i]; e != null; e = e.next) {

    //这次的e其实是第一次的world

Object k;

if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {

V oldValue = e.value;

e.value = value;

e.recordAccess(this);

return oldValue;

//走这里其实是没有添加元素

}

}

 

modCount++;

addEntry(hash, key, value, i); //把元素添加

return null;

}

 

transient int hashSeed = 0;

 

final int hash(Object k) { //k=key=e=hello,

int h = hashSeed;

if (0 != h && k instanceof String) {

return sun.misc.Hashing.stringHash32((String) k);

}

 

h ^= k.hashCode(); //这里调用的是对象的hashCode()方法

 

// This function ensures that hashCodes that differ only by

// constant multiples at each bit position have a bounded

// number of collisions (approximately 8 at default load factor).

h ^= (h >>> 20) ^ (h >>> 12);

return h ^ (h >>> 7) ^ (h >>> 4);

}

}

 

 

hs.add("hello");

hs.add("world");

hs.add("java");

hs.add("world");

/*****************************************************************************************************************************************/

/*

* 需求:存储自定义对象,并保证元素的唯一性

* 要求:如果两个对象的成员变量值都相同,则为同一个元素。

*

* 目前是不符合我的要求的:因为我们知道HashSet底层依赖的是hashCode()equals()方法。

* 而这两个方法我们在学生类中没有重写,所以,默认使用的是Object类。

* 这个时候,他们的哈希值是不会一样的,根本就不会继续判断,执行了添加操作。

*/

public class HashSetDemo2 {

    public static void main(String[] args) {

        // 创建集合对象

        HashSet<Student> hs = new HashSet<Student>();

 

        // 创建学生对象

        Student s1 = new Student("林青霞", 27);

        Student s2 = new Student("柳岩", 22);

        Student s3 = new Student("王祖贤", 30);

        Student s4 = new Student("林青霞", 27);

        Student s5 = new Student("林青霞", 20);

        Student s6 = new Student("范冰冰", 22);

 

        // 添加元素

        hs.add(s1);

        hs.add(s2);

        hs.add(s3);

        hs.add(s4);

        hs.add(s5);

        hs.add(s6);

 

        // 遍历集合

        for (Student s : hs) {

            System.out.println(s.getName() + "---" + s.getAge());

        }

    }

}

public class Student {

    private String name;

    private int age;

    public Student() {}

    public Student(String name, int age) {

        super();

        this.name = name;

        this.age = age;

    }

    //getXxx()/setXxx()

}

/*****************************************************************************************************************************************/

 

 

/*****************************************************************************************************************************************/

/*

* LinkedHashSet:底层数据结构由哈希表和链表组成。

* 哈希表保证元素的唯一性。

* 链表保证元素有序。(存储和取出是一致)

*/

public class LinkedHashSetDemo {

    public static void main(String[] args) {

        // 创建集合对象

        LinkedHashSet<String> hs = new LinkedHashSet<String>();

 

        // 创建并添加元素

        hs.add("hello");

        hs.add("world");

        hs.add("java");

        hs.add("world");

        hs.add("java");

 

        // 遍历

        for (String s : hs) {

            System.out.println(s);// hello \n world \n java \n

        }

    }

}

/*****************************************************************************************************************************************/

3.    TreeSet集合:使用元素的自然顺序对元素进行排序,或者根据创建 set 时提供的 Comparator 进行排序,具体取决于使用的构造方法。(详情看API:TreeSet的构造方法)

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

B:保证元素的排序方式

a:自然排序(元素具备比较性)

存储的元素所属的类实现自然排序接口:Comparable接口

b:比较器排序(集合具备比较性)

让集合构造方法接收Comparator的实现类对象

C:把代码看一遍即可

/*****************************************************************************************************************************************/

public class TreeSetDemo {

    public static void main(String[] args) {

        // 创建集合对象

        // 自然顺序进行排序

        TreeSet<Integer> ts = new TreeSet<Integer>(); // 存储的元素所属的类Integer以实现了Comparable<Integer>接口

 

        // 创建元素并添加

        // 20,18,23,22,17,24,19,18,24

        ts.add(20);

        ts.add(18);

        ts.add(23);

        ts.add(22);

        ts.add(17);

        ts.add(24);

        ts.add(19);

        ts.add(18);

        ts.add(24);

 

        // 遍历

        for (Integer i : ts) {

            System.out.println(i);

        }

    }

}

-------------------------------------------------------------------------------------------------------------------

TreeSet的add()方法的源码解析:

interface Collection {...}

 

interface Set extends Collection {...}

 

interface NavigableMap {

 

}

 

class TreeMap implements NavigableMap {

     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();

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;

}

}

 

class TreeSet implements Set {

    private transient NavigableMap<E,Object> m;

    

    public TreeSet() {

         this(new TreeMap<E,Object>());

    }

 

    public boolean add(E e) {

return m.put(e, PRESENT)==null;

}

}

 

真正的比较是依赖于元素的compareTo()方法,而这个方法是定义在 Comparable里面的。

所以,你要想重写该方法,就必须是先 Comparable接口。这个接口表示的就是自然排序。

---------------------------------------------------------------------------------------------------------------------------------------------------------

TreeSet存储元素自然排序和唯一(元素不重复)的图解:

/*****************************************************************************************************************************************/

/*

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

*         要求:自然排序,按照年龄从小到大排序

*             成员变量值都相同即为同一个元素

* 如果Student不实现Comparable接口就会抛出以下异常

* java.lang.ClassCastException: cn.cast.Student cannot be cast to java.lang.Comparable

*/

public class TreeSetDemo2 {

    public static void main(String[] args) {

        // 创建集合对象

        TreeSet<Student> ts = new TreeSet<Student>();

 

        // 创建元素

        Student s1 = new Student("l__", 27);

        Student s2 = new Student("z__", 29);

        Student s3 = new Student("w__", 23);

        Student s4 = new Student("l__", 27); // 重此为复数据

        Student s5 = new Student("l__", 22);

        Student s6 = new Student("w__", 40);

        Student s7 = new Student("f__", 22);

        Student s8 = new Student("G__", 22);

 

        // 添加元素

        ts.add(s1);

        ts.add(s2);

        ts.add(s3);

        ts.add(s4);

        ts.add(s5);

        ts.add(s6);

        ts.add(s7);

        ts.add(s8);

 

        // 遍历

        for (Student s : ts) {

            System.out.println(s.getName() + "---" + s.getAge());

        }

    }

}

/*

* 如果一个类的元素要想能够进行自然排序,就必须实现自然排序接口存储的元素所属的类

*/

public class Student implements Comparable<Student> {

    private String name;

    private int age;

    public Student() {}

    public Student(String name, int age) {

        super();

        this.name = name;

        this.age = age;

    }

    // getXxx()/setXxx()...

 

    public int compareTo(Student s) {

        // return 0;// 由源码可知,返回0说明元素相同,后面的元素不会被添加,所以只添加第一个。

        // return 1;// 由源码可知,返回1说明新添加的元素是最大的,所以放在树的最右边,按添加顺序输出。

        // return -1;// 由源码可知,返回-1说明新添加的元素是最小的,所以放在树的最左边,按添加倒序输出。

 

        // 这里返回什么,其实应该根据我的排序规则来做

        // 按照年龄排序,主要条件

        int num = this.age - s.age;

        // return num;// 如果年龄相同,就会返回0,并没有判断姓名。不符合要求。

        

        // 次要条件

        // 年龄相同的时候,还得去看姓名是否也相同。如果年龄和姓名都相同,才是同一个元素

        int num2 = num == 0 ? this.name.compareTo(s.name) : num;

        //String类实现了Comparable接口,按字典顺序比较两个字符

        return num2;

    }

}

/*****************************************************************************************************************************************/

/*

* 需求:请按照姓名的长度排序

*/

public class TreeSetDemo {

    public static void main(String[] args) {

        // 创建集合对象

        TreeSet<Student> ts = new TreeSet<Student>();

 

        // 创建元素

        Student s1 = new Student("l", 27);

        Student s2 = new Student("zhang", 29);

        Student s3 = new Student("wangl", 23);

        Student s4 = new Student("linqing", 29);

        Student s5 = new Student("linqing", 22);

        Student s6 = new Student("liu", 22);

        Student s7 = new Student("wuqilong", 40);

        Student s8 = new Student("linqingxia", 29);

 

        // 添加元素

        ts.add(s1);

        ts.add(s2);

        ts.add(s3);

        ts.add(s4);

        ts.add(s5);

        ts.add(s6);

        ts.add(s7);

        ts.add(s8);

 

        // 遍历

        for (Student s : ts) {

            System.out.println(s.getName() + "---" + s.getAge());

        }

    }

}

/*

* 如果一个类的元素要想能够进行自然排序,就必须实现自然排序接口

*/

public class Student implements Comparable<Student> {

    private String name;

    private int age;

    public Student() {}

    public Student(String name, int age) {

        super();

        this.name = name;

        this.age = age;

    }

    // getXxx()/setXxx()...

 

    public int compareTo(Student s) {

        // 主要条件 姓名的长度

        int num = this.name.length() - s.name.length();

        // 姓名的长度相同,不代表姓名的内容相同

        int num2 = num == 0 ? this.name.compareTo(s.name) : num;

        // 姓名的长度和内容相同,不代表年龄相同,所以还得继续判断年龄

        int num3 = num2 == 0 ? this.age - s.age : num2;

        return num3;

    }

}

如果很多地方使用Student类,要求的排序方式又不同,那就没办法实现了。这时就需要使用比较器排序,看下面的例子

/*****************************************************************************************************************************************/

/*

* 需求:请按照姓名的长度排序

*

* TreeSet集合保证元素排序和唯一性的原理

*         唯一性:是根据比较的返回是否是0来决定。

*         排序:

*             A:自然排序(元素具备比较性)

*                 让存储的元素所属的类实现自然排序接口 Comparable

*             B:比较器排序(集合具备比较性)

*                 让集合的构造方法接收一个比较器接口(Comparator)的子类对象

*/

public class TreeSetDemo {

    public static void main(String[] args) {

        // 创建集合对象

        // TreeSet<Student> ts = new TreeSet<Student>(); //自然排序

        // public TreeSet(Comparator<? super E> comparator)//比较器排序

        // TreeSet<Student> ts = new TreeSet<Student>(new MyComparator());

 

        // 如果一个方法的参数是接口,那么真正要的是接口的实现类的对象

        // 而匿名内部类就可以实现这个东西,没必要单独写一个类,而且那样灵活度也不高。

        // 而且比较器排序的匿名内部类实现,要比上面(自然排序)例子Student类实现Comparable接口要好,因为如果很多地方使用Student类,要求的排序方式又不同,那就没办法实现了。该种方式灵活度更高。

        TreeSet<Student> ts = new TreeSet<Student>(new Comparator<Student>() {

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

            }

        });

 

        // 创建元素

        Student s1 = new Student("l", 27);

        Student s2 = new Student("zhang", 29);

        Student s3 = new Student("wangl", 23);

        Student s4 = new Student("linqing", 29);

        Student s5 = new Student("linqing", 22);

        Student s6 = new Student("liu", 22);

        Student s7 = new Student("wuqilong", 40);

        Student s8 = new Student("linqingxia", 29);

 

        // 添加元素

        ts.add(s1);

        ts.add(s2);

        ts.add(s3);

        ts.add(s4);

        ts.add(s5);

        ts.add(s6);

        ts.add(s7);

        ts.add(s8);

 

        // 遍历

        for (Student s : ts) {

            System.out.println(s.getName() + "---" + s.getAge());

        }

    }

}

----------------------

public class MyComparator implements Comparator<Student> {

    public int compare(Student s1, Student s2) {

        // int num = this.name.length() - s.name.length();

        // this -- s1

        // s -- 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;

    }

}

----------------------

public class Student {

    private String name;

    private int age;

    public Student() {}

    public Student(String name, int age) {

        super();

        this.name = name;

        this.age = age;

    }

    // getXxx()/setXxx()...

}

/*****************************************************************************************************************************************/

4.    案例:

A:获取无重复的随机数

B:键盘录入学生按照总分从高到底输出

 

/*****************************************************************************************************************************************/

案例A:

/*

* 编写一个程序,获取10120的随机数,要求随机数不能重复。

*

* 分析:

*         A:创建随机数对象

*         B:创建一个HashSet集合

*         C:判断集合的长度是不是小于10

*             是:就创建一个随机数添加

*             否:不搭理它

*         D:遍历HashSet集合

*/

public class HashSetDemo {

    public static void main(String[] args) {

        // 创建随机数对象

        Random r = new Random();

 

        // 创建一个Set集合

        HashSet<Integer> ts = new HashSet<Integer>();

 

        // 判断集合的长度是不是小于10

        while (ts.size() < 10) {

            int num = r.nextInt(20) + 1;

            ts.add(num);

        }

 

        // 遍历Set集合

        for (Integer i : ts) {

            System.out.println(i);

        }

    }

}

/*****************************************************************************************************************************************/

案例B:

/*

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

*

* 分析:

*         A:定义学生类

*         B:创建一个TreeSet集合

*         C:总分从高到底如何实现呢?        

*         D:键盘录入5个学生信息

*         E:遍历TreeSet集合

*/

public class TreeSetDemo {

    public static void main(String[] args) {

        // 创建一个TreeSet集合

        TreeSet<Student> ts = new TreeSet<Student>(new Comparator<Student>() {

            @Override

            public int compare(Student s1, Student s2) {

                // 总分从高到低

                int num = s2.getSum() - s1.getSum();

                // 总分相同的不一定语文相同

                int num2 = num == 0 ? s1.getChinese() - s2.getChinese() : num;

                // 总分相同的不一定数序相同

                int num3 = num2 == 0 ? s1.getMath() - s2.getMath() : num2;

                // 总分相同的不一定英语相同

                int num4 = num3 == 0 ? s1.getEnglish() - s2.getEnglish() : num3;

                // 姓名还不一定相同呢

                int num5 = num4 == 0 ? s1.getName().compareTo(s2.getName()) : num4;

                return num5;

            }

        });

 

        System.out.println("学生信息录入开始");

        // 键盘录入5个学生信息

        for (int x = 1; x <= 5; x++) {

            Scanner sc = new Scanner(System.in);

            System.out.println("请输入第" + x + "个学生的姓名:");

            String name = sc.nextLine();

            System.out.println("请输入第" + x + "个学生的语文成绩:");

            String chineseString = sc.nextLine();

            System.out.println("请输入第" + x + "个学生的数学成绩:");

            String mathString = sc.nextLine();

            System.out.println("请输入第" + x + "个学生的英语成绩:");

            String englishString = sc.nextLine();

 

            // 把数据封装到学生对象中

            Student s = new Student();

            s.setName(name);

            s.setChinese(Integer.parseInt(chineseString));

            s.setMath(Integer.parseInt(mathString));

            s.setEnglish(Integer.parseInt(englishString));

 

            // 把学生对象添加到集合

            ts.add(s);

        }

        System.out.println("学生信息录入完毕");

 

        System.out.println("学习信息从高到低排序如下:");

        System.out.println("姓名\t语文成绩\t数学成绩\t英语成绩");

        // 遍历集合

        for (Student s : ts) {

            System.out.println(s.getName() + "\t" + s.getChinese() + "\t" + s.getMath() + "\t" + s.getEnglish());

        }

    }

}

public class Student {

    private String name;// 姓名

    private int chinese;// 语文成绩

    private int math;// 数学成绩

    private int english;// 英语成绩

    public Student() {}

    public Student(String name, int chinese, int math, int english) {

        super();

        this.name = name;

        this.chinese = chinese;

        this.math = math;

        this.english = english;

    }

    //getXxx()/setXxx()...

}

/*****************************************************************************************************************************************/

 

 

 

猜你喜欢

转载自www.cnblogs.com/zhaolanqi/p/9261701.html