HashMap的实现与优化

转载自 HashMap的实现与优化

HashMap的优化与实践

本文是基于作者在github上的Android 问题交流讨论坛提问而产生的一篇文章,也是自己早打算开坑的一篇文章。文章首先介绍了hashMap的一些基本知识,然后介绍了它在JDK8下的实现原理,最后着重介绍了几个面试中处理大数据的方法,文章比较长,我也写了好久,希望各位能够读完并发表意见。

Android 题交流讨论坛是开源达人 Trinea 在gitHub上组建的一个讨论组织,那里的提问与回答都非常靠谱。

HashMap的复杂度

如图是ArrayList/LinkedList/HashMap三个数据结构的复杂度对比,可以看出HashMap整体上性能都非常不错,但是不稳定,为O(N/Buckets),N就是以数组中没有发生碰撞的元素。

 获取查找添加/删除空间 
ArrayListO(1)O(1)O(N)O(N)
LinkedListO(N)O(N)O(1)O(N)
HashMapO(N/Bucket_size)O(N/Bucket_size)O(N/Bucket_size)O(N)

注:发生碰撞实际上是非常稀少的,所以N/Bucket_size约等于1

HashMap是对Array与Link的折衷处理,Array与Link可以说是两个速度方向的极端,Array注重于数据的获取,而处理修改(添加/删除)的效率非常低;Link由于是每个对象都保持着下一个对象的指针,查找某个数据需要遍历之前所有的数据,所以效率比较低,而在修改操作中比较快。

复杂度是如何考察的?

对于数据结构,在时间上我们需要考察Acessing ,Search, Deletion/Insertion的平均与最差的复杂度。在空间上,我们要考虑维护这个数据结构所占用的内存空间。

常见的数据结构与排序的复杂度都在这里

HashMap的实现

本文以JDK8的API实现进行分析

1. 什么是hash,什么是碰撞?

  • Hash:是一种信息摘要算法,它还叫做哈希,或者散列。我们平时使用的MD5,SHA1,SSL中的公私钥验证都属于Hash算法,通过输入key进行Hash计算,就可以获取key的HashCode(),比如我们通过校验MD5来验证文件的完整性。
  • 碰撞:好的Hash算法可以出计算几乎出独一无二的HashCode,如果出现了重复的hashCode,就称作碰撞;
<code>&gt;就算是MD5这样优秀的算法也会发生碰撞,即两个不同的key也有可能生成相同的MD5。</code>

2. HashMap中是如何实现写入与读取的?

HashMap实现了Map接口,保存着K-V这样的集合。我们以put操作为例

2.1. 对key进行Hash计算

在JDK8中,由于使用了红黑树来处理大的链表开销,所以hash这边可以更加省力了,只用计算hashCode并移动到低位就可以了

1
2
3
4
5
static final int hash(Object key) {
    int h;
    //计算hashCode,并无符号移动到低位
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

下面给出几个常用的哈希码的算法。

  1. Object类的hashCode.返回对象的内存地址经过处理后的结构,由于每个对象的内存地址都不一样,所以哈希码也不一样。这个是native方法,这个取决于JVM的设计,一般是某种地址的偏移。
  2. String类的hashCode.根据String类包含的字符串的内容,根据一种特殊算法返回哈希码,只要字符串的内容相同,返回的哈希码也相同。
  3. Integer等包装类,返回的哈希码就是Integer对象里所包含的那个整数的数值,例如Integer i1=new Integer(100),i1.hashCode的值就是100 。由此可见,2个一样大小的Integer对象,返回的哈希码也一样。
  4. int,char这样的基础类,它们不需要hashCode,如果需要存储时,将进行自动装箱操作,计算方法同上。

2.2. 获取到当前的位置

计算了Hash,我们现在要把它插入数组中了

1
i = (tab.length - 1) & hash;

通过位运算,确定了当前的位置,因为HashMap数组的大小总是2^n,所以实际的运算就是 (0xfff…ff) & hash ,这里的tab.length-1相当于一个mask,滤掉了大于当前长度位的hash,使每个i都能插入到数组中。

2.3. 生成包装类

这个对象是一个包装类,Node<K,V>,内部有key,value,hash还有next,可以看出来它是一个链表。

1
2
3
4
5
6
7
static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        V value;
        Node<K,V> next;
        //getter and setter .etc.
}

