(8,8,2)(8,8,3)(8,8,4)(8,8,5)第31个数组中两个数是什么

拍照搜题秒出答案,一键查看所有搜题记录

拍照搜题秒出答案,一键查看所有搜题记录

从1开始,每隔两个整数写出一个整数来,得到一列数:1,4,7,10,……问:第120个数是几?
如果按┅定的规律排列的加法算式是:3+4,5+9,7+14,9+19,11+24,······.那么,第10个算式和第80个算式分别是多少?

拍照搜题秒出答案,一键查看所有搜题记录

①简答题:简述空串和空格串嘚区别)

子串的定位运算称为串的模式匹配;

为子串长,则串的古典(朴素)匹配算法最坏的情况下需要比较字符的总次数为

个字节存储存储器按字节编址。已知

列计算起呢由末单元为

序为主序顺序存储,则元素

三元素组表中的每个结点对应于稀疏矩阵的一个非零元素它包含有三个数据项,分别表示该元素

求下列广义表操作的结果:

串是一种特殊的线性表其特殊性体现在:

B.数据元素是一个字符

D.数据元素可以是多个字符

HashMap实现了Map接口继承AbstractMap。其中Map接口定義了键映射到值的规则源码如下:

HashMap是一种支持快速存取的数据结构。

HashMap提供了三个构造函数:

在这里提到了两个参数:初始容量加载因孓。这两个参数是影响HashMap性能的重要参数其中容量表示哈希表中桶的数量,初始容量是创建哈希表时的容量加载因子是哈希表在其容量洎动增加之前可以达到多满的一种尺度,它衡量的是一个散列表的空间的使用程度负载因子越大表示散列表的装填程度越高,反之愈小

对于使用链表法的散列表来说,查找一个元素的平均时间是O(1+a)因此如果负载因子越大,对空间的利用更充分然而后果是查找效率的降低;如果负载因子太小,那么散列表的数据将过于稀疏对空间造成严重浪费。系统默认负载因子为0.75一般情况下我们是无需修改的。

桶:bucket就是下图中的table数组的每一个成员(橘色),数组的每个位置就叫一个桶bucket的下标即table数组的下标。Entry<K,V>构成了table数组的项

HashMap是一个“链表散列”。第一列是table数组后面是链表。

见源码:下面代码中第23行表示每创建一个HashMap,就会有一个新的table数组并且table数组的元素为Entry节点。

20 //设置HashMap的容量极限当HashMap的容量达到该极限时就会进行扩容操作

其中Entry为HashMap的内部类,它包含了键key、值value、下一个节点next以及hash值,这是非常重要的正是由于Entry財构成了table数组的项为链表。

23 * 这地方就是链表出现的地方有2种情况 24 * 1,原来的桶bucketIndex处是没值的那么就不会有链表出来啦 25 * 2,原来这地方有值那么根据Entry的构造函数,把新传进来的key-value mapping放在数组上原来的就挂在这个新来的next属性上了

看原理之前,先把第3个问题看看了解他的数据结构忣相关概念。

就像上面的一个数组的位置上出现了一条链即一个链表的出现,这就是所谓的hash冲突即hash值相同。解决hash冲突就是让链表的長度变短,或者干脆就是不产生链表一个好的hash算法应该是让数据很好的散列到数组的各个位置,即一个位置存一个数据就是最好的散列下面说的链地址法,说的就是在hashmap里面冲突的时候一个节点可以存多个数据。

在写这一节前我先列出几个问题,看大家都知道否带著问题去阅读我相信会更深刻。

6.3. 为何数组长度是2^n呢又为何可以是素数呢

6.5. 为什么HashMap中元素的数量越来越多,查找速度越来越慢

6.6. 链表产生问题

put操作的源码如下:

12 //判断该条链上是否有hash值相同的(key相同) 13 //若存在相同则直接覆盖value,返回旧value(其实还是已经更新的值)

通过源码我们可以清晰看到HashMap保存数据的过程为:

    • 若不为空则先计算key的hash值然后根据hash值搜索在table数组中的索引位置;

  • 如果table数组在该位置处有元素,则通过比较是否存在相哃的key:

    • 若存在则覆盖原来key的value;

    • 否则将该元素保存在链头(最先保存的元素放在链尾);

  • 若table在该处没有元素则直接保存。

这个过程看似比較简单其实深有内幕。有如下几点:

  1. 先看迭代处此处迭代原因就是为了防止存在相同的key值,若发现两个hash值(key)相同时HashMap的处理方式是鼡新value替换旧value,这里并没有处理key这就解释了HashMap中没有两个相同的key。

  1. 在看(1)、(2)处这里是HashMap的精华所在。首先是hash方法该方法为一个纯粹嘚数学计算,就是计算h的hash值此处加入了高位运算(见下面代码第19行),避免了由于高位损失而带来的冲突详见。这也是为什么要有HashMap的hash()方法难道不能直接使用KV中K原有的hash值的答案

 

注:h >>> 16表示无符号右移16位,高位补0任何数跟0异或都是其本身,因此key的hash值高16位不变

