1、HashSet底层是使用HashMap实现的。当使用add方法将对象添加到set当中时,实际上是将该对象作为底层所维护的Map对象的Key,,而value则都是同一个Object对象(该对象我们用不上);
HashSet部分源代码:
[java] view plain copy print ?
- private transient HashMap<E,Object> map;
private transient HashMap<E,Object> map;
[java] view plain copy print ?
- public HashSet() {
- map = new HashMap<E,Object>();
- }
public HashSet() {
map = new HashMap<E,Object>();
}
[java] view plain copy print ?
- public boolean add(E e) {
- return map.put(e, PRESENT)==null;
- }
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
HashSet的默认构造方法是生成一个HashMap对象,赋给变量map,HashSet的add()方法是向生成的HashMap中放入一个键值对,这个键值对的键key就是我们要存入HashSet的数据,值value是一个常量PRESENT,这个PRESENT是这样定义的:
[java] view plain copy print ?
- private static final Object PRESENT = new Object();
private static final Object PRESENT = new Object();
其他方法的定义:
[java] view plain copy print ?
- public boolean remove(Object o) {
- return map.remove(o)==PRESENT;
- }
-
- public void clear() {
- map.clear();
- }
-
- public boolean isEmpty() {
- return map.isEmpty();
- }
-
- public int size() {
- return map.size();
- }
-
- public Iterator<E> iterator() {
- return map.keySet().iterator();
- }
public boolean remove(Object o) {
return map.remove(o)==PRESENT;
}
public void clear() {
map.clear();
}
public boolean isEmpty() {
return map.isEmpty();
}
public int size() {
return map.size();
}
public Iterator<E> iterator() {
return map.keySet().iterator();
}
HashSet的底层都是借助于HashMap来实现的。
2、HashMap的源代码分析(这个很复杂)。先看一下最后面的内存映像图,理解HashMap的存储形式。
首先HashMap定义了几个重要的成员变量
[java] view plain copy print ?
- static final int DEFAULT_INITIAL_CAPACITY = 16;
-
- static final float DEFAULT_LOAD_FACTOR = 0.75f;
-
- transient Entry[] table;
-
- int threshold;
-
- transient int size;
-
- final float loadFactor;
static final int DEFAULT_INITIAL_CAPACITY = 16; //默认初始容量,是一个常量16
static final float DEFAULT_LOAD_FACTOR = 0.75f; //默认负载因子,常量float 0.75,就是说数组数据达到数组容量75%时,扩展数组
transient Entry[] table; //用来存放数据的数组,数组元素为Entry类型的。这是存放Map键值对的主体。
int threshold;
transient int size;
final float loadFactor;
[java] view plain copy print ?
-
HashMap底层维护一个数组(Entry[]),我们向HashMap中所放置的对象实际上是存储在该数组中。
再看一下Entry的定义:
[java] view plain copy print ?
- static class Entry<K,V> implements Map.Entry<K,V> {
- final K key;
- V value;
- Entry<K,V> next;
- final int hash;
-
-
-
-
- Entry(int h, K k, V v, Entry<K,V> n) {
- value = v;
- next = n;
- key = k;
- hash = h;
- }
-
- public final K getKey() {
- return key;
- }
-
- public final V getValue() {
- return value;
- }
-
- public final V setValue(V newValue) {
- V oldValue = value;
- value = newValue;
- return oldValue;
- }
-
- public final boolean equals(Object o) {
- if (!(o instanceof Map.Entry))
- return false;
- Map.Entry e = (Map.Entry)o;
- Object k1 = getKey();
- Object k2 = e.getKey();
- if (k1 == k2 || (k1 != null && k1.equals(k2))) {
- Object v1 = getValue();
- Object v2 = e.getValue();
- if (v1 == v2 || (v1 != null && v1.equals(v2)))
- return true;
- }
- return false;
- }
-
- public final int hashCode() {
- return (key==null ? 0 : key.hashCode()) ^
- (value==null ? 0 : value.hashCode());
- }
-
- public final String toString() {
- return getKey() + "=" + getValue();
- }
static class Entry<K,V> implements Map.Entry<K,V> {
final K key;
V value;
Entry<K,V> next;
final int hash;
/**
* Creates new entry.
*/
Entry(int h, K k, V v, Entry<K,V> n) {
value = v;
next = n;
key = k;
hash = h;
}
public final K getKey() {
return key;
}
public final V getValue() {
return value;
}
public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
}
public final boolean equals(Object o) {
if (!(o instanceof Map.Entry))
return false;
Map.Entry e = (Map.Entry)o;
Object k1 = getKey();
Object k2 = e.getKey();
if (k1 == k2 || (k1 != null && k1.equals(k2))) {
Object v1 = getValue();
Object v2 = e.getValue();
if (v1 == v2 || (v1 != null && v1.equals(v2)))
return true;
}
return false;
}
public final int hashCode() {
return (key==null ? 0 : key.hashCode()) ^
(value==null ? 0 : value.hashCode());
}
public final String toString() {
return getKey() + "=" + getValue();
}
Entry定义了四个成员变量,key和value分别用来存放键值对映射的键和值,这里注意key的定义是final的,也就是说key初始化完毕后就不能改变了,也因此HashMap只提供key的getter方法,value则提供了setter和getter方法。换句话说,如果我的HashMap中保存了“aaa”键和“value1”值这样一个键值对,而我想改成键为“bbb”而值不变依然为“value1”,只能通过先删除aaa键值对,再增加“bbb”键值对,不能直接修改“aaa”这个键。next指向另一个Entry'对象(相当于链表的指针),hash为一个final的int变量,也是只有在初始化时赋值,他是键key的hash值,Entry只有一个带参数的构造方法,所以生成新对象时就确定了key和hash。
HashMap默认构造方法:
[java] view plain copy print ?
- public HashMap() {
- this.loadFactor = DEFAULT_LOAD_FACTOR;
- threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);
- table = new Entry[DEFAULT_INITIAL_CAPACITY];
- init();
- }
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR;
threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);
table = new Entry[DEFAULT_INITIAL_CAPACITY];
init();
}
生成一个默认长度(16)的Entry数组。
往HashMap对象中存放键值对方法put():
[java] view plain copy print ?
- public V put(K key, V value) {
- if (key == null)
- return putForNullKey(value);
- int hash = hash(key.hashCode());
- 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;
- }
public V put(K key, V value) {
if (key == null)
return putForNullKey(value);
int hash = hash(key.hashCode());
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;
}
对于key==null的键值对,调用了putForNullKey()方法:
[java] view plain copy print ?
- private V putForNullKey(V value) {
- for (Entry<K,V> e = table[0]; e != null; e = e.next) {
- if (e.key == null) {
- V oldValue = e.value;
- e.value = value;
- e.recordAccess(this);
- return oldValue;
- }
- }
- modCount++;
- addEntry(0, null, value, 0);
- return null;
- }
private V putForNullKey(V value) {
for (Entry<K,V> e = table[0]; e != null; e = e.next) {
if (e.key == null) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(0, null, value, 0);
return null;
}
从这个方法看,好像是key为null的键值对是固定保存在table[0]的。(这里有疑问)。e.recordAccess()方法不明白意思。最后是调用了addEntry(0, null, value, 0);这个方法(应该就是table[0]处,说明key为null的hash值为0,保存在数组table第一位):
[java] view plain copy print ?
- void addEntry(int hash, K key, V value, int bucketIndex) {
- Entry<K,V> e = table[bucketIndex];
- table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
- if (size++ >= threshold)
- resize(2 * table.length);
- }
void addEntry(int hash, K key, V value, int bucketIndex) {
Entry<K,V> e = table[bucketIndex];
table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
if (size++ >= threshold)
resize(2 * table.length);
}
这个方法先把bucketIndex处的对象赋值给e(bucketIndex也就是要添加的键值对在table数组的下标,从put方法看,就是i,i = indexFor(hash, table.length);),然后生成一个新的Entry对象,存放在table[bucketIndex]处,而这个新Entry对象的next指向了原来的那个Entry对象,就是e。
put()方法中的for循环用来判断链上是否已经存有相同键(key)的元素,判断是否相等,就是要hash值相等并且((k = e.key) == key || key.equals(k)),如果有相同的键存在,就修改其值,反之就是没有相同的键,那么就执行addEntry()方法,添加新的键值对。
HashMap在内存中的示意映像图:
HashMap底层维护一个数组(Entry[]),我们向HashMap中所放置的对象实际上是存储在该数组中。
当向HashMap中put一对键值时,他会根据key的hashCode值计算出一个位置,该位置就是此对象准备往数组中存放的位置。
如果该位置没有对象存在就将此对象直接放进数组当中;如果该位置已经有对象存在了,则顺着此存在的对象的链开始寻找(Entry类有一个Entry类型的next成员变量,指向了该对象的下一个对象),如果此链上有对象的话,再去使用equals方法进行比较,如果对此链上的某个对象的equals方法比较为false,则将该对象放到数组当中,然后将数组中该位置以前存在的那个对象连接到此对象的后面。
3、Properties类
主要用于一些属性文件,key = value形式
Properties的key和value都是String的
获取环境变量:
[java] view plain copy print ?
- import java.util.Iterator;
- import java.util.Properties;
- import java.util.Set;
-
- public class PropertiesTest
- {
- public static void main(String[] args)
- {
- Properties p = System.getProperties();
-
- Set set = p.keySet();
-
- for(Iterator iter = set.iterator();iter.hasNext();)
- {
- String key = (String)iter.next();
- String value = p.getProperty(key);
-
- System.out.println(key + "=" + value);
- }
- }
- }