2.4. 插入包装类到数组

(1). 如果输入当前的位置是空的,就插进去,如图,左为插入前,右为插入后

1
2
3
4
5
6
7
8
9
10
11
0           0
|           |
1 -> null   1 - > null
|           |
2 -> null   2 - > null
|           |
..-> null   ..- > null
|           |
i -> null   i - > new node
|           |
n -> null   n - > null

(2). 如果当前位置已经有了node,且它们发生了碰撞,则新的放到前面,旧的放到后面,这叫做链地址法处理冲突。

1
2
3
4
5
6
7
8
9
10
11
0           0
|           |
1 -> null   1 - > null
|           |
2 -> null   2 - > null
|           |
..-> null   ..- > null
|           |
i -> old    i - > new - > old
|           |
n -> null   n - > null

我们可以发现,失败的hashCode算法会导致HashMap的性能下降为链表,所以想要避免发生碰撞,就要提高hashCode结果的均匀性。当然,在JDK8中,采用了红黑二叉树进行了处理,这个我们后面详细介绍。

什么是Hash攻击?

通过请求大量key不同,但是hashCode相同的数据,让HashMap不断发生碰撞,硬生生的变成了SingleLinkedList

1
2
3
4
5
6
7
8
9
0
|
1 -> a ->b -> c -> d(撞!撞!撞!复杂度由O(1)变成了O(N))
|
2 -> null(本应该均匀分布,这里却是空的)
|
3 -> null
|
4 -> null

这样put/get性能就从O(1)变成了O(N),CPU负载呈直线上升,形成了放大版DDOS的效果,这种方式就叫做hash攻击。在Java8中通过使用TreeMap,提升了处理性能,可以一定程度的防御Hash攻击。

3. 扩容

如果当表中的75%已经被占用,即视为需要扩容了

1
(threshold = capacity * load factor ) < size

它主要有两个步骤:

1. 容量加倍

左移1位,就是扩大了两倍,用位运算取代了乘法运算

1
2
newCap = oldCap << 1;
newThr = oldThr << 1;

2. 遍历计算Hash

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
for (int j = 0; j < oldCap; ++j) {
                Node<K,V> e;
                //如果发现当前有Bucket
                if ((e = oldTab[j]) != null) {
                    oldTab[j] = null;
                    //如果这里没有碰撞
                    if (e.next == null)
                        //重新计算Hash,分配位置
                        newTab[e.hash & (newCap - 1)] = e;
                    //这个见下面的新特性介绍,如果是树,就填入树
                    else if (e instanceof TreeNode)
                        ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                    //如果是链表,就保留顺序....目前就看懂这点
                    else { // preserve order
                        Node<K,V> loHead = null, loTail = null;
                        Node<K,V> hiHead = null, hiTail = null;
                        Node<K,V> next;
                        do {
                            next = e.next;
                            if ((e.hash & oldCap) == 0) {
                                if (loTail == null)
                                    loHead = e;
                                else
                                    loTail.next = e;
                                loTail = e;
                            }
                            else {
                                if (hiTail == null)
                                    hiHead = e;
                                else
                                    hiTail.next = e;
                                hiTail = e;
                            }
                        } while ((e = next) != null);
                        if (loTail != null) {
                            loTail.next = null;
                            newTab[j] = loHead;
                        }
                        if (hiTail != null) {
                            hiTail.next = null;
                            newTab[j + oldCap] = hiHead;
                        }
                    }
                }
            }

由此可以看出扩容需要遍历并重新赋值,成本非常高,所以选择一个好的初始容量非常重要。

如何提升性能?

  1. 解决扩容损失:如果知道大致需要的容量,把初始容量设置好以解决扩容损失;
    比如我现在有1000个数据,需要 1000/0.75 = 1333 ,又 1024 < 1333 < 2048,所以最好使用2048作为初始容量。
  2. 解决碰撞损失:使用高效的HashCode与loadFactor,这个…由于JDK8的高性能出现,这儿问题也不大了。
  3. 解决数据结构选择的错误:在大型的数据与搜索中考虑使用别的结构比如TreeMap,这个就是积累了,一般需要key排序时,建议使用TreeMap;

HashMap与HashTable的主要区别

