标签:之间 was bin factor jdb 填充 creates 原理 set
Map
与 Collection
并列存在,用于保存具有映射关系的数据:key-value
Map
中的 key
和 value
都可以是任何引用类型的数据 // 常用 String
作为 Map
的“键”Map
中的 key
用 Set
来存放,不允许重复,即同一个 Map
对象所对应的类,须重写hashCode()
和 equals()
key
和 value
之间存在单向一对一关系,即通过指定的 key
总能找到唯一的、确定的 value
Map<I>
的常用实现类:HashMap
、TreeMap
、LinkedHashMap
和 Properties
。其中,HashMap
是 Map
接口使用频率最高的实现类HashMap
线程不安全,效率高;可以存储 null
值的 key
和 value
;JDK 7实现方式是 {数组 + 链表},JDK 8实现方式是 {数组 + 链表 + 红黑树}LinkedHashMap
是通过 {双向链表} 和 {散列表} 这两种数据结构组合实现的。LinkedHashMap
中的“Linked”实际上是指的是 {双向链表},并非指 {用链表法解决散列冲突}TreeMap
保证按照添加的 key-value
进行排序(按key排序),实现有序遍历;底层使用 {红黑树}Hashtable
作为古老的实现类;线程安全,效率低;不能存储 null
值的 key
和 value
Properties
常用来处理配置文件;key
和 value
都是 String
类型key-value
的特点Set
存储所有的 key
Collection
存储所有的 value
key-value
构成了一个 Entry
对象HashMap
是 Map<I>
使用频率最高的实现类null键
和 null值
,与 HashSet
一样,不保证映射的顺序key
构成的集合是 Set
:无序的、不可重复的。所以,key 所在的类要重写:equals()
和 hashCode()
value
构成的集合是 Collection
:无序的、可以重复的。所以,value 所在的类 要重写:equals()
key-value
构成 一个entry,所有的 entry
构成的集合是Set
:无序的、不可重复的equals()
方法返回 true, hashCode 值也相等equals()
方法返回 truepublic class HashMap<K,V>
extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable
{
// The default initial capacity - MUST be a power of two.
static final int DEFAULT_INITIAL_CAPACITY = 16;
// The load factor used when none specified in constructor.
static final float DEFAULT_LOAD_FACTOR = 0.75f;
transient Entry<K,V>[] table;
// The number of key-value mappings contained in this map.
transient int size;
// The next size value at which to resize (capacity * load factor).
int threshold;
final float loadFactor;
// Constructs an empty HashMap with the default initial capacity (16)
// and the default load factor (0.75).
public HashMap() {
this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
}
// Constructs an empty HashMap with the specified initial capacity
// and the default load factor (0.75).
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
// Find a power of 2 >= initialCapacity
int capacity = 1;
while (capacity < initialCapacity)
capacity <<= 1;
this.loadFactor = loadFactor;
threshold = (int)Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
table = new Entry[capacity];
useAltHashing = sun.misc.VM.isBooted() &&
(capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
init();
}
public V put(K key, V value) {
if (key == null)
return putForNullKey(value);
int hash = hash(key);
int i = indexFor(hash, table.length);
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(hash, key, value, i);
return null;
}
final int hash(Object k) {
int h = 0;
if (useAltHashing) {
if (k instanceof String) {
return sun.misc.Hashing.stringHash32((String) k);
}
h = hashSeed;
}
h ^= k.hashCode();
// This function ensures that hashCodes that differ only by
// constant multiples at each bit position have a bounded
// number of collisions (approximately 8 at default load factor).
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
static int indexFor(int h, int length) {
return h & (length-1); // A % B = A & (B-1)
}
void addEntry(int hash, K key, V value, int bucketIndex) {
if ((size >= threshold) && (null != table[bucketIndex])) {
resize(2 * table.length);
hash = (null != key) ? hash(key) : 0;
bucketIndex = indexFor(hash, table.length);
}
createEntry(hash, key, value, bucketIndex);
}
void createEntry(int hash, K key, V value, int bucketIndex) {
Entry<K,V> e = table[bucketIndex];
table[bucketIndex] = new Entry<>(hash, key, value, e);
size++;
}
static class Entry<K,V> implements Map.Entry<K,V> {
final K key;
V value;
Entry<K,V> next;
int hash;
// Creates new entry.
Entry(int h, K k, V v, Entry<K,V> n) {
value = v;
next = n;
key = k;
hash = h;
}
}
void resize(int newCapacity) {
Entry[] oldTable = table;
int oldCapacity = oldTable.length;
if (oldCapacity == MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return;
}
Entry[] newTable = new Entry[newCapacity];
boolean oldAltHashing = useAltHashing;
useAltHashing |= sun.misc.VM.isBooted() &&
(newCapacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
boolean rehash = oldAltHashing ^ useAltHashing;
transfer(newTable, rehash);
table = newTable;
threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
}
// Transfers all entries from current table to newTable.
void transfer(Entry[] newTable, boolean rehash) {
int newCapacity = newTable.length;
for (Entry<K,V> e : table) {
while(null != e) {
Entry<K,V> next = e.next;
if (rehash) {
e.hash = null == e.key ? 0 : hash(e.key);
}
int i = indexFor(e.hash, newCapacity);
e.next = newTable[i];
newTable[i] = e;
e = next;
}
}
}
}
public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable {
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
static final float DEFAULT_LOAD_FACTOR = 0.75f;
static final int TREEIFY_THRESHOLD = 8;
static final int UNTREEIFY_THRESHOLD = 6;
static final int MIN_TREEIFY_CAPACITY = 64;
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
Node(int hash, K key, V value, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
}
static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
TreeNode<K,V> parent; // red-black tree links
TreeNode<K,V> left;
TreeNode<K,V> right;
TreeNode<K,V> prev; // needed to unlink next upon deletion
boolean red;
TreeNode(int hash, K key, V val, Node<K,V> next) {
super(hash, key, val, next);
}
}
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor;
this.threshold = tableSizeFor(initialCapacity);
}
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash); // 树化
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table;
int oldCap = (oldTab == null) ? 0 : oldTab.length;
int oldThr = threshold;
int newCap, newThr = 0;
if (oldCap > 0) {
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1; // double threshold
}
else if (oldThr > 0) // initial capacity was placed in threshold
newCap = oldThr;
else { // zero initial threshold signifies using defaults
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
if (newThr == 0) {
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
threshold = newThr;
@SuppressWarnings({"rawtypes","unchecked"})
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab;
if (oldTab != null) {
for (int j = 0; j < oldCap; ++j) {
Node<K,V> e;
if ((e = oldTab[j]) != null) {
oldTab[j] = null;
if (e.next == null)
newTab[e.hash & (newCap - 1)] = e;
else if (e instanceof TreeNode)
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
else { // preserve order
Node<K,V> loHead = null, loTail = null;
Node<K,V> hiHead = null, hiTail = null;
Node<K,V> next;
do {
next = e.next;
if ((e.hash & oldCap) == 0) {
if (loTail == null)
loHead = e;
else
loTail.next = e;
loTail = e;
}
else {
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
} while ((e = next) != null);
if (loTail != null) {
loTail.next = null;
newTab[j] = loHead;
}
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}
public V remove(Object key) {
Node<K,V> e;
return (e = removeNode(hash(key), key, null, false, true)) == null ?
null : e.value;
}
final Node<K,V> removeNode(int hash, Object key, Object value,
boolean matchValue, boolean movable) {
Node<K,V>[] tab; Node<K,V> p; int n, index;
if ((tab = table) != null && (n = tab.length) > 0 &&
(p = tab[index = (n - 1) & hash]) != null) {
Node<K,V> node = null, e; K k; V v;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
node = p;
else if ((e = p.next) != null) {
if (p instanceof TreeNode)
node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
else {
do {
if (e.hash == hash &&
((k = e.key) == key ||
(key != null && key.equals(k)))) {
node = e;
break;
}
p = e;
} while ((e = e.next) != null);
}
}
if (node != null && (!matchValue || (v = node.value) == value ||
(value != null && value.equals(v)))) {
if (node instanceof TreeNode)
((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
else if (node == p)
tab[index] = node.next;
else
p.next = node.next;
++modCount;
--size;
afterNodeRemoval(node);
return node;
}
}
return null;
}
}
DEFAULT_INITIAL_CAPACITY
:HashMap
的默认容量,16MAXIMUM_CAPACITY
:HashMap
的最大支持容量,2^30DEFAULT_LOAD_FACTOR
:HashMap
的默认加载因子TREEIFY_THRESHOLD
:Bucket
中链表长度大于该默认值8,转化为红黑树UNTREEIFY_THRESHOLD
:Bucket
中红黑树存储的 Node
小于该默认值6,转化为链表MIN_TREEIFY_CAPACITY
:桶中的 Node
被树化时最小的 hash表容量64(当桶中 Node
的数量大到需要变红黑树时,若 hash 表容量小于 MIN_TREEIFY_CAPACITY
时,此时应执行 resize
扩容操作这个 MIN_TREEIFY_CAPACITY
的值至少是 TREEIFY_THRESHOLD
的 4 倍)table
:存储元素的数组,总是 2 的 n 次幂entrySet
:存储具体元素的集size
:HashMap 中存储的键值对的数量modCount
:HashMap 扩容和结构改变的次数threshold
:扩容的临界值,= 容量 * 填充因子loadFactor
:填充因子HashMap map = new HashMap()
在实例化以后,底层创建了长度是16的一维数组 Entry[] table
。当执行 put(key1,value1)
操作:
hashCode()
计算 key1 哈希值,此哈希值再经过散列函数 hash()
计算以后,得到在 Entry[]
中的存放位置key1-value1
添加成功 → 【情况1】key1-value1
添加成功 → 【情况2】key2-value2
的哈希值相同,继续比较:调用 key1 所在类的equals(key2)
方法,比较:
equals()
返回 false
:此时 key1-value1
添加成功 → 【情况3】equals()
返回 true
:使用 value1 替换 value2关于 HashMap 的扩容:
resize()
要做的事负载因子值的大小,对HashMap有什么影响?
new HashMap()
:底层没有创建一个长度为 16 的数组Node[]
而非 Entry[]
。每一个 Node 对象可以带 一个引用变量 next,用于指向下一个元素,因此,在一个桶中,就有可能 生成一个 Node 链。也可能是一个一个 TreeNode 对象,每一个 TreeNode 对象可以有两个叶子结点 left 和 right,因此,在一个桶中,就有可能生成一个 TreeNode 树。而新添加的元素作为链表的 last,或树的叶子结点put()
时,底层创建长度为 16 的数组链表形式存在的数据个数 > 8 && 当前数组的长度 > 64
时,此索引位置上的所数据改为使用红黑树存储resize()
时判断树的结点个数低于 6 个,会由红黑树转变成链表,当 HashMap 中结点数为 6 时,链表的平均查找 6/2=3。当 HashMap 中结点多于 8 个时,会由链表转化成红黑树,红黑树的平均查找为 log(8)=3。如果继续使用链表,平均查找长度为 8/2=4,这才有转换为树的必要return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16)
的设计:
equals()
必须重写 hashcode()
的重要原因hashcode ^ (hashcode >>> 16)
,这一步甚是巧妙,是将 高16位 移到 低16位,这样计算出来的整型值将“具有”高位和低位的性质A % B = A & (B - 1)
,所以,p = tab[i = ((tab = resize()).length - 1) & hash]
,可以看出这里本质上是使用了「除留余数法」映射关系存储到 HashMap 中会存储 key 的 hash 值,这样就不用在每次查找时重新计算每一个 Entry
或 Node(TreeNode)
的 hash 值了,因此如果已经 put 到 Map 中的映射关系,再修改 key 的属性,而这个属性又参与 hashcode 值的计算,最终会导致匹配不上。
LinkedHashMap
是 HashMap
的子类HashMap
存储结构的基础上,使用了一对双向链表来记录添加元素的顺序LinkedHashSet
类似,LinkedHashMap
可以维护 Map 的迭代顺序:迭代顺序与 Key-Value
对的插入顺序一致因为我们的散列表是通过链表法解决散列冲突的,所以每个 Entry 会在两条链中。一个链是刚刚我们提到的双向链表,另一个链是散列表中的拉链。前驱和后继指针是为了将 Entry 串在双向链表中,hnext 指针是为了将结点串在散列表的拉链中。
JDK 8
public class LinkedHashMap<K,V>
extends HashMap<K,V>
implements Map<K,V>
{
transient LinkedHashMap.Entry<K,V> head;
transient LinkedHashMap.Entry<K,V> tail;
final boolean accessOrder;
public LinkedHashMap() {
super();
accessOrder = false;
}
public LinkedHashMap(int initialCapacity) {
super(initialCapacity);
accessOrder = false;
}
public LinkedHashMap(int initialCapacity, float loadFactor) {
super(initialCapacity, loadFactor);
accessOrder = false;
}
Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
LinkedHashMap.Entry<K,V> p =
new LinkedHashMap.Entry<K,V>(hash, key, value, e);
linkNodeLast(p);
return p;
}
private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
LinkedHashMap.Entry<K,V> last = tail;
tail = p;
if (last == null)
head = p;
else {
p.before = last;
last.after = p;
}
}
}
public void test() {
Map map = new HashMap();
map.put("1001", "白世珠");
map.put("1002", "金敏喜");
map.put("1003", "刘心悠");
// 遍历所有key。但要注意!keySet 既不是 HashSet,也不
// 是 TreeSet,而是实现了 Set<I> 的某个其他类的对象
Set set = map.keySet();
Iterator it = set.iterator();
while(it.hasNext())
System.out.println(it.next());
// 遍历所有 value
Collection c = map.values();
for(Object o : c)
System.out.println(o);
// 遍历 key - value
Set entrySet = map.entrySet();
Iterator iterator = entrySet.iterator();
while(iterator.hasNext()) {
Map.Entry entry = (Map.Entry)iterator.next();
System.out.println(entry.getKey() + "-" + entry.getValue());
}
// 遍历 key : value
Set keySet = map.keySet();
Iterator i = keySet.iterator();
while(i.hasNext()) {
Object key = i.next();
Object value = map.get(key);
System.out.println(key + ":" + value);
}
}
Key-Value
对时,需要根据 key-value
对进行排序Key-Value
对处于有序状态Comparable
接口,而且所有的 Key 应该是同一个类的对象,否则将会抛出 ClassCastException
Comparator
对象,该对象负责对 TreeMap 中的所有 key 进行排序。此时不需要 Map 的 Key 实现 Comparable<I>
compareTo()
或者 compare()
返回 0Hashtable
是个古老的 Map 实现类,JDK1.0 就提供了。不同于 HashMap
, Hashtable
是线程安全的Hashtable
实现原理和 HashMap
相同,功能相同。底层都使用哈希表结构,查询速度快,很多情况下可以互用HashMap
不同,Hashtable
不允许使用 null 作为 key 和 valueHashMap
一样,Hashtable
也不能保证其中 Key-Value
对的顺序Hashtable
判断两个 key 相等、两个 value 相等的标准,与 HashMap 一致setProperty(String key, String value)
和 getProperty(String key)
public void test() {
FileInputStream fis = null;
try {
Properties prop = new Properties();
fis = new FileInputStream("jdbc.properties");
prop.load(fis);
System.out.println(prop.getProperty("name"));
System.out.println(prop.getProperty("password"));
} catch (IOException e) {
e.printStackTrace();
} finally {
if(fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
读取项目下的 jdbc.properties
文件下的内容,抛了 FileNotFoundException
,而导致相对路径找不到文件的原因是因为当前执行类的工作目录不是当前项目路径。现作出如下修改:
Collections
是一个操作 Set
、List
和 Map
等集合的工具类Collections
中提供了一系列静态的方法对集合元素进行排序、查询和修改等操作, 还提供了对集合对象设置不可变、对集合对象实现同步控制等方法void reverse(List<?> list)
反转指定列表中元素的顺序void shuffle(List<?> list)
使用默认随机源对指定列表进行置换<T extends Comparable<? super T>> void sort(List<T> list)
根据元素的自然顺序 对指定列表按升序进行排序<T> void sort(List<T> list, Comparator<? super T> c)
根据指定比较器产生的顺序对指定列表进行排序void swap(List<?> list, int i, int j)
在指定列表的指定位置处交换元素<T extends Object & Comparable<? super T>> T max(Collection<? extends T> coll)
根据元素的自然顺序,返回给定 collection 的最大元素<T> T max(Collection<? extends T> coll, Comparator<? super T> comp)
根据指定比较器产生的顺序,返回给定 collection 的最大元素<T extends Object & Comparable<? super T>> T min(Collection<? extends T> coll)
根据元素的自然顺序返回给定 collection 的最小元素<T> T min(Collection<? extends T> coll, Comparator<? super T> comp)
根据指定比较器产生的顺序,返回给定 collection 的最小元素int frequency(Collection<?> c, Object o)
返回 collection 中满足 (o == null ? e == null : o.equals(e)) 的 e 元素的数量<T> void copy(List<? super T> dest, List<? extends T> src)
将所有元素从一个列表复制到另一个列表。执行此操作后,目标列表中每个已复制元素的索引将等同于源列表中该元素的索引。目标列表的长度至少必须等于源列表。如果目标列表更长一些,也不会影响目标列表中的其余元素<T> boolean replaceAll(List<T> list, T oldVal, T newVal)
使用另一个值替换列表中出现的所有某一指定值标签:之间 was bin factor jdb 填充 creates 原理 set
原文地址:https://www.cnblogs.com/liujiaqi1101/p/13289284.html