Appendix-Understanding-equals-and-hashCode
附录: 理解equals 和hashCode 方法
假设有一个容器使用
equals 规范
当你创建一个类的时候,它自动继承自
// equalshashcode/DefaultComparison.java
class DefaultComparison {
private int i, j, k;
DefaultComparison(int i, int j, int k) {
this.i = i;
this.j = j;
this.k = k;
}
public static void main(String[] args) {
DefaultComparison
a = new DefaultComparison(1, 2, 3),
b = new DefaultComparison(1, 2, 3);
System.out.println(a == a);
System.out.println(a == b);
}
}
/*
Output:
true
false
*/
通常你会希望放宽这个限制。一般来说如果两个对象有相同的类型和相同的字段,你会认为这两个对象相等,但也会有一些你不想加入
一个合适的
- 反身性:对于任何
x ,x.equals(x) 应该返回 true 。 - 对称性:对于任何
x 和 y ,x.equals(y) 应该返回 true 当且仅当y.equals(x) 返回 true 。 - 传递性:对于任何
x , y , 还有z ,如果x.equals(y) 返回 true 并且 y.equals(z) 返回 true ,那么x.equals(z) 应该返回 true 。 - 一致性:对于任何
x 和y ,在对象没有被改变的情况下,多次调用x.equals(y) 应该总是返回 true 或者 false 。 - 对于任何非
null 的x ,x.equals(null) 应该返回false 。
下面是满足这些条件的测试,并且判断对象是否和自己相等(我们这里称呼其为右值
- 如果右值是
null ,那么不相等。 - 如果右值是
this ,那么两个对象相等。 - 如果右值不是同一个类型或者子类,那么两个对象不相等。
- 如果所有上面的检查通过了,那么你必须决定 右值 中的哪些字段是重要的,然后比较这些字段。
Java 7 引入了Objects 类型来帮助这个流程,这样我们能够写出更好的 equals() 函数。
下面的例子比较了不同类型的
// equalshashcode/EqualityFactory.java
import java.util.*;
interface EqualityFactory {
Equality make(int i, String s, double d);
}
现在我们来定义
// equalshashcode/Equality.java
import java.util.*;
public class Equality {
protected int i;
protected String s;
protected double d;
public Equality(int i, String s, double d) {
this.i = i;
this.s = s;
this.d = d;
System.out.println("made 'Equality'");
}
@Override
public boolean equals(Object rval) {
if(rval == null)
return false;
if(rval == this)
return true;
if(!(rval instanceof Equality))
return false;
Equality other = (Equality)rval;
if(!Objects.equals(i, other.i))
return false;
if(!Objects.equals(s, other.s))
return false;
if(!Objects.equals(d, other.d))return false;
return true;
}
public void test(String descr, String expected, Object rval) {
System.out.format("-- Testing %s --%n" + "%s instanceof Equality: %s%n" +
"Expected %s, got %s%n",
descr, descr, rval instanceof Equality,
expected, equals(rval));
}
public static void testAll(EqualityFactory eqf) {
Equality
e = eqf.make(1, "Monty", 3.14),
eq = eqf.make(1, "Monty", 3.14),
neq = eqf.make(99, "Bob", 1.618);
e.test("null", "false", null);
e.test("same object", "true", e);
e.test("different type",
"false", Integer.valueOf(99));e.test("same values", "true", eq);
e.test("different values", "false", neq);
}
public static void main(String[] args) {
testAll( (i, s, d) -> new Equality(i, s, d));
}
}
/*
Output:
made 'Equality'
made 'Equality'
made 'Equality'
-- Testing null --
null instanceof Equality: false
Expected false, got false
-- Testing same object --
same object instanceof Equality: true
Expected true, got true
-- Testing different type --
different type instanceof Equality: false
Expected false, got false-- Testing same values --
same values instanceof Equality: true
Expected true, got true
-- Testing different values --
different values instanceof Equality: true
Expected false, got false
*/
在
上述的
instanceof 检查减少了null 检查的需要。- 和
this 的比较是多余的。一个正确书写的equals() 函数能正确地和自己比较。
因为
// equalshashcode/SuccinctEquality.java
import java.util.*;
public class SuccinctEquality extends Equality {
public SuccinctEquality(int i, String s, double d) {
super(i, s, d);
System.out.println("made 'SuccinctEquality'");
}
@Override
public boolean equals(Object rval) {
return rval instanceof SuccinctEquality &&
Objects.equals(i, ((SuccinctEquality)rval).i) &&
Objects.equals(s, ((SuccinctEquality)rval).s) &&
Objects.equals(d, ((SuccinctEquality)rval).d);
}
public static void main(String[] args) {
Equality.testAll( (i, s, d) ->
new SuccinctEquality(i, s, d));
}
}
/* Output:
made 'Equality'
made 'SuccinctEquality'
made 'Equality'
made 'SuccinctEquality'
made 'Equality'
made 'SuccinctEquality'
-- Testing null --
null instanceof Equality: false
Expected false, got false
-- Testing same object --
same object instanceof Equality: true
Expected true, got true
-- Testing different type --
different type instanceof Equality: false
Expected false, got false
-- Testing same values --
same values instanceof Equality: true
Expected true, got true
-- Testing different values --different values instanceof Equality: true
Expected false, got false
*/
对于每个
// equalshashcode/ComposedEquality.java
import java.util.*;
class Part {
String ss;
double dd;
Part(String ss, double dd) {
this.ss = ss;
this.dd = dd;
}
@Override
public boolean equals(Object rval) {
return rval instanceof Part &&
Objects.equals(ss, ((Part)rval).ss) &&
Objects.equals(dd, ((Part)rval).dd);
}
}
public class ComposedEquality extends SuccinctEquality {
Part part;
public ComposedEquality(int i, String s, double d) {
super(i, s, d);
part = new Part(s, d);
System.out.println("made 'ComposedEquality'");
}
@Override
public boolean equals(Object rval) {
return rval instanceof ComposedEquality &&
super.equals(rval) &&
Objects.equals(part,
((ComposedEquality)rval).part);
}
public static void main(String[] args) {
Equality.testAll( (i, s, d) ->
new ComposedEquality(i, s, d));
}
}
/*
Output:
made 'Equality'
made 'SuccinctEquality'
made 'ComposedEquality'
made 'Equality'
made 'SuccinctEquality'
made 'ComposedEquality'
made 'Equality'
made 'SuccinctEquality'
made 'ComposedEquality'
-- Testing null --null instanceof Equality: false
Expected false, got false
-- Testing same object --
same object instanceof Equality: true
Expected true, got true
-- Testing different type --
different type instanceof Equality: false
Expected false, got false
-- Testing same values --
same values instanceof Equality: true
Expected true, got true
-- Testing different values --
different values instanceof Equality: true
Expected false, got false
*/
注意
不同子类的相等性
继承意味着两个不同子类的对象当其向上转型的时候可以是相等的。假设你有一个
我们通过
// equalshashcode/SubtypeEquality.java
import java.util.*;
enum Size { SMALL, MEDIUM, LARGE }
class Animal {
private static int counter = 0;
private final int id = counter++;
private final String name;
private final Size size;
Animal(String name, Size size) {
this.name = name;
this.size = size;
}
@Override
public boolean equals(Object rval) {
return rval instanceof Animal &&
// Objects.equals(id, ((Animal)rval).id) && // [1]
Objects.equals(name, ((Animal)rval).name) &&
Objects.equals(size, ((Animal)rval).size);
}
@Override
public int hashCode() {
return Objects.hash(name, size);
// return Objects.hash(name, size, id); // [2]
}
@Override
public String toString() {
return String.format("%s[%d]: %s %s %x",
getClass().getSimpleName(), id,
name, size, hashCode());
}
}
class Dog extends Animal {
Dog(String name, Size size) {
super(name, size);
}
}
class Pig extends Animal {
Pig(String name, Size size) {
super(name, size);
}
}
public class SubtypeEquality {
public static void main(String[] args) {
Set<Animal> pets = new HashSet<>();
pets.add(new Dog("Ralph", Size.MEDIUM));
pets.add(new Pig("Ralph", Size.MEDIUM));
pets.forEach(System.out::println);
}
}
/*
Output:
Dog[0]: Ralph MEDIUM a752aeee
*/
如果我们只考虑类型的话,某些情况下它的确说得通——只从基类的角度看待问题,这是李氏替换原则的基石。这个代码完美符合替换理论因为派生类没有添加任何额外不再基类中的额外函数。派生类只是在表现上不同,而不是在接口上
但是当我们提供了两个有着相同数据的不同的对象类型,然后将他们放置在
旁注: 在
hashCode() 中,如果你只能够使用一个字段,使用Objcets.hashCode() 。如果你使用多个字段,那么使用Objects.hash() 。
我们也可以通过标准方式,将
// equalshashcode/SubtypeEquality2.java
import java.util.*;
class Dog2 extends Animal {
Dog2(String name, Size size) {
super(name, size);
}
@Override
public boolean equals(Object rval) {
return rval instanceof Dog2 &&super.equals(rval);
}
}
class Pig2 extends Animal {
Pig2(String name, Size size) {
super(name, size);
}
@Override
public boolean equals(Object rval) {
return rval instanceof Pig2 &&
super.equals(rval);
}
}
public class SubtypeEquality2 {
public static void main(String[] args) {
Set<Animal> pets = new HashSet<>();
pets.add(new Dog2("Ralph", Size.MEDIUM));
pets.add(new Pig2("Ralph", Size.MEDIUM));
pets.forEach(System.out::println);
}
}
/*
Output:
Dog2[0]: Ralph MEDIUM a752aeee
Pig2[1]: Ralph MEDIUM a752aeee
*/
注意
一种说法是
哈希和哈希码
在 集合 章节中,我们使用预先定义的类作为
当创建自己的类作为
// equalshashcode/Groundhog.java
// Looks plausible, but doesn't work as a HashMap key
public class Groundhog {
protected int number;
public Groundhog(int n) { number = n; }
@Override
public String toString() {
return "Groundhog #" + number;
}
}
// equalshashcode/Prediction.java
// Predicting the weather
import java.util.*;
public class Prediction {
private static Random rand = new Random(47);
@Override
public String toString() {
return rand.nextBoolean() ?
"Six more weeks of Winter!" : "Early Spring!";
}
}
// equalshashcode/SpringDetector.java
// What will the weather be?
import java.util.*;
import java.util.stream.*;
import java.util.function.*;
import java.lang.reflect.*;
public class SpringDetector {
public static <T extends Groundhog>
void detectSpring(Class<T> type) {
try {
Constructor<T> ghog =
type.getConstructor(int.class);
Map<Groundhog, Prediction> map =
IntStream.range(0, 10)
.mapToObj(i -> {
try {
return ghog.newInstance(i);
} catch(Exception e) {
throw new RuntimeException(e);
}
})
.collect(Collectors.toMap(
Function.identity(),
gh -> new Prediction()));
map.forEach((k, v) ->
System.out.println(k + ": " + v));
Groundhog gh = ghog.newInstance(3);
System.out.println(
"Looking up prediction for " + gh);
if(map.containsKey(gh))
System.out.println(map.get(gh));
else
System.out.println("Key not found: " + gh);
} catch(NoSuchMethodException |
IllegalAccessException |
InvocationTargetException |
InstantiationException e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) {
detectSpring(Groundhog.class);
}
}
/* Output:
Groundhog #3: Six more weeks of Winter!
Groundhog #0: Early Spring!
Groundhog #8: Six more weeks of Winter!
Groundhog #6: Early Spring!
Groundhog #4: Early Spring!
Groundhog #2: Six more weeks of Winter!
Groundhog #1: Early Spring!
Groundhog #9: Early Spring!
Groundhog #5: Six more weeks of Winter!
Groundhog #7: Six more weeks of Winter!
Looking up prediction for Groundhog #3
Key not found: Groundhog #3
*/
每个detectSpring()
方法通过反射来实例化
这里的
这看起来十分简单,但是这样做并没有奏效 —— 它无法找到数字
我们需要恰当的重写
这是因为默认的
// equalshashcode/Groundhog2.java
// A class that's used as a key in a HashMap
// must override hashCode() and equals()
import java.util.*;
public class Groundhog2 extends Groundhog {
public Groundhog2(int n) { super(n); }
@Override
public int hashCode() { return number; }
@Override
public boolean equals(Object o) {
return o instanceof Groundhog2 &&
Objects.equals(
number, ((Groundhog2)o).number);
}
}
// equalshashcode/SpringDetector2.java
// A working key
public class SpringDetector2 {
public static void main(String[] args) {
SpringDetector.detectSpring(Groundhog2.class);
}
}
/* Output:
Groundhog #0: Six more weeks of Winter!
Groundhog #1: Early Spring!
Groundhog #2: Six more weeks of Winter!
Groundhog #3: Early Spring!
Groundhog #4: Early Spring!
Groundhog #5: Six more weeks of Winter!
Groundhog #6: Early Spring!
Groundhog #7: Early Spring!
Groundhog #8: Six more weeks of Winter!
Groundhog #9: Six more weeks of Winter!
Looking up prediction for Groundhog #3
Early Spring!
*/
如何定义
理解hashCode
前面的例子只是正确解决问题的第一步。它只说明,如果不为你的键覆盖
首先,使用散列的目的在于:想要使用一个对象来查找另一个对象。不过使用
// equalshashcode/SlowMap.java
// A Map implemented with ArrayLists
import java.util.*;
import onjava.*;
public class SlowMap<K, V> extends AbstractMap<K, V> {
private List<K> keys = new ArrayList<>();
private List<V> values = new ArrayList<>();
@Override
public V put(K key, V value) {
V oldValue = get(key); // The old value or null
if(!keys.contains(key)) {
keys.add(key);
values.add(value);
} else
values.set(keys.indexOf(key), value);
return oldValue;
}
@Override
public V get(Object key) { // key: type Object, not K
if(!keys.contains(key))
return null;
return values.get(keys.indexOf(key));
}
@Override
public Set<Map.Entry<K, V>> entrySet() {
Set<Map.Entry<K, V>> set= new HashSet<>();
Iterator<K> ki = keys.iterator();
Iterator<V> vi = values.iterator();
while(ki.hasNext())
set.add(new MapEntry<>(ki.next(), vi.next()));
return set;
}
public static void main(String[] args) {
SlowMap<String,String> m= new SlowMap<>();
m.putAll(Countries.capitals(8));
m.forEach((k, v) ->
System.out.println(k + "=" + v));
System.out.println(m.get("BENIN"));
m.entrySet().forEach(System.out::println);
}
}
/* Output:
CAMEROON=Yaounde
ANGOLA=Luanda
BURKINA FASO=Ouagadougou
BURUNDI=Bujumbura
ALGERIA=Algiers
BENIN=Porto-Novo
CAPE VERDE=Praia
BOTSWANA=Gaberone
Porto-Novo
CAMEROON=Yaounde
ANGOLA=Luanda
BURKINA FASO=Ouagadougou
BURUNDI=Bujumbura
ALGERIA=Algiers
BENIN=Porto-Novo
CAPE VERDE=Praia
BOTSWANA=Gaberone
*/
同样遵循了
// equalshashcode/MapEntry.java
// A simple Map.Entry for sample Map implementations
import java.util.*;
public class MapEntry<K, V> implements Map.Entry<K, V> {
private K key;
private V value;
public MapEntry(K key, V value) {
this.key = key;
this.value = value;
}
@Override
public K getKey() { return key; }
@Override
public V getValue() { return value; }
@Override
public V setValue(V v) {
V result = value;
value = v;
return result;
}
@Override
public int hashCode() {
return Objects.hash(key, value);
}
@SuppressWarnings("unchecked")
@Override
public boolean equals(Object rval) {
return rval instanceof MapEntry &&
Objects.equals(key,
((MapEntry<K, V>)rval).getKey()) &&
Objects.equals(value,
((MapEntry<K, V>)rval).getValue());
}
@Override
public String toString() {
return key + "=" + value;
}
}
这里hashCode()
时,你可以使用这个方法。如果你的对象只有一个属性,可以直接使用 Objects.hashCode()
。
尽管这个解决方案非常简单,并且看起来在
为了速度而散列
散列的价值在于速度:散列使得查询得以快速进行。由于瓶颈位于键的查询速度,因此解决方案之一就是保持键的排序状态,然后使用
散列则更进一步,它将键保存在某处,以便能够很快找到。存储一组元素最快的数据结构是数组,所以使用它来表示键的信息(请小心留意,我是说键的信息,而不是键本身
答案就是:数组并不保存键本身。而是通过键对象生成一个数字,将其作为数组的下标。这个数字就是散列码,由定义在
于是查询一个值的过程首先就是计算散列码,然后使用散列码查询数组。如果能够保证没有冲突(如果值的数量是固定的,那么就有可能
理解了散列的原理,我们就能够实现一个简单的散列
// equalshashcode/SimpleHashMap.java
// A demonstration hashed Map
import java.util.*;
import onjava.*;
public
class SimpleHashMap<K, V> extends AbstractMap<K, V> {
// Choose a prime number for the hash table
// size, to achieve a uniform distribution:
static final int SIZE = 997;
// You can't have a physical array of generics,
// but you can upcast to one:
@SuppressWarnings("unchecked")
LinkedList<MapEntry<K, V>>[] buckets =
new LinkedList[SIZE];
@Override
public V put(K key, V value) {
V oldValue = null;
int index = Math.abs(key.hashCode()) % SIZE;
if(buckets[index] == null)
buckets[index] = new LinkedList<>();
LinkedList<MapEntry<K, V>> bucket = buckets[index];
MapEntry<K, V> pair = new MapEntry<>(key, value);
boolean found = false;
ListIterator<MapEntry<K, V>> it =
bucket.listIterator();
while(it.hasNext()) {
MapEntry<K, V> iPair = it.next();
if(iPair.getKey().equals(key)) {
oldValue = iPair.getValue();
it.set(pair); // Replace old with new
found = true;
break;
}
}
if(!found)
buckets[index].add(pair);
return oldValue;
}
@Override
public V get(Object key) {
int index = Math.abs(key.hashCode()) % SIZE;
if(buckets[index] == null) return null;
for(MapEntry<K, V> iPair : buckets[index])
if(iPair.getKey().equals(key))
return iPair.getValue();
return null;
}
@Override
public Set<Map.Entry<K, V>> entrySet() {
Set<Map.Entry<K, V>> set= new HashSet<>();
for(LinkedList<MapEntry<K, V>> bucket : buckets) {
if(bucket == null) continue;
for(MapEntry<K, V> mpair : bucket)
set.add(mpair);
}
return set;
}
public static void main(String[] args) {
SimpleHashMap<String,String> m =
new SimpleHashMap<>();
m.putAll(Countries.capitals(8));
m.forEach((k, v) ->
System.out.println(k + "=" + v));
System.out.println(m.get("BENIN"));
m.entrySet().forEach(System.out::println);
}
}
/* Output:
CAMEROON=Yaounde
ANGOLA=Luanda
BURKINA FASO=Ouagadougou
BURUNDI=Bujumbura
ALGERIA=Algiers
BENIN=Porto-Novo
CAPE VERDE=Praia
BOTSWANA=Gaberone
Porto-Novo
CAMEROON=Yaounde
ANGOLA=Luanda
BURKINA FASO=Ouagadougou
BURUNDI=Bujumbura
ALGERIA=Algiers
BENIN=Porto-Novo
CAPE VERDE=Praia
BOTSWANA=Gaberone
*/
由于散列表中的“槽位”(slot)通常称为桶位(bucket
对于
注意,这个实现并不意味着对性能进行了调优,它只是想要展示散列映射表执行的各种操作。如果你浏览一下
重写hashCode()
在明白了如何散列之后,编写自己的
首先,你无法控制
设计
此外,也不应该使
下面以
// equalshashcode/StringHashCode.java
public class StringHashCode {
public static void main(String[] args) {
String[] hellos = "Hello Hello".split(" ");
System.out.println(hellos[0].hashCode());
System.out.println(hellos[1].hashCode());
}
}
/* Output:
69609650
69609650
*/
对于
因此,要想使
因为在生成桶的下标前,
还有另一个影响因素:好的
在
- 给
int 变量result 赋予某个非零值常量,例如17 。 - 为对象内每个有意义的字段(即每个可以做
equals )操作的字段计算出一个int 散列码c :
字段类型 | 计算公式 |
---|---|
boolean | c = (f ? 0 : 1) |
byte , char , short , or int | c = (int)f |
long | c = (int)(f ^ (f»>32)) |
float | c = Float.floatToIntBits(f); |
double | long l =Double.doubleToLongBits(f); c = (int)(l ^ (l »> 32)) |
Object , where equals() calls equals() for this field | c = f.hashCode() |
Array | 应用以上规则到每一个元素中 |
- 合并计算得到的散列码: result = 37 * result + c;
- 返回
result 。 - 检查
hashCode() 最后生成的结果,确保相同的对象有相同的散列码。
下面便是遵循这些指导的一个例子。提示,你没有必要书写像如下的代码 —— 相反,使用 Objects.hash()
去用于散列多字段的对象(如同在本例中的那样Objects.hashCode()
如散列单字段的对象。
// equalshashcode/CountedString.java
// Creating a good hashCode()
import java.util.*;
public class CountedString {
private static List<String> created =
new ArrayList<>();
private String s;
private int id = 0;
public CountedString(String str) {
s = str;
created.add(s);
// id is the total number of instances
// of this String used by CountedString:
for(String s2 : created)
if(s2.equals(s))
id++;
}
@Override
public String toString() {
return "String: " + s + " id: " + id +
" hashCode(): " + hashCode();
}
@Override
public int hashCode() {
// The very simple approach:
// return s.hashCode() * id;
// Using Joshua Bloch's recipe:
int result = 17;
result = 37 * result + s.hashCode();
result = 37 * result + id;
return result;
}
@Override
public boolean equals(Object o) {
return o instanceof CountedString &&
Objects.equals(s, ((CountedString)o).s) &&
Objects.equals(id, ((CountedString)o).id);
}
public static void main(String[] args) {
Map<CountedString,Integer> map = new HashMap<>();
CountedString[] cs = new CountedString[5];
for(int i = 0; i < cs.length; i++) {
cs[i] = new CountedString("hi");
map.put(cs[i], i); // Autobox int to Integer
}
System.out.println(map);
for(CountedString cstring : cs) {
System.out.println("Looking up " + cstring);
System.out.println(map.get(cstring));
}
}
}
/* Output:
{String: hi id: 4 hashCode(): 146450=3, String: hi id:
5 hashCode(): 146451=4, String: hi id: 2 hashCode():
146448=1, String: hi id: 3 hashCode(): 146449=2,
String: hi id: 1 hashCode(): 146447=0}
Looking up String: hi id: 1 hashCode(): 146447
0
Looking up String: hi id: 2 hashCode(): 146448
1
Looking up String: hi id: 3 hashCode(): 146449
2
Looking up String: hi id: 4 hashCode(): 146450
3
Looking up String: hi id: 5 hashCode(): 146451
4
*/
在
作为第二个示例,请考虑
在这里替换了手工去计算 hashCode()
,我们使用了更合适的方式 Objects.hash()
:
// typeinfo/pets/Individual.java
package typeinfo.pets;
import java.util.*;
public class
Individual implements Comparable<Individual> {
private static long counter = 0;
private final long id = counter++;
private String name;
public Individual(String name) { this.name = name; }
// 'name' is optional:
public Individual() {}
@Override
public String toString() {
return getClass().getSimpleName() +
(name == null ? "" : " " + name);
}
public long id() { return id; }
@Override
public boolean equals(Object o) {
return o instanceof Individual &&
Objects.equals(id, ((Individual)o).id);
}
@Override
public int hashCode() {
return Objects.hash(name, id);
}
@Override
public int compareTo(Individual arg) {
// Compare by class name first:
String first = getClass().getSimpleName();
String argFirst = arg.getClass().getSimpleName();
int firstCompare = first.compareTo(argFirst);
if(firstCompare != 0)
return firstCompare;
if(name != null && arg.name != null) {
int secondCompare = name.compareTo(arg.name);
if(secondCompare != 0)
return secondCompare;
}
return (arg.id < id ? -1 : (arg.id == id ? 0 : 1));
}
}
// equalshashcode/IndividualTest.java
import collections.MapOfList;
import typeinfo.pets.*;
import java.util.*;
public class IndividualTest {
public static void main(String[] args) {
Set<Individual> pets = new TreeSet<>();
for(List<? extends Pet> lp :
MapOfList.petPeople.values())
for(Pet p : lp)
pets.add(p);
pets.forEach(System.out::println);
}
}
/* Output:
Cat Elsie May
Cat Pinkola
Cat Shackleton
Cat Stanford
Cymric Molly
Dog Margrett
Mutt Spot
Pug Louie aka Louis Snorkelstein Dupree
Rat Fizzy
Rat Freckly
Rat Fuzzy
*/
由于所有的宠物都有名字,因此它们首先按照类型排序,然后在同类型中按照名字排序。
调优HashMap
我们有可能手动调优
- 容量(Capacity
) :表中存储的桶数量。 - 初始容量(Initial Capacity
) :当表被创建时,桶的初始个数。HashMap 和HashSet 有可以让你指定初始容量的构造器。 - 个数(Size
) :目前存储在表中的键值对的个数。 - 负载因子(Load factor
) :通常表现为$\frac{size}{capacity}$ 。当负载因子大小为0 的时候表示为一个空表。当负载因子大小为0.5 表示为一个半满表(half-full table) ,以此类推。轻负载的表几乎没有冲突,因此是插入和查找的最佳选择(但会减慢使用迭代器进行遍历的过程) 。HashMap 和HashSet 有可以让你指定负载因子的构造器。当表内容量达到了负载因子,集合就会自动扩充为原始容量(桶的数量)的两倍,并且会将原始的对象存储在新的桶集合中(也被称为rehashing )
get()
和 put()
方法
如果你知道存储在