一個手機號碼剔重的問題

問題

QQ羣上有人問了這樣一個問題,
現有2個待推廣手機號碼數據文件,A文件1000W行,B文件是100W,文件中每行記錄只有手機號,號碼有重複,
請設計高效方案先對A、B數據文件分別進行號碼剔重,再找出B文件中在A文件存在的號碼,
請寫出核心設計思想,並編寫代碼完整實現。(注:請用純c實現,不準採用數據庫、Memcached等第三方中間件,號碼數據文件請自行模擬生成) 
答題要求:請同時提供Word設計文檔,和程序源代碼(如程序中使用了第三方Jar請註明,jar包請不要上傳),打包上傳。
 

思路及分析

我想了一下,看看《編程珠璣》,而後我決定用位圖(Bit Map)來解決這個問題。 
有些朋友可能還不瞭解位圖(Bit Map),在這裏我先用一個例子來講明。
 
假設我有一個0到31的集合,集合裏面的元素不重複,好比這樣{0,3,1,5,2,19,7,8,31,21,10}。經過位圖,我能夠將這樣的集合表示爲11110001101000000001010000000001, 其中1表示該數值爲下標的數存在在集合中,好比第一個1表示0存在集合中,第二個1表示1存在集合中,等等。經過這樣作,咱們起碼能夠獲得兩個好處
1) 節省空間--咱們能夠用二進制一個位來存儲存儲兩個信息,一是存不存在,而是存在的數是多少(經過一個bit就能夠獲得這麼多信息,真了不得)。
2) 排序--從左到右遍歷這個爲圖,咱們能夠獲得排序的集合,好比上例中,咱們能夠獲得集合 {0,1,2,3,7,8,10,19,21,31}
 
若是將全部這電話號碼用位圖表示,那麼須要9999999999個bit (10個9, 考慮到手機號碼的第一位都是1)。 9999999999 bit = (9999999999/8) byte = (9999999999 / (8 * 1024)) KB = (9999999999 / 8 *1024*1024) M = 1192M
 
嗯....,1000萬條手機號碼裝到內存中只用114M左右的內存空間,若是用位圖表示,看起來用的內存更多。行不行啊.... 可是考慮到手機號碼的前3位都差很少,並且總數最多30個,全部可能考慮把存放1000萬條記錄的手機號碼的大文件拆分一下,這樣就位圖的位數就降2個數量級了。因此若是咱們要在內存中裝入手機號碼後8位,須要99999999(8個9)個bit,算一下
     99999999 bit = (99999999/8) byte = (99999999 / (8 * 1024)) KB = (99999999 / 8 *1024*1024) M = 11.92M
 
這樣好辦了,內存夠用了。
 

號碼剔重步驟

根據以上的思路,第一步要大文件進行拆分, 如下是Java代碼
package art.programming.algorithm;
 
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
 
import org.junit.Test;
 
