为什么不用HashMap做词典

jn0115 2014-07-28 05:44:09
最近在看中文分词,发现mmseg跟ictclas都没有直接用HashMap做词典结构,很是不解,求指点???

1. mmseg 用的是trie树,查找复杂度为词的长度O(len).
2. ictclas 按汉字分了6700多个块,块内用2分查找,查找复杂度O(log 块大小)

而如果用HashMap,理论上应该是O(1)。一般词典大小也就20来万个词,内存也不是什么问题。为什么简单高效的不用,非要搞这么复杂呢???
...全文
1355 13 打赏 收藏 转发到动态 举报
写回复
用AI写文章
13 条回复
切换为时间正序
请发表友善的回复…
发表回复
zhang98722 2014-11-18
  • 打赏
  • 举报
回复
引用 12 楼 gagewang1 的回复:
[quote=引用 2 楼 BiologyPianoProgram 的回复:] 1、hashmap,线程不安全,这个很重要 2、二分查找或tree树,可以做到索引自动按照大小排序,同时能够支持计算机的内存分页机制 3、hashmap本身有很多缺陷,例如: (1)无法保证数组与bucket的平衡性(理论上应该让每个bucket只有一个元素)。如果分词很多,则这个hashmap初始化时应该定义成多大容量呢?20、100、500?如果不能估计容量,则随着分词量不同,必然涉及hashmap的扩容,而扩容设计到数组和桶的复制,相当耗费内存,时间复杂度巨大,并非o(1)。虽然查询的效率是很高 (2)hashmap容易发生空位置。即:无法保证每个k-v在数组位置上的均衡分布,很多位置都是空的,造成巨大的空间浪费 (3)hashmap容易发生元素碰撞。这是由hash算法引起的。例如:两个不同的k-v对,最终可能hash值完全相同,这样造成的结果是:原先的k-v的v将被新的v值代替。后果很严重
hash值相同会覆盖?看看下图hash值相同的数据用链表连接[/quote] hashmap对处理hash冲突是通过链表处理的 k-v的v实际上存的不是一个单独的object,而是一个链表,链表里面存的是k-v,如果链表长度为1,直接返回v,如果有多个,则遍历链表,当k和请求的k一致时,返回v 所以只会影响性能,值不会被覆盖 1.线程安全,不论是读写锁还是concurrentHashMap都能解决这个问题,而且性能很高 2.对操作系统不太了解,不太清楚这个问题
中华雪碧 2014-11-12
  • 打赏
  • 举报
回复
引用 2 楼 BiologyPianoProgram 的回复:
1、hashmap,线程不安全,这个很重要
2、二分查找或tree树,可以做到索引自动按照大小排序,同时能够支持计算机的内存分页机制
3、hashmap本身有很多缺陷,例如:
(1)无法保证数组与bucket的平衡性(理论上应该让每个bucket只有一个元素)。如果分词很多,则这个hashmap初始化时应该定义成多大容量呢?20、100、500?如果不能估计容量,则随着分词量不同,必然涉及hashmap的扩容,而扩容设计到数组和桶的复制,相当耗费内存,时间复杂度巨大,并非o(1)。虽然查询的效率是很高
(2)hashmap容易发生空位置。即:无法保证每个k-v在数组位置上的均衡分布,很多位置都是空的,造成巨大的空间浪费
(3)hashmap容易发生元素碰撞。这是由hash算法引起的。例如:两个不同的k-v对,最终可能hash值完全相同,这样造成的结果是:原先的k-v的v将被新的v值代替。后果很严重
hash值相同会覆盖?看看下图hash值相同的数据用链表连接
brave_panda 2014-08-12
  • 打赏
  • 举报
