Java中的查找树和哈希表(一级)

下面我们来看一下JAVA中有哪些查找树和哈希表,我们分两块内容来讲呗,第一块我们首先来讲查找树,第二块我们来讲哈希表,

JAVA里面我们有一个TreeSet,还有一个TreeMap,他们底层都是使用了红黑树,这个大家记住,我们在这里有一个图,这个图说明

一个什么道理呢,我是不是创建了一个TreeSet,我加的数都是1,2,3,4,5,但是我是不是按照不同的顺序来加的,我想问的一个

问题是,当我们按照不同的顺序来加的话,最终会不会出现这中偏激的情况,会不会出现,不会,为什么不会啊,因为他是红黑树,

红黑树他是一种二叉平衡树,当你没加一个元素,他都会保证这个数加完之后,加之前是平衡树,加之后还是个平衡树,会有相关

的算法来实现自平衡,比如我们按照1,2,3,4,5来加的话,先加一个1是平衡,再加一个2是放在右边,因为它大于1,这时候还是

平衡的,当我们再加个3的时候,他就不平衡了,他有各种算法,比如这个时候我们可以把2的左指针改一下,把1放到这里,这个1

就没有了,这个时候首先左右是平衡的,同时还是二叉平衡树,保证左边的小于根,根小于右边的,你还可以再加一个4,这个时候

还是平衡的,那我们再加5的时候,要加5了,是不是又不平衡了,他还要有一个相应的解决方案,算法把它修改为平衡的状态,

这个大家知道,这是我们所说的一个红黑树,那我顺便想问大家,TreeSet和TreeMap是什么关系,或者说我们的HashSet和HashMap

是什么关系,什么关系我们可以来看一下相关的源码
下面我们来看HashMap的底层结构:

我知道HashSet的底层结构是HashMap了,那你能告诉我HashMap的底层结构是什么吗?

我告诉你,HashMap的底层结构就是哈希表,他就是哈希表,只要以后你见到这个名字,只要这个名字是以Hash开始的,都说明他的

底层结构是哈希表,都说明它是非常快的,速度是很快的,这是我们选择相应集合的一个依据,这个大家知道,底层就是一个哈希表,

主结构是一个数组,每个数组上面可以引一个链,这个链叫做一个桶,我们来看,但是这个时候还有一个变化,在JDK及其之前,我们 

现在用的是哪一个,咱们是8,有的同学用的可能是7,那我告诉大家,在JDK1.7之前的话,HashMap底层就是一个哈希表,主结构是一个

数组,从结构是链表,大家来看,主结构是一个数组,然后从结构是一个链表,一个链表连起来就是一个桶,bucket这样的一个单词,

但是我们知道,它是有缺点的,万一这个冲突比较多的话,如果这个链表引得特别长的话,引了20个,30个的话,引了几十个的话,

他的查询效率也是比较低的,有没有别的办法,当然有了,比如后面我们可以给他扩容,这个都是有的,但是即使扩容,也难免会

出现这种情况,那我们怎么来解决,在JDK1.8的时候,给了一个更好的,当然也是一个更好的解决方案,大家再来看下面一个图,

我们来看下面这个图,这个图还是1.7之前的图,每个节点里面存储的还是entry,什么叫entry,有一个key,有一个value,

还有一个指针,这是每个节点,至少包括这三部分,数据就是存在这里面的,数据就是存在这儿的,每个Entry就是一个键值对,

当然还存放着指向下一个节点的指针,这是我们所说的内容,下边我们看,在1.8里面出现了一个什么变化,出现了这样的一个变化,

告诉我这个变化是什么,主结构是不是还是一个数组,如果出现了冲突,是不是还会引这个链表,但是下面一大堆一看就知道是什么,

这是不是红黑树,他怎么引出了红黑树,如果你这个列表的长度,大于等于8的时候,你这个列表太长了,大于等于8个的时候,

他就会把这一块做一下修改,给你变成下面这个结构,为什么要这么变,因为如果要是16个的话,那你这里要查找16次,当你变成一棵树

的话,层次就少了,可能3次4次就可以了,目的还是为了提高查询的效率,他还是提供啊查询的效率,这个主要是查询的时间复杂度上,

链表为O(n),而红黑树是log(2 n),如果冲突比较多,超过8,就采用红黑树来提高效率,这一点大家知道,当然红黑树难度就高了,

代码也就难理解了,这是一个
当我们再来看一下,HashSet的底层结构已经说了,底层就是HashMap,那我们再来看一下HashMap的源码分析,我们以JDK1.7的为准,

那我们现在是1.8的,1.8的可以简单的看一下,怎么看呢,我们就看HashMap就可以了



总结一下:

注意TreeSet和TreeMap用的是红黑树,TreeSet的底层用的是TreeMap,HashMap的底层结构1.7是表加链表,1.8有什么变化,加入了

红黑树,链表长度大于等于8的时候,源码大家去网上找一些相关的源码,最好看JDK1.7的,或者你的版本是1.7,直接来看,看哪些

操作,看他有哪些属性,看他有哪些方法,看他的put和get,来看这个就可以了,网上找资料有什么好处,它会给你加一些中文的

