zoukankan      html  css  js  c++  java
  • 【Java集合系列四】HashSet和LinkedHashSet解析

    2017-07-29 16:58:13

    一、简介

    1、Set概念

    Set可以理解为集合,非常类似数据概念中的集合,集合三大特征:1、确定性;2、互异性;3、无序性,因此Set实现类也有类似的特征。

    2、HashSet

    HashSet继承自AbstractSet,实现了Set接口,但是其源码非常少,也非常简单。内部使用HashMap来存储数据,数据存储在HashMap的key中,value都是同一个默认值:

    二、HashSet几个重要的方法

    1、add(E e)

    HashSet的确定性,也可以理解为唯一性,是通过HashMap的put方法来保证的,往HashMap中put数据时,如果key是一样的,只会替换key对应的value,不会新插入一条数据。所以往HashSet中add相同的元素没有什么用,这里的相同是通过equals方法保证的,具体的在HashMap中细说。

    2、remove(Object o)

    简单粗暴,从HashMap中移除一条数据。

    3、contains(Object o)

    4、iterator()

    5、其他

    其他的方法诸如:size()、isEmpty()、contains()、clear()等都完全委托给了HashMap。需要注意的是:HashSet没有提供set、get等方法。

     源码如下:

      1 package java.util;
      2 
      3 import java.io.InvalidObjectException;
      4 
      5 /**
      6  * This class implements the <tt>Set</tt> interface, backed by a hash table
      7  * (actually a <tt>HashMap</tt> instance).  It makes no guarantees as to the
      8  * iteration order of the set; in particular, it does not guarantee that the
      9  * order will remain constant over time.  This class permits the <tt>null</tt>
     10  * element.
     11  *
     12  * <p>This class offers constant time performance for the basic operations
     13  * (<tt>add</tt>, <tt>remove</tt>, <tt>contains</tt> and <tt>size</tt>),
     14  * assuming the hash function disperses the elements properly among the
     15  * buckets.  Iterating over this set requires time proportional to the sum of
     16  * the <tt>HashSet</tt> instance's size (the number of elements) plus the
     17  * "capacity" of the backing <tt>HashMap</tt> instance (the number of
     18  * buckets).  Thus, it's very important not to set the initial capacity too
     19  * high (or the load factor too low) if iteration performance is important.
     20  *
     21  * <p><strong>Note that this implementation is not synchronized.</strong>
     22  * If multiple threads access a hash set concurrently, and at least one of
     23  * the threads modifies the set, it <i>must</i> be synchronized externally.
     24  * This is typically accomplished by synchronizing on some object that
     25  * naturally encapsulates the set.
     26  *
     27  * If no such object exists, the set should be "wrapped" using the
     28  * {@link Collections#synchronizedSet Collections.synchronizedSet}
     29  * method.  This is best done at creation time, to prevent accidental
     30  * unsynchronized access to the set:<pre>
     31  *   Set s = Collections.synchronizedSet(new HashSet(...));</pre>
     32  *
     33  * <p>The iterators returned by this class's <tt>iterator</tt> method are
     34  * <i>fail-fast</i>: if the set is modified at any time after the iterator is
     35  * created, in any way except through the iterator's own <tt>remove</tt>
     36  * method, the Iterator throws a {@link ConcurrentModificationException}.
     37  * Thus, in the face of concurrent modification, the iterator fails quickly
     38  * and cleanly, rather than risking arbitrary, non-deterministic behavior at
     39  * an undetermined time in the future.
     40  *
     41  * <p>Note that the fail-fast behavior of an iterator cannot be guaranteed
     42  * as it is, generally speaking, impossible to make any hard guarantees in the
     43  * presence of unsynchronized concurrent modification.  Fail-fast iterators
     44  * throw <tt>ConcurrentModificationException</tt> on a best-effort basis.
     45  * Therefore, it would be wrong to write a program that depended on this
     46  * exception for its correctness: <i>the fail-fast behavior of iterators
     47  * should be used only to detect bugs.</i>
     48  *
     49  * <p>This class is a member of the
     50  * <a href="{@docRoot}/../technotes/guides/collections/index.html">
     51  * Java Collections Framework</a>.
     52  *
     53  * @param <E> the type of elements maintained by this set
     54  *
     55  * @author  Josh Bloch
     56  * @author  Neal Gafter
     57  * @see     Collection
     58  * @see     Set
     59  * @see     TreeSet
     60  * @see     HashMap
     61  * @since   1.2
     62  */
     63 
     64 public class HashSet<E>
     65     extends AbstractSet<E>
     66     implements Set<E>, Cloneable, java.io.Serializable
     67 {
     68     static final long serialVersionUID = -5024744406713321676L;
     69 
     70     private transient HashMap<E,Object> map;
     71 
     72     // Dummy value to associate with an Object in the backing Map
     73     private static final Object PRESENT = new Object();
     74 
     75     /**
     76      * Constructs a new, empty set; the backing <tt>HashMap</tt> instance has
     77      * default initial capacity (16) and load factor (0.75).
     78      */
     79     public HashSet() {
     80         map = new HashMap<>();
     81     }
     82 
     83     /**
     84      * Constructs a new set containing the elements in the specified
     85      * collection.  The <tt>HashMap</tt> is created with default load factor
     86      * (0.75) and an initial capacity sufficient to contain the elements in
     87      * the specified collection.
     88      *
     89      * @param c the collection whose elements are to be placed into this set
     90      * @throws NullPointerException if the specified collection is null
     91      */
     92     public HashSet(Collection<? extends E> c) {
     93         map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
     94         addAll(c);
     95     }
     96 
     97     /**
     98      * Constructs a new, empty set; the backing <tt>HashMap</tt> instance has
     99      * the specified initial capacity and the specified load factor.
    100      *
    101      * @param      initialCapacity   the initial capacity of the hash map
    102      * @param      loadFactor        the load factor of the hash map
    103      * @throws     IllegalArgumentException if the initial capacity is less
    104      *             than zero, or if the load factor is nonpositive
    105      */
    106     public HashSet(int initialCapacity, float loadFactor) {
    107         map = new HashMap<>(initialCapacity, loadFactor);
    108     }
    109 
    110     /**
    111      * Constructs a new, empty set; the backing <tt>HashMap</tt> instance has
    112      * the specified initial capacity and default load factor (0.75).
    113      *
    114      * @param      initialCapacity   the initial capacity of the hash table
    115      * @throws     IllegalArgumentException if the initial capacity is less
    116      *             than zero
    117      */
    118     public HashSet(int initialCapacity) {
    119         map = new HashMap<>(initialCapacity);
    120     }
    121 
    122     /**
    123      * Constructs a new, empty linked hash set.  (This package private
    124      * constructor is only used by LinkedHashSet.) The backing
    125      * HashMap instance is a LinkedHashMap with the specified initial
    126      * capacity and the specified load factor.
    127      *
    128      * @param      initialCapacity   the initial capacity of the hash map
    129      * @param      loadFactor        the load factor of the hash map
    130      * @param      dummy             ignored (distinguishes this
    131      *             constructor from other int, float constructor.)
    132      * @throws     IllegalArgumentException if the initial capacity is less
    133      *             than zero, or if the load factor is nonpositive
    134      */
    135     HashSet(int initialCapacity, float loadFactor, boolean dummy) {
    136         map = new LinkedHashMap<>(initialCapacity, loadFactor);
    137     }
    138 
    139     /**
    140      * Returns an iterator over the elements in this set.  The elements
    141      * are returned in no particular order.
    142      *
    143      * @return an Iterator over the elements in this set
    144      * @see ConcurrentModificationException
    145      */
    146     public Iterator<E> iterator() {
    147         return map.keySet().iterator();
    148     }
    149 
    150     /**
    151      * Returns the number of elements in this set (its cardinality).
    152      *
    153      * @return the number of elements in this set (its cardinality)
    154      */
    155     public int size() {
    156         return map.size();
    157     }
    158 
    159     /**
    160      * Returns <tt>true</tt> if this set contains no elements.
    161      *
    162      * @return <tt>true</tt> if this set contains no elements
    163      */
    164     public boolean isEmpty() {
    165         return map.isEmpty();
    166     }
    167 
    168     /**
    169      * Returns <tt>true</tt> if this set contains the specified element.
    170      * More formally, returns <tt>true</tt> if and only if this set
    171      * contains an element <tt>e</tt> such that
    172      * <tt>(o==null&nbsp;?&nbsp;e==null&nbsp;:&nbsp;o.equals(e))</tt>.
    173      *
    174      * @param o element whose presence in this set is to be tested
    175      * @return <tt>true</tt> if this set contains the specified element
    176      */
    177     public boolean contains(Object o) {
    178         return map.containsKey(o);
    179     }
    180 
    181     /**
    182      * Adds the specified element to this set if it is not already present.
    183      * More formally, adds the specified element <tt>e</tt> to this set if
    184      * this set contains no element <tt>e2</tt> such that
    185      * <tt>(e==null&nbsp;?&nbsp;e2==null&nbsp;:&nbsp;e.equals(e2))</tt>.
    186      * If this set already contains the element, the call leaves the set
    187      * unchanged and returns <tt>false</tt>.
    188      *
    189      * @param e element to be added to this set
    190      * @return <tt>true</tt> if this set did not already contain the specified
    191      * element
    192      */
    193     public boolean add(E e) {
    194         return map.put(e, PRESENT)==null;
    195     }
    196 
    197     /**
    198      * Removes the specified element from this set if it is present.
    199      * More formally, removes an element <tt>e</tt> such that
    200      * <tt>(o==null&nbsp;?&nbsp;e==null&nbsp;:&nbsp;o.equals(e))</tt>,
    201      * if this set contains such an element.  Returns <tt>true</tt> if
    202      * this set contained the element (or equivalently, if this set
    203      * changed as a result of the call).  (This set will not contain the
    204      * element once the call returns.)
    205      *
    206      * @param o object to be removed from this set, if present
    207      * @return <tt>true</tt> if the set contained the specified element
    208      */
    209     public boolean remove(Object o) {
    210         return map.remove(o)==PRESENT;
    211     }
    212 
    213     /**
    214      * Removes all of the elements from this set.
    215      * The set will be empty after this call returns.
    216      */
    217     public void clear() {
    218         map.clear();
    219     }
    220 
    221     /**
    222      * Returns a shallow copy of this <tt>HashSet</tt> instance: the elements
    223      * themselves are not cloned.
    224      *
    225      * @return a shallow copy of this set
    226      */
    227     @SuppressWarnings("unchecked")
    228     public Object clone() {
    229         try {
    230             HashSet<E> newSet = (HashSet<E>) super.clone();
    231             newSet.map = (HashMap<E, Object>) map.clone();
    232             return newSet;
    233         } catch (CloneNotSupportedException e) {
    234             throw new InternalError(e);
    235         }
    236     }
    237 
    238     /**
    239      * Save the state of this <tt>HashSet</tt> instance to a stream (that is,
    240      * serialize it).
    241      *
    242      * @serialData The capacity of the backing <tt>HashMap</tt> instance
    243      *             (int), and its load factor (float) are emitted, followed by
    244      *             the size of the set (the number of elements it contains)
    245      *             (int), followed by all of its elements (each an Object) in
    246      *             no particular order.
    247      */
    248     private void writeObject(java.io.ObjectOutputStream s)
    249         throws java.io.IOException {
    250         // Write out any hidden serialization magic
    251         s.defaultWriteObject();
    252 
    253         // Write out HashMap capacity and load factor
    254         s.writeInt(map.capacity());
    255         s.writeFloat(map.loadFactor());
    256 
    257         // Write out size
    258         s.writeInt(map.size());
    259 
    260         // Write out all elements in the proper order.
    261         for (E e : map.keySet())
    262             s.writeObject(e);
    263     }
    264 
    265     /**
    266      * Reconstitute the <tt>HashSet</tt> instance from a stream (that is,
    267      * deserialize it).
    268      */
    269     private void readObject(java.io.ObjectInputStream s)
    270         throws java.io.IOException, ClassNotFoundException {
    271         // Read in any hidden serialization magic
    272         s.defaultReadObject();
    273 
    274         // Read capacity and verify non-negative.
    275         int capacity = s.readInt();
    276         if (capacity < 0) {
    277             throw new InvalidObjectException("Illegal capacity: " +
    278                                              capacity);
    279         }
    280 
    281         // Read load factor and verify positive and non NaN.
    282         float loadFactor = s.readFloat();
    283         if (loadFactor <= 0 || Float.isNaN(loadFactor)) {
    284             throw new InvalidObjectException("Illegal load factor: " +
    285                                              loadFactor);
    286         }
    287 
    288         // Read size and verify non-negative.
    289         int size = s.readInt();
    290         if (size < 0) {
    291             throw new InvalidObjectException("Illegal size: " +
    292                                              size);
    293         }
    294 
    295         // Set the capacity according to the size and load factor ensuring that
    296         // the HashMap is at least 25% full but clamping to maximum capacity.
    297         capacity = (int) Math.min(size * Math.min(1 / loadFactor, 4.0f),
    298                 HashMap.MAXIMUM_CAPACITY);
    299 
    300         // Create backing HashMap
    301         map = (((HashSet<?>)this) instanceof LinkedHashSet ?
    302                new LinkedHashMap<E,Object>(capacity, loadFactor) :
    303                new HashMap<E,Object>(capacity, loadFactor));
    304 
    305         // Read in all elements in the proper order.
    306         for (int i=0; i<size; i++) {
    307             @SuppressWarnings("unchecked")
    308                 E e = (E) s.readObject();
    309             map.put(e, PRESENT);
    310         }
    311     }
    312 
    313     /**
    314      * Creates a <em><a href="Spliterator.html#binding">late-binding</a></em>
    315      * and <em>fail-fast</em> {@link Spliterator} over the elements in this
    316      * set.
    317      *
    318      * <p>The {@code Spliterator} reports {@link Spliterator#SIZED} and
    319      * {@link Spliterator#DISTINCT}.  Overriding implementations should document
    320      * the reporting of additional characteristic values.
    321      *
    322      * @return a {@code Spliterator} over the elements in this set
    323      * @since 1.8
    324      */
    325     public Spliterator<E> spliterator() {
    326         return new HashMap.KeySpliterator<E,Object>(map, 0, -1, 0, 0);
    327     }
    328 }
    View Code

    三、LinkedHashSet

    1、LinekdHashSet简介

    LinkedHashSet继承自HashSet,源码更少、更简单,唯一的区别是LinkedHashSet内部使用的是LinkHashMap。这样做的意义或者好处就是LinkedHashSet中的元素顺序是可以保证的,也就是说遍历序和插入序是一致的。

    2、Demo使用

    打印日志如上,HashSet和HashMap都不保证顺序,Link**能保证顺序。

    源码如下:

     1 public class MainSet {
     2     public static void main(String[] args) {
     3         Object value = new Object();
     4         HashMap<String, Object> hashMap = new HashMap<>();
     5         HashSet<String> hashSet = new HashSet<>();
     6         LinkedHashMap<String, Object> linkedHashMap = new LinkedHashMap<>();
     7         LinkedHashSet<String> linkedHashSet = new LinkedHashSet<>();
     8         hashSet.add("java");
     9         hashMap.put("java", value);
    10         linkedHashSet.add("java");
    11         linkedHashMap.put("java", value);
    12 
    13         hashSet.add("golang");
    14         hashMap.put("golang", value);
    15         linkedHashSet.add("golang");
    16         linkedHashMap.put("golang", value);
    17 
    18         hashSet.add("python");
    19         hashMap.put("python", value);
    20         linkedHashSet.add("python");
    21         linkedHashMap.put("python", value);
    22 
    23         hashSet.add("ruby");
    24         hashMap.put("ruby", value);
    25         linkedHashSet.add("ruby");
    26         linkedHashMap.put("ruby", value);
    27 
    28         hashSet.add("scala");
    29         hashMap.put("scala", value);
    30         linkedHashSet.add("scala");
    31         linkedHashMap.put("scala", value);
    32 
    33         hashSet.add("c");
    34         hashMap.put("c", value);
    35         linkedHashSet.add("c");
    36         linkedHashMap.put("c", value);
    37 
    38         System.out.println("默认插入序:
    java	golang	python	ruby	scala	c");
    39 
    40         System.out.println(" 
    HashSet:-------------------");
    41         for (String str : hashSet) {
    42             System.out.print(str + "	");
    43         }
    44 
    45         System.out.println(" 
    
    HashMap:-------------------");
    46         for (Map.Entry<String, Object> entry : hashMap.entrySet()) {
    47             System.out.print(entry.getKey() + "	");
    48         }
    49 
    50         System.out.println(" 
    
    LinkedHashSet:-------------------");
    51         for (String str : linkedHashSet) {
    52             System.out.print(str + "	");
    53         }
    54 
    55         System.out.println(" 
    
    LinkedHashMap:-------------------");
    56         for (Map.Entry<String, Object> entry : linkedHashMap.entrySet()) {
    57             System.out.print(entry.getKey() + "	");
    58         }
    59     }
    60 }
    View Code

    3、debug中奇异现象

    如上,我们明明添加了6个元素,但是table中只有5个,怎么回事呢?初步猜测应该是“c”元素和其中某一个元素位于同一个bucket,验证如下:

    我们发现,“java”竟然和“c”位于同一个bucket,他俩在同一个链表中。唯一的疑惑是:“c”是后加入的元素,按理说应该在链表的表头才对啊,这个问题还需要探究。

    同时这里也介绍了一种能debug到HashMap内部数据结构的方法,但是需要注意2个问题:

    1、需要在AS中设置一下,否则debug看到的信息不是这样的,如下图:

    2、直接使用table[3].next是不行的, 需要像图上那样,些完整的包名才行。

    4、继续debug

    如上所示,设置HashSet之后,同样能看到如下信息:

    看看LinkedHashMap,如下图,LinkedHashMap中多了head和tail,这是指向表头、表尾的指针,head指向“java”,tail指向“c”,这和我们的插入序保持一致,但是实际存储和之前是一样的。

     LinkedHashSet如下图:

  • 相关阅读:
    Java命名规范
    用U盘安装系统2
    将Sublime Text3添加到右键菜单中
    Git版本控制使用介绍
    Sublime Text各种插件使用方法
    安装Sublime Text 3插件
    Sublime Text修改显示图标
    卸载Visual Studio Code后删除右键Open with Code…
    做最好的自己(Be Your Personal Best)
    Eclipse全屏及插件下载
  • 原文地址:https://www.cnblogs.com/wlrhnh/p/7256969.html
Copyright © 2011-2022 走看看