《java collections》学习笔记
Dictionary以及其子类是key-value
的数据结构,不像Vector使用index
来寻找值,Dictonary使用key
来查找值。
HashTable<K,V>
: key和value都可以为任意Object类型。Properties<String,String>
: key和value只能为String
Dictionary
Dictionary
是个抽象类,特殊的地方是:它多有的方法都是abstract
,
至于为什么不设计为接口,
据说
是当时设计Dictionary类时,java语言还没有接口
,当后续java语言支持接口后,这个问题也没有解决。
Dictionary 不允许key或者value 任一为空
。
APIs
public abstract class Dictionary<K,V> {
public Dictionary() {
}
//返回 key的个数
abstract public int size();
//判断 key的个数是否 > 0
abstract public boolean isEmpty();
//返回 enumeration of the keys
abstract public Enumeration<K> keys();
//返回 enumeration of the values
abstract public Enumeration<V> elements();
//根据key 获取value
abstract public V get(Object key);
//新增key-value : key,或者 value为空时 抛出NPE
abstract public V put(K key, V value);
/**
* key为空时, 抛出NPE
* @param key 待删除的key
* @return 被删除的value
*/
abstract public V remove(Object key);
}
由于Dictionary是个抽象类,所有的方法都是abstract
的,所以我们从不直接使用Dictionary, 而是使用它的子类。接下来分析它的子类之一:HashTable.
Dictionary
: key,value都不允许为空
HashTable
: 继承自Dictionary
,同Dictionary
HashMap
: key,value 都可以为空。
HashSet
: 内部使用HashMap
,key可以为空
TreeSet
: key不允许为空
HashTable
Hashtable
是一个特殊Dictionary,它使用hash算法
,将key
转换为hash值
,并用它来寻找dictionay中的value
.
Hash算法
哈希表是一种提供几乎恒定时间
插入和搜索的数据结构(时间复杂度为0(1)
),这就意味着,无论数据量有多大,它都需要几乎相同的时间去定位元素。
当我们使用hashtable中的key−value
键值队时,会通过hash算法
将keys
转换为一个被称为hashcode的integer数值
。
hashCode()方法在Object类中定义,以生成哈希码,并且通常被
子类重写
。
hash碰撞
当两个对象equals()时,是指使用equals()方法比较时,返回true. 两个不相等的对象,他们的hashcode可能相同,也可能不同。
当两个unequal的对象hashcode 相同时,被称为hash碰撞。
当碰撞次数增加时,会很明显的降低插入和搜索的效率
。
下图是一个存在hash碰撞的输出存储结构。
存储过程
当对HashTable进行搜索时,首先将key转换为hashcode,然后进行某种算法
定位到索引(index),如果有多个元素相同在散列表中的索引,必须遍历链表以查找具有特定键的元素。
假如有如下简单算法:
// 计算hashcode的过程
//1.首先查找各个字符的assci码的值
J = 74
o = 111
h = 104
n = 110
//2.计算各个名字的hashcode
John = 74*103 + 111*102 + 104*101 + 110*100 = 86250
Cary: 67*103 + 97*102 + 114*101 + 121*100 = 77961
Cody: 67*103 + 111*102 + 100*101 + 121*100 = 79221
Omar: 79*103 + 109*102 + 97*101 + 114*100 = 90984
//3.计算index
index = hashcode % arraySize;
假设我们将上述的names放置在一个长度为13(一般都为质数)
的数组.它的存储结果如下图:
hashtable本质上的数据结构是一个
LinkedList[]
,具有相同的locate
的元素放置在同一个链表。
当hash碰撞次数较多时,则具有相同locate
的链表长度变长,查找元素时需要遍历整个链表,这严重降低了遍历的效率。
在jdk8中,hashmap为了解决这个问题,当链表的长度到达一定值(默认为8)后,会进行treefy
属性&构造函数
public class Hashtable<K,V>
extends Dictionary<K,V>
implements Map<K,V>, Cloneable, java.io.Serializable {
//实际上数据存储的结构: 是一个"链表的数组"
private transient Entry<?,?>[] table;
//记录所有元素的个数;
private transient int count;
//元素个数阈值:通常 threshold = initialCapacity * loadFactor
//当count > threshold 时,则需要将table[] 数组扩容,
//扩容算法为:当前容量 * 2 + 1 ;
private int threshold;
//负载系数:默认值为0.75
private float loadFactor;
private transient int modCount = 0;
public Hashtable() {
this(11, 0.75f);
}
public Hashtable(int initialCapacity) {
this(initialCapacity, 0.75f);
}
public Hashtable(Map<? extends K, ? extends V> t) {
this(Math.max(2*t.size(), 11), 0.75f);
putAll(t);
}
/**
* @param initialCapacity 是哈希表中"桶" 的数量
* @param loadFactor 负载系数,默认为 0.75
*/
public Hashtable(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal Load: "+loadFactor);
if (initialCapacity==0)
initialCapacity = 1;
this.loadFactor = loadFactor;
table = new Entry<?,?>[initialCapacity];
threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
}
}
HashTable是线程安全的,基本上所有的方法都是synchronized
新增元素
put()
public synchronized V put(K key, V value) {
if (value == null) {
throw new NullPointerException();
}
Entry<?,?> tab[] = table;
int hash = key.hashCode();
//若hash为负数,转换为正数
//定位 桶index
int index = (hash & 0x7FFFFFFF) % tab.length;
@SuppressWarnings("unchecked")
Entry<K,V> entry = (Entry<K,V>)tab[index];
//遍历Entry(链表)
for(; entry != null ; entry = entry.next) {
//判断key是否已经存在,若已经存在则更新值,返回原先的oldValue
if ((entry.hash == hash) && entry.key.equals(key)) {
V old = entry.value;
entry.value = value;
return old;
}
}
addEntry(hash, key, value, index);
return null;
}
?? hash & 0x7FFFFFFF的作用,为什么不用Math.abs(int)
// “-2^31”到“2^31-1”即“-2147483648”到“2147483647”
int a = 1 << 31;
System.out.println(a); // -2147483648
System.out.println(a & Integer.MAX_VALUE); // 0
System.out.println(Math.abs(a)); //-2147483648
addEntry()
private void addEntry(int hash, K key, V value, int index) {
modCount++;
Entry<?,?> tab[] = table;
// count >= threshold ,给数组扩容
if (count >= threshold) {
//1.扩容
rehash();
tab = table;
hash = key.hashCode();
index = (hash & 0x7FFFFFFF) % tab.length;
}
// 创建一个新的Entry(链表中的节点)
// tab[index] 总是指向链表中的“最后插入的Entry”,但最后插入的位置是链表:header位置;
Entry<K,V> e = (Entry<K,V>) tab[index];
tab[index] = new Entry<>(hash, key, value, e);
//元素个数自增1,在新增完成之后才增加1;
//说明到新增到第 threshold+1 个元素时,才可以触发上面的rehash()函数
count++;
}
private static class Entry<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Entry<K,V> next;
protected Entry(int hash, K key, V value, Entry<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
//后插入的元素,排在链表的前置位;
this.next = next;
}
}
假如先后向链表中插入:
a,b,c,d
四个元素,
在链表中的结构为:d->c->b->a
当整体元素个数count > threshold
时,就要对Entry[]
数组进行扩容
rehash()
protected void rehash() {
int oldCapacity = table.length;
Entry<?,?>[] oldMap = table;
// 容量*2 + 1
int newCapacity = (oldCapacity << 1) + 1;
//newCapacity 与 MAX_ARRAY_SIZE 大小比较(略)
//创建一个新的数组,数组长度为newCapacity,
Entry<?,?>[] newMap = new Entry<?,?>[newCapacity];
modCount++;
threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
table = newMap;
//填充newMap
for (int i = oldCapacity ; i-- > 0 ;) {
//获取oldMap[i]链表的header元素,进行遍历链表元素,重定位
for (Entry<K,V> old = (Entry<K,V>)oldMap[i] ; old != null ; ) {
//old为链表的header元素;
Entry<K,V> e = old;
old = old.next;
//根据新的长度,计算新的index;
int index = (e.hash & 0x7FFFFFFF) % newCapacity;
//table[index] 总是指向桶的第一个元素的;
//则将之前的header元素,设置为当前e.next
e.next = (Entry<K,V>)newMap[index];
//最后将e设为第一个元素
newMap[index] = e;
}
}
}
rehash()例,详解:
为了更好的理解rehash方法,接下来将分析一个例子:
public void test01(){
//
// 默认 initialCapacity = 11, loadFactor = 0.75
// threshold = 11 * 0.75 = 8
// count >= threshold 触发rehash,即当count==8时,触发rehash,
//而每次addEntry()完毕之后,才进行count++,即进行到第9个元素新增时,触发rehash
Hashtable hashtable1 = new Hashtable();
//创建一个lenth == 9的数组
int[] array = {1,11,12,20,21,22,23,30,34};
Arrays.stream(array).forEach(item ->{
hashtable1.put(item, item);
});
}
当插入到第九个元素时,触发rehash条件,此时插入前
的HashTable数据结构如下图:
rehash执行完之后,capacity变为23
,变换后执行插入操作,数据变为如下结构:
putAll()
public synchronized void putAll(Map<? extends K, ? extends V> t) {
for (Map.Entry<? extends K, ? extends V> e : t.entrySet())
put(e.getKey(), e.getValue());
}
删除元素
remove()
public synchronized V remove(Object key) {
Entry<?,?> tab[] = table;
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
//首先定位到“桶”
Entry<K,V> e = (Entry<K,V>)tab[index];
//遍历桶,定位到指定的元素,并找到prev元素
for(Entry<K,V> prev = null ; e != null ; prev = e, e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
modCount++;
if (prev != null) {
//将e.next 设置为prev.next; 即将e从 桶链表中移除;
prev.next = e.next;
} else {
tab[index] = e.next;
}
count--;
V oldValue = e.value;
//设置为null,用于gc回收
e.value = null;
//返回删除的值
return oldValue;
}
}
return null;
}
clear
public synchronized void clear() {
Entry<?,?> tab[] = table;
modCount++;
//遍历table,将table中所有桶header元素设为null;
for (int index = tab.length; --index >= 0; )
tab[index] = null;
//设置元素个数为0
count = 0;
}
获取元素
public Object get(Object key)
public synchronized V get(Object key) {
Entry<?,?> tab[] = table;
int hash = key.hashCode();
//找到指定的“桶”
int index = (hash & 0x7FFFFFFF) % tab.length;
//遍历链表
for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
return (V)e.value;
}
}
//未找到,返回null;
return null;
}
通过keySet()遍历
demo
Iterator iterator = hashtable1.keySet().iterator();
while(iterator.hasNext()){
Object key = iterator.next();
System.out.println(key +"->" +hashtable1.get(key));
}
源码分析
// keySet().iterator()
//1.
public Set<K> keySet() {
if (keySet == null)
//1.1 创建内部类KeySet
//1.2 调用KeySet.iterator()
keySet = Collections.synchronizedSet(new KeySet(), this);
return keySet;
}
//1.1 KeySet
private class KeySet extends AbstractSet<K> {
//1.2
public Iterator<K> iterator() {
//1.3 执行Hashtable.getIterator(Keys)
return getIterator(KEYS);
}
}
//1.3
private <T> Iterator<T> getIterator(int type) {
if (count == 0) {
return Collections.emptyIterator();
} else {
//3.返回内部类Enumerator
return new Enumerator<>(type, true);
}
}
Enumerator
Enumerator即实现了Enumeration
接口,也实现了Iterator
接口
private class Enumerator<T> implements Enumeration<T>, Iterator<T> {
Entry<?,?>[] table = Hashtable.this.table;
//记录下一次遍历的位置
int index = table.length;
//记录下一次遍历的对象
Entry<?,?> entry;
//记录上一次返回的entry
Entry<?,?> lastReturned;
//KEYS,VALUES, ENTRIES
int type;
/**
* true -> Iterator ====>只有在iterator遍历时才允许删除
* false -> Enumeration
*/
boolean iterator;
protected int expectedModCount = modCount;
Enumerator(int type, boolean iterator) {
this.type = type;
this.iterator = iterator;
}
//判断是否有元素:只需要判断每个桶的header元素是否为空即可;
public boolean hasMoreElements() {
Entry<?,?> e = entry;
int i = index;
Entry<?,?>[] t = table;
//当e == null时,寻找到第一个不为null的桶的header元素.
//当e != null时, 直接跳过while()往下执行;
while (e == null && i > 0) {
e = t[--i];
}
//修改下一次遍历的对象为: e
entry = e;
//修改下一次遍历的位置为: i
index = i;
return e != null;
}
public T nextElement() {
//获取当前遍历的对象 entry;
Entry<?,?> et = entry;
int i = index;
// 同hasMoreElements()逻辑 ----------
Entry<?,?>[] t = table;
while (et == null && i > 0) {
et = t[--i];
}
entry = et;
index = i;
// ------------------------------------
if (et != null) {
//返回当前entry
Entry<?,?> e = lastReturned = entry;
//将entry元素的next 设置为下一次即将遍历的对象
//!!!!注意, 这里不需要修改index,因为是在同一个桶内,遍历“列表”
entry = e.next;
//根据不同的类型返回不同的值
return type == KEYS ? (T)e.key : (type == VALUES ? (T)e.value : (T)e);
}
throw new NoSuchElementException("Hashtable Enumerator");
}
public boolean hasNext() {
return hasMoreElements();
}
public T next() {
//fast-fail机制
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
return nextElement();
}
public void remove() {
//只有在iterator == true时才允许删除
if (!iterator)
throw new UnsupportedOperationException();
if (lastReturned == null)
throw new IllegalStateException("Hashtable Enumerator");
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
//略.....
}
}
通过keys()遍历
例:
Enumeration enumeration = hashtable1.keys();
while(enumeration.hasMoreElements()){
Object key = enumeration.nextElement();
System.out.println(key +"->" +hashtable1.get(key));
}
源码
public synchronized Enumeration<K> keys() {
return this.<K>getEnumeration(KEYS);
}
private <T> Enumeration<T> getEnumeration(int type) {
if (count == 0) {
return Collections.emptyEnumeration();
} else {
return new Enumerator<>(type, false);
}
}
遍历值:values(),elements(),entrySet()
public Collection<V> values() {
if (values==null)
//同keySet(),最终返回:return new Enumerator<>(VALUES, true);
values = Collections.synchronizedCollection(new ValueCollection(),
this);
return values;
}
public synchronized Enumeration<V> elements() {
//同同keys(),最终返回 new Enumerator<>(VALUES, false);
return this.<V>getEnumeration(VALUES);
}
public Set<Map.Entry<K,V>> entrySet() {
if (entrySet==null)
//最终返回 new Enumerator<>(ENTRIES, true);
entrySet = Collections.synchronizedSet(new EntrySet(), this);
return entrySet;
}
containsKey(Object key),containsValue(Object value),contains(Object value)
public synchronized boolean containsKey(Object key) {
Entry<?,?> tab[] = table;
int hash = key.hashCode();
//定位到桶,
int index = (hash & 0x7FFFFFFF) % tab.length;
//遍历桶列表
for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
return true;
}
}
return false;
}
public boolean containsValue(Object value) {
return contains(value);
}
public synchronized boolean contains(Object value) {
if (value == null) {
throw new NullPointerException();
}
Entry<?,?> tab[] = table;
//遍历各个桶
for (int i = tab.length ; i-- > 0 ;) {
//遍历桶中各个元素
for (Entry<?,?> e = tab[i] ; e != null ; e = e.next) {
if (e.value.equals(value)) {
return true;
}
}
}
return false;
}
其他APIs
size相关
public synchronized int size() {
return count;
}
public synchronized boolean isEmpty() {
return count == 0;
}
equals()
public synchronized boolean equals(Object o) {
if (o == this)
return true;
if (!(o instanceof Map))
return false;
Map<?,?> t = (Map<?,?>) o;
//元素个数相同
if (t.size() != size())
return false;
Iterator<Map.Entry<K,V>> i = entrySet().iterator();
while (i.hasNext()) {
Map.Entry<K,V> e = i.next();
K key = e.getKey();
V value = e.getValue();
if (value == null) {
//value == null时, 若t不含有key,或者含有key但是value不为null,则返回false
if (!(t.get(key)==null && t.containsKey(key)))
return false;
} else {
//value != null时, 相同的key对应的value必须equal
//隐含了另一个条件: 同时含有key;
if (!value.equals(t.get(key)))
return false;
}
}
return true;
}
equals()总结一句话
: 必须size()相同,且所有的key和value相同,但是(桶的个数,同内部的顺序)可以不同,
hashCode()
Hashtable的哈希码是sum(所有元素的哈希码)
Immutability
Hashtable hashtable1 = new Hashtable();
Collections.unmodifiableMap(hashtable1)
Properties
Properties继承自HashTable.与HashTable不同的是,Properties的key-value都只能是String
类型.
但是由于它Hashtable的子类,因此可以直接调用Hashtable方法来存储其他对象类型
。
对象类型。
虽然如此,我们也应该避免使用这些方法,因为如果key或者value使用了其他类型,
- 在使用如:list(),load(),save()等方法会出现异常;
- 在使用:
getProperty()
时,获取为空.
properties.put(1, 1); //调用HashTable方法
System.out.println(properties.get(1)); //调用HashTable方法: 1
System.out.println(properties.getProperty("1")); //null, getProperty的key,value只能为String
//抛出异常:
// java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
properties.list(System.out);
APIs
构造函数
public class Properties extends Hashtable<Object,Object> {
protected Properties defaults;
public Properties() {
this(null);
}
//如果当前Properties没有key的值,会尝试从defaults中获取
public Properties(Properties defaults) {
this.defaults = defaults;
}
}
getter,setter
public synchronized Object setProperty(String key, String value) {
//调用父类Hashtable的方法
return put(key, value);
}
public String getProperty(String key) {
Object oval = super.get(key);
String sval = (oval instanceof String) ? (String)oval : null;
//若当前不存在key,尝试从defaults中获取
return ((sval == null) && (defaults != null)) ? defaults.getProperty(key) : sval;
}
//若不存在key,则返回默认值
public String getProperty(String key, String defaultValue) {
String val = getProperty(key);
return (val == null) ? defaultValue : val;
}
list
若key,或者value存在非String类型的值,则list()
方法会抛出异常。。。
public void list(PrintStream out) {
out.println("-- listing properties --");
//新增一个hashtable, 来存放全量的 key-value;
Hashtable<String,Object> h = new Hashtable<>();
//递归所有defaults,获取所有的key-value
enumerate(h);
//默认认为keys都是
for (Enumeration<String> e = h.keys() ; e.hasMoreElements() ;) {
String key = e.nextElement();
//将value强制转换为String
String val = (String)h.get(key);
if (val.length() > 40) {
val = val.substring(0, 37) + "...";
}
out.println(key + "=" + val);
}
}
private synchronized void enumerate(Hashtable<String,Object> h) {
if (defaults != null) {
defaults.enumerate(h);
}
for (Enumeration<?> e = keys() ; e.hasMoreElements() ;) {
//强制将key转换为String
String key = (String)e.nextElement();
h.put(key, get(key));
}
}
//同上list(PrintStream) 略..
public void list(PrintWriter out);
如:
properties.put(1, 1);
properties.list(System.out);
后抛出如下异常
load(InputStream)
Properties的load(InputStream)方法
支持从输入流中加载数据。
foo:bar
one
two
three=four
five six seven eight
nine ten
#ten
!ele
properties文件的分隔符支持:=
,,
,space
,table
等。
上述properties加载后结果如下:
store()
@Test
public void testStore() throws IOException {
Properties properties = new Properties();
properties.setProperty("a", "A");
//抛出java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
// properties.put(1, "1");
properties.store(new FileOutputStream("out.properties"),"this is comments");
}
执行后结果:
System Properties
APIs
public final class System {
private static Properties props;
public static String getProperty(String key);
public static Properties getProperties() ;
public static String setProperty(String key, String value);
//替换所有的系统环境变量
public static void setProperties(Properties props);
}
列出所有配置信息
System.getProperties().list(System.out);
结果如下图:
使用
java -Dmsg=hello,moto Program
命令,可以设置环境变量信息
PropertyResourceBundle
配置文件
java
public void testPropertyResourceBundle() throws IOException {
ResourceBundle bundle = ResourceBundle
.getBundle("i18n/message", Locale.ENGLISH);
System.out.println(bundle.getString("name")); //Trump
System.out.println(bundle.getString("welcome")); // 取自message.properties: hello,body
ResourceBundle bundleZhCN = ResourceBundle
.getBundle("i18n/message", new Locale("zh", "CN"));
System.out.println(bundleZhCN.getString("name")); //乱码
}
执行结果:
中文乱码问题
properties文件中使用unicode:
PropertyResourceBundle
它继承自ResourceBundle:
PropertyResourceBundle propertyResourceBundle = (PropertyResourceBundle) ResourceBundle
.getBundle("i18n/message", Locale.SIMPLIFIED_CHINESE);