zoukankan      html  css  js  c++  java
  • 集合框架-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存储元素自然排序和唯一的图解:

  • 相关阅读:
    python 多进程操作
    python 什么是全局解释器锁GIL
    cloudstack api调用python
    cloudstack模板
    微型计算器
    a++与=++a的区别
    js自调用函数的实现方式
    .net利用NPOI导入导出Excel
    linux系统下c程序分多文件实现
    线程的优先级
  • 原文地址:https://www.cnblogs.com/samuraihuang/p/9830040.html
Copyright © 2011-2022 走看看