在很多的Java基础书上都已经说过了,他们的主要区别其实就是Table加了线程同步保护

  • HashTable线程更加安全,代价就是因为它粗暴的添加了同步锁,所以会有性能损失。
  • 其实有更好的concurrentHashMap可以替代HashTable

JDK8中HashMap的新特性

如果某个桶中的链表记录过大的话(当前是TREEIFY_THRESHOLD = 8),就会把这个链动态变成红黑二叉树,使查询最差复杂度由O(N)变成了O(logN)。

1
2
3
4
5
6
7
8
9
10
11
12
13
//e 为临时变量,p为当前的链
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;
}

JDK8在其他地方也有提升,更多的可以看这里。

HashMap的装箱空间效率

在笔试题中,一般“内存”是完全能够使用的,而在现实中HashMap空间效率之低,你却不一定知道。

比如定义了一个 HashMap<Long,Long>

1. Long的装箱

在对象头中,加入额外的指针8Bype,加入8Bype的MarkWord(hashcode与锁信息),这里就是16Byte

也就是说,long在装箱后,效率为 8/24 = 1/3

2. Map.Entry的装箱

字段空间: hash(4) + padding(4) + next(8) = 16Byte,这里的padding是字节对齐

对象头: 16Byte,指针+MarkWord

也就是说,维护一个Entry需要32Byte的空间

1
2
3
4
5
6
7
static class Node<K,V> implements Map.Entry<K,V>
{   
    final int hash;   
    final K key;   
    V value;   
    Node<K,V> next;
}

3. 总效率

8/(24 + 32) = 1/7

计算结果可能有差异,本文主要在强调装箱过程造成的损失

在Android中使用SparseArray代替HashMap

