在使用jieba分詞模塊進行分詞的處理之后,由於項目的需要,要寫一個java的分詞模塊。瀏覽了jieba的GitHub網頁之后發現:jieba的java部分已經是好久沒有更新過了,並且jieba的java版本功能非常不完善(甚至沒有按照詞性分詞的功能)。不過無可厚非就是了,畢竟jieba的口號是做最好的Python分詞。於是我就去網上查閱,發現另外一個評價非常高的分詞模塊——HanLP分詞。
代碼要實現的目標與之前的四次隨筆相同,鏈接:https://www.cnblogs.com/aLieb/
1.HanLP分詞簡介(摘自官網)
HanLP是由一系列模型與算法組成的工具包,目標是普及自然語言處理在生產環境中的應用。HanLP具備功能完善、性能高效、架構清晰、語料時新、可自定義的特點;提供詞法分析(中文分詞、詞性標注、命名實體識別)、句法分析、文本分類和情感分析等功能。
HanLp分詞的功能是非常強大的,在把幾個常用的java分詞模塊拿到一起比較之后(拿了數學課本上面的三段文本來進行分詞處理),HanLP分詞的分詞效果是其中最好的,甚至有根據語境區分詞性的功能,所以決定使用HanLP分詞來寫java版本的代碼。
2.涉及到的一些問題
2.1數據結構
在之前的python版本的代碼當中,分詞所得到的關鍵詞和其對應的三級知識點之間的數量關系(一張二維表)是存儲在二維的字典當中的,但是java當中並沒有這種數據結構(我的java學的又不是那么好)。所以找了半天發現使用java當中的HashMap就可以解決這個問題,二維HashMap,也就是HashMap<String,HashMap<String,Integer>>這樣的數據結構,通過其自帶的get()和put()方法就可以實現二維HashMap的初始化和值的更改。
2.2Excel文件的讀取
項目需要的分詞語料存儲在Excel表當中,我本以為使用java的poi工具就可以實現讀取的工作,在一開始做的小demo當中也沒有問題,但是當我把文件大小為27187kb的excel文件導入的時候,就出現了JVM內存不夠的問題,后來到網上一查發現如果Workbook使用XSSFWorkBook創建對象,導入的Excel大小又大於2M,就很容易出現OOM的問題,所以要使用poi里面的事件驅動的讀取方式來進行excel文件的讀取。
錯誤(不適用於大容量excel文件導入)的方式:(這個try-catch是eclipse自動插入的)
1 Workbook wb_Workbook=null;//初始化一個workbook類型的對象 2 try { 3 wb_Workbook=new XSSFWorkbook(exceFile); 4 } catch (InvalidFormatException e) { 5 // TODO Auto-generated catch block 6 e.printStackTrace(); 7 } catch (IOException e) { 8 // TODO Auto-generated catch block 9 e.printStackTrace(); 10 }//獲取excel文件當中的workbook到wb當中,需要try-catch
正確(適合大容量Excel文件導入的方式):
1 FileInputStream in=new FileInputStream(new File(data_path)); 2 Workbook wb1=null; 3 wb1=new StreamingReader(null).builder() 4 .rowCacheSize(100) 5 .bufferSize(4096) 6 .open(in);
以上內容參考:https://www.cnblogs.com/iliuyuet/p/4361201.html
3.代碼及大致思路
3.1maven依賴部分
需要添加的依賴:
1 <dependency> 2 <groupId>com.hankcs</groupId> 3 <artifactId>hanlp</artifactId> 4 <version>portable-1.7.4</version> 5 </dependency> 6 <!-- 以下兩個為關於excel讀寫的依賴 --> 7 <dependency> 8 <groupId>org.apache.poi</groupId> 9 <artifactId>poi</artifactId> 10 <version>3.15</version> 11 </dependency> 12 <dependency> 13 <groupId>org.apache.poi</groupId> 14 <artifactId>poi-ooxml</artifactId> 15 <version>3.15</version> 16 </dependency> 17 <dependency> 18 <groupId>com.monitorjbl</groupId> 19 <artifactId>xlsx-streamer</artifactId> 20 <version>1.2.0</version> 21 </dependency> 22 <dependency> 23 <groupId>org.slf4j</groupId> 24 <artifactId>slf4j-log4j12</artifactId> 25 <version>1.7.2</version> 26 </dependency>
3.2App.java部分