注释,会给你讲解,当然我们刚才找了半天也没有找到合适的,大家耐性来找
package com.learn.search;

import java.util.HashMap;
import java.util.HashSet;
import java.util.TreeSet;

/**
 * HashSet的底层结构
 * 底层结构采用HashMap
 * HashSet的元素作为HashMap的key,
 * 统一使用Object对象作为value
 * 我们可以看源码
 * 这是我们所说的一个内容
 * 我们这里还可以再加一句话,
 * TreeSet的底层结构是TreeMap,都使用了红黑树
 * 因为它底层就是TreeMap,TreeMap的底层是红黑树
 * TreeSet当然也是红黑树
 * @author Leon.Sun
 *
 */
public class TestCollection {

	public static void main(String[] args) {
		/**
		 * 然后我们来创建一个TreeSet
		 * 当我们创建TreeSet的时候,
		 * 点他进来,
		 * this(new TreeMap<E,Object>());
		 * 底层实际上是创建了一个TreeMap
		 * 你创建了一个TreeSet,实际上是创建了一个TreeMap
		 * 他给你创建了一个TreeMap
		 * 这个大家记住了
		 * TreeSet(NavigableMap<E,Object> m)
		 * this.m = m;
		 * 这个m是谁看一下
		 * private transient NavigableMap<E,Object> m;
		 * m实际上是一个Map
		 * 这个明确一下
		 * 我们再来看一下size
		 * return m.size();
		 * 你这个哈希size,你这个TreeSet的size
		 * 实际上就是map的size
		 * m.clear();
		 * 还是map的清空啊
		 * 为什么,因为我刚才做了一个清空的操作
		 * this(new TreeMap<E,Object>());
		 * 是不是new了一个TreeMap
		 * 结果把这个TreeMap做了一个实参
		 * TreeSet(NavigableMap<E,Object> m)
		 * 这个就是一个TreeMap,
		 * this.m = m;
		 * 就给这个m了
		 * 就给他了
		 * 
		 */
		TreeSet set = new TreeSet();
		/**
		 * 加入一个内容
		 */
		set.add("aaa");
		set.size();
		/**
		 * 很多的函数
		 */
		set.clear();
		
		
		/**
		 * 同样顺便我们来说一下
		 * 我们创建一个HashSet
		 * 当你new了一个HashSet的时候,
		 * 你实际上是new了一个HashMap
		 * map = new HashMap<>();
		 * 这个map是谁,点一下这个map
		 * private transient HashMap<E,Object> map;
		 * 底层就是一个HashMap
		 * 是这么来的
		 * 那我们再来看,
		 * 当我们add的时候呢
		 * public boolean add(E e)
		 * 你往set里面加个数据,
		 * return map.put(e, PRESENT)==null;
		 * put我们都用过,放了一个键值对
		 * 那我们这里放的abc,到map里面是key
		 * 那value是谁啊
		 * 一个键一个值
		 * 就是我们往set里面放的元素
		 * 放到map里面,
		 * 会变成map里面的key
		 * 值是这个PRESENT
		 * PRESENT他是谁啊
		 * private static final Object PRESENT = new Object();
		 * 它是一个空的Object对象
		 * 他就是一个Object对象
		 * 表面上是Set,实际上都放到Map里面去了
		 * 作为Map的key存在
		 * 那他的value是什么啊
		 * value都是统一的new Object(),Object对象
		 * 这个大家知道
		 * 
		 */
		HashSet set2 = new HashSet();
		set2.add("abc");
		/**
		 * 我们再来看size,
		 * return map.size();
		 * map的size
		 * 
		 */
		set2.size();
		/**
		 * map.clear();
		 * map的clear
		 * 
		 */
		set2.clear();
		/**
		 * return map.isEmpty();
		 * map是不是空的
		 * 这一点大家一定要记住
		 */
		set2.isEmpty();
		
		
		/**
		 * 看我们添加对象的时候是怎么添加的
		 * this.loadFactor = DEFAULT_LOAD_FACTOR;
		 * 这个什么意思,相当于我们一创建HashMap的时候,
		 * 是不是只引用了一个参数,别的参数有没有指定
		 * 没有指定,
		 * static final float DEFAULT_LOAD_FACTOR = 0.75f;
		 * 这个Default_load_factor什么意思
		 * 0.75是什么啊,就是我们将哈希表说的装填因子
		 * 他采用默认的是0.75
		 * 如果你要是100个,数组的总长度是100,
		 * 达到75个之后就扩容了
		 * 他就开始扩了
		 * 那我想知道默认的总长度是多少
		 * 我想知道哈希表默认的总长度是多少
		 * 那我们是可以找一下的
		 * static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
		 * 默认的总长度,这什么意思啊
		 * 1左移4位,每左移相当于乘以2
		 * 相当于16嘛
		 * 默认的初始化长度是16
		 * 但是你要知道
		 * 如果从源码上来看的话
		 * 他里面有没有指定默认的值
		 * 没有指定
		 * 初始化容量,默认装填因子
		 * public HashMap(int initialCapacity, float loadFactor)
		 * 这里面会有的
		 * this.threshold = tableSizeFor(initialCapacity);
		 * 会有这样的一个操作
		 * 从而把16传进去,赋给另外一个属性
		 * 这个就是1.8,1.8来说就要难一些了
		 * 我们再来看这个put了
		 * 
		 */
		HashMap map = new HashMap();
		/**
		 * 这个相当于添加
		 * 
		 * put什么意思
		 * 就调了个putVal,
		 * return putVal(hash(key), key, value, false, true);
		 * putVal里面怎么办
		 * 这里面来说就是相对比较复杂了
		 * 我们这里面甚至都有TreeNode了
		 * 那就涉及到了红黑树了
		 * 红黑树的节点
		 * 就是链表的节点,可以这么来理解
		 * 这是我们所说的一个内容
		 * 
		 */
		map.put("cn", "China");
		map.put("us", "USA");
		/**
		 * 请问这个时候我输出us是谁
		 * 是USA还是America
		 * 是不是America
		 * 因为我们来放是按照key来放的
		 * 我们按照key来放的
		 * 值是跟着key走的
		 * 如果我们放了一次us
		 * 现在再放一次us
		 * us只存一个
		 * 但是这个value就变了
		 * 就由USA变成了America
		 * 就变成他了
		 * 那我们看一下是不是这个代码
		 * 打开他来看
		 * 哈希码相同并且对象相同
		 * 新值替换旧值
		 * 这什么意思啊
		 * 返回的就是一个旧值
		 * V oldValue = e.value;
		 * 把原来的值赋给旧值
		 * e.value = value;
		 * 把现在的值相当于旧值替换
		 * return oldValue;
		 * 最后返回的是旧值
		 * 是这么来的
		 * 大家还是在网上多搜一下1.7的代码
		 * 还是比较好看的
		 * 因为1.8加了一个我们红黑树
		 * 代码比较难懂
		 * 大家就可以到网上找一些相关的资料来看
		 * 那JAVA中的查找和哈希表就讲到这里了
		 */
		map.put("us", "America");
		/**
		 * 大家想一下,这个相当于获取
		 * 
		 * 我们再来看一个内容,
		 * get怎么办呢,
		 * 找HashMap的get呗
		 * 他最终调的是getNode,
		 * return (e = getNode(hash(key), key)) == null ? null : e.value;
		 * 先得到这个e,
		 * e就是那个Entry
		 * getNode可以看一下里面的这个代码
		 * 因为自从1.8之后这个难度就比较高了
		 * 比较难理解
		 * 那我们就看一下1.7的
		 * 1.7的就非常好理解了
		 * 怎么看1.7呢
		 * 那我们就网上来找吧
		 * 很多人关注他
		 * https://www.cnblogs.com/stevenczp/p/7028071.html
		 * 很多人关注这个
		 * http://www.cnblogs.com/wuhuangdi/p/4175991.html
		 * 至少找一个排版比较好的
		 * HashMap的存储结构
		 * 主结构是一个数组
		 * 这是不是一个链表啊
		 * 而每个Entry里面一放大,
		 * 有key,有value,是不是还要有一个指针指向下一个
		 * 我们给大家介绍一下,大家看资料的时候也比较方便
		 * 我们找他无参构造方法吧
		 * 他这个不好
		 * https://blog.csdn.net/jevonscsdn/article/details/54619114
		 * 先了解一下他的成员
		 * static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; 
		 * 这是我们刚才看到的默认长度16
		 * static final float DEFAULT_LOAD_FACTOR = 0.75f;//默认加载因子
		 * 默认的加载因子0.75
		 * 这个也不好
		 * 总可以找到很好的
		 * 构造方法,
		 * 比如我们来看这个,
		 * https://blog.csdn.net/u011617742/article/details/54576890
		 * 我们就是要找无参构造方法
		 * https://blog.csdn.net/ghsau/article/details/16843543/
		 * public V put(K key, V value)
		 * 他这个put就是1.7的
		 * 这个put什么含义
		 * 我们找最关键的,
		 * int hash = hash(key);
		 * 得到这个方法
		 * 把key传进来,调用这个hash,他就会得到哈希码
		 * 就是key本身是有哈希码的,这个二次进行了hash,这是一个细节
		 * 把这个哈希码又重新转换了一下,这一步就相当于得到哈希码
		 * int i = indexFor(hash, table.length);
		 * 这一步indexFor,根据哈希码和数组的长度去得到这个地址
		 * 这是我们哈希表原理里面的第一步
		 * 第一步计算哈希码,第二步根据哈希码算出地址,
		 * 第三步我们要干什么了,
		 * 就是1这个位置,是不是要顺着链表往下找了
		 * 往下找怎么办,如果找到一个14呢
		 * 不是不放,我们放的是map,
		 * map是什么意思,
		 * map就是这个意思,
		 * 
		 * 
		 */
		map.get("cn");
		
		
	}
}

猜你喜欢

转载自blog.csdn.net/Leon_Jinhai_Sun/article/details/89746543