前言:
因為中間有一些其他的任務工作,所以有一些時日沒有再關心爬蟲的程序了。今天想到了另一個優化爬蟲的思路。
在上篇中,我們說到可以使用布隆過濾器可以很好地實現URL的去重操作。可是,如果在某一個時刻我們不小心中止了爬蟲的繼續運行。這個時候要怎麼辦呢?
本篇博客的重點正是解決這個問題。
本文鏈接:http://blog.csdn.net/lemon_tree12138/article/details/50069047 -- Coding-Naga
問題描述:
上面也有提到,現在假設我們的程序需要在中途暫停一下。這樣,會直接導致一個問題,我們的程序無法保留之前使用BloomFilter保存的URL信息。如果你對BloomFilter還不瞭解,歡迎移步到我的上一篇博客《網絡爬蟲:URL去重策略之布隆過濾器(BloomFilter)的使用》瞭解一下。
對於爬蟲程序中使用兩個隊列“對象”是好處理的,因為這部分數據是直接存放在數據庫中(磁盤裡)的。這個不用擔心。可是如果這個BloomFilter如果沒有得到一個很好的處理就是一個比較麻煩的事情了,這使得我們在後期程序執行的過程中無法很準確地判斷一個URL是否有訪問過,這樣程序的效率勢必會受到不了的影響。這裡我提供了兩種解決方案。當然一種是好的解決方法,一種是不那麼好的解決方法。
前一種處理方案:
這裡我想到的是如何通過現在有的數據(數據庫中的數據)信息,儘可能完整地構建原來的BloomFilter。我的做法是在程序重新啟動的時候去讀數據庫,把數據庫中的信息一個一個地往過濾器中填。試想一下,如果這個時候數據庫中有千萬級的數據,我們也要一個一個地往裡填,這樣勢必有點太耗時了。
因為這裡我們是需要先從數據庫中去獲得數據,再將數據添加到過濾器中。這兩步都是耗時的操作,所以,如果能不用這種方法就不用這種方法,這是下下策。
對象持久化方案:
1.格式化數據保存到文件
從之前的博客中,我們可以知道BloomFilter的核心是一個很長的數組,這個數組是保存在BitSet中。那麼這裡我們就可以把這麼多位的每一位保存到文件或是數據庫中。這樣在程序啟動的時候就可以直接讀入了。關於這個想法,我猜是可行的。之所以說是“猜”,因為我也沒有使用過這樣方法。感覺是Ok的,不過沒實踐過,如果讀者感興趣可以試試看。這裡就不多說了,說這個思路的目的,主要還是為了引出下面的這種方法。
2.基於Serializable的實現
思路分析:
說過了下下策和保存到文件這兩種,是不是這裡可以說一下上上策了?因為還不知道有沒有更好的方法,所以上上策還不敢斷言,不過這裡要說的可以說是上策。我們在學習可序列化類Serializable的時候,應該就已經知道了這個類可以讓一個對象固化到磁盤,也就是說這個對象我們可以把它保存到磁盤上。下次在需要用的時候再去讀一下就OK了。所以,這裡我們就可以這樣來做。
首先,我們需要讓BloomFilter及其相關類實現Serializable接口,因為這些對象需要被持久化。並且添加上serialVersionUID成員常量。
保存到磁盤:
/**
* 將一個對象寫入到磁盤
*
* @param s
* 待寫入的對象
* @param path
* 寫入的路徑
*/
public static void writeObject(Serializable s, String path) {
try {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(path));
objectOutputStream.writeObject(s);
objectOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
從磁盤中讀取對象:
public static Object readObject(String path) {
Object object = null;
try {
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(path));
object = objectInputStream.readObject();
objectInputStream.close();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return object;
}
測試過程:
測試的方法很簡單,我們先在過濾中添加一些數據。並將持有數據的過濾器對象寫到磁盤中。在我們需要的時候去讀取磁盤上保存的對應文件即可。代碼邏輯如下:
public class BloomFilterTest {
public static void main(String[] args) {
String path = "F:/Temp/bloom.obj";
BloomFilterTest test = new BloomFilterTest();
test.testWriteBloomFilter(path);
BloomFilter readFilter = test.testReadBloomFilter(path);
boolean b1 = readFilter.contains("baidu");
boolean b2 = readFilter.contains("google");
boolean b3 = readFilter.contains("naga");
boolean b4 = readFilter.contains("hello");
boolean b5 = readFilter.contains("world");
boolean b6 = readFilter.contains("java");
System.out.println(b1);
System.out.println(b2);
System.out.println(b3);
System.out.println(b4);
System.out.println(b5);
System.out.println(b6);
}
private void testWriteBloomFilter(String path) {
BloomFilter filter = new BloomFilter();
filter.add("baidu");
filter.add("google");
filter.add("naga");
filter.add("hello");
filter.add("world");
SerializationUtils.writeObject(filter, path);
}
private BloomFilter testReadBloomFilter(String path) {
Object object = SerializationUtils.readObject(path);
return (BloomFilter)object;
}
}
測試結果:
我們在過濾器中添加了"baidu", "google", "naga", "hello", "world"這些字符串值。在驗證的時候,我們多驗證了一個"java"字符串。如果方案可行,我們將獲得5個true和1個false的結果。以下是測試結果:
true
true
true
true
true
false
由此驗證此方法可行。
注意事項:
1.過濾器內部的SimpleHash內部類也需要實現Serializable接口。因為這個SimpleHash也有對象在過濾器中,在持久化的時候,SimpleHash對象也會被持久化到磁盤;
2.本文的測試實例,可以在下面GitHub工程的org.naga.demo.bloom包下獲得;
3.本方案的作用點是在於停止程序的後勤工作。所以,必須保證程序能夠完成這些後勤工作。也就是說,我們不能突然去停止程序的運行,這樣程序因為來不及保存數據而讓BloomFilter對象持久化失敗。如果想要規避這個問題,就必須要作出一些其他的犧牲——性能下降。我們可以通過定時給BloomFilter進行持久化,這樣如果程序被突然中止,也只是會損失一部分數據的記錄,不會造成很大的影響。因為,這樣會是程序的性能有所下降,所以如何取捨還是要看需求了。
測試源碼工程GitHub鏈接:
https://github.com/William-Hai/SimpleDemo
---------------------
本文來自 Q-WHai 的CSDN 博客 ,全文地址請點擊:https://blog.csdn.net/lemon_tree12138/article/details/50069047?utm_source=copy
閱讀更多 java執行官 的文章