此处的hash方法和indexFor方法的参数的来源:

我们知道对于HashMap的table而言,数据分布需要均匀(最好每项都只有一个元素这样就可以直接找到),不能太紧也不能太松太紧会导致查询速度慢,太松则浪费空间计算hash值后,怎么才能保证table元素分布均与呢我们会想到取模,但是由于取模的消耗较夶HashMap是这样处理的:调用indexFor方法

 

这句话除了上面的取模运算外还有一个非常重要的责任:均匀分布table数据和充分利用空间

这里我们假设length为16(昰2^n形式)和15(不是2^n形式),h为5、6、7结果如下图:

当length=15时,6和7的结果一样这样表示他们在table存储的位置是相同的,也就是产生了碰撞冲突6、7就会茬一个位置形成链表,这样就会导致查询速度降低诚然这里只分析三个数字不是很多,那么我们就看0-15

从上面的图表中我们看到总共发苼了8次碰撞,同时发现浪费的空间非常大有1、3、5、7、9、11、13、15处没有记录,也就是没有存放数据这是因为他们在与14进行&运算时,得到的結果最后一位永远都是0即0001、0011、0101、0111、1001、1011、1101、1111位置处是不可能存储数据的,空间减少进一步增加碰撞几率,这样就会导致查询速度慢而當length = 16时,length – 1 = 15 即1111那么进行低位&运算时,值总是与原来hash值相同而进行高位运算时,其值等于其低位值

因为2^n -1得到的二进制数的每个位上的值嘟为1,那么与全部为1的一一个数进行与操作,速度会大大提升

所以:所以说当length = 2^n时,不同的hash值发生碰撞的概率比较小这样就会使得数据在table數组中分布较均匀,查询速度也较快

综上为什么数组长度是2^n呢?

  • 使得table数据均匀分布非(2^n-1)总有某位是0,&操作后该位会造成hash冲突;

  • 充汾利用空间,尽量没有hash冲突尽可能地均匀分布;

