最近遇到一个需求,有一个黑名单文件,数据量很大,有几万条数据,将来还有可能更大,需要实现快速查找,检查指定的名单是否存在于黑名单中。
涉及到快速查找,我想到了使用哈希表:
哈希表最适合的求解问题是查找与给定值相等的记录。
先了解哈希表的原理:
先简单理解:
1,先建立哈希表:
通过某种方式,对一条记录做计算,获得一个地址。
将该记录存放在该地址中。
如此循环,建立一个哈希表。
2,在查找时,对指定记录做同样的计算,获得一个地址,到该地址下查看,若不存在,则说明该记录不在表中,若存在,则找到。
这样,查找的效率非常高,一次性找到,与数据量的大小并没有关系。
这个效率高到让我吃惊:居然有这么好的东西,一次定位!
然而,实际的情况是,我们使用的地址空间是有限的,需要考虑有效的使用内存空间。
而有限的使用空间就带来一个不可避免的问题,地址冲突:两个不同的记录,有可能会计算出来一个相同的地址。
这时,就需要添加一个冲突时的处理方法了。
常用处理冲突的思路:
1.换个位置:开放地址法
2.将同一位置冲突对象组织在一起:链地址法
具体方法就不细说了,可以参考文末的引用文章链接。
虽然这样可以解决冲突,但是冲突多了,自然效率就降下来了。那么,怎样能保证冲突比较少呢?
首先,可以考虑使用比较大的内存空间。
数据量与给定的内存空间大小之间有一个比例值:
哈希表装填因子定义为:α= 填入表中的元素个数 / 哈希表的长度
α是哈希表装满程度的标志因子。由于表长是定值,α与“填入表中的元素个数”成正比。
所以,α越大,内存使用率越高,填入表中的元素较多,产生冲突的可能性就越大;α越小,填入表中的元素较少,产生冲突的可能性就越小,但是,内存使用率也越低。
由此可以看出:哈希表的真正核心,其实就是通过空间换取时间。
其次,寻找合适的算法,使冲突尽量减少。
具体到android上来,有没有直接使用哈希表的api呢?
我查了一下,有好几个:hashMap、hashSet、Hashtable
考虑到我的实际需求,并没有键值对,只是需要将黑名单存储到哈希表中,然后判断某个名单,是否在哈希表中存在。
只需要HashSet即可。
做的动作也很少,一个是逐项添加黑名单,建立哈希表:
boolean add(Object o)
一个是查询,某个名单,是否属于黑名单:
boolean contains(Object o)
使用起来,真的是好简单好方便!
代码:
HashSet<String> hashSetBlackList = null;
//判断是否属于黑名单(26000条,耗时0.2s)
boolean getBlackList(){
try {
//读取文件,我的文件存放在assets目录下
String fileName = "BlackList.txt";
InputStream is = context.getAssets().open(fileName);
if(is != null){
InputStreamReader inputreader = new InputStreamReader(is);
BufferedReader buffreader = new BufferedReader(inputreader);
if(hashSetBlackList!=null) {
hashSetBlackList.clear();
} else {
hashSetBlackList = new HashSet<String>();
}
String line;
int recordNum=0;
//分行读取
while (( line = buffreader.readLine()) != null) {
recordNum++;
//添加一条记录
hashSetBlackList.add(line);
if(recordNum%10000==1) {//注意,过多的打印信息,会严重拖慢运行速度
LogUtil.logWithMethod(new Exception(), "line " + recordNum + " = " + line);
}
}
LogUtil.logWithMethod(new Exception(), "file end. recordNum = " + recordNum + " size="+hashSetBlackList.size() );
is.close();
}
} catch (Exception e){
e.printStackTrace();
LogUtil.logWithMethod(new Exception(),"failed : "+e.getMessage());
}
return true;
}
//判断是否属于黑名单,(哈希表,超快,几乎不耗时)
boolean isInBlackList(String findRecord){
boolean isBL = false;
if(findRecord == null){
LogUtil.logWithMethod(new Exception(),"findRecord is null");
return isBL;
}
LogUtil.logWithMethod(new Exception(),"contains findRecord ?");
if(hashSetBlackList.contains(findRecord)){
isBL = true;
LogUtil.logWithMethod(new Exception(),"findRecord isInBlackList ");
} else {
isBL = false;
}
LogUtil.logWithMethod(new Exception(),"isBL="+isBL);
return isBL;
}
通过我的实测,26000条数据,添加到哈希表中,耗时0.2s。
而查找呢,几乎不耗时,因为我的日志已经分辨不出来它的操作耗时了,都是在同一毫秒内完成。
所以,那些计算哈希值的方法,那些冲突处理的方法,以及数据量与内存的关系,我们都不用考虑,Android系统早就已经做好了。
这是我第一次使用哈希表,感觉真的是很好很强大!
参考:
http://blog.csdn.net/u010297957/article/details/51974340
http://blog.csdn.net/xiaokang123456kao/article/details/54583062
https://www.jianshu.com/p/c1118ea82a51