集合框架-Set集合

代码:
Collection c = new ArrayList();
c.add("hello");
c.add("world");
c.add("java");

System.out.println(c);

为什么c输出的不是地址值呢?
A:Collection c = new ArrayList();
这是多态,所以输出c的toString()方法,其实是输出ArrayList的toString()
B:看ArrayList的toString()
而我们在ArrayList里面却没有发现toString()。
以后遇到这种情况,也不要担心,你认为有,它却没有,就应该去它父亲里面看看。
C:toString()的方法源码

 1         public String toString() {
 2             Iterator<E> it = iterator(); //集合本身调用迭代器方法,得到集合迭代器
 3             if (! it.hasNext())
 4                 return "[]";
 5     
 6             StringBuilder sb = new StringBuilder();
 7             sb.append('[');
 8             for (;;) {
 9                 E e = it.next(); //e=hello,world,java
10                 sb.append(e == this ? "(this Collection)" : e);
11                 if (! it.hasNext())
12                     //[hello, world, java]
13                     return sb.append(']').toString();
14                 sb.append(',').append(' ');
15             }
16         }

 

需求:用户登录注册案例。

 

假如用户类的内容比较对,将来维护起来就比较麻烦,为了更清晰的分类,我们就把用户又划分成了两类

用户基本描述类

成员变量:用户名,密码

构造方法:无参构造

成员方法:getXxx()/setXxx()

用户操作类

登录,注册

 

分包:
A:功能划分
B:模块划分
C:先按模块划分,再按功能划分

今天我们选择按照功能划分:
用户基本描述类包 cn.itcast.pojo
用户操作接口 cn.itcast.dao
用户操作类包 cn.itcast.dao.impl
今天是集合实现,过几天是IO实现,再过几天是GUI实现,就业班我们就是数据库实现
用户测试类 cn.itcast.test

 

// 为了让多个方法能够使用同一个集合,就把集合定义为成员变量
// 为了不让外人看到,用private
// 为了让多个对象共享同一个成员变量,用static
private static ArrayList<User> array = new ArrayList<User>();

 

* Collection
* |--List
* 有序(存储顺序和取出顺序一致),可重复
* |--Set
* 无序(存储顺序和取出顺序不一致),唯一
*
* HashSet:它不保证 set 的迭代顺序;特别是它不保证该顺序恒久不变。
* 注意:虽然Set集合的元素无序,但是,作为集合来说,它肯定有它自己的存储顺序,
* 而你的顺序恰好和它的存储顺序一致,这代表不了有序,你可以多存储一些数据,就能看到效果。

 1         // 创建集合对象
 2         Set<String> set = new HashSet<String>();
 3 
 4         // 创建并添加元素
 5         set.add("hello");
 6         set.add("java");
 7         set.add("world");
 8         set.add("java");
 9         set.add("world");
10 
11         // 增强for
12         for (String s : set) {
13             System.out.println(s);
14         }

 

* HashSet:存储字符串并遍历
* 问题:为什么存储字符串的时候,字符串内容相同的只存储了一个呢?
* 通过查看add方法的源码,我们知道这个方法底层依赖 两个方法:hashCode()和equals()。
* 步骤:
* 首先比较哈希值
* 如果相同,继续走,比较地址值或者走equals()
* 如果不同,就直接添加到集合中
* 按照方法的步骤来说:
* 先看hashCode()值是否相同
* 相同:继续走equals()方法
* 返回true: 说明元素重复,就不添加
* 返回false:说明元素不重复,就添加到集合
* 不同:就直接把元素添加到集合
* 如果类没有重写这两个方法,默认使用的Object()。一般来说不同相同。
* 而String类重写了hashCode()和equals()方法,所以,它就可以把内容相同的字符串去掉。只留下一个。

 

* 需求:存储自定义对象,并保证元素的唯一性
* 要求:如果两个对象的成员变量值都相同,则为同一个元素。
*
* 目前是不符合我的要求的:因为我们知道HashSet底层依赖的是hashCode()和equals()方法。
* 而这两个方法我们在学生类中没有重写,所以,默认使用的是Object类。
* 这个时候,他们的哈希值是不会一样的,根本就不会继续判断,执行了添加操作。

 

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

 1 interface Collection {
 2     ...
 3 }
 4 
 5 interface Set extends Collection {
 6     ...
 7 }
 8 
 9 class HashSet implements Set {
10     private static final Object PRESENT = new Object();
11     private transient HashMap<E,Object> map;
12     
13     public HashSet() {
14         map = new HashMap<>();
15     }
16     
17     public boolean add(E e) { //e=hello,world
18         return map.put(e, PRESENT)==null;
19     }
20 }
21 
22 class HashMap implements Map {
23     public V put(K key, V value) { //key=e=hello,world
24     
25         //看哈希表是否为空,如果空,就开辟空间
26         if (table == EMPTY_TABLE) {
27             inflateTable(threshold);
28         }
29         
30         //判断对象是否为null
31         if (key == null)
32             return putForNullKey(value);
33         
34         int hash = hash(key); //和对象的hashCode()方法相关
35         
36         //在哈希表中查找hash值
37         int i = indexFor(hash, table.length);
38         for (Entry<K,V> e = table[i]; e != null; e = e.next) {
39             //这次的e其实是第一次的world
40             Object k;
41             if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
42                 V oldValue = e.value;
43                 e.value = value;
44                 e.recordAccess(this);
45                 return oldValue;
46                 //走这里其实是没有添加元素
47             }
48         }
49 
50         modCount++;
51         addEntry(hash, key, value, i); //把元素添加
52         return null;
53     }
54     
55     transient int hashSeed = 0;
56     
57     final int hash(Object k) { //k=key=e=hello,
58         int h = hashSeed;
59         if (0 != h && k instanceof String) {
60             return sun.misc.Hashing.stringHash32((String) k);
61         }
62 
63         h ^= k.hashCode(); //这里调用的是对象的hashCode()方法
64 
65         // This function ensures that hashCodes that differ only by
66         // constant multiples at each bit position have a bounded
67         // number of collisions (approximately 8 at default load factor).
68         h ^= (h >>> 20) ^ (h >>> 12);
69         return h ^ (h >>> 7) ^ (h >>> 4);
70     }
71 }
72 
73 
74 hs.add("hello");
75 hs.add("world");
76 hs.add("java");
77 hs.add("world");

 

