• Guava源码学习(四)新集合类型


    基于版本:Guava 22.0

    Wiki:New collection types

    0. 简介

    Guava提供了很多好用的集合工具,比如Multiset和BiMap,本文介绍了这些新集合类型的使用方式与实现原理。

    1. Multiset

      a. 简介

      一般的Set会对相同元素去重,而Multiset则会记下某个元素重复出现的次数。可以理解为Multiset内部维护了一个HashMap,对每个元素的重复次数进行计数,每次插入或者删除元素,都会更新这个HashMap。

      b. Multiset类图

      c. Multiset接口

      int size();
      int count(@Nullable @CompatibleWith("E") Object element);
      int add(@Nullable E element, int occurrences);
      int remove(@Nullable @CompatibleWith("E") Object element, int occurrences);
      int setCount(E element, int count);
      boolean setCount(E element, int oldCount, int newCount);
      Set<E> elementSet();
      Set<Entry<E>> entrySet();
      default void forEachEntry(ObjIntConsumer<? super E> action)
      boolean equals(@Nullable Object object);
      int hashCode();
      String toString();
      Iterator<E> iterator();
      boolean contains(@Nullable Object element);
      boolean containsAll(Collection<?> elements);
      boolean add(E element);
      boolean remove(@Nullable Object element);
      boolean removeAll(Collection<?> c);
      boolean retainAll(Collection<?> c);
      default void forEach(Consumer<? super E> action) 
      default Spliterator<E> spliterator()

      Multiset的子类很多,后续只介绍最有代表性的HashMultiset的实现

      d. HashMultiset的类图

      

      e. HashMultiset.add方法

    Multiset.add  
    @CanIgnoreReturnValue @Override
    boolean add(E element); AbstractMultiset.add @CanIgnoreReturnValue @Override public boolean add(@Nullable E element) { add(element, 1); return true; } @CanIgnoreReturnValue @Override public int add(@Nullable E element, int occurrences) { throw new UnsupportedOperationException(); }
    AbstractMapBasedMultiset.add @CanIgnoreReturnValue @Override
    public int add(@Nullable E element, int occurrences) { if (occurrences == 0) { return count(element); } checkArgument(occurrences > 0, "occurrences cannot be negative: %s", occurrences); Count frequency = backingMap.get(element); int oldCount; if (frequency == null) { oldCount = 0; backingMap.put(element, new Count(occurrences)); } else { oldCount = frequency.get(); long newCount = (long) oldCount + (long) occurrences; checkArgument(newCount <= Integer.MAX_VALUE, "too many occurrences: %s", newCount); frequency.add(occurrences); } size += occurrences; return oldCount; }

       从add方法中,我们就能看出HashMultiset的基本逻辑:内部维护了一个Map,每次add key的时候,更新Map中key对应的value(计数器)

    2. BiMap

      a. 简介

      一般的Map是维护了从key到value的单向映射,某些场景下我们可能会需要双向映射。一般的做法是同时维护两个Map,一个Map的key是另外一个Map的value。但是这样麻烦而且容易出错。为了解决这一需求,Guava提供了BiMap接口与若干实现类。

      b. BiMap类图

      c. BiMap接口

      

      V put(@Nullable K key, @Nullable V value);
    
      /**
       * An alternate form of {@code put} that silently removes any existing entry
       * with the value {@code value} before proceeding with the {@link #put}
       * operation. If the bimap previously contained the provided key-value
       * mapping, this method has no effect.
       *
       * <p>Note that a successful call to this method could cause the size of the
       * bimap to increase by one, stay the same, or even decrease by one.
       *
       * <p><b>Warning:</b> If an existing entry with this value is removed, the key
       * for that entry is discarded and not returned.
       *
       * @param key the key with which the specified value is to be associated
       * @param value the value to be associated with the specified key
       * @return the value which was previously associated with the key, which may
       *     be {@code null}, or {@code null} if there was no previous entry
       */
      @CanIgnoreReturnValue
      @Nullable
      V forcePut(@Nullable K key, @Nullable V value);
    
      void putAll(Map<? extends K, ? extends V> map);
      Set<V> values();
    
      /**
       * Returns the inverse view of this bimap, which maps each of this bimap's
       * values to its associated key. The two bimaps are backed by the same data;
       * any changes to one will appear in the other.
       *
       * <p><b>Note:</b>There is no guaranteed correspondence between the iteration
       * order of a bimap and that of its inverse.
       *
       * @return the inverse view of this bimap
       */
      BiMap<V, K> inverse();

    跟一般的Map的区别在于加黑标出的forcePut与inverse方法

    forcePut:在BiMap中,如果你想把键映射到已经存在的值,会抛出IllegalArgumentException异常。如果对特定值,你想要强制替换它的键,需要使用forcePut方法

    inverse:返回的BiMap是原BiMap的反转

      d. HashBiMap的基本原理

      内部维护了两个等长Entry数组hashTableKToV与hashTableVToK,采用链地址法解决哈希冲突,每次操作会同时维护这两个数组。在插入hashTableVToK时如果value已经存在且不为强制更新,则抛出异常。

      e. HashBiMap.put

      private V put(@Nullable K key, @Nullable V value, boolean force) {
        int keyHash = smearedHash(key);
        int valueHash = smearedHash(value);
    
        BiEntry<K, V> oldEntryForKey = seekByKey(key, keyHash);//去hashTableKToV里根据key寻找Entry
        if (oldEntryForKey != null
            && valueHash == oldEntryForKey.valueHash
            && Objects.equal(value, oldEntryForKey.value)) {
          return value;//Entry已经存在,无需更新,函数可以直接返回
        }
    
        BiEntry<K, V> oldEntryForValue = seekByValue(value, valueHash);//去hashTableVToK里根据value选择Entry
        if (oldEntryForValue != null) {
          if (force) {//如果是强制更新,则删除关联的Entry
            delete(oldEntryForValue);
          } else {//抛出错误,否则会出现一个value对应多个key的情况,inverse后无法处理
            throw new IllegalArgumentException("value already present: " + value);
          }
        }
    
        BiEntry<K, V> newEntry = new BiEntry<K, V>(key, keyHash, value, valueHash);//创建新BiEntry
        if (oldEntryForKey != null) {//更新key对应的value的情况
          delete(oldEntryForKey);//先删除老的BiEntry
          insert(newEntry, oldEntryForKey);//插入新的BiEntry
          oldEntryForKey.prevInKeyInsertionOrder = null;
          oldEntryForKey.nextInKeyInsertionOrder = null;
          rehashIfNecessary();//扩容
          return oldEntryForKey.value;
        } else {//插入新键值对的情况
          insert(newEntry, null);
          rehashIfNecessary();//扩容
          return null;
        }
      }
    
      private void delete(BiEntry<K, V> entry) {
        int keyBucket = entry.keyHash & mask;//删除hashTableKToV中的Entry
        BiEntry<K, V> prevBucketEntry = null;
        for (BiEntry<K, V> bucketEntry = hashTableKToV[keyBucket];
            true;
            bucketEntry = bucketEntry.nextInKToVBucket) {
          if (bucketEntry == entry) {
            if (prevBucketEntry == null) {
              hashTableKToV[keyBucket] = entry.nextInKToVBucket;
            } else {
              prevBucketEntry.nextInKToVBucket = entry.nextInKToVBucket;
            }
            break;
          }
          prevBucketEntry = bucketEntry;
        }
    
        int valueBucket = entry.valueHash & mask;//删除hashTableVToK中的Entry
        prevBucketEntry = null;
        for (BiEntry<K, V> bucketEntry = hashTableVToK[valueBucket];
            true;
            bucketEntry = bucketEntry.nextInVToKBucket) {
          if (bucketEntry == entry) {
            if (prevBucketEntry == null) {
              hashTableVToK[valueBucket] = entry.nextInVToKBucket;
            } else {
              prevBucketEntry.nextInVToKBucket = entry.nextInVToKBucket;
            }
            break;
          }
          prevBucketEntry = bucketEntry;
        }
    
        if (entry.prevInKeyInsertionOrder == null) {
          firstInKeyInsertionOrder = entry.nextInKeyInsertionOrder;
        } else {
          entry.prevInKeyInsertionOrder.nextInKeyInsertionOrder = entry.nextInKeyInsertionOrder;
        }
    
        if (entry.nextInKeyInsertionOrder == null) {
          lastInKeyInsertionOrder = entry.prevInKeyInsertionOrder;
        } else {
          entry.nextInKeyInsertionOrder.prevInKeyInsertionOrder = entry.prevInKeyInsertionOrder;
        }
    
        size--;
        modCount++;
      }
    
      private void insert(BiEntry<K, V> entry, @Nullable BiEntry<K, V> oldEntryForKey) {
        int keyBucket = entry.keyHash & mask;
        entry.nextInKToVBucket = hashTableKToV[keyBucket];
        hashTableKToV[keyBucket] = entry;
    
        int valueBucket = entry.valueHash & mask;
        entry.nextInVToKBucket = hashTableVToK[valueBucket];
        hashTableVToK[valueBucket] = entry;
    
        if (oldEntryForKey == null) {
          entry.prevInKeyInsertionOrder = lastInKeyInsertionOrder;
          entry.nextInKeyInsertionOrder = null;
          if (lastInKeyInsertionOrder == null) {
            firstInKeyInsertionOrder = entry;
          } else {
            lastInKeyInsertionOrder.nextInKeyInsertionOrder = entry;
          }
          lastInKeyInsertionOrder = entry;
        } else {
          entry.prevInKeyInsertionOrder = oldEntryForKey.prevInKeyInsertionOrder;
          if (entry.prevInKeyInsertionOrder == null) {
            firstInKeyInsertionOrder = entry;
          } else {
            entry.prevInKeyInsertionOrder.nextInKeyInsertionOrder = entry;
          }
          entry.nextInKeyInsertionOrder = oldEntryForKey.nextInKeyInsertionOrder;
          if (entry.nextInKeyInsertionOrder == null) {
            lastInKeyInsertionOrder = entry;
          } else {
            entry.nextInKeyInsertionOrder.prevInKeyInsertionOrder = entry;
          }
        }
    
        size++;
        modCount++;
      }

      f. HashBiMap.inverse

      @Override
      public BiMap<V, K> inverse() {
        return (inverse == null) ? inverse = new Inverse() : inverse;
      }
    
    
      private final class Inverse extends IteratorBasedAbstractMap<V, K>
          implements BiMap<V, K>, Serializable {
        BiMap<K, V> forward() {
          return HashBiMap.this;//原来的正向的HashBiMap
        }
    
        @Override
        public int size() {
          return size;
        }
    
        @Override
        public void clear() {
          forward().clear();//调用原来的HashBiMap的方法
        }
    
        @Override
        public boolean containsKey(@Nullable Object value) {
          return forward().containsValue(value);
        }
    
        @Override
        public K get(@Nullable Object value) {
          return Maps.keyOrNull(seekByValue(value, smearedHash(value)));
        }
    
        @CanIgnoreReturnValue
        @Override
        public K put(@Nullable V value, @Nullable K key) {
          return putInverse(value, key, false);
        }
    
        @Override
        public K forcePut(@Nullable V value, @Nullable K key) {
          return putInverse(value, key, true);
        }
    
        @Override
        public K remove(@Nullable Object value) {
          BiEntry<K, V> entry = seekByValue(value, smearedHash(value));
          if (entry == null) {
            return null;
          } else {
            delete(entry);
            entry.prevInKeyInsertionOrder = null;
            entry.nextInKeyInsertionOrder = null;
            return entry.key;
          }
        }
    
        @Override
        public BiMap<K, V> inverse() {
          return forward();//直接返回原HashBiMap
        }
    
        @Override
        public Set<V> keySet() {
          return new InverseKeySet();
        }
        ....
    }
  • 相关阅读:
    CF Round #569 Div2(contest1180)
    HY中考游记
    Luogu P2309 loidc,卖卖萌
    点击按钮使用window.open打开页面后,再次点击按钮会再打开一个页面,如何解决?
    line-height:150%/1.5em与line-height:1.5的区别
    table使用display:block时会多出一条边框
    让360双核浏览器默认极速模式,避免采用兼容模式
    使用nodejs安装http-server
    行内元素内边距对左右下起作用,外边距只对左右起作用
    微信小程序示例
  • 原文地址:https://www.cnblogs.com/stevenczp/p/7278111.html
Copyright © 2020-2023  润新知