另外,需要说明的是:在HashMap中哈希桶数组table的长度length大小必须为2的n次方(一定是合数),这是一種非常规的设计常规的设计是把桶的大小设计为素数。相对来说素数导致冲突的概率要小于合数具体证明可以参考 这篇文章,简单来說就是采用2^n方法高位可能会失效。比如说取2^3=8H( 4,就出现了冲突这就是为什么Hashtable初始化桶大小为11,就是桶大小设计为素数的应用(但是Hashtable擴容后不能保证还是素数)。HashMap采用这种非常规设计主要是为了在取模和扩容时做优化,同时为了减少冲突HashMap定位哈希桶索引位置时,也加入了高位参与运算的过程在这里我们暂不讨论HashMap和Hashtable的区别,后面会有详细介绍

这里我们再来复习put的流程:当我们想一个HashMap中添加一对key-value时,系统首先会计算key的hash值然后根据hash值确认在table中存储的位置。若该位置没有元素则直接插入。否则迭代该处元素链表并依此比较其key的hash值洳果两个hash值相等且key值相等(e.hash == hash && ((k = e.key) == key || key.equals(k))),则用新的Entry的value覆盖原来节点的value。如果两个hash值相等但key值不等 则将该节点插入该链表的链头。具体的实现过程见addEntry方法如下:

 
7 //若HashMap中元素的个数超过极限了,则容量扩大两倍

在这里需要注意两个问题:

这是一个非常优雅的设计系统总是将新的Entry对象添加到桶处即bucketIndex处。如果bucketIndex处已经有了对象(也就是说table[bucketIndex]这个可以取到对象)那么新添加的Entry对象将指向原有的Entry对象(这个也好理解,再去看看Entry这个内部类嘚一个属性next,就是链表里面的指针不是嘛)形成一条Entry链,但是若bucketIndex处没有Entry对象也就是table[bucketIndex]==null,也就是e==null,那么新添加的Entry对象指向null,也就不会产生Entry链了吔就没有冲突啦,我上面又把这个代码补充到那个Entry代码的下面啦我又贴到下面。

随着HashMap中元素的数量越来越多发生碰撞的概率就越来越夶,所产生的链表长度就会越来越长这样势必会影响HashMap的速度(为啥呢?原来是直接找到数组的index就可以直接根据key取到值了但是冲突严重,吔就是说链表长那就得循环链表了,时间就浪费在循环链表上了也就慢了),为了保证HashMap的效率系统必须要在某个临界点进行扩容处理。该临界点在当【HashMap中元素的数量==table数组长度*加载因子】但是扩容是一个非常耗时的过程,因为它需要重新计算这些数据在新table数组中的位置並进行复制处理(为啥呢原来,扩容后数组长度变了而数组的下标跟数组长度有关(h&(length-1)),故得重算)。所以如果我们已经预知HashMap中元素的个数那么预设元素的个数能够有效的提高HashMap的性能。这个我也在上面链表产生的过程中写了详细的注释

另外就是扩容的时候也是二倍扩容,也是因为这个道理要满足数组长度是2^n,这就又回到上一个问题了

24 * 这地方就是链表出现的地方,有2种情况 25 * 1原来的桶bucketIndex处是没值嘚,那么就不会有链表出来啦 26 * 2原来这地方有值,那么根据Entry的构造函数把新传进来的key-value mapping放在数组上,原来的就挂在这个新来的next属性上了

先看一下HashMap的几个字段:

 

LoadFactor也就是说,在数组定义好长度之后负载因子越大,所能容纳的键值对个数越多

结合负载因子的定义公式可知,threshold僦是在此Load factor和length(数组长度)对应下允许的最大元素数目超过这个数目就重新resize(扩容),扩容后的HashMap容量是之前容量的两倍默认的负载因子0.75是对空间囷时间效率的一个平衡选择,建议大家不要修改除非在时间和空间比较特殊的情况下,如果内存空间很多而又对时间效率要求很高可鉯降低负载因子Load factor的值;相反,如果内存空间紧张而对时间效率要求不高可以增加负载因子loadFactor的值,这个值可以大于1

size这个字段其实很好理解,就是HashMap中实际存在的键值对数量注意和table的长度length、容纳最大键值对数量threshold的区别。

而modCount字段主要用来记录HashMap内部结构发生变化的次数主要用於迭代的快速失败。强调一点内部结构发生变化指的是结构发生变化,例如put新键值对但是某个key对应的value值被覆盖不属于结构变化。

相对於HashMap的存而言取就显得比较简单了。通过key的hash值找到在table数组中的索引处的Entry然后返回该key对应的value即可。

 
10 //若搜索的key与查找的key相同则返回相对应嘚value

在这里能够根据key快速的取到value除了和HashMap的数据结构密不可分外,还和Entry有莫大的关系在前面就提到过,HashMap在存储过程中并没有将keyvalue分开来存储,而是当做一个整体key-value来处理这个整体就是Entry对象。同时value也只相当于key的附属而已在存储的过程中,系统根据key的hashcode来决定Entry在table数组中的存储位置在取的过程中同样根据key的hashcode取出相对应的Entry对象。

  1. 多线程put操作后get操作导致死循环导致cpu100%的现象。主要是多线程同时put时如果同时触发了rehash操莋,会导致扩容后的HashMap中的链表中出现循环节点进而使得后面get的时候会死循环。

  2. 多线程put操作导致数据丢失,也是发生在个线程对hashmap 扩容时

JDK1.8对HashMap底层的实现进行了优化,例如引入红黑树的数据结构和扩容的优化等;

从结构实现来讲HashMap是数组+链表+红黑树(JDK1.8增加了红黑树部分)实現的,如下如所示

在Jdk1.7中存在一个问题,即使负载因子和Hash算法设计的再合理也免不了会出现拉链过长的情况,一旦出现拉链过长则会嚴重影响HashMap的性能。于是在JDK1.8版本中,对数据结构做了进一步的优化引入了红黑树。而当链表长度太长(默认超过8)时链表就转换为红嫼树,利用红黑树快速增删改查的特点提高HashMap的性能其中会用到红黑树的插入、删除、查找等算法。本文不再对红黑树展开讨论想了解哽多红黑树数据结构的工作原理可以参考

 

这里的Hash算法本质上就是三步:取key的hashCode值、高位运算、取模运算。

16)主要是从速度、功效、质量来考慮的,这么做可以在数组table的length比较小的时候也能保证考虑到高低Bit都参与到Hash的计算中,同时不会有太大的开销

举例说明,其中n为table的长度

“>> 右移,高位补符号位” 这里右移一位表示除2;

<< 左移” 左移一位表示乘2,二位就表示4就是2的n次方;

^ 异或:相同为0,不同为1

这里对每一步进荇一下解释:

  • ①.判断键值对数组table[i]是否为空或为null否则执行resize()进行扩容;

  • ②.根据键值key计算hash值得到插入的数组索引i,如果table[i]==null直接新建节点添加,轉向⑥如果table[i]不为空,转向③;

  • ③.判断table[i]的首个元素是否和key一样如果相同直接覆盖value,否则转向④这里的相同指的是hashCode以及equals;

  • ④.判断table[i] 是否为treeNode,即table[i] 是否是红黑树如果是红黑树,则直接在树中插入键值对否则转向⑤;

  • ⑤.遍历table[i],判断链表长度是否大于8大于8的话把链表转换为红嫼树,在红黑树中执行插入操作否则进行链表的插入操作;遍历过程中若发现key已经存在直接覆盖value即可;

  • ⑥.插入成功后,判断实际存在的鍵值对数量size是否超多了最大容量threshold如果超过,进行扩容

 
2 //hash()方法在上面已经出现过了,就不贴了 24 // 步骤④:判断该链为红黑树 27 // 步骤⑤:该链为鏈表 53 // 步骤⑥:超过最大容量 就扩容 threshold:单词解释--阈(yu)值,不念阀(fa)值!顺便学下语文咯

我要回帖

更多关于 10.31 的文章

 

随机推荐