1 package Big_Create.Word_Cut; 2 3 import java.io.BufferedReader; 4 import java.io.BufferedWriter; 5 import java.io.File; 6 import java.io.FileInputStream; 7 import java.io.FileNotFoundException; 8 import java.io.FileReader; 9 import java.io.FileWriter; 10 import java.io.IOException; 11 import java.util.ArrayList; 12 import java.util.HashMap; 13 14 import org.apache.poi.openxml4j.exceptions.InvalidFormatException; 15 import org.apache.poi.ss.usermodel.Cell; 16 import org.apache.poi.ss.usermodel.Row; 17 import org.apache.poi.ss.usermodel.Workbook; 18 import org.apache.poi.xssf.usermodel.XSSFWorkbook; 19 20 import com.graphbuilder.curve.Point; 21 import com.hankcs.hanlp.dictionary.CustomDictionary; 22 import com.hankcs.hanlp.seg.common.Term; 23 import com.hankcs.hanlp.tokenizer.StandardTokenizer; 24 import com.monitorjbl.xlsx.StreamingReader; 25 26 /** 27 * Hello world! 28 * 29 */ 30 public class App 31 { 32 33 //1.從excel文件當中導入詞匯的方法(是xlsx類型的文件) 34 public static void add_words_from_excel(String excel_path,int cell_num) { 35 File exceFile=new File(excel_path);//以傳進來的參數創建文件 36 Workbook wb_Workbook=null;//初始化一個workbook類型的對象 37 try { 38 wb_Workbook=new XSSFWorkbook(exceFile); 39 } catch (InvalidFormatException e) { 40 // TODO Auto-generated catch block 41 e.printStackTrace(); 42 } catch (IOException e) { 43 // TODO Auto-generated catch block 44 e.printStackTrace(); 45 }//獲取excel文件當中的workbook到wb當中,需要try-catch 46 org.apache.poi.ss.usermodel.Sheet sheet_readSheet=wb_Workbook.getSheetAt(0); 47 int firstRowIndex=sheet_readSheet.getFirstRowNum()+1; 48 int lastRowIndex=sheet_readSheet.getLastRowNum(); 49 //獲取第一行和最后一行的行號 50 for (int i = firstRowIndex; i < lastRowIndex; i++) { 51 Row row=sheet_readSheet.getRow(i); 52 if (row!=null) { 53 Cell cell=row.getCell(cell_num); 54 if (cell!=null) { 55 CustomDictionary.add(cell.getStringCellValue()); 56 //測試用System.out.println(cell.toString()); 57 } 58 } 59 } 60 } 61 62 //2.從txt文件當中導入停用詞表的方法,返回值為一個存儲字符串的arraylist 63 //這里用的停用詞表是網上下載的哈工大的停用詞表 64 public static ArrayList<String> get_stop_list(String txt_path) throws IOException { 65 ArrayList<String> stop_list=new ArrayList<String>(); 66 //創建arraylist來存儲讀出的停用詞 67 //現在已知停用詞表(txt格式)是一個詞一行存儲,詞的末尾有空格 68 try { 69 FileReader file_Reader=new FileReader(txt_path); 70 BufferedReader buffer_BufferedReader=new BufferedReader(file_Reader); 71 String read_line; 72 while((read_line=buffer_BufferedReader.readLine()) != null) { 73 String out_str=delete_space(read_line);//去掉了空格之后的字符串,將其添加進輸出的arraylist里 74 stop_list.add(out_str); 75 } 76 } catch (FileNotFoundException e) { 77 // TODO Auto-generated catch block 78 e.printStackTrace(); 79 } 80 return stop_list; 81 } 82 83 //3.用於去掉讀取的字符串中的空格的方法,返回為string 84 public static String delete_space(String str_with_space) { 85 String str_with_out_space = ""; 86 for (int i = 0; i < str_with_space.length(); i++) { 87 char temp=str_with_space.charAt(i); 88 if (temp!=' ') { 89 str_with_out_space=str_with_out_space+temp; 90 //因為這個情景下的文本不是很長,所以直接用字符串拼接即可 91 } 92 } 93 return str_with_out_space; 94 } 95 96 //4.主體分詞方法,將所有的題干信息提取並且做分詞處理 97 public static void striped(String data_path,String stop_path,String result_path1,String result_path2) throws IOException { 98 //三個參數分別為:源數據路徑、停用詞表路徑、輸出結果路徑 99 //設置變量部分: 100 int text_num=1;//題干所在的列號 101 int class_one_num=7;//一級知識點所在的列號 102 int knowledge_num=9;//三級知識點所在的列號 103 int knowledge_code_num=6;//三級知識點對應的知識點編碼所在的列號 104 105 //設置數據結構部分: 106 ArrayList<String>knowledge_points=new ArrayList<String>();//存儲所有的三級單知識點 107 ArrayList<String>point_code=new ArrayList<String>();//存儲所有的三級知識點的知識點代碼 108 ArrayList<String>word_list=new ArrayList<String>();//存儲所有分出來的詞的索引 109 HashMap<String, String>point_to_code=new HashMap<String, String>();//存儲三級知識點和其代碼的對應關系 110 HashMap<String, HashMap<String, Integer>>word_to_point=new HashMap<String, HashMap<String,Integer>>(); 111 //用於記錄詞在知識點當中出現次數的數據結構(相當於二維表,列為詞,行為知識點) 112 HashMap<String, HashMap<String, Integer>>point_to_word=new HashMap<String, HashMap<String,Integer>>(); 113 //用於記錄知識點在詞當中出現次數的數據結構(相當於二維表,列為知識點,行為詞) 114 //以上兩個hashmap的初始化工作將在循環當中進行 115 116 //獲取停用詞表:stop_list(ArrayList) 117 try { 118 ArrayList<String>stop_list=get_stop_list(stop_path); 119 } catch (IOException e) { 120 // TODO Auto-generated catch block 121 e.printStackTrace(); 122 } 123 124 File excel_file=new File(data_path); 125 ArrayList<String>all_textStrings=new ArrayList<String>();//創建arraylist來存儲所有的題干信息 126 127 FileInputStream in=new FileInputStream(new File(data_path)); 128 Workbook wb1=null; 129 wb1=new StreamingReader(null).builder() 130 .rowCacheSize(100) 131 .bufferSize(4096) 132 .open(in); 133 134 org.apache.poi.ss.usermodel.Sheet sheet1=wb1.getSheetAt(0); 135 //int firstRowIndex=sheet1.getFirstRowNum()+1;//獲取第一行的index 136 //int lastRowIndex=sheet1.getLastRowNum();//獲取最后一行的index 137 for (Row row:sheet1) { 138 //Row row=sheet1.getRow(i); 139 if (row!=null&&row.getRowNum()!=0) { 140 Cell text_cell=row.getCell(text_num);//獲取存儲題干信息的單元格 141 Cell point_cell=row.getCell(knowledge_num);//獲取存儲三級知識點信息的單元格 142 Cell point_code_cell=row.getCell(knowledge_code_num);//獲取存儲知識點代碼信息的單元格 143 String temp_text=text_cell.getStringCellValue(); 144 all_textStrings.add(temp_text);//將這個文本添加進arraylist里 145 String pointString=point_cell.getStringCellValue();//知識點,這里還需要篩選單知識點 146 String[]tempStrings=pointString.split("\\^\\.\\^"); 147 if (tempStrings.length==1) { 148 //經過分割之后的長度為1,說明是單知識點,將其添加進arraylist當中 149 String temp_point=tempStrings[0]; 150 if (knowledge_points.contains(temp_point)==false) { 151 //若長度為1,並且在arraylist當中沒有出現過,就將其加入其中 152 knowledge_points.add(temp_point); 153 point_code.add(point_code_cell.getStringCellValue()); 154 point_to_code.put(temp_point, point_code_cell.getStringCellValue()); 155 //因為是單知識點,所以說可以同時將知識點代碼也添加進去 156 } 157 //獲取了無重復的知識點、知識點代碼索引 158 java.util.List<Term>temp_List=StandardTokenizer.segment(temp_text); 159 for (int j = 0; j < temp_List.size() ; j++) { 160 String str_out=temp_List.get(j).toString(); 161 String[] str_splited=str_out.split("/"); 162 if ((word_list.contains(str_splited[0])==false)&&str_splited[1].equals("n")) { 163 word_list.add(str_splited[0]); 164 } 165 }//獲得了所有的分詞得到的詞匯的無重復索引 166 } 167 }//第一個for循環,獲取了詞匯、知識點、知識點代碼三個索引 168 } 169 System.out.println("三級知識點總數為(不重復):"+knowledge_points.size()); 170 System.out.println("分詞所得詞數為(不重復):"+word_list.size()); 171 172 for (int i = 0; i < word_list.size(); i++) { 173 HashMap<String, Integer>word_to_point_num=new HashMap<String, Integer>(); 174 //這里要把HashMap的創建放在外面,否則會導致數據的覆蓋,出現null 175 for (int j = 0; j < knowledge_points.size(); j++) { 176 word_to_point_num.put(knowledge_points.get(j), 0); 177 } 178 word_to_point.put(word_list.get(i), word_to_point_num); 179 }//至此完成了二維哈希圖的初始化工作,這時數據應當為一張二維表,值全部為0 180 181 for (int i = 0; i < knowledge_points.size(); i++) { 182 HashMap <String, Integer>point_to_word_num=new HashMap<String, Integer>(); 183 for (int j = 0; j < word_list.size(); j++) { 184 point_to_word_num.put(word_list.get(j), 0); 185 } 186 point_to_word.put(knowledge_points.get(i), point_to_word_num); 187 }//初始化一張二維哈希圖,記錄知識點對應關鍵詞的出現次數 188 189 //接下來開始再次遍歷excel表來獲取數據 190 File excel_file_again=new File(data_path); 191 FileInputStream in_again=new FileInputStream(excel_file_again); 192 Workbook data_Workbook=null; 193 data_Workbook=new StreamingReader(null).builder() 194 .rowCacheSize(100) 195 .bufferSize(4096) 196 .open(in_again); 197 198 org.apache.poi.ss.usermodel.Sheet data_Sheet=data_Workbook.getSheetAt(0); 199 for (Row row:data_Sheet) { 200 if(row!=null&&row.getRowNum()!=0) { 201 //Row row=data_Sheet.getRow(i); 202 Cell read_point_cell=row.getCell(knowledge_num); 203 Cell read_text_cell=row.getCell(text_num); 204 String point_to_check=read_point_cell.getStringCellValue(); 205 String[] temp_Strings=point_to_check.split("\\^\\.\\^"); 206 if (temp_Strings.length==1) { 207 String temp_point=temp_Strings[0]; 208 String text_to_cut=read_text_cell.getStringCellValue(); 209 java.util.List<Term>temp_Terms=StandardTokenizer.segment(text_to_cut); 210 for (int j = 0; j < temp_Terms.size(); j++) { 211 String strs_to_put=temp_Terms.get(j).toString(); 212 String[]str_striped=strs_to_put.split("/"); 213 String str_to_put=str_striped[0]; 214 if (word_list.contains(str_striped[0])&&str_striped[1].equals("n")) { 215 //若word_list里存在這個詞,那么就將其出現次數+1 216 Integer last_value=word_to_point.get(str_to_put).get(temp_point); 217 Integer an_last_value=point_to_word.get(temp_point).get(str_to_put); 218 //獲取之前的數值 219 HashMap<String, Integer>temp_map=new HashMap<String, Integer>(); 220 temp_map=word_to_point.get(str_to_put); 221 if (temp_map.get(temp_point)!=null) { 222 temp_map.put(temp_point, (last_value+1)); 223 //word_to_point.get(str_to_put).put(temp_point, (last_value+1)); 224 word_to_point.put(str_to_put, temp_map); 225 //將數值修改 226 } 227 HashMap<String, Integer>an_temp_map=new HashMap<String, Integer>(); 228 an_temp_map=point_to_word.get(temp_point); 229 if (an_temp_map.get(str_to_put)!=null) { 230 an_temp_map.put(str_to_put, (an_last_value+1)); 231 point_to_word.put(temp_point, an_temp_map); 232 }//修改另外一個表的數值 233 //System.out.println("成功修改:word:"+str_to_put+",在知識點:"+temp_point+" 當中的出現次數為:"+word_to_point.get(str_to_put).get(temp_point)+" 原始值為:"+last_value); 234 } 235 } 236 } 237 } 238 } 239 //寫入txt文件的過程 240 File write_File=new File(result_path1); 241 BufferedWriter out_to_file=new BufferedWriter(new FileWriter(write_File)); 242 for (int i = 0; i < word_list.size(); i++) { 243 out_to_file.write("word:"+word_list.get(i)+" 出現情況:"); 244 //System.out.print("word:"+word_list.get(i)+" 出現情況:"); 245 int appear_num=0;//統計詞頻用 246 for (int j = 0; j < knowledge_points.size(); j++) { 247 appear_num=appear_num+word_to_point.get(word_list.get(i)).get(knowledge_points.get(j)); 248 } 249 out_to_file.write("出現總數:"+appear_num); 250 //System.out.print("出現總數:"+appear_num); 251 if (appear_num!=0) { 252 for (int k = 0; k < knowledge_points.size(); k++) { 253 double f=(double)word_to_point.get(word_list.get(i)).get(knowledge_points.get(k))/appear_num; 254 if (f!=0) { 255 out_to_file.write("知識點:"+knowledge_points.get(k)+" 出現頻率:"+ f); 256 //System.out.print("知識點:"+knowledge_points.get(k)+" 出現頻率:"+ f); 257 } 258 } 259 } 260 out_to_file.write("\r\n"); 261 //System.out.print("\n"); 262 } 263 out_to_file.flush(); 264 out_to_file.close(); 265 266 File write_File2=new File(result_path2); 267 BufferedWriter out_to_file2=new BufferedWriter(new FileWriter(write_File2)); 268 for (int i = 0; i < knowledge_points.size(); i++) { 269 out_to_file2.write("知識點:"+knowledge_points.get(i)+"包含詞語情況:"); 270 int appear_num=0; 271 for (int j = 0; j < word_list.size(); j++) { 272 appear_num=appear_num+point_to_word.get(knowledge_points.get(i)).get(word_list.get(j)); 273 } 274 out_to_file2.write("出現總數:"+appear_num); 275 if (appear_num!=0) { 276 for (int k = 0; k < word_list.size(); k++) { 277 double f=(double)point_to_word.get(knowledge_points.get(i)).get(word_list.get(k))/appear_num; 278 if (f!=0) { 279 out_to_file2.write("詞語:"+word_list.get(k)+"頻率:"+f); 280 } 281 } 282 } 283 out_to_file2.write("\r\n"); 284 } 285 out_to_file2.flush(); 286 out_to_file2.close(); 287 } 288 289 //主方法 290 public static void main( String[] args ) throws IOException 291 { 292 String data_path="src/";//這里是要處理的exel文件名,將其改成你自己的路徑,或者直接放到maven項目的src下即可 293 String stop_path="src/stop_words1.txt"; 294 String result_path="src/final_result.txt"; 295 String point_to_word_path="src/point_to_word.txt"; 296 striped(data_path, stop_path, result_path,point_to_word_path); 297 } 298 }
4.總結
跟之前的Python代碼思路大體無異,還是要先獲取所有詞語和三級知識點的索引,初始化二維表之后再進行一次遍歷,同時將數據記錄下來。
這次在小組成員的提醒之下,改正了平時編程時的一個壞習慣:總是喜歡使用絕對路徑,這樣把代碼發給別人的時候還要根據別的機子的路徑去修改才能跑通,會給別人帶來麻煩,所以說以后要注意使用項目內的相對路徑。學到了。
最后還是要贊美一下HanLP的開發者,分詞模塊做的真是好!