当前位置: 首页 > 知识库问答 >
问题:

指定精确容量时,为什么HashMap resize()再次出现?

淳于新
2023-03-14

代码比文字更重要,所以:

final int size = 100;
Map<Integer, String> m = new HashMap<>(size);
for (int i = 0; i < size; i++) m.put(i, String.valueOf(i));

为什么HashMap在内部调用了21次!(感谢Andreas发现JVM在内部使用哈希映射,21个CAL中有19个来自其他进程)

我的应用程序仍然不能接受两个resize()调用。我需要对此进行优化。

如果我是一名新的java开发人员,我对HashMap构造函数中“容量”的第一个直观猜测是,它是我(HashMap的消费者)将放入映射中的元素数量的容量。但事实并非如此。

如果我想优化HashMap的使用,使其完全不需要调整自身大小,那么我需要充分了解HashMap的内部结构,以准确了解HashMap bucket数组需要有多稀疏。在我看来这很奇怪。HashMap应该隐式地为您实现这一点。这是OOP中封装的全部要点。

注意:我已经确认resize()是我的应用程序用例的瓶颈,所以这就是为什么我的目标是减少对resize()的调用次数。

问题是:

如果我事先知道要放入地图的条目的确切数量。我选择了什么容量来防止任何额外的调用resize()操作?类似size*10的东西?我还想了解一些为什么HashMap是这样设计的背景知识。

编辑:我被问到很多为什么这个优化是必要的。我的应用程序在hashmap中花费了大量的CPU时间。调整大小()。我的应用程序使用的哈希映射被初始化,其容量等于我们放入其中的元素数。因此,如果我们可以减少resize()调用(通过选择更好的初始容量),那么我的应用程序性能就会提高。

共有3个答案

闾丘成礼
2023-03-14

这很容易证明:

private static <K, V> void debugResize(Map<K, V> map, K key, V value) throws Throwable {

    Field table = map.getClass().getDeclaredField("table");
    AccessibleObject.setAccessible(new Field[] { table }, true);
    Object[] nodes = ((Object[]) table.get(map));

    // first put
    if (nodes == null) {
        map.put(key, value);
        return;
    }

    map.put(key, value);

    Field field = map.getClass().getDeclaredField("table");
    AccessibleObject.setAccessible(new Field[] { field }, true);
    int x = ((Object[]) field.get(map)).length;
    if (nodes.length != x) {
        ++currentResizeCalls;
    }
}

和一些用法:

static int currentResizeCalls = 0;

public static void main(String[] args) throws Throwable {

    int size = 100;
    Map<Integer, String> m = new HashMap<>(size);
    for (int i = 0; i < size; i++) {
        DeleteMe.debugResize(m, i, String.valueOf(i));
    }

    System.out.println(DeleteMe.currentResizeCalls);
}     

我只记录了实际调整大小所需的时间,因为第一个调用正在初始化;按照文件规定:

初始化或加倍表大小

你的第二点要有趣得多。哈希映射定义了容量,现在容量是多少?这并不明显:

对于HashMapcapacity是调整大小之前的存储桶数,对于ConcurrentHashMap是执行调整大小之前的条目数。

因此,不要在内部调用resize,在使用HashMap的情况下,使用以下公式:

(int)(1.0 + (long)initialCapacity / LOAD_FACTOR)

但这并不理想,假设您想要1024条目而不调整大小,通过使用该公式,您可以获得1367桶,这些桶在内部四舍五入为2的幂,因此2048-嗯,比您要求的要多得多。

对于CHM,直接指定尺寸。在前面的代码中使用一个简单的修改很容易证明:

 // use CHM instead of HashMap
 Map<Integer, String> m = new ConcurrentHashMap<>(size);

这将导致调整大小为零,实际上是html" target="_blank">数组的两倍。但有时,即使是CHM内部代码也很混乱,几乎不需要修补。

汤弘文
2023-03-14

如有疑问,请阅读文档。HashMap的文档很好地解释了初始容量和负载因子之间的权衡。

根据留档ifinit容量=(maxEntry/loadFactor)1,添加条目时不会发生重新散列操作。在这种情况下,maxEntry是您指定的100loadFactor将是.75的默认加载因子。

