必修选修: 必修html
参考本博客:点击进入对Java中的红黑树(TreeMap,HashMap)进行源码分析,并在实验报告中体现分析结果。java
实验1:实现二叉树的解决过程及结果
数组
实验2:中序先序序列构造二叉树的解决过程及结果
安全
实验3:决策树的解决过程及结果
数据结构
实验4:表达式树的解决过程及结果
app
实验5:二叉查找树的解决过程及结果
框架
实验6 : 红黑树分析的解决过程及结果dom
写在前面:刚找到TreeMap和HashMap的源码,实际上是有些慌张不知所措的,静下心来看一看,发现实际上是对不少方法的注释很长,因此两个源码都是很长。函数
首先,咱们先要去了解Map是啥?Key是啥?而Value又是啥?源码分析
在数组中咱们是经过数组下标来对其内容索引的,而在Map中咱们经过对象来对对象进行索引,用来索引的对象叫作key,其对应的对象叫作value。这就是平时说的键值对Key - value。
TreeMap 是一个有序的key-value集合,它是经过红黑树实现的。TreeMap继承于AbstractMap,因此它是一个Map,即一个key-value集合。TreeMap实现了NavigableMap接口,意味着它支持一系列的导航方法。好比返回有序的key集合。TreeMap实现了Cloneable接口,意味着它能被克隆。TreeMap实现了java.io.Serializable接口,意味着它支持序列化。TreeMap基于红黑树(Red-Blacktree)实现。该映射根据其键的天然顺序进行排序,或者根据建立映射时提供的 Comparator 进行排序,具体取决于使用的构造方法。TreeMap的基本操做 containsKey、get、put 和 remove 的时间复杂度是 log(n) 。
另外,TreeMap是非同步的。 它的iterator 方法返回的迭代器是fail-fastl的。
// 默认构造函数。 HashMap() // 指定“容量大小”的构造函数 HashMap(int capacity) // 指定“容量大小”和“加载因子”的构造函数 HashMap(int capacity, float loadFactor) // 包含“子Map”的构造函数 HashMap(Map<? extends K, ? extends V> map)
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable
static class Entry<K,V> implements Map.Entry<K,V> { final K key; V value; Entry<K,V> next; int hash; Entry(int h, K k, V v, Entry<K,V> n) { value = v; next = n; key = k; hash = h; } // setter, getter, equals, toString 方法省略 public final int hashCode() { //用key的hash值与上value的hash值做为Entry的hash值 return Objects.hashCode(getKey()) ^ Objects.hashCode(getValue()); } /** * This method is invoked whenever the value in an entry is * overwritten by an invocation of put(k,v) for a key k that's already * in the HashMap. */ void recordAccess(HashMap<K,V> m) { } /** * This method is invoked whenever the entry is * removed from the table. */ void recordRemoval(HashMap<K,V> m) { } }
transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;
public V get(Object key) { //单独处理key为null的状况 if (key == null) return getForNullKey(); Entry<K,V> entry = getEntry(key); return null == entry ? null : entry.getValue(); } private V getForNullKey() { if (size == 0) { return null; } //key为null的Entry用于放在table[0]中,可是在table[0]冲突链中的Entry的key不必定为null //因此须要遍历冲突链,查找key是否存在 for (Entry<K,V> e = table[0]; e != null; e = e.next) { if (e.key == null) return e.value; } return null; } final Entry<K,V> getEntry(Object key) { if (size == 0) { return null; } int hash = (key == null) ? 0 : hash(key); //首先定位到索引在table中的位置 //而后遍历冲突链,查找key是否存在 for (Entry<K,V> e = table[indexFor(hash, table.length)]; e != null; e = e.next) { Object k; if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) return e; } return null; }
private void inflateTable(int toSize) { //辅助函数,用于填充HashMap到指定的capacity // Find a power of 2 >= toSize int capacity = roundUpToPowerOf2(toSize); //threshold为resize的阈值,超事后HashMap会进行resize,内容的entry会进行rehash threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1); table = new Entry[capacity]; initHashSeedAsNeeded(capacity); } /** * Associates the specified value with the specified key in this map. * If the map previously contained a mapping for the key, the old * value is replaced. */ public V put(K key, V value) { if (table == EMPTY_TABLE) { inflateTable(threshold); } if (key == null) return putForNullKey(value); int hash = hash(key); int i = indexFor(hash, table.length); //这里的循环是关键 //当新增的key所对应的索引i,对应table[i]中已经有值时,进入循环体 for (Entry<K,V> e = table[i]; e != null; e = e.next) { Object k; //判断是否存在本次插入的key,若是存在用本次的value替换以前oldValue,至关于update操做 //并返回以前的oldValue if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } //若是本次新增key以前不存在于HashMap中,modCount加1,说明结构改变了 modCount++; addEntry(hash, key, value, i); return null; } void addEntry(int hash, K key, V value, int bucketIndex) { //若是增长一个元素会后,HashMap的大小超过阈值,须要resize if ((size >= threshold) && (null != table[bucketIndex])) { //增长的幅度是以前的1倍 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) { //首先获得该索引处的冲突链Entries,有可能为null,不为null Entry<K,V> e = table[bucketIndex]; //而后把新的Entry添加到冲突链的开头,也就是说,后插入的反而在前面(第一次还真没看明白) //须要注意的是table[bucketIndex]自己并不存储节点信息, //它就至关因而单向链表的头指针,数据都存放在冲突链中。 table[bucketIndex] = new Entry<>(hash, key, value, e); size++; } //下面看看HashMap是如何进行resize,庐山真面目就要揭晓了 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]; //initHashSeedAsNeeded(newCapacity)的返回值决定了是否须要从新计算Entry的hash值 transfer(newTable, initHashSeedAsNeeded(newCapacity)); 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; //遍历当前的table,将里面的元素添加到新的newTable中 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]; //最后这两句用了与put放过相同的技巧 //将后插入的反而在前面 newTable[i] = e; e = next; } } } /** * Initialize the hashing mask value. We defer initialization until we * really need it. */ final boolean initHashSeedAsNeeded(int capacity) { boolean currentAltHashing = hashSeed != 0; boolean useAltHashing = sun.misc.VM.isBooted() && (capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD); //这里说明了,在hashSeed不为0或知足useAltHash时,会重算Entry的hash值 //至于useAltHashing的做用能够参考下面的连接 // http://stackoverflow.com/questions/29918624/what-is-the-use-of-holder-class-in-hashmap boolean switching = currentAltHashing ^ useAltHashing; if (switching) { hashSeed = useAltHashing ? sun.misc.Hashing.randomHashSeed(this) : 0; } return switching; }
public V remove(Object key) { Entry<K,V> e = removeEntryForKey(key); //能够看到删除的key若是存在,就返回其所对应的value return (e == null ? null : e.value); } final Entry<K,V> removeEntryForKey(Object key) { if (size == 0) { return null; } int hash = (key == null) ? 0 : hash(key); int i = indexFor(hash, table.length); //这里用了两个Entry对象,至关于两个指针,为的是防治冲突链发生断裂的状况 //这里的思路就是通常的单向链表的删除思路 Entry<K,V> prev = table[i]; Entry<K,V> e = prev; //当table[i]中存在冲突链时,开始遍历里面的元素 while (e != null) { Entry<K,V> next = e.next; Object k; if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) { modCount++; size--; if (prev == e) //当冲突链只有一个Entry时 table[i] = next; else prev.next = next; e.recordRemoval(this); return e; } prev = e; e = next; } return e; }
// 默认构造函数。使用该构造函数,TreeMap中的元素按照天然排序进行排列。 TreeMap() // 建立的TreeMap包含Map TreeMap(Map<? extends K, ? extends V> copyFrom) // 指定Tree的比较器 TreeMap(Comparator<? super K> comparator) // 建立的TreeSet包含copyFrom TreeMap(SortedMap<K, ? extends V> copyFrom)
public class TreeMap<K,V> extends AbstractMap<K,V> implements NavigableMap<K,V>, Cloneable, java.io.Serializable
public boolean containsKey(Object key) { return getEntry(key) != null; }
public boolean containsValue(Object value) { for (Entry<K,V> e = getFirstEntry(); e != null; e = successor(e)) if (valEquals(value, e.value)) return true; return false; }
public V get(Object key) { Entry<K,V> p = getEntry(key); return (p==null ? null : p.value); }
public void putAll(Map<? extends K, ? extends V> map) { int mapSize = map.size(); if (size==0 && mapSize!=0 && map instanceof SortedMap) { Comparator<?> c = ((SortedMap<?,?>)map).comparator(); if (c == comparator || (c != null && c.equals(comparator))) { ++modCount; try { buildFromSorted(mapSize, map.entrySet().iterator(), null, null); } catch (java.io.IOException | ClassNotFoundException cannotHappen) { } return; } } super.putAll(map); }
final Entry<K,V> getEntry(Object key) { // Offload comparator-based version for sake of performance if (comparator != null) return getEntryUsingComparator(key); if (key == null) throw new NullPointerException(); @SuppressWarnings("unchecked") Comparable<? super K> k = (Comparable<? super K>) key; Entry<K,V> p = root; while (p != null) { int cmp = k.compareTo(p.key); if (cmp < 0) p = p.left; else if (cmp > 0) p = p.right; else return p; } return null; }
问题1的解决:经过询问王文彬同窗,他教我理解了我代码中存在的问题,实际上是由于我构造了两个子树,但却没有链接在一块儿造成一个完整的树,修改后将左子树left
加入整个树的构造中就能够了。
问题2:在实验二我理解完前序输出和中序输出的奥妙以后,终于在苦苦的编码战斗中写完了程序时,测试一下,结果却很适合给学长学姐们国考加油!
解决过程:看图说话
经过认真的屡次研究修改,终于个人决策树完美出道了。
我以为不少东西理解和代码实现不是一回事,理解了我也不知道如何精确下手,可是在编写的时候我又能更深入的理解好多遍。虽然过程及其“撕心裂肺”,可是仍是要多多受虐,才能在下次受虐的时候减轻疼痛。