Java HashSet与ArrayList CPU使用率高
我有104k个字符串值,其中89k是唯一的。我想检查此列表中是否存在字符串 这是我的类及其方法,保存了所有这些记录Java HashSet与ArrayList CPU使用率高,java,performance,arraylist,hashset,Java,Performance,Arraylist,Hashset,我有104k个字符串值,其中89k是唯一的。我想检查此列表中是否存在字符串 这是我的类及其方法,保存了所有这些记录 public class TestClass { private static TestClass singletonObj = null; private List<String> stringList= null; public static synchronized TestClass getInstance() { if
public class TestClass {
private static TestClass singletonObj = null;
private List<String> stringList= null;
public static synchronized TestClass getInstance() {
if(singletonObj == null) {
singletonObj = new TestClass();
}
return singletonObj;
}
public boolean isValidString(String token) {
if(stringList == null) {
init();
}
if(stringList != null && token != null && !token.isEmpty())
return stringList.contains(token.toLowerCase());
return false;
}
private init() {
stringList = new ArrayList<String>();
// put all 104k values in this data structure.
}
}
公共类TestClass{
私有静态测试类singletonObj=null;
私有列表stringList=null;
公共静态同步测试类getInstance(){
if(singletonObj==null){
singletonObj=newtestclass();
}
返回singletonObj;
}
公共布尔值isValidString(字符串标记){
if(stringList==null){
init();
}
if(stringList!=null&&token!=null&&token.isEmpty())
返回stringList.contains(token.toLowerCase());
返回false;
}
私有init(){
stringList=新的ArrayList();
//将所有104k值放入此数据结构中。
}
}
我的应用程序尝试同时使用这个
isValidString()
方法,每秒大约有20个请求。这很好,但当我尝试将数据结构更改为HashSet
时,CPU使用率非常高。根据我的理解,Hashset应该比ArrayList[o(n)]性能更好。有人能解释一下为什么会发生这种情况吗?JDKHashSet
构建在一个HashMap
之上,其中value是一个单一的“present”对象。这意味着HashSet的内存消耗与HashMap相同:为了存储大小值,您需要32*大小+4*容量
字节(加上值的大小)
对于ArrayList
,它是java.util.ArrayList的容量乘以参考大小(32位4字节,64位8字节)+[对象头+一个int和一个references]
因此,HashSet绝对不是一个内存友好的集合
取决于您使用的是32位
还是64位
VM。也就是说,与ArrayList相比,8字节
引用会对HashSet造成更大的伤害——根据链接的内存消耗图表,每个引用添加一个额外的4字节
,使ArrayList每个元素最多~12字节,HashSet每个元素最多~52字节。)
ArrayList是使用对象数组实现的。下图显示了32位Java运行时上ArrayList的内存使用和布局:
32位Java运行时上ArrayList的内存使用和布局
上图显示,当创建ArrayList
时,结果是使用32字节
内存的ArrayList
对象,以及默认大小为10
的对象数组,对于空ArrayList
,总共88字节
内存。这意味着ArrayList
的大小不准确,因此具有默认容量,恰好是10个条目
ArrayList的属性
默认容量
-10
空大小
-88字节
开销
-48字节加上每个条目4字节
开销
用于10K收集~40K
搜索/插入/删除性能
-O(n)-所用时间与元素数量成线性关系
HashSet的功能比HashMap少,因为它不能包含多个空条目,也不能有重复条目。该实现是围绕HashMap的包装器,HashSet对象管理允许放入HashMap对象的内容。限制HashMap功能的附加功能意味着hashset的内存开销略高
32位Java运行时上哈希集的内存使用和布局
上图显示了java.util.HashSet对象的浅堆(单个对象的内存使用量)(以字节为单位)和保留堆(单个对象及其子对象的内存使用量)(以字节为单位)。浅堆大小为16字节
,保留堆大小为144字节
。创建哈希集时,其默认容量(可放入该集的条目数)为16条
。当以默认容量创建哈希集且没有任何条目放入该集时,它将占用144个字节。这是HashMap内存使用量的额外16字节
下表显示了哈希集的属性:
哈希集的属性
默认容量
-16个条目
空大小
-144字节
开销
-16字节加上HashMap开销
10K集合的开销
-16字节加上HashMap开销
搜索/插入/删除性能
-O(1)-
所用的时间是常数时间,与元素的数量无关
(假设没有哈希冲突)我的猜测是,HashSet
是一种基于哈希的结构,从将每个字符串插入HashSet的那一刻起,即在方法init
中,计算每个字符串的hashCode。这可能是CPU变高的时期,这是我们在迭代结构值时为获得更好的吞吐量而付出的代价的一部分
如果我是对的,在方法init
结束后,CPU应该下降,程序的速度应该大大提高,这就是使用HashSet的好处
顺便说一下:优化的一个可靠方法是预先确定结构尺寸:
- ArrayList的初始大小应等于将包含的最大元素数
- 并将初始大小设置为大于最大值1.7
顺便说一句:String的标准哈希算法。hash
计算字符串的所有字符。也许你可以满足于只计算前100个字符,例如(d
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Scanner;
public class TestClass {
private static TestClass singletonObj = null;
//private List<String> stringList = null;
private HashSet<String> stringList = null;
public static synchronized TestClass getInstance() {
if (singletonObj == null) {
singletonObj = new TestClass();
}
return singletonObj;
}
public boolean isValidString(String token) {
if (stringList == null) {
init();
}
if (stringList != null && token != null && !token.isEmpty())
return stringList.contains(token.toLowerCase());
return false;
}
private void init() {
String dictDir = "C:\\Users\\Richard\\Documents\\EOWL_CSVs";
File[] csvs = (new File(dictDir)).listFiles();
stringList = new HashSet<String>();
Scanner inFile = null;
for (File f : csvs) {
try {
inFile = new Scanner(new FileReader(f));
} catch (FileNotFoundException e) {
e.printStackTrace();
System.exit(1);
}
while (inFile.hasNext()) {
stringList.add(inFile.next().toLowerCase()
.replaceAll("[^a-zA-Z ]", ""));
}
inFile.close();
}
System.out.println("Dictionary initialised with " + stringList.size()
+ " members");
}
}
import java.io.FileNotFoundException;
public class DictChecker extends Thread {
TestClass t = null;
public static int classId = 0;
String className = null;
public void doWork()
{
String testString = "Baby";
if (t.isValidString(testString))
{
System.out.println("Got a valid string " + testString + " in class " + className);
}
else
{
System.out.println(testString + " not in the dictionary");
}
}
public void run()
{
while (true)
{
try {
DictChecker.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
doWork();
}
}
public DictChecker()
{
t = TestClass.getInstance();
className = "dChecker" + classId;
classId += 1;
System.out.println("Initialised " + className + " in thread " + this.getName());
}
public static void main(String[] args) throws FileNotFoundException
{
for (int i = 0; i < 20; i++)
{
(new DictChecker()).start();
try {
DictChecker.sleep(50);//simply to distribute load over the second
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}