public class BigFileSpliter {
 
private Map<String, Writer> fileWriterMap = new HashMap<String, Writer>();
 
public void split(File file) throws IOException{
InputStream is;
BufferedReader br;
String line;
is = new FileInputStream(file);
br = new BufferedReader(new InputStreamReader(is));
 
while((line = br.readLine()) != null){
//System.out.println(line);
append(line.substring(0,3)+".txt", line);
}
closeAllWriters();
br.close();
is.close();
}
 
public void append(String fileName, String appendStr) throws IOException{
 
if (!fileWriterMap.containsKey(fileName)){
File file = new File(fileName);
if(!file.exists()){
file.createNewFile();
}
FileWriter fw = new FileWriter(file);
fileWriterMap.put(fileName, fw);
}
fileWriterMap.get(fileName).write(appendStr);
fileWriterMap.get(fileName).write('\n');
}
 
public void closeAllWriters(){
for(Writer fw : fileWriterMap.values()){
try {
fw.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally{
try {
fw.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
 
 
public List<String> getAllFileNames(){
Set<String> keys = fileWriterMap.keySet();
List<String> klist = new ArrayList<String>();
for (String key : keys){
klist.add(key);
}
 
Collections.sort(klist);
return klist;
}
 
@Test
public void testSplit() throws IOException{
new BigFileSpliter().split(new File("cellphone.txt"));
}
}

第二步,將文件按照文件名排序,依次讀取文件中的電話號碼java

第三步,將電話號碼放到位圖中,若是對應的位是1,那麼就不要放了(這樣就去重了)
第四步,遍歷位圖,位的值爲1的輸出到文件中,若是算出的數不足8爲10進制的數,前面補0, 好比838,前面補0,變成00000838
如下的Java程序是第二到第四步的實現
package art.programming.algorithm;
 
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
 
public class BigFileSort {
 
private final static int RANGE = 32;
private int[] bitMap;
 
private File outputFile;
private File inputFile;
private int bitMapLen = (99999999/RANGE) + RANGE;
FileWriter fw;
 
public BigFileSort(String inputFileName, String outputFileName) throws IOException{
bitMap = new int[bitMapLen];
outputFile = new File(outputFileName);
if (outputFile.exists()){
outputFile.delete();
}
outputFile.createNewFile();
inputFile = new File(inputFileName);
fw = new FileWriter(outputFile);
}
 
 
public void sortAndOutput() throws IOException{
BigFileSpliter bigFileSpliter = new BigFileSpliter();
bigFileSpliter.split(inputFile);
for (String fileName : bigFileSpliter.getAllFileNames()){
readAndSetBitMap(fileName);
output(fileName.substring(0, 3));
}
//Clean up
fw.close();
}
 
 
private void readAndSetBitMap(String fileName) throws IOException{
InputStream is;
BufferedReader br;
String line;
is = new FileInputStream(fileName);
br = new BufferedReader(new InputStreamReader(is));
while((line = br.readLine()) != null){
long tmp = Long.parseLong(line.substring(3, 11));
int mod = (int) tmp / RANGE;
int offset = (int) tmp % RANGE;
int bit = bitMap[(int)mod];
if (getBit(bit, offset) != 1){
bit = setBit(bit, offset, 1);
bitMap[(int)mod] = bit;
}else{
System.out.println(fileName.subSequence(0, 3) + toStringRep(tmp) + " duplicates!");
}
}
br.close();
is.close();
}
 
private void output(String prefix) throws IOException{
 
for (int i=0; i< bitMapLen; i++){
for (int offset=0; offset<RANGE; offset++){
if (getBit(bitMap[i], offset) == 1){
long tmp = i * RANGE + offset;
String cellphoneNum = toStringRep(tmp);
fw.write(prefix+cellphoneNum);
fw.write("\n");
}
}
}
 
for (int i=0; i< bitMapLen; i++){
bitMap[i] = 0;
}
}
 
private String toStringRep(long tmp){
String cellphoneNum = String.valueOf(tmp);
if (cellphoneNum.length()<8){
StringBuilder sb = new StringBuilder();
for (int j=0; j< 8 - cellphoneNum.length(); j++){
sb.append('0');
}
sb.append(cellphoneNum);
cellphoneNum = sb.toString();
}
return cellphoneNum;
}
 
private int getBit(int signinNum, int bitIndex) {
if( (signinNum & ( 1 << bitIndex)) != 0){
return 1;
}
return 0;
}
 
private int setBit(int num, int bitIndex, int zeroOrOne){
if (zeroOrOne == 1){
num = num | (1 << bitIndex);
}else{
num = num - (1 << bitIndex);
}
return num;
}
 
        //總體測試如下
        @Test
public void testSort() throws IOException{
long begin = System.currentTimeMillis();
new BigFileSort("cellphone.txt", "sortedCellphone.txt").sortAndOutput();
System.out.println(System.currentTimeMillis() - begin);
}    
 
}

整個過程,事件花費大約60秒左右數據庫

 

手機號碼交集

將A、B文件分別剔重,能夠獲得電話號碼排好序的兩個文件。兩個文件順序對比就能夠找出交集了。
相關文章
相關標籤/搜索