回复
引用 5 楼 BiologyPianoProgram 的回复:
1、线程安全始终是个大问题。不好的实现和一个好的实现,性能上可以相差几千几万倍。你说这是不是个问题?否则也不会有那么多前赴后继地花那么多精力解决这个问题了。 2、扩容要看怎么扩法。举例来说:java的string和stringBuffer两个类,同样可以对字符串扩容,但是执行成千上万次之后,性能上照样相差成千上万倍,时空复杂度都无限增加。你说这是不是也是个问题? 3、关于碰撞,建议你再研究研究,是不是你想象的那样 4、空间浪费不是指容器的填充程度,而是指由于hash算法引起的,例如导入100万条数据,结果可能有50万个位置始终没有数据存放,虽然此时空间利用率才50%,远未达到默认的0.8装载因子。 另外,hashmap还有其他方方面面的问题,例如:内部使用装箱拆箱进行对象包装,这也极大影响性能。 所以,大部分的组件、引擎之类的,内部的容器都是根据自己的业务特点自己实现的,基本没有谁会直接使用hashmap
hashmap 根据hashcode找插入位置,根据equals判断是否存在,是否覆盖,用链表来解决hashcode冲突问题。
jn0115 2014-08-08
  • 打赏
  • 举报
回复
引用 7 楼 jn0115 的回复:
[quote=引用 5 楼 BiologyPianoProgram 的回复:] 1、线程安全始终是个大问题。不好的实现和一个好的实现,性能上可以相差几千几万倍。你说这是不是个问题?否则也不会有那么多前赴后继地花那么多精力解决这个问题了。 2、扩容要看怎么扩法。举例来说:java的string和stringBuffer两个类,同样可以对字符串扩容,但是执行成千上万次之后,性能上照样相差成千上万倍,时空复杂度都无限增加。你说这是不是也是个问题? 3、关于碰撞,建议你再研究研究,是不是你想象的那样 4、空间浪费不是指容器的填充程度,而是指由于hash算法引起的,例如导入100万条数据,结果可能有50万个位置始终没有数据存放,虽然此时空间利用率才50%,远未达到默认的0.8装载因子。 另外,hashmap还有其他方方面面的问题,例如:内部使用装箱拆箱进行对象包装,这也极大影响性能。 所以,大部分的组件、引擎之类的,内部的容器都是根据自己的业务特点自己实现的,基本没有谁会直接使用hashmap
关于碰撞不同的V肯定不会被覆盖,不然这是JDK巨大BUG了。看下测试结果就知道:

package main;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;



public class Test {

	public static void main(String[] args) {
		
		Map<HashTestObject,HashTestObject> hash = new HashMap<>();
		
		HashTestObject o1 = new HashTestObject(1);
		HashTestObject o2 = new HashTestObject(2);
		
		hash.put(o1, o1);
		hash.put(o2, o2);
		
		System.out.println("=====key,value的hashcode跟equals都返回任意两对象相等======");
		System.out.println("o1=" + hash.get(o1).data);
		System.out.println("o2=" + hash.get(o2).data);
		
		Map<HashTestObject,String> hash2 = new HashMap<>();
		hash2.put(o1, "o1");
		hash2.put(o2, "o2");
		
		System.out.println("=====key的hashcode跟equals都返回任意两key对象相等======");
		System.out.println("o1=" + hash2.get(o1));
		System.out.println("o2=" + hash2.get(o2));
		
	
	}
	
	static class HashTestObject{
		
		public int data;
		
		public HashTestObject(int data){
			this.data = data;
		}
		
		public int hashCode(){
			return 1;
		}
		
		public boolean equals(Object  obj){
			return true;
		}
		
//		public boolean equals(HashTestObject obj){
//			if(obj == null)
//				return false;
//			if(obj == this) 
//				return true;
//			
//			if(obj instanceof HashTestObject){
//				obj = (HashTestObject)obj;
//				if(obj.data == data)
//					return true;
//			}
//			return false;
//		}
	}

}
结果: =====key,value的hashcode跟equals都返回任意两对象相等====== o1=2 o2=2 =====key的hashcode跟equals都返回任意两key对象相等====== o1=o2 o2=o2 [/quote] 这里的结果写错了: =====key,value的hashcode跟equals都返回任意两对象相等====== o1=2 o2=2 =====key的hashcode跟equals都返回任意两key对象相等====== o1=o1 o2=o2
jn0115 2014-08-08
  • 打赏
  • 举报
回复
代码改下,验证当任意两个key都被认为是同个对象,而V的hashCode永远返回1,equlas根据对象值判断。结果如下:

package main;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;



public class Test {

