我將一個自定義對象的數百萬條目添加到List
。事實證明,這是非常緩慢的,並且圖形用戶界面也隨機保持冷凍(即使在Windows上沒有響應),即使添加操作被包裝在SwingWorker
中,所以它不應該影響EDT。此外,CPU
利用率高達70%-90%左右。Java List可怕的添加性能
我初始化列表如下:
List<SearchResult> updatedSearchResults = new ArrayList<>();
然後我一邊喊
SearchResult searchResult = new SearchResult(...);
updatedSearchResults.add(searchResult);
來添加元素。
我知道ArrayList
內部使用一個數組,它在滿了時被調整大小,並且所有元素都被複制過來。但是,使用LinkedList
已顯示比ArrayList
慢,並且不存在其他List實現。
根據this的回答,添加到ArrayList
的性能爲O(1)
。問題在於爲什麼我收集所有結果的方法在性能方面仍然非常糟糕。 SearchResult
是一個簡單的數據對象封裝字段並初始化它很便宜,因爲只有字段被設置。
作爲比較,添加到列表明顯慢於通過網絡套接字發送相同數量的數據。這沒有任何意義,因爲本地操作總是比網絡相關操作快得多。
1 million
元素約在0.2 seconds
完成,但67 million
甚至不會在合理的時間內完成。它應該最多幾秒鐘完成。
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;
public class ListPerformanceTester
{
public enum ValueSize
{
EIGHT_BIT("8-Bit"),
SIXTEEN_BIT("16-Bit"),
THIRTY_TWO_BIT("32-Bit"),
SIXTY_FOUR_BIT("64-Bit"),
NINETY_SIX_BIT("96-Bit");
private String name;
ValueSize(String name)
{
this.name = name;
}
public int getBytesCount()
{
switch (this)
{
case EIGHT_BIT:
return 1;
case SIXTEEN_BIT:
return 2;
case THIRTY_TWO_BIT:
return 4;
case SIXTY_FOUR_BIT:
return 8;
case NINETY_SIX_BIT:
return 12;
}
throw new IllegalStateException("Bytes count undefined for " + this);
}
@Override
public String toString()
{
return name;
}
public static ValueSize parse(String name)
{
ValueSize[] valueSizes = values();
for (ValueSize valueSize : valueSizes)
{
String currentName = valueSize.toString();
if (currentName.equals(name))
{
return valueSize;
}
}
throw new IllegalArgumentException("No value size associated");
}
}
public static class SearchResult implements Cloneable, Comparable
{
private int address;
private ValueSize valueSize;
private BigInteger previousValue;
private BigInteger currentValue;
private BigInteger valueDifference;
public SearchResult(int address, BigInteger previousValue,
BigInteger currentValue, ValueSize valueSize)
{
this.address = address;
this.valueSize = valueSize;
this.previousValue = previousValue;
this.currentValue = currentValue;
setValueDifference();
}
private void setValueDifference()
{
BigInteger subtractionResult = previousValue.subtract(currentValue);
valueDifference = subtractionResult.abs();
}
private byte[] getBytes(BigInteger bigInteger) throws IOException
{
byte[] retrieved = bigInteger.toByteArray();
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
int bytesCount = getValueSize().getBytesCount();
int paddingBytes = bytesCount - retrieved.length;
if (paddingBytes >= 0)
{
byteArrayOutputStream.write(new byte[paddingBytes]);
byteArrayOutputStream.write(retrieved);
} else
{
writeWithoutLeadingNullBytes(byteArrayOutputStream, retrieved);
}
return byteArrayOutputStream.toByteArray();
}
private void writeWithoutLeadingNullBytes(ByteArrayOutputStream byteArrayOutputStream, byte[] bytes)
{
int index = 0;
boolean nonNullByteFound = false;
while (index < bytes.length)
{
byte value = bytes[index];
if (value != 0 || nonNullByteFound)
{
nonNullByteFound = true;
byteArrayOutputStream.write(value);
}
index++;
}
}
public int getAddress()
{
return address;
}
@Override
public boolean equals(Object object)
{
if (!(object instanceof SearchResult))
{
return false;
}
SearchResult searchResult = (SearchResult) object;
return searchResult.getAddress() == getAddress();
}
@Override
public int hashCode()
{
return Integer.hashCode(address);
}
public ValueSize getValueSize()
{
return valueSize;
}
@Override
public SearchResult clone()
{
return new SearchResult(address, previousValue, currentValue, valueSize);
}
@Override
public int compareTo(Object object)
{
return new Integer(address).compareTo(((SearchResult) object).getAddress());
}
}
public static void main(String[] arguments)
{
long milliseconds = System.currentTimeMillis();
int elementsCount = 2000000;
/*List<Integer> list = new ArrayList<>();
for (int elementIndex = 0; elementIndex < elementsCount; elementIndex++)
{
list.add(0);
}*/
List<SearchResult> searchResults = new ArrayList<>();
for (int elementIndex = 0; elementIndex < elementsCount; elementIndex++)
{
SearchResult searchResult = new SearchResult(0x12345678, new BigInteger("3"), new BigInteger("1"), ValueSize.EIGHT_BIT);
searchResults.add(searchResult);
}
System.out.println((System.currentTimeMillis() - milliseconds)/(double) 1000 + " seconds");
}
}
這裏有什麼大問題?
第一個明顯的事情是指定用於從一開始列出大小合適,但我懷疑這個問題實際上是其他地方:您的JVM是否有足夠的內存用於數百萬個元素?它們包含什麼?構造函數是做什麼的。發佈一個完整的重現問題的最小例子。 –
需要多長時間將相同的項目添加到列表67M次?這是*添加*需要多長時間。當前代碼中的其餘部分都是其他內容,比如創建要放入列表中的元素。 –
在jdk中使用像visualvm這樣的分析器來確定瓶頸。你可能會感到驚訝 –