* LinkedHashSet:底层数据结构由哈希表和链表组成。
* 哈希表保证元素的唯一性。
* 链表保证元素有素。(存储和取出是一致)

 1         // 创建集合对象
 2         LinkedHashSet<String> hs = new LinkedHashSet<String>();
 3 
 4         // 创建并添加元素
 5         hs.add("hello");
 6         hs.add("world");
 7         hs.add("java");
 8         hs.add("world");
 9         hs.add("java");
10 
11         // 遍历
12         for (String s : hs) {
13             System.out.println(s);
14         }

 

* TreeSet:能够对元素按照某种规则进行排序。
* 排序有两种方式
* A:自然排序
* B:比较器排序
*
* TreeSet集合的特点:排序和唯一
*
* 通过观察TreeSet的add()方法,我们知道最终要看TreeMap的put()方法。

 

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

 1 interface Collection {...}
 2 
 3 interface Set extends Collection {...}
 4 
 5 interface NavigableMap {
 6 
 7 }
 8 
 9 class TreeMap implements NavigableMap {
10      public V put(K key, V value) {
11         Entry<K,V> t = root;
12         if (t == null) {
13             compare(key, key); // type (and possibly null) check
14 
15             root = new Entry<>(key, value, null);
16             size = 1;
17             modCount++;
18             return null;
19         }
20         int cmp;
21         Entry<K,V> parent;
22         // split comparator and comparable paths
23         Comparator<? super K> cpr = comparator;
24         if (cpr != null) {
25             do {
26                 parent = t;
27                 cmp = cpr.compare(key, t.key);
28                 if (cmp < 0)
29                     t = t.left;
30                 else if (cmp > 0)
31                     t = t.right;
32                 else
33                     return t.setValue(value);
34             } while (t != null);
35         }
36         else {
37             if (key == null)
38                 throw new NullPointerException();
39             Comparable<? super K> k = (Comparable<? super K>) key;
40             do {
41                 parent = t;
42                 cmp = k.compareTo(t.key);
43                 if (cmp < 0)
44                     t = t.left;
45                 else if (cmp > 0)
46                     t = t.right;
47                 else
48                     return t.setValue(value);
49             } while (t != null);
50         }
51         Entry<K,V> e = new Entry<>(key, value, parent);
52         if (cmp < 0)
53             parent.left = e;
54         else
55             parent.right = e;
56         fixAfterInsertion(e);
57         size++;
58         modCount++;
59         return null;
60     }
61 }
62 
63 class TreeSet implements Set {
64     private transient NavigableMap<E,Object> m;
65     
66     public TreeSet() {
67          this(new TreeMap<E,Object>());
68     }
69 
70     public boolean add(E e) {
71         return m.put(e, PRESENT)==null;
72     }
73 }
74 
75 真正的比较是依赖于元素的compareTo()方法,而这个方法是定义在 Comparable里面的。
76 所以,你要想重写该方法,就必须是先 Comparable接口。这个接口表示的就是自然排序。

 

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

 

* TreeSet集合保证元素排序和唯一性的原理
* 唯一性:是根据比较的返回是否是0来决定。
* 排序:
* A:自然排序(元素具备比较性)
* 让元素所属的类实现自然排序接口 Comparable
* B:比较器排序(集合具备比较性)
* 让集合的构造方法接收一个比较器接口的子类对象 Comparator

 1         // 如果一个方法的参数是接口,那么真正要的是接口的实现类的对象
 2         // 而匿名内部类就可以实现这个东西
 3         TreeSet<Student> ts = new TreeSet<Student>(new Comparator<Student>() {
 4             @Override
 5             public int compare(Student s1, Student s2) {
 6                 // 姓名长度
 7                 int num = s1.getName().length() - s2.getName().length();
 8                 // 姓名内容
 9                 int num2 = num == 0 ? s1.getName().compareTo(s2.getName())
10                         : num;
11                 // 年龄
12                 int num3 = num2 == 0 ? s1.getAge() - s2.getAge() : num2;
13                 return num3;
14             }
15         });

 

HashSet存储元素保证唯一性的代码及图解:

 

TreeSet存储元素自然排序和唯一的图解:

 

posted @ 2018-10-22 14:48  武士黄  阅读(242)  评论(0编辑  收藏  举报