	public static void main(String[] args) {
		
		Map<HashTestObject,HashTestObject> hash = new HashMap<>();
		
		HashTestObject o1 = new HashTestObject(1);
		HashTestObject o2 = new HashTestObject(2);
		
		hash.put(o1, o1);
		hash.put(o2, o2);
		
		System.out.println("o1=" + hash.get(o1).data);
		System.out.println("o2=" + hash.get(o2).data);
	}
	
	static class HashTestObject{
		
		public int data;
		
		public HashTestObject(int data){
			this.data = data;
		}
		
		public int hashCode(){
			return 1;
		}
		
//		public boolean equals(Object  obj){
//			return true;
//		}
		
		public boolean equals(Object obj){
			if(obj == null)
				return false;
			if(obj == this) 
				return true;
			
			if(obj instanceof HashTestObject){
				HashTestObject tmp = (HashTestObject)obj;
				if(tmp.data == data)
					return true;
			}
			return false;
		}
	}

}

结果: o1=1 o2=2
jn0115 2014-08-08
  • 打赏
  • 举报
回复
引用 5 楼 BiologyPianoProgram 的回复:
1、线程安全始终是个大问题。不好的实现和一个好的实现,性能上可以相差几千几万倍。你说这是不是个问题?否则也不会有那么多前赴后继地花那么多精力解决这个问题了。 2、扩容要看怎么扩法。举例来说:java的string和stringBuffer两个类,同样可以对字符串扩容,但是执行成千上万次之后,性能上照样相差成千上万倍,时空复杂度都无限增加。你说这是不是也是个问题? 3、关于碰撞,建议你再研究研究,是不是你想象的那样 4、空间浪费不是指容器的填充程度,而是指由于hash算法引起的,例如导入100万条数据,结果可能有50万个位置始终没有数据存放,虽然此时空间利用率才50%,远未达到默认的0.8装载因子。 另外,hashmap还有其他方方面面的问题,例如:内部使用装箱拆箱进行对象包装,这也极大影响性能。 所以,大部分的组件、引擎之类的,内部的容器都是根据自己的业务特点自己实现的,基本没有谁会直接使用hashmap
关于碰撞不同的V肯定不会被覆盖,不然这是JDK巨大BUG了。看下测试结果就知道:

package main;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;



public class Test {

	public static void main(String[] args) {
		
		Map<HashTestObject,HashTestObject> hash = new HashMap<>();
		
		HashTestObject o1 = new HashTestObject(1);
		HashTestObject o2 = new HashTestObject(2);
		
		hash.put(o1, o1);
		hash.put(o2, o2);
		
		System.out.println("=====key,value的hashcode跟equals都返回任意两对象相等======");
		System.out.println("o1=" + hash.get(o1).data);
		System.out.println("o2=" + hash.get(o2).data);
		
		Map<HashTestObject,String> hash2 = new HashMap<>();
		hash2.put(o1, "o1");
		hash2.put(o2, "o2");
		
		System.out.println("=====key的hashcode跟equals都返回任意两key对象相等======");
		System.out.println("o1=" + hash2.get(o1));
		System.out.println("o2=" + hash2.get(o2));
		
	
	}
	
	static class HashTestObject{
		
		public int data;
		
		public HashTestObject(int data){
			this.data = data;
		}
		
		public int hashCode(){
			return 1;
		}
		
		public boolean equals(Object  obj){
			return true;
		}
		
//		public boolean equals(HashTestObject obj){
//			if(obj == null)
//				return false;
//			if(obj == this) 
//				return true;
//			
//			if(obj instanceof HashTestObject){
//				obj = (HashTestObject)obj;
//				if(obj.data == data)
//					return true;
//			}
//			return false;
//		}
	}

}
结果: =====key,value的hashcode跟equals都返回任意两对象相等====== o1=2 o2=2 =====key的hashcode跟equals都返回任意两key对象相等====== o1=o2 o2=o2
shaozengwei 2014-08-08
  • 打赏
  • 举报
回复
顶一下,说实话,我觉得碰撞确实是个问题,还有空间浪费也是个问题。 其他的线程安全和扩容觉得问题不是很大。
福清仔 2014-08-08
  • 打赏
  • 举报
回复
学习
pricks 2014-08-06
  • 打赏
  • 举报