官方推荐使用SparseArray([spɑ:s] [ə'reɪ],稀疏的数组)或者LongSparseArray代替HashMap,目前国内好像涉及的比较少,容我先粘贴一段

Note that this container keeps its mappings in an array data structure, using a binary search to find keys. The implementation is not intended to be appropriate for data structures that may contain large numbers of items. It is generally slower than a traditional HashMap, since lookups require a binary search and adds and removes require inserting and deleting entries in the array.

For containers holding up to hundreds of items, the performance difference is not significant, less than 50%.
To help with performance, the container includes an optimization when removing keys: instead of compacting its array immediately, it leaves the removed entry marked as deleted. The entry can then be re-used for the same key, or compacted later in a single garbage collection step of all removed entries. This garbage collection will need to be performed at any time the array needs to be grown or the the map size or entry values are retrieved.

总的来说就是:

  • SparseArray使用基本类型(Primitive)中的int作为Key,不需要Pair<K,V>或者Entry<K,V>这样的包装类,节约了内存;
  • SpareAraay维护的是一个排序好的数组,使用二分查找数据,即O(log(N)),每次插入数据都要进行排序,同样耗时O(N);而HashMap使用hashCode来加入/查找/删除数据,即O(N/buckets_size);
  • 总的来说,就是SparseArray针对Android嵌入式设备进行了优化,牺牲了微小的时间性能,换取了更大的内存优化;同时它还有别的优化,比如对删除操作做了优化;
  • 如果你的数据非常少(实际上也是如此),那么使用SpareArray也是不错的;

在笔试中的使用

1. 查重与分组问题

某公司正在做一个寻找走失儿童的公益项目,现在有一个函数,可以输入两个图片,并返回这个儿童是否重复。请你设计一个系统,帮助他们寻找儿童。

  1. 网友可以同时上传一批图片
  2. 系统能够把所有图片分类并归为一组
  3. 网友上传图片后,网页要尽快返回该照片所在的组。

A:假设你现在有一个机器,请写出你的数据结构与处理流程,设计的思路。
B:如果你有多台机器,如果缩短请求的时间?

A:我们可以把它分解为两个部分,一个是数据结构一个是上传流程。

(1). 对于数据结构来说,一个是对儿童信息进行包装,另一个是实现儿童信息的高效查找。对于儿童信息包装类来说,除了加入儿童的图片,姓名,生日等基本信息外,特别要注意重写equals与hashCode,这个equals就是题目所说的比较函数。对于查找的实现来说,首先我们建立一个HashSet,用于存储儿童信息。网友上传后,服务器通过对图像计算出特征Hash值,并查Hash表,如果HashCode相同,则返回所在的组;如果不相同,就加入hash表中。(2). 对于多图上传问题,使用生产者-消费者阻塞队列就可以实现尽快的依次返回照片所在的组。

B:

  1. 考虑将Hash计算后取余,分配给其他计算机进行存储与查询,我就想到这一个…..其它的嘛,大家帮我想想。
  2. 常规做法,Select反代,管道回调

TOP10的实现

搜索引擎会通过日志文件把用户每次检索使用的所有检索串都记录下来,每个查询串的长度为1-255字节。
假设目前有一千万个记录(这些查询串的重复度比较高,虽然总数是1千万,但如果除去重复后,不超过3百万个。一个查询串的重复度越高,说明查询它的用户越多,也就是越热门。),请你统计最热门的10个查询串,要求使用的内存不能超过1G。

这个题目与上个题目类似,我们选择使用HashMap,key为查询值,val为计数,内存使用为 3 * 256 M == 768M < 1G,然后我们不断的put就可以了,伪代码

1
2
3
4
5
6
7
HashMap<String, Integer> map = new HashMap<String,Integer>();
//如果内存再多点的话,我们就可以把初始化容量凑个1024的整数,减少扩容损失。
while(fileLog.hasNext()){
    String queue = fileLog.next();
    map.put(queue, map.get(queue) + 1);
}

接着使用堆排序遍历即可,堆的大小为10,复杂度为10xO(LogN)。

综上,O(10^7) +10 * O(Log(3×10^6));

使用WeakHashMap作为缓冲

在动态代理等耗时操作中,为了实现复用,使用了HashMap实现缓存,下面的一个例子是Picasso的Transformation操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public final class PaletteTransformation implements Transformation {
    private static final PaletteTransformation INSTANCE = new PaletteTransformation();
    private static final Map<Bitmap, Palette> CACHE = new WeakHashMap<>();
    public static PaletteTransformation instance() {
        return INSTANCE;
    }
    public static Palette getPalette(Bitmap bitmap) {
        return CACHE.get(bitmap);
    }
    private PaletteTransformation() {
    }
    @Override
    public Bitmap transform(Bitmap source) {
        Palette palette = Palette.generate(source);
        CACHE.put(source, palette);
        return source;
    }
    @Override
    public String key() {
        return "PaletteTransformation";
    }
}

附录一:集合中元素的排序方式:

刚刚对比SparseArray与HashMap,我怕各位绕进去了,讲下Java中集合的排序方式

  • 按照添加/删除的顺序,比如FIFO,FILO,常见的比如Queue,Stack就是按照这个顺序实现的;
  • 按照Hash表进行排序,比如HashMap的table中的元素是通过hash运算随机均匀排列的(至少理论上是),可以通过计算Key的Hash值快速查找到Value
  • 按照自定义的排序,比如按照数字大小,拼音首字母排序,常见的有线性顺序表,二叉查找树,以及高度封装好的TreeMap,它们需要实现Comaparable接口以进行进一步的自定义CompareTo操作。

附录二:hashCode, == ,equals, Comparable的区别?

  • == : 这个就是内存地址的比较,从C语言开始我们就知道这个了。
  • hashCode/equals:对于Object来说,hashCode就是地址的取样运算,而equals就是判断地址是否相同;在实际使用中,特别是在集合(Set)中,特别是HashSet,为了防止插入的是两个相同的值,我们更注重内容上的对比而不是地址的对比,需要通过计算hashCode来判断是否equals,所以两个方法要同时重写,比如String。
  • Comparable: 在集合需要排序(SortSet)的情况下,就需要给对象实现Comparable接口,比如TreeMap。这个在Android开发中实际需要手动写的情况并不多,毕竟包多,在ORM框架中一般都帮你写好了。

HashMap在Android项目中的使用

  1. Bundle与Parcelable,实际上就是Android中对HashMap与Serializable的接口实现(当然底层已经是IPC了);
  2. 反射时使用WeakReference做缓存,比如Retrofit等动态代理框架;
  3. 广播的底层结构是HashMap<IBinder, ReceiverList>;

后记

看这种JDK源码又累又欣赏,特别是if((n.next=p) != null)这样的代码频频出现却忘了运行顺序,说明了自己的基础不足(然并卵,现在已经能写出这种WTF的代码了)。这是我第一次写分析而不是写过程,希望有问题能够提出,无论是文章排版还是技术上的问题都可以提出来。

最后打个广告
我目前正在准备技术面试,所以关于Java所有的文章有个总结,不妨关注一下。

Reference

  1. http://bigocheatsheet.com/
  2. https://github.com/android-cn/android-discuss
  3. http://www.ibm.com/developerworks/cn/java/j-jtp08223/
  4. http://www.jianshu.com/p/9a48bcbdfece
  5. http://www.jianshu.com/p/4e13fcdf9ab5
  6. http://blog.csdn.net/zimo2013/article/details/39692245
  7. http://coderbee.net/index.php/java/20131018/519
  8. http://www.guokr.com/blog/747926/
  9. http://blog.csdn.net/v_JULY_v/article/details/6256463

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/330003.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如何确定python开发环境已经配置好_搭建 python 开发环境 前面安装选位置我直接回车了现在我想测试查看目录该怎么办...

展开全部 1 ubuntu中一般安装e5a48de588b662616964757a686964616f31333431343036后的默认路径如下#!/usr/bin/env python #!/usr/locat/bin/python 如果没有找到&#xff0c;可以通过命令查询python路径whereis python which python 2 Windows一般可以进入python>>> i…

转- java单例模式几种实现方式

转自&#xff1a; https://www.cnblogs.com/ngy0217/p/9006716.html &#xff1b; 单例模式的五种实现方式 1、饿汉式(线程安全&#xff0c;调用效率高&#xff0c;但是不能延时加载)&#xff1a; 1 2 3 4 5 6 7 public class ImageLoader{ private static ImageLoade…

IDEA集成maven流程图详细介绍

前言 最近利用两天时间学习了MyBatis以及maven,避免经典的学过就忘记&#xff0c;我打算做出点总结以便日后复习&#xff0c;当然如果能帮到需要的人也是极好的。 一. 初识maven 1.maven是什么 maven是用来帮助我们快速搭建项目结构与开发环境的好工具。回想一下每次新建项…

pythonnumpy教程_Python教程:numpy的基本介绍

标准安装的Python中用列表(list)保存一组值&#xff0c;可以用来当作数组使用&#xff0c;不过由于列表的元素可以是任何对象&#xff0c;因此列表中所保存的是对象的指针。这样为了保存一个简单的[1,2,3]&#xff0c;需要有3个指针和三个整数对象。对于数值运算来说这种结构显…

Java8系列之重新认识HashMap

转载自 Java8系列之重新认识HashMap简介 Java为数据结构中的映射定义了一个接口java.util.Map&#xff0c;此接口主要有四个常用的实现类&#xff0c;分别是HashMap、Hashtable、LinkedHashMap和TreeMap&#xff0c;类继承关系如下图所示&#xff1a;下面针对各个实现类的特点做…

mysql-on duplicate key update实现insertOrUpdate官方文档

【README】 mysql 基于 on duplicate key update filedvalue ; 实现有则更新&#xff0c;没有则插入&#xff1b; 以下内容总结于 https://dev.mysql.com/doc/refman/8.0/en/insert-on-duplicate.html 【1】api 描述 如果指定 ON DUPLICATE KEY UPDATE 子句并且要插入的行…

python统计段落单词词频_使用Python统计文件中词频,并且生成词云

wordcloud Table of Contents 1 怎样使用Python产生词云 from wordcloud import WordCloud import matplotlib.pyplot as plt import jieba # Now, There is no word.txt under this path path_txt "/home/alan/Desktop/word.txt" f open(path_txt, r, encoding U…

IDEA中maven配置MyBatis简单流程

前言 刚学完javaweb&#xff0c;对自己的Dao层代码很不满意的话&#xff0c;可得来学学MyBatis。学习MyBatis既可以改进JDBC的使用&#xff0c;实现Dao层也会变得很简便&#xff0c;下面我将介绍IDEA中maven配置MyBatis简单流程。 如果想了解maven请转到我的上一篇文章中&…

(转)构建微服务:Spring boot 入门篇

转自&#xff1a; Spring Boot(一)&#xff1a;入门篇 - 纯洁的微笑 - 博客园 &#xff1b; 什么是Spring Boot Spring Boot 是由 Pivotal 团队提供的全新框架&#xff0c;其设计目的是用来简化新 Spring 应用的初始搭建以及开发过程。该框架使用了特定的方式来进行配置&#…

python参数_python参数的介绍

一、函数 1.为什么要使用函数&#xff1f; 减少代码的冗余 2.函数先定义后使用&#xff08;相当于变量一样先定义后使用&#xff09; 3.函数的分类&#xff1a; 内置函数&#xff1a;python解释器自带的&#xff0c;直接拿来用就行了 自定义函数&#xff1a;根据自己的需求自己…

一篇文章指明做JavaWeb项目需要的前置知识+完整项目初解读(萌新必看,十分友好)

前言 过了web这个阶段了&#xff0c;项目也完成了的我想给各位后来者总结一下我整个项目从开始到结束的经验&#xff0c;当然&#xff0c;也不是一帆风顺&#xff0c;报错有时候折磨的要死&#xff0c;废话不多说&#xff0c;现在就开始吧。 本文一共分为两个部分&#xff1a;…

HashMap 实现原理

转载自 HashMap 实现原理HashMap是常考点&#xff0c;而一般不问List的几个实现类(偏简单)。以下基于JDK1.8.0_102分析。 内部存储 HashMap的内部存储是一个数组&#xff08;bucket&#xff09;&#xff0c;数组的元素Node实现了是Map.Entry接口(hash, key, value, next)&#…

mybatis-启动源码分析

【1】测试用例 mybatis-config.xml <?xml version"1.0" encoding"UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configurat…

git 合并冲突_git分支管理的策略和冲突问题

备注&#xff1a;知识点关于分支中的冲突分支管理的策略分支策略备注&#xff1a;本文参考于廖雪峰老师的博客Git教程。依照其博客进行学习和记录&#xff0c;感谢其无私分享&#xff0c;也欢迎各位查看原文。知识点git log --graph --prettyoneline --abbrev-commit查看分支合…

mysql duplicate key与replace into对比

【REDME】 有些业务场景如下&#xff1a; 对于数据已经存在的&#xff0c;则更新&#xff1b;否则新增&#xff1b; 怎么判定数据已经存在&#xff0c;通过主键或唯一索引来判断&#xff1b; 业务场景&#xff1a;业务库的全局参数表的参数值的新增或更新就是 有则更细无则…

基于springboot+vue的前后端分离商城系统

springboot前后端分离商城 介绍 springboot前后端分离商城 本项目由本人根据教程实现的一个springboot项目&#xff0c;基本已实现项目&#xff0c;但是本人希望加入自己的小功能&#xff0c; 请期待下一次的更新~ 教程地址&#xff1a;教程 软件架构 软件架构说明: 本项目…

Java 8 HashMap键与Comparable接口

转载自 Java 8 HashMap键与Comparable接口 这篇文章主要介绍了 Java 8 在 HashMap 哈希冲突处理方面的新特性。 相对之前的版本&#xff0c;Java 8 在许多方面有了提升。其中有很多类被更新了&#xff0c;HashMap 作为最常使用的集合类之一也不例外。这篇文章将介绍 Java 8 中…

hash地址_一致性Hash在负载均衡中的应用

作者&#xff1a;marklux原文&#xff1a;http://marklux.cn/blog/90简介一致性Hash是一种特殊的Hash算法&#xff0c;由于其均衡性、持久性的映射特点&#xff0c;被广泛的应用于负载均衡领域&#xff0c;如nginx和memcached都采用了一致性Hash来作为集群负载均衡的方案。本文…

(转)linux上nginx源码编译安装

亲测有效&#xff1b; 转&#xff1a; https://segmentfault.com/a/1190000007116797https://segmentfault.com/a/1190000007116797 nginx服务器详细安装过程&#xff08;使用yum 和 源码包两种安装方式&#xff0c;并说明其区别&#xff09; 正在上传…重新上传取消​fuyi…

牛客网JAVA专项联系共899题--个人记录学习经历

总览 共刷900题 其中也有许多知识点是未曾涉足的&#xff0c;但大部分还是java的基础。 基本数据 正确题数&#xff1a;正确率百分之67&#xff0c;即&#xff1a;对了603题&#xff1b; 时间&#xff1a;5天&#xff08;每天4小时左右&#xff09; 错题&#xff1a; 收藏数…