“1000万字符串,去掉重复”的一些思考和java实现

题目:1000万字符串,其中有些是重复的,需要把重复的全部去掉,保留没有重复的字符串。请怎么设计和实现?

大数据的字符串处理我一般想到了trie树和hashmap,jdk里有hashmap的实现,所以想先用hashmap来试试效果,在用hashmap来测试前先编个小代码,用来生成1000万的字符串,使用随机函数来选择字符:

	//生成sum个单词,并输入到word.txt文件中去。
	public static void produceWord(int sum){
		char[] c = {'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o'
			    ,'p','q','r','s','t','u','v','w','x','y','z'};
		Random r = new Random();
		int length =0;
		int count =0;
		StringBuffer sb = new StringBuffer("");
		
		while(count<sum){
			length = r.nextInt(10)+1;
			for(int i=0;i<length;i++){
				sb.append(c[r.nextInt(26)]);
			}
			FileUtils.writeWord(new String(sb),FileUtils.getFile("word.txt"));//这是我写的一个工具类,直接写入带刺到word.txt文件中
			sb.delete(0, sb.length());
			count++;
		}
	}
我用上面的函数生成100万个单词并写入到文件中花了670秒,也就是10分钟,因为每个单词的最大长度是10,while循环最坏会有1000万次的执行(1000万的字符串太久了,所以不想试了),生成的word.txt文件中一行对应一个单词,下面来用jdk自带的hashmap来排除100万个单词里面的重复单词,即题目的要求:

public static void main(String[] args) {
			long time = 0;
			time = TimeUtils.printTime();//这是我的工具类,直接输出当前的时间
//			produceWord(1000000);
			try {
				BufferedReader br = new BufferedReader(new FileReader(FileUtils.getFile("word.txt")));
				FileWriter fw = new FileWriter(FileUtils.getFile("treeWord.txt"),true);
				HashMap<String, Integer> hm = new HashMap<String, Integer>();
				String s = null;
				while((s=br.readLine())!=null){
					if(!hm.containsKey(s)){  //如果hm没有包含s这个单词,则把s加入到hm,同时写入文件treeWord.txt中
						hm.put(s, 1);
						//输出到文件
						fw.write(s);
						fw.write("\r\n");
						fw.flush();
					}
						
				}
				fw.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
			time = TimeUtils.printTime()-time;
			TimeUtils.takeTime(time);//总共花费了多少秒
	}
输出:

1333521185421
1333521201546
花费时间:16.125秒


上面用hashmap测试,从word.txt中读取100万个单词,并去重后写入treeWord文件,总共花了16秒的样子。看来还是比较快的。

接下来想试试用trie树来解决这个问题,jdk没有trie树,只好自己写一个了,先简单的介绍一下trie树,原理很简单,比如要表示i am a coder 这个句子的trie树,画个大致图就明白了:“1000万字符串,去掉重复”的一些思考和java实现_第1张图片

 当然,trie有很多变种,节点存储的东西也不一定一样,我这里节点存储了字符和一个数字,数字为0表示当前所在的节点没有单词,为1表示当前节点有单词,应该很好理解。

好了,接下来开始构建trie树,

static class treeNode {
		treeNode[] node = null;
		char value;
		int count;// 单词计数,如果为0表示以此节点结尾的字符串,大于0则表示有count个以它结尾的字符串
		int size;// 有几个孩子
             ................................


这是我的trie节点,node是他的子节点。节点会有一些很重要的操作:
// 循环添加字符串s到当前节点中,前提是已经判断了当前节点不存在以s开头的字符
		public void addString(String s) {
			char[] c = s.toCharArray();
			treeNode tn = this;
			int i = 0;
			while (i < c.length) {
				tn.addChild(new treeNode(c[i]));
				tn = tn.node[tn.contain(c[i])];
				i++;
			}
			tn.count++; //当前节点的字符串树加一
		}

		// 添加一个孩子,前提是已经判断了当前节点不存在这个子节点
		public void addChild(treeNode child) {
			if (getSize() == node.length) {
				treeNode[] temp = new treeNode[node.length * 2];
				System.arraycopy(node, 0, temp, 0, node.length);
				node = temp;
			}
			node[size] = child;
			size++;
		}

		// 如果有包含c的子节点则返回节点的位置
		public int contain(char c) {
			treeNode tn = new treeNode(c);
			for (int i = 0; i < size; i++) {
				if (node[i].isEqual(tn)) {
					return i;
				}
			}
			return -1;
		}


注意,上面的代码都属于节点的操作,下面介绍在trie树中的一些操作:


//判断一个字典树中是否存在字符串str,只有返回值大于0才表示有这个字符串
	private int contain(String str) {
		if(str==null || str == "")
			return -1;
		treeNode nd = root;
		int i =0;
		int place;
		while(i<str.length() && (place =nd.contain(str.charAt(i)))!=-1){
			nd = nd.node[place];
			i++;
		}
		if(i==str.length())
			return nd.count;
		else return -1;
	}
	
	

	// 在字典树中添加一个字符串 ,具体实现
	private void addString(String str, treeNode node) {
		treeNode tn = node;
		int count;
		int i = 0;
		while (i < str.length()&& (count = tn.contain(str.charAt(i))) != -1 ) {  //如果tn节点包含值为str[i]的子节点,则赋值这个子节点为新的tn,继 												//续向下查找
			tn = tn.node[count];
			i++;
			if(i==str.length()){
				tn.count++;//所在树本来已经包含这个字符串,所以count数加一
			}
		}
		if (i != str.length()) {      //在str[i]后面的没有添加进trie树中
			String s = str.substring(i);
			tn.addString(s); // 直接调用节点的方法addString,插入str[i]后面的字符串
		}
	}

用word.txt的100万单词测试所写的trie树,看解决一开始说提到的问题,

	public static void main(String[] args) {
		long time = TimeUtils.printTime();
		TrieTree t = new TrieTree(FileUtils.getFile("word.txt"));
		time = TimeUtils.printTime()-time;
		System.out.println("构建trie树花费了:");
		TimeUtils.takeTime(time);
		System.out.println("开始输出......");
		time = TimeUtils.printTime();
		t.printFile(FileUtils.getFile("treeWord.txt"));
		time = TimeUtils.printTime()-time;
		System.out.println("输出trie树到文件花费了...");
		TimeUtils.takeTime(time);
	}

输出:

1333521079359
1333521087187
构建trie树花费了:
花费时间:7.828秒
开始输出......
1333521087187
输出到treeWord.txt成功
1333521099843
输出trie树到文件花费了...
花费时间:12.656秒


加起来总共花费20秒的样子,跟上面haspmap差不多的速度,当然,我的机子运行起来比较卡,所以严格的来说,速度可能不是很准确,但大致还是知道了,hashmap的速度会要快一些,因为在添加一个字符串的时候,hashmap直接用哈希函数就能定位,然后选择是否put和写入文件,但是trie树需要在子节点中比较。

总结:1:这是根据“1000万字符串”而写的trie树,没有比较好的结构性,封装的不是很好。

             2:个人觉得trie树对hashmap的优势是,在大量重复的单词中,trie树需要的内存会低一些,hashmap的优势是查找快一些。

 

欢迎拍砖!

以上只给了关键代码,需要完整代码留下邮箱.....

        

 

你可能感兴趣的:(java,jdk,String,HashMap,Integer,Random)