全书以零基础的读者自学完成一个中文分词系统作为目标。从Java基础语法开始,然后到文本处理相关的数据结构和算法,最后实现文本切分和词性标注。本书是少有的介绍业界热门的Java开发中文分词的书籍。本书选取相关领域的经典内容深入理解和挖掘,也综合了实践性强的创新想法。适用于对软件开发感兴趣的青少年或者大学生。
“前门到了,请在后门下车。”把“前门”标注成地名就容易理解这句话了。从种地到买菜、买房、养生保健以及投资理财等,都可以用到中文分词等文本信息挖掘技术。
各行业都在构建越来越复杂的软件系统,很多系统都会用到文本处理技术。但是即使在计算机专业,也有很多人对文本信息处理相关技术不太了解。其实,学习相关技术的门槛并不高。而本书就是为了普及相关开发而做的一次新的尝试,其中也结合了作者自己的研究成果,希望为推动相关应用的发展做出贡献。
本书借助计算机语言Java实现中文文本信息处理,试图通过恰当的数据结构和算法来应对一些常见的文本处理任务。相关代码可以从清华大学出版社的网站下载。
本书的第1章到第3章介绍了相关的Java开发基础。第4章介绍处理文本所用到的有限状态机基本概念和具体实现。第5章介绍相关的基础数据结构。第6章到第9章介绍中文分词原理与实现。
书中的很多内容来源于作者的开发和教学实践。作者的实践经验还体现在相关的其他书中,如《自己动手写搜索引擎》、《自然语言处理原理与技术实现》、《自己动手写网络爬虫》、《使用C#开发搜索引擎》、《解密搜索引擎技术实战》等。相对于作者编写的其他书籍,本书更加注意零基础入门。
学习是个循序渐进的过程。可以在读者群中共同学习。群体往往比单个人有更多的智慧产出。为了构建出更好的技术群体,请加读者QQ群(453406621)交流。希望快速入门的读者也可以参加相关培训。这本书最开始是为一位从苏州专门来北京现场学习的学员入门中文分词而编写。感谢他为编写本书提供的帮助。
也希望通过本书能结识更多的同行。有您真诚的建议,我们会发展得更好。例如,通过与同行的交流,让我们的数量、日期等量化信息的提取工具更加成熟。当前,语义分析等文本处理技术仍然需要更深入的发展,来更好地支持各行业的智能软件开发。
本书由罗刚、张子宪、崔智杰编著,参与本书编写的还有石天盈、张继红、童晓军,在此一并表示感谢。感谢开源软件和我们的家人、关心我们的老师和朋友、创业伙伴,以及选择猎兔自然语言处理软件的客户多年来的支持。
编 者
罗刚,计算机软件硕士,毕业于吉林工业大学。2005年创立北京盈智星科技发展有限公司,2008年联合创立上海数聚软件公司。猎兔搜索创始人,当前猎兔搜索在北京和上海以及石家庄均设有研发部。带领猎兔搜索技术开发团队先后开发出猎兔中文分词系统、猎兔文本挖掘系统,智能垂直搜索系统以及网络信息监测系统等,实现互联网信息的采集、过滤、搜索和实时监测,其开发的搜索软件日用户访问量达万次以上。
第4章 处理文本
网上聊天时,可能会遇到过找错对象的尴尬事情。程序应该可以帮助判断聊天对象是否正确。
XML和JSON这样的文本格式很流行,因为不仅程序可以读,人也是可以读懂的。这样的文本格式也需要解析。
4.1 字符串操作
经常需要分割字符串。例如IP地址127.0.0.1按.分割。可以先用String类中的indexOf方法来查找子串“.”,然后再截取子串。例如:
String inputIP = "127.0.0.1"; //本机IP地址
int p = inputIP.indexOf('.'); //返回位置3
这里的‘.’在字符串“127.0.0.1”中出现了多次。因为是从头开始找起,所以返回第一次出现的位置3。
如果没有找到子串,则indexOf返回-1。例如要判断虚拟机是否为64位的:
//当在32位虚拟机时,将返回32;而在64位虚拟机时,返回64
String x = System.getProperty("sun.arch.data.model");
System.out.println(x); //在32位虚拟机中输出32
System.out.println(x.indexOf("64")); //输出-1
如果找到了,则返回的值不小于0。所以可以这样写:
if (x.indexOf("64") < 0) {
System.out.println("32位虚拟机");
}
indexOf(String str, int fromIndex)从指定位置开始查找。例如:
String inputIP = "127.0.0.1";
System.out.println(inputIP.indexOf('.', 4)); //输出5,也就是第二个.所在的位置
从字符串inputIP里寻找点“.”的位置,但寻找的时候,要从inputIP的索引为4的位置开始,这就是第二个参数4的作用,由于索引是从0开始的,这样,实际寻找的时候是从字符0开始的,所以输出5,也就是第二个点“.”所在的位置。
String.subString取得原字符串其中的一段,也就是子串。传入两个参数:开始位置和结束位置。例如:
String inputIP = "127.0.0.1";
int p = inputIP.indexOf('.');
int q = inputIP.indexOf('.', p+1);
String IPsection1 = inputIP.substring(0, p); //得到"127"
String IPsection2 = inputIP.substring(p+1, q); //得到"0"
StringTokenizer类专门用来按指定字符分割字符串。StringTokenizer的nextToken()方法取得下一段字符串。
hasMoreElements()方法判断是否还有字符串可以读出。可以在StringTokenizer的构造方法中指定用来分隔字符串的字符。
例如分割IP地址:
String inputIP = "127.0.0.1";
StringTokenizer token =
new StringTokenizer(inputIP, "."); //用.分割IP地址串
while(token.hasMoreElements()) { //有更多的子串
System.out.print(token.nextToken() + " "); //输出下一个子串
}
StringTokenizer默认按空格分割字符串。例如翻译英文句子:
HashMap ecMap = new HashMap();
ecMap.put("I", "我"); //放入一个键/值对
ecMap.put("love", "爱");
ecMap.put("you", "你");
String english = "I love you";
StringTokenizer tokenizer =
new StringTokenizer(english); //用空格分割英文句子
while(tokenizer.hasMoreElements()) { //有更多的词没遍历完
System.out.print(ecMap.get(tokenizer.nextToken())); //输出:我爱你
}
StringTokenizer有几个构造方法,其中最复杂的构造方法是:
StringTokenizer(String str, String delim, boolean returnDelims)
如果最后这个参数returnDelims标记是false,则分隔字符只作为分隔词使用,一个返回的词是不包括分隔符号的最长序列。如果最后一个参数标记是true,则返回的词可以是分隔字符。默认是false,也就是不返回分隔字符。
如果需要把字符串存入二进制文件。可能会用到字符串和字节数组间的互相转换。首先看一下如何从字符串得到字节数组:
String word = "的";
byte[] validBytes = word.getBytes("utf-8"); //字符串转换成字节数组
System.out.println(validBytes.length); //输出长度是3
可以直接调用Charset.encode实现字符串转字节数组:
Charset charset = Charset.forName("utf-8"); //得到字符集
CharBuffer data = CharBuffer.wrap("数据".toCharArray());
ByteBuffer bb = charset.encode(data);
System.out.println(bb.limit()); //输出数据的实际长度6
Charset.decode把字节数组转回字符串:
byte[] validBytes = "程序设计".getBytes("utf-8"); //字节数组
//对字节数组赋值
Charset charset = Charset.forName("utf-8"); //得到字符集
//字节数组转换成字符
CharBuffer buffer = charset.decode(ByteBuffer.wrap(validBytes));
System.out.println(buffer); //输出结果