但是除了设置初始大小以避免重复(resize())之外,您还应该仔细阅读HashMap的文档,以便正确调整它,同时考虑初始容量和负载因子。

如果您关心的是查找成本而不是空间,那么可以尝试使用较低的加载因子,如。5或更低。在这种情况下,您将使用以下两个参数创建哈希映射:

final float loadFactor = 0.5;
final int maxEntries   = 100;
final int initCapacity = (int) maxEntries / loadFactor + 1;
new HashMap<>(initCapacity, loadFactor);

(重点矿山)

HashMap的一个实例有两个影响其性能的参数:初始容量和负载因子。容量是哈希表中的桶数,初始容量只是创建哈希表时的容量。负载因子是在自动增加哈希表容量之前,允许哈希表达到的满度的度量。当哈希表中的条目数超过负载因子和当前容量的乘积时,哈希表将被重新刷新(即,重建内部数据结构),以便哈希表的存储桶数约为两倍
<作为一般规则,默认负载系数(0.75)在时间和空间成本之间提供了良好的权衡。较高的值会减少空间开销,但会增加查找成本(反映在HashMap类的大多数操作中,包括get和put)。在设置初始容量时,应考虑map中的预期条目数及其负载系数,以尽量减少再灰烬操作的次数。如果初始容量大于最大入口数除以负载系数,则不会发生再灰化操作。

伏建修
2023-03-14

默认负载因子为0.75,即3/4,这意味着在添加了100个值中的75个后,将调整内部哈希表的大小。

仅供参考:resize()只调用两次。添加第一个值时调用一次,当它达到75%满时调用一次。

为了防止调整大小,您需要确保第100个值不会导致调整大小,即size

capacity = size * 4/3 + 1

使用size=100,这意味着容量=134

 类似资料:
  • 我需要解析一个给定的类型(例如:长整型),它用科学记数法表示。例子: 我知道给定字符串的类型,但我不能使用strtoll,因为数字是用科学符号表示的。我所做的是使用strtod转换它,对int64_t进行错误检查,并将其转换回int64_t。ErrCheckInt和ErrCheck Double对整型和浮点型进行错误检查(溢出、下溢等),并将数字强制转换为任何类型。 问题是,当我用双精度解析int

  • 问题内容: 我看到了很多有关JDBC / MySQL的“最佳实践”指南,它们告诉我指定setFetchSize()。 但是,我不知道何时指定以及要指定什么(语句,结果集)。 在这两个中,我应该指定什么? 从javadoc和oracle文档中,这是我对“何时”感到困惑的地方 Java文档 默认值由创建结果集的Statement对象设置。提取大小可以随时更改。 甲骨文文档 生成结果集后,对语句对象的提

  • 我正在寻找Python的第n个根函数/算法,但在发布之前:没有整数根,见鬼 我从哪里至少可以获得一个指南,指导如何编程生成精确的/ 对于(第一个参数是数字,第二个参数是根深度(或其他内容))不返回或的函数。 编辑:所以,你给了我这个解决方案:,当我问这个问题时,我就知道了,但它不适用于,例如,。你不能用有理数来表示,因此给出了不正确的结果

  • 可再次下载过去已下载的内容。 1. 轻触(选项)>[下载列表]。 显示可再次下载过去已下载的内容。若有使用PS3™等其它主机下载的内容,也会一并显示。 2. 选择想下载的内容后,轻触[下载]。 开始下载。若要确认下载的进度,请在按下PS键后,轻触画面右上角的最新资讯指示灯。 若要下载内容,需先将PS Vita专用存储卡插入PS Vita。 部分内容可能会因无法使用Wi-Fi与互联网连接而无法下载。

  • 但似乎每个tensorflow实现(包括这个和官方的tensorflow实现)都使用(指数)移动平均和方差。 请原谅我,但我不明白为什么。是不是因为使用移动平均值对性能更好?还是纯粹为了计算速度? 参考:原稿

  • 我是编程新手,我们从学校得到了一个例子来了解扫描仪是如何工作的。我的问题是,我不明白为什么消息“Input”(while循环中的System.out.println)会被打印两次。