在人民銀行那里,每個銀行的每一個營業網點都有自己唯一的銀行聯行號,根據這個號碼能快速定位一間銀行具體的分支行,就像根據一個身份證號碼能快速確定一個人一樣。例如匯款時,匯款單上要求填寫收款人開戶行,然后銀行會把收款人開戶行的聯行號連其他信息發到人民銀行進行清算,這樣能保證以最快的速度匯到收款人的手上。如果聯行號不准確,那么在匯款的時候會發生分行落地,支行間調撥等操作,影響導致時間,尤其是跨行匯款的時候。一般銀行的代收付接口,都會要求提供此參數。
銀行聯行號一般是根據輸入的分支行信息模糊查詢出來的,有的銀行接口也會提供類似的根據傳入的信息返回聯行號的接口,其實現的技術也是根據模糊匹配思路,只是不同的銀行實現的水准高低不同,如輸入"工行海淀支行"有的返回的是中國工商銀行北京市分行海淀鎮支行營業室102100000458,有的返回的是中國工商銀行北京市海淀支行四季青分理處102100024537。
本文主要是基於前兩年在支付行業的代碼實戰,通過聯行號模糊查詢示例講解KMP與Levenshtein模糊匹配算法,有關此兩種算法的介紹可以參考Levenshtein字符串距離算法介紹與KMP字符串匹配算法,本文只是整個查詢功能的代碼示例,為了專注算法重點,略去了銀行同義詞之間的匹配與模糊地市查詢能力。(銀行同義詞如工行、工商銀行、中國工商銀行股份有限公司,模糊地市如江西省南昌市、江西南昌)
先看整體效果
主要代碼說明:
- swing初始化及數據加載
1 try { 2 JFrame frame = new JFrame("銀行模糊匹配---edited by Dimmacro"); 3 textLabel = new JLabel("請輸入待匹配的字符串:"); 4 textLabel.setFont(new Font("Default", Font.PLAIN, 18)); 5 textField = new JTextField(30); 6 textField.setFont(new Font("Default", Font.PLAIN, 18)); 7 resultArea = new JTextArea(); 8 resultArea.setFont(new Font("Default", Font.BOLD, 15)); 9 resultArea.setEditable(false); 10 // 設置窗口初始化大小為屏幕大小的1/4,位置在最中間 11 JPanel panel = new JPanel(); 12 panel.add(textLabel); 13 panel.add(textField); 14 frame.getContentPane().add(panel, BorderLayout.NORTH); 15 frame.getContentPane().add(new JScrollPane(resultArea), BorderLayout.CENTER); 16 17 frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 18 Dimension d = Toolkit.getDefaultToolkit().getScreenSize(); 19 frame.setSize(d.width / 2, d.height / 2); 20 frame.setLocation((d.width - frame.getSize().width) / 2, (d.height - frame.getSize().height) / 2); 21 frame.setVisible(true); 22 textField.addKeyListener(new KeyAdapter() { 23 public void keyReleased(KeyEvent e) { 24 startTime = System.nanoTime(); 25 readyCheck = true; 26 } 27 28 public void keyPressed(KeyEvent e) { 29 startTime = System.nanoTime(); 30 readyCheck = false; 31 } 32 33 }); 34 } catch (Exception e) { 35 e.printStackTrace(); 36 resultArea.setText("執行出錯!"); 37 }
- 聯行號數據加載:需要把聯行號數據庫先加載到內存中,其單行格式為:102100000030,中國工商銀行北京市分行營業部
1 private static long initSourceData() { 2 long counts = 0; 3 try { 4 InputStream bankCodeInputStream = BankMatch.class.getClassLoader().getResourceAsStream(bankCodeFile); 5 BufferedReader bReader = new BufferedReader(new InputStreamReader(bankCodeInputStream, "GBK"),20480); 6 String lineString; 7 bankMap = new HashMap<String, String>(); 8 String code, name; 9 while ((lineString = bReader.readLine()) != null) { 10 int firstCommaIndex = lineString.indexOf(","); 11 code = lineString.substring(0, firstCommaIndex); 12 name = lineString.substring(firstCommaIndex + 1); 13 // System.out.println("code=" + code + " and name=" + name+"=========="+counts); 14 bankMap.put(code, name); 15 counts++; 16 } 17 } catch (Exception e) { 18 e.printStackTrace(); 19 } 20 return counts; 21 }
- 根據傳入的參數模糊查詢,返回符合條件的列表,並按最佳匹配程度進行排序
1 public List<String> handleMatch() { 2 List<String> resultList = new ArrayList<String>(); 3 String code, name; 4 String[] nameArray; 5 String findResult; 6 for (Map.Entry<String, String> entry : bankMap.entrySet()) { 7 code = entry.getKey(); 8 name = entry.getValue(); 9 nameArray = name.split(","); 10 findResult = code + "," + nameArray[0]; 11 List<String> arrangeList = new ArrayList<String>(); 12 resultStr = new String[nameArray.length]; 13 arrageArray(arrangeList, nameArray); // 如果有省份城市,重排其順序以保證匹配的准確性 14 for (String oneArrangeStr : arrangeList) { 15 name = oneArrangeStr.replaceAll(",", ""); 16 // 處理BMP全字匹配的情況 17 if ((KMPMatchString.kmpMatch(name, matchStr) || KMPMatchString.kmpMatch(matchStr, name)) && !resultList.contains(findResult)) { 18 resultList.add(findResult); 19 match.printOut(findResult); 20 match.getShowArea().selectAll(); 21 } 22 } 23 } 24 // Levenshtein 模糊算法 25 if (resultList.size() > 0) { 26 // 根據Levenshtein 模糊算法排序 27 Collections.sort(resultList, new Comparator<String>() { 28 public int compare(String s1, String s2) { 29 return LevenshteinMacthString.levenshteinMacth(s1.split(",")[1], matchStr) 30 - LevenshteinMacthString.levenshteinMacth(s2.split(",")[1], matchStr); 31 } 32 }); 33 } 34 return resultList; 35 }
- KMP算法
1 public static boolean kmpMatch(String source, String target) 2 { 3 if(null == source || null == target || "".equals(source.trim()) || "".equals(target.trim())) 4 { 5 return false; 6 } 7 8 int bl = source.length(); 9 int al = target.length(); 10 11 for(int bi = 0,ai = 0;bi < al;ai++) 12 { 13 if(bi == al || ai == bl) 14 { 15 return false; 16 } 17 else if(source.charAt(ai) == target.charAt(bi)) 18 { 19 bi++; 20 } 21 } 22 return true; 23 }
- Levenshtein算法
1 public static int levenshteinMacth(String source,String target) { 2 int n = target.length(); 3 int m = source.length(); 4 int[][] d = new int[n + 1][m + 1]; 5 6 // Step 1 7 if (n == 0) { 8 return m; 9 } 10 11 if (m == 0) { 12 return n; 13 } 14 15 // Step 2 16 for (int i = 0; i <= n; d[i][0] = i++) { 17 } 18 19 for (int j = 0; j <= m; d[0][j] = j++) { 20 } 21 22 // Step 3 23 for (int i = 1; i <= n; i++) { 24 // Step 4 25 for (int j = 1; j <= m; j++) { 26 // Step 5 27 // System.out.println(t.charAt(j - 1)); 28 // System.out.println(s.charAt(i - 1)); 29 // int cost = (t.charAt(j - 1) == s.charAt(i - 1)) ? 0 : 1; 30 int cost = (source.substring(j - 1, j) == target.substring(i - 1, i) ? 0 : 1); 31 32 // Step 6 33 d[i][j] = Math.min(Math.min(d[i - 1][j] + 1, d[i][j - 1] + 1), d[i - 1][j - 1] + cost); 34 } 35 } 36 // Step 7 37 return d[n][m]; 38 }
- 附件下載:Eclipse工程,直接導入運行BankMatch類即可看到效果。下載
- 遺留代碼問題:如整體效果看到的那樣,每次從輸入框輸入完釋放最后一次按鍵時,如果1秒內沒有接着按下一個鍵,才會開始查詢,這樣既可以做到根據輸入的效果實時查詢,又不至於要每次輸入一個字符就開始查。對於這個實現采用的是wihe(true)的方式,但是發現如果不加線程sleep的話會出現不響應查詢的情況,請萬能的博客園高手看看。