• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2011 The Guava Authors
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5  * in compliance with the License. You may obtain a copy of the License at
6  *
7  * http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software distributed under the License
10  * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11  * or implied. See the License for the specific language governing permissions and limitations under
12  * the License.
13  */
14 
15 package com.google.common.collect;
16 
17 import static com.google.common.base.Preconditions.checkNotNull;
18 
19 import com.google.common.annotations.GwtCompatible;
20 import com.google.common.annotations.VisibleForTesting;
21 import com.google.common.base.Objects;
22 import com.google.common.collect.Multisets.ImmutableEntry;
23 import com.google.common.primitives.Ints;
24 import com.google.errorprone.annotations.concurrent.LazyInit;
25 import java.util.Arrays;
26 import java.util.Collection;
27 import javax.annotation.CheckForNull;
28 import org.checkerframework.checker.nullness.qual.Nullable;
29 
30 /**
31  * Implementation of {@link ImmutableMultiset} with zero or more elements.
32  *
33  * @author Jared Levy
34  * @author Louis Wasserman
35  */
36 @GwtCompatible(emulated = true, serializable = true)
37 @SuppressWarnings("serial") // uses writeReplace(), not default serialization
38 @ElementTypesAreNonnullByDefault
39 class RegularImmutableMultiset<E> extends ImmutableMultiset<E> {
40   private static final ImmutableEntry<?>[] EMPTY_ARRAY = new ImmutableEntry<?>[0];
41   static final ImmutableMultiset<Object> EMPTY = create(ImmutableList.<Entry<Object>>of());
42 
create(Collection<? extends Entry<? extends E>> entries)43   static <E> ImmutableMultiset<E> create(Collection<? extends Entry<? extends E>> entries) {
44     int distinct = entries.size();
45     @SuppressWarnings({"unchecked", "rawtypes"})
46     ImmutableEntry<E>[] entryArray = new ImmutableEntry[distinct];
47     if (distinct == 0) {
48       return new RegularImmutableMultiset<>(entryArray, EMPTY_ARRAY, 0, 0, ImmutableSet.of());
49     }
50     int tableSize = Hashing.closedTableSize(distinct, MAX_LOAD_FACTOR);
51     int mask = tableSize - 1;
52     @SuppressWarnings({"unchecked", "rawtypes"})
53     @Nullable
54     ImmutableEntry<E>[] hashTable = new @Nullable ImmutableEntry[tableSize];
55 
56     int index = 0;
57     int hashCode = 0;
58     long size = 0;
59     for (Entry<? extends E> entryWithWildcard : entries) {
60       @SuppressWarnings("unchecked") // safe because we only read from it
61       Entry<E> entry = (Entry<E>) entryWithWildcard;
62       E element = checkNotNull(entry.getElement());
63       int count = entry.getCount();
64       int hash = element.hashCode();
65       int bucket = Hashing.smear(hash) & mask;
66       ImmutableEntry<E> bucketHead = hashTable[bucket];
67       ImmutableEntry<E> newEntry;
68       if (bucketHead == null) {
69         boolean canReuseEntry =
70             entry instanceof ImmutableEntry && !(entry instanceof NonTerminalEntry);
71         newEntry =
72             canReuseEntry ? (ImmutableEntry<E>) entry : new ImmutableEntry<E>(element, count);
73       } else {
74         newEntry = new NonTerminalEntry<E>(element, count, bucketHead);
75       }
76       hashCode += hash ^ count;
77       entryArray[index++] = newEntry;
78       hashTable[bucket] = newEntry;
79       size += count;
80     }
81 
82     return hashFloodingDetected(hashTable)
83         ? JdkBackedImmutableMultiset.create(ImmutableList.asImmutableList(entryArray))
84         : new RegularImmutableMultiset<E>(
85             entryArray, hashTable, Ints.saturatedCast(size), hashCode, null);
86   }
87 
hashFloodingDetected(@ullable ImmutableEntry<?>[] hashTable)88   private static boolean hashFloodingDetected(@Nullable ImmutableEntry<?>[] hashTable) {
89     for (int i = 0; i < hashTable.length; i++) {
90       int bucketLength = 0;
91       for (ImmutableEntry<?> entry = hashTable[i]; entry != null; entry = entry.nextInBucket()) {
92         bucketLength++;
93         if (bucketLength > MAX_HASH_BUCKET_LENGTH) {
94           return true;
95         }
96       }
97     }
98     return false;
99   }
100 
101   /**
102    * Closed addressing tends to perform well even with high load factors. Being conservative here
103    * ensures that the table is still likely to be relatively sparse (hence it misses fast) while
104    * saving space.
105    */
106   @VisibleForTesting static final double MAX_LOAD_FACTOR = 1.0;
107 
108   /**
109    * Maximum allowed false positive probability of detecting a hash flooding attack given random
110    * input.
111    */
112   @VisibleForTesting static final double HASH_FLOODING_FPP = 0.001;
113 
114   /**
115    * Maximum allowed length of a hash table bucket before falling back to a j.u.HashMap based
116    * implementation. Experimentally determined.
117    */
118   @VisibleForTesting static final int MAX_HASH_BUCKET_LENGTH = 9;
119 
120   private final transient ImmutableEntry<E>[] entries;
121   private final transient @Nullable ImmutableEntry<?>[] hashTable;
122   private final transient int size;
123   private final transient int hashCode;
124 
125   @LazyInit @CheckForNull private transient ImmutableSet<E> elementSet;
126 
RegularImmutableMultiset( ImmutableEntry<E>[] entries, @Nullable ImmutableEntry<?>[] hashTable, int size, int hashCode, @CheckForNull ImmutableSet<E> elementSet)127   private RegularImmutableMultiset(
128       ImmutableEntry<E>[] entries,
129       @Nullable ImmutableEntry<?>[] hashTable,
130       int size,
131       int hashCode,
132       @CheckForNull ImmutableSet<E> elementSet) {
133     this.entries = entries;
134     this.hashTable = hashTable;
135     this.size = size;
136     this.hashCode = hashCode;
137     this.elementSet = elementSet;
138   }
139 
140   private static final class NonTerminalEntry<E> extends ImmutableEntry<E> {
141     private final ImmutableEntry<E> nextInBucket;
142 
NonTerminalEntry(E element, int count, ImmutableEntry<E> nextInBucket)143     NonTerminalEntry(E element, int count, ImmutableEntry<E> nextInBucket) {
144       super(element, count);
145       this.nextInBucket = nextInBucket;
146     }
147 
148     @Override
nextInBucket()149     public ImmutableEntry<E> nextInBucket() {
150       return nextInBucket;
151     }
152   }
153 
154   @Override
isPartialView()155   boolean isPartialView() {
156     return false;
157   }
158 
159   @Override
count(@heckForNull Object element)160   public int count(@CheckForNull Object element) {
161     @Nullable ImmutableEntry<?>[] hashTable = this.hashTable;
162     if (element == null || hashTable.length == 0) {
163       return 0;
164     }
165     int hash = Hashing.smearedHash(element);
166     int mask = hashTable.length - 1;
167     for (ImmutableEntry<?> entry = hashTable[hash & mask];
168         entry != null;
169         entry = entry.nextInBucket()) {
170       if (Objects.equal(element, entry.getElement())) {
171         return entry.getCount();
172       }
173     }
174     return 0;
175   }
176 
177   @Override
size()178   public int size() {
179     return size;
180   }
181 
182   @Override
elementSet()183   public ImmutableSet<E> elementSet() {
184     ImmutableSet<E> result = elementSet;
185     return (result == null) ? elementSet = new ElementSet<E>(Arrays.asList(entries), this) : result;
186   }
187 
188   @Override
getEntry(int index)189   Entry<E> getEntry(int index) {
190     return entries[index];
191   }
192 
193   @Override
hashCode()194   public int hashCode() {
195     return hashCode;
196   }
197 }
198