HashMap 和HashSet 的区别:
HashMap 是线程不安全的,而HashSet 是线程安全的。原因在于两个数据结构的set 和get方法的实现。HashMap代码如下:
public V get(Object key) { Nodee; return (e = getNode(hash(key), key)) == null ? null : e.value; }final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { Node [] tab; Node 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 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 )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; }
而HashTable ,则是使用了synchronized 来保证线程安全,代码如下:
1 public synchronized V get(Object key) { 2 Entry tab[] = table; 3 int hash = key.hashCode(); 4 int index = (hash & 0x7FFFFFFF) % tab.length; 5 for (Entry e = tab[index] ; e != null ; e = e.next) { 6 if ((e.hash == hash) && e.key.equals(key)) { 7 return (V)e.value; 8 } 9 }10 return null;11 }12 13 public synchronized V put(K key, V value) {14 // Make sure the value is not null15 if (value == null) {16 throw new NullPointerException();17 }18 19 // Makes sure the key is not already in the hashtable.20 Entry tab[] = table;21 int hash = key.hashCode();22 int index = (hash & 0x7FFFFFFF) % tab.length;23 @SuppressWarnings("unchecked")24 Entryentry = (Entry )tab[index];25 for(; entry != null ; entry = entry.next) {26 if ((entry.hash == hash) && entry.key.equals(key)) {27 V old = entry.value;28 entry.value = value;29 return old;30 }31 }32 33 addEntry(hash, key, value, index);34 return null;35 }
java 中,hash 散列表,是由链表数组实现的。但是在Java SE 8中,桶满时,hash 的数据结构,会从链表数组变为平衡二叉树
HashSet ,是Set 的实现类。hash 保证拥有相同的值的元素将会拥有相同的hashCode(),由于hashCode()的唯一性,所以相同的元素最多只能插入hashSet 中一次。这也是Set需要保证的。
TreeSet,也是Set的实现类。相对比HashSet ,TreeSet是一个有序的集合,插入数据会比HashSet慢,但是检索查找元素会比数组、链表的重复元素会快很多,它的数据结构是红黑树。使用TreeSet,TreeSet里面的元素必须实现Comparable接口,或者构造集合时候提供一个Comparator。
如果两个变量使用equals() 相等,那么它们的hash值也必然是相等的。原因如下:
public final boolean equals(Object o) { if (o == this) return true; if (o instanceof Map.Entry) { Map.Entry e = (Map.Entry )o; if (Objects.equals(key, e.getKey()) && Objects.equals(value, e.getValue())) return true; } return false; }
因为equals的实现,就是判断key 和value 是否都相等。对于hash算法来说说,拥有着相同的key,必然也拥有相同hash值
-------------------------------------------------------------------------------------------------------
队列 ,有队列Queue 和双端队列Deque 的分别。Queue 和Deque 都可以指定容量Capacity,也可以不指定。Deque 中还提供了offer*()这样的系列方法。《Java核心技术》中提到的,Deque 的add() 如果超过Capacity 会抛出异常,而offer*()不会抛出异常,只会返回null,这个说法是错的。经过实践以及源码查看你,这两个方法都不会返回null。代码如下:
@Test public void addArrayDeque() { System.out.println("下面是由ArrayDeque实现的Deque"); DequestringDeque = new ArrayDeque<>(3); stringDeque.add("1"); stringDeque.add("2"); stringDeque.add("2"); stringDeque.add("3"); stringDeque.offer("4"); for (String item : stringDeque) { System.out.print(item + "|"); } } @Test public void addLinkedListQueue() { System.out.println("下面是由LinkedList实现的Deque"); Deque stringDeque = new LinkedList<>(); stringDeque.add("1"); stringDeque.add("2"); stringDeque.add("2"); stringDeque.add("3"); stringDeque.offer("4"); for (String item : stringDeque) { System.out.print(item + "|"); } }
打印出来的结果如下:
下面是由ArrayDeque实现的Deque1|2|2|3|4|下面是由LinkedList实现的Deque1|2|2|3|4|
优先级队列 的数据结构是堆,本质上是一个二叉树。
--------------------------------------------------------------------------
Map不能对同一个key put 两次value。事实上,第二次的put的value 将会覆盖掉第一次的value,并返回第一次的value。代码如下:
@Test public void add() { //Map里面定义的value 不能是primitive(原始)类型,因此不能使用int、boolean 这些 Map map = new HashMap<>(); map.put(1, "james"); map.put(2, "lily"); map.put(3, "tom"); //Map不能对同一个key put 两次value。事实上,第二次的put的value 将会覆盖掉第一次的value,并返回第一次的value String oldValue = map.put(1, "tony"); System.out.println(map.get(1)); System.out.println(oldValue); //get 将返回null System.out.println(map.get(5)); //getOrDefault 如果为null,返回自定义的值 System.out.println(map.getOrDefault(5, "empty")); //Map循环 map.forEach((k, v) -> { System.out.println("{key=" + k + ",value=" + v + "}"); }); }
下面是map的集的一些代码,不过这个不是TreeSet,也不是HashSet。
@Test public void set(){ Mapmap = new HashMap<>(); map.put(1, "james"); map.put(2, "lily"); map.put(3, "tom"); //键集 Set integers = map.keySet(); //键-值集 Set > entries = map.entrySet(); Iterator > iterator = entries.iterator(); while (iterator.hasNext()){ Map.Entry next = iterator.next(); System.out.println(next.getValue()); } //值集 Collection values = map.values(); }