回复
1、线程安全始终是个大问题。不好的实现和一个好的实现,性能上可以相差几千几万倍。你说这是不是个问题?否则也不会有那么多前赴后继地花那么多精力解决这个问题了。 2、扩容要看怎么扩法。举例来说:java的string和stringBuffer两个类,同样可以对字符串扩容,但是执行成千上万次之后,性能上照样相差成千上万倍,时空复杂度都无限增加。你说这是不是也是个问题? 3、关于碰撞,建议你再研究研究,是不是你想象的那样 4、空间浪费不是指容器的填充程度,而是指由于hash算法引起的,例如导入100万条数据,结果可能有50万个位置始终没有数据存放,虽然此时空间利用率才50%,远未达到默认的0.8装载因子。 另外,hashmap还有其他方方面面的问题,例如:内部使用装箱拆箱进行对象包装,这也极大影响性能。 所以,大部分的组件、引擎之类的,内部的容器都是根据自己的业务特点自己实现的,基本没有谁会直接使用hashmap
jn0115 2014-08-05
  • 打赏
  • 举报
回复
引用 2 楼 BiologyPianoProgram 的回复:
1、hashmap,线程不安全,这个很重要 2、二分查找或tree树,可以做到索引自动按照大小排序,同时能够支持计算机的内存分页机制 3、hashmap本身有很多缺陷,例如: (1)无法保证数组与bucket的平衡性(理论上应该让每个bucket只有一个元素)。如果分词很多,则这个hashmap初始化时应该定义成多大容量呢?20、100、500?如果不能估计容量,则随着分词量不同,必然涉及hashmap的扩容,而扩容设计到数组和桶的复制,相当耗费内存,时间复杂度巨大,并非o(1)。虽然查询的效率是很高 (2)hashmap容易发生空位置。即:无法保证每个k-v在数组位置上的均衡分布,很多位置都是空的,造成巨大的空间浪费 (3)hashmap容易发生元素碰撞。这是由hash算法引起的。例如:两个不同的k-v对,最终可能hash值完全相同,这样造成的结果是:原先的k-v的v将被新的v值代替。后果很严重
1. 我觉得线程安全应该不是个问题,因为本身3种数据结构都不具备线程安全的性质,除非自己实现,再者作为词典几乎很少修改。 2. 扩容的时间消耗应该也不是问题吧,因为词典预加载到内存的,后期也很少修改。 3. 还有第4点,如果发生碰撞,应该不会替代掉原来的V,只是增加了时间复杂度。我记得HashMap的实现是根据Key的hashcode计算出相应的bucket,如果已经被占用,是调用V.equals方法进行比较而不是比较V的hashcode。 4. 空间浪费这倒是个问题,不过就算我们以0.5的装载因子算,字典树需要的空间貌似更到。
业余草 2014-07-30
  • 打赏
  • 举报
回复
这个具体就不知道了,顶你
pricks 2014-07-29
  • 打赏
  • 举报
回复
1、hashmap,线程不安全,这个很重要 2、二分查找或tree树,可以做到索引自动按照大小排序,同时能够支持计算机的内存分页机制 3、hashmap本身有很多缺陷,例如: (1)无法保证数组与bucket的平衡性(理论上应该让每个bucket只有一个元素)。如果分词很多,则这个hashmap初始化时应该定义成多大容量呢?20、100、500?如果不能估计容量,则随着分词量不同,必然涉及hashmap的扩容,而扩容设计到数组和桶的复制,相当耗费内存,时间复杂度巨大,并非o(1)。虽然查询的效率是很高 (2)hashmap容易发生空位置。即:无法保证每个k-v在数组位置上的均衡分布,很多位置都是空的,造成巨大的空间浪费 (3)hashmap容易发生元素碰撞。这是由hash算法引起的。例如:两个不同的k-v对,最终可能hash值完全相同,这样造成的结果是:原先的k-v的v将被新的v值代替。后果很严重
高坚果兄弟 2014-07-28
  • 打赏
  • 举报
回复
学习了 也很好奇这个问题 坐等答案

25,980

社区成员

发帖
与我相关
我的任务
社区描述
高性能WEB开发
社区管理员
  • 高性能WEB开发社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