我需要迭代HashMap
包含10^5
转的整数。我随机生成整数。然后,我对该整数执行所需的算术运算。之后,我正在检查HashMap是否包含此Integer =>如果它包含这个整数,我增加整数并重新检查HashMap是否包含新的整数,直到HashMap中不存在整数。如果它不包含整数,我将整数添加到HashMap。
我在下面添加了我的代码。 “开始”和“结束”注释之间的代码需要太长时间。
如果我在{start'和'end'之间comment
这段代码,它会在不到一秒的时间内执行。
因此,时间不会在Random.nextInt()
或HashMap.containsKey()
MyProgram.java
import java.util.HashMap;
import java.util.Random;
public class MyProgram {
public static void main(String[] args) {
long total = 0;
int randomInt;
int count = 100000;
int divider = 3;
Random random = new Random();
HashMap<Integer, Integer> map = new HashMap<>();
for(int i=0; i < count; i++){
randomInt = random.nextInt(count);
// start
int value1 = randomInt / divider;
int value2 = (randomInt % divider != 0) ? 1 : 0;
randomInt = value1 + value2;
// end
while(map.containsKey(randomInt)){
randomInt++;
}
map.put(randomInt, 0); // don't care about value
total += randomInt;
}
System.out.println("Total : " + total);
}
}
此实施需要30秒以上。
如果您认为它们很快,我可以使用List,Arrays,ArrayLists等。请举例说明。
答案 0 :(得分:4)
由于您不关心存储在地图中的值,而只关心整数键,只需使用JsonConvert.DeSerializeObject<MyClass1>(target)
:
用适当大小的BitSet
替换地图:
BitSet
使用BitSet bits = new BitSet(count);
方法查找下一个未设置位:
nextClearBit
然后设置该位:
randomInt = bits.nextClearBit(randomInt);
这对我很快完成(0.16s,在Ideone中):Ideone demo。
我没有测量过,但我猜测OP代码缓慢的主要原因是行中的隐式对象创建:
bits.set(randomInt);
由于地图包含引用,因此必须将while(map.containsKey(randomInt)){
加到randomInt
;只保证在-128到127范围内的整数被缓存,因此这将导致创建大量对象。 Integer
避免创建对象,因为它在原始BitSet
上运行。
这将在 Effective Java 2nd Ed 第5项:“避免创建不必要的对象”中进行讨论(查找“Hideously slow program!”的位置。)
答案 1 :(得分:2)
问题不在于除法代码,问题是由于在尝试插入randomInt
之前为map
生成较小的范围,因此会产生更多的碰撞
如果您查看代码的以下变体
for(int i=0; i < count; i++){
randomInt = random.nextInt(33333);
// start
// int value1 = randomInt / divider;
// int value2 = (randomInt % divider != 0) ? 1 : 0;
// randomInt = value1 + value2;
// end
while(map.containsKey(randomInt)){
randomInt++;
}
map.put(randomInt, 0); // don't care about value
total += randomInt;
}
您将看到它与执行分割的代码大致相同。所以你应该考虑你的插入策略,看看你是否可以改进它。 (我无法帮助你,因为我还没有完全理解你用你的代码想要实现的目标)。
答案 2 :(得分:1)
问题在于内部while(map.containsKey(randomInt))
循环运行至少30亿次(查看下面粘贴的输出中的Entered
变量的值),因为与最终的数字发生了巨大的冲突计算后生成。
int value1 = randomInt / divider;
int value2 = (randomInt % divider != 0) ? 1 : 0;
randomInt = value1 + value2;
此代码在循环100000
次时生成许多常用值,并且与自动装箱一起使用可能会导致性能问题。
您可以检查发生碰撞时执行while循环的时间。
int randomInt;
int count = 100000;
int divider = 3;
long entered = 0;
Random random = new Random();
HashMap<Integer, Integer> map = new HashMap<>();
for(int i=0; i < count; i++){
randomInt = random.nextInt(count);
// start
int value1 = randomInt / divider;
int value2 = (randomInt % divider != 0) ? 1 : 0;
randomInt = value1 + value2;
// end
while(map.containsKey(randomInt)){
entered++;
randomInt++;
}
map.put(randomInt, 0); // don't care about value
total += randomInt;
}
System.out.println("Total : " + total);
System.out.println("Entered : " + entered);
输出
Total : 4999950000
Entered : 3335662228
因此,您应该重新审视value1+value2
的逻辑,而只使用random.nextInt(count)
并计算您想要生成的范围。
randomInt = random.nextInt(count);
while(map.containsKey(randomInt)){
randomInt = random.nextInt(count);
}
答案 3 :(得分:1)
一些微不足道的事情:
randomInt = (randomInt + divider - 1) / divider
; 这些都是次要的事情,但评论之间的部分可能不是您的绩效问题的主要原因。
因为你在0&lt; = n&lt; 0的范围内生成100K数字了。 100K,你的值会非常密集,所以我希望你会在相当长的时间内进行迭代。你可能会更好地维护一个Intervals数组,如下所示:
import java.util.HashSet;
import java.util.Random;
class Interval {
int min, max;
public Interval(int min, int max) {
this.min = min;
this.max = max;
}
public String toString() {
return "[" + min + "," + max + "]";
}
}
public class MyProgram {
private static void checkConsistency(Interval[] intv) {
for(int i=0; i<intv.length; i++) {
Interval v = intv[i];
if (v != null && (i < v.min || i > v.max)) {
throw new Error(i + " -> " + v);
}
}
}
public static void main(String[] args) {
long total = 0;
int randomInt;
int count = 100000;
int divider = 3;
Random random = new Random();
HashSet<Integer> hs = new HashSet<>();
Interval[] data = new Interval[count];
for(int i=0; i < count; i++){
randomInt = random.nextInt(count);
// start
randomInt = (randomInt + divider -1) / divider;
// end
Interval intv = data[randomInt];
if (intv != null) {
randomInt = intv.max + 1;
}
int idx = randomInt < count ? randomInt : count - 1;
hs.add(randomInt);
Interval pre = randomInt > 0 ? data[randomInt-1] : null;
Interval post = randomInt < count-1 ? data[randomInt+1] : null;
if (pre == null && post == null) {
data[idx] = new Interval(randomInt, randomInt);
} else if (pre != null && post != null) {
if (pre.max-pre.min < post.max-post.min) {
for (int j=pre.min; j <= pre.max; j++) {
data[j] = post;
}
data[idx] = post;
} else {
for (int j=post.min; j <= post.max; j++) {
data[j] = pre;
}
data[idx] = pre;
}
data[idx].min = pre.min;
data[idx].max = post.max;
} else if (pre != null) {
data[idx] = pre;
data[idx].max = randomInt;
} else {
data[idx] = post;
data[idx].min = randomInt;
}
// just for verifying consistency
checkConsistency(data);
total += randomInt;
}
System.out.println("Total : " + total);
}
}
答案 4 :(得分:0)
如果您不关心这些值(当然,如果您的程序中不需要它),请使用HashSet
代替HashMap
。它具有与HashMap键列表相同的行为。没有重复的值。
这意味着如果您的集合已经包含值102
并且生成的下一个随机整数也是102
,则将其添加到集合中将不执行任何操作。 (对于Hashmap,它只会替换键/值对,但如果您的值始终为0,则不会引人注意)。
因此,您不需要检查您的集合是否包含新的随机int并且此部分变得无用:
while(map.containsKey(randomInt)){
randomInt++;
}
另外,这部分时间在地图上以containsKey
循环为时间,因此当地图中有许多键时,你可以循环一段时间(如果增量值也在列表中,会发生什么?和下一个递增的值?依此类推?)。
这应该会让你的编程速度更快。
现在您仍然可以通过删除value1
和value2
的声明来优化您的代码,但时间消耗低于您的while
循环,这样可能不会很大差。
最后,您的代码应如下所示:
int randomInt;
int count = 100000;
int divider = 3;
Random random = new Random();
HashSet<Integer> set = new HashSet<>();
while(set.size()<count){
randomInt = random.nextInt(count);
// start
map.add((randomInt/divider) + ((randomInt % divider != 0) ? 1 : 0));
// end
}