2013-04-16 57 views
2

我有一個5000萬行的文件,我必須隨機輸出1000行。這是用shell打印特定行的最快方法嗎?

首先,我創建了1000個隨機數;那麼我使用

sed -n "$random{p;q}" file 

它真的很慢;一行輸出將花費至少5-6秒。

所以我認爲我應該優化打印特定線速度。

我們有很多方法來打印特定行:

sed -n "$line{p;q}" file 

awk "NR==$line{print}" file 

head -$line file | tail -1 

這一切都慢......耗資約5-6秒鐘,以打印特定的行。

shell中是否有其他方式來打印特定的行?或者python,perl可以比shell更快?或者我的方式來解決這個問題錯了?

---------------------------------------- SPLIT ----- -----------------------------------------

迭代1000個隨機數和每次使用shell一次,可能會產生1000次io操作。也許我應該使用一個數組來先保存隨機數並迭代文件一次。

random_array=() 

awk '{if (NR in $random_array) print;}' file 

好吧,我將測試這種方法和結果粘貼任何後來

+0

我不認爲AWK理解Bash的數組。 – icktoofay

+0

你的要求是什麼?以隨機順序50,000,000行中的1,000行,還是可以按文件中顯示的順序選擇1,000行?無論哪種方式,我可能會使用Perl,並讓它產生範圍爲1..50,000,000的1,000個數字,然後逐行掃描文件(但只有一次,而不是1,000次),打印或保存行對應於所需的行號之一。打印使用較少的內存,但按順序打印行;保存允許您按生成數字的順序打印行。 –

+0

也許「更快」的解決方案而不是「最快」就足夠了?特別是,一個打印1000個隨機行的'sed'腳本將比1000個獨立的'sed'運行快得多。 'sed -n'17p; 42p; 57p; 89 {p; q}'文件' – tripleee

回答

1

在文件中的行的順序,無需在內存中的所有行:

awk ' 
    NR==FNR { next } 
    FNR==1{ 
    srand; 
    n=NR-1 
    for(i=1; i<=1000; i++) { 
     line=0 
     while(!line || line in A) line=int(rand*n)+1 
     A[line] 
    } 
    } 
    FNR in A 
' infile infile 
0

無論哪一種工具你使用,找到這些線有固有的成本。實質上,您需要每次遍歷該大文件,查找並計算換行符號。

有兩個解決方案,我可以看到:

  1. 預計算一個合格的文件中的行偏移,然後用lseek找到打印他們。您可以存儲每隔100或1000行的偏移量以節省空間。

  2. 預先生成行號的整個列表,並在文件中一次收集行。然後打印它們。 (如果您希望線條的順序是隨機的,您不能隨意打印)。

其中任何一種都很難在shell中完成。對於僅限shell的解決方案,請嘗試devnull的建議,shuf。但不是1,你想要使用1000:

shuf -n 1000 file 
0

如果你只是想從一個更大規模的數據文件中的特定行,成本會根據您的要求增加。如果你的文件是在一個週期(一週或更長時間),預處理是必要一成不變的,還有就是你的問題的解決方案:

  1. 文件分割成一些更小的尺寸與同線
  2. 貼每個文件放到一個文件中;之後,鏈接1將包含1 1 + n 1 + 2n信息的信息
  3. 一個包裝殼來計算行將是必要的。

如您所知,以上只是一種方法。

+0

IO是關鍵點 – kevinsun

2

爲避免讀取整個文件,您可以獲取文件的大小,然後生成一個包含0和該數字之間的1000個偏移量的列表。這些通常是位於一行中間的位置,但是您可以閱讀下一個換行符,然後閱讀並打印以下行。但是,這會對文件的第一行引入偏見。如果您對平均線長有一個猜測,您可以從生成的偏移量中減去該數(任何負面結果將意味着從偏移量0讀取和打印。)

這是一個概念的快速證明。爲了說明的目的,我假設平均線長約75個字符。這也影響公平性(選擇長線後的線更有可能)。最後一行的處理也不公平;如果它短於75個字符,則永遠不能選擇(!) - 您可以嘗試通過計算實際讀取的行的實際平均行長度來解決該問題,但我將其留作練習,以便保留這個例子相當緊湊。

#!/usr/bin/perl 

use strict; 
use warnings; 

use Fcntl (qw(SEEK_SET SEEK_CUR SEEK_END)); 

my $n = (defined @ARGV ? shift @ARGV : '--help'); 
die "Syntax: $0 number file\n" unless @ARGV == 1 and $n =~ m/^[0-9]+$/; 

open (F, "<", $ARGV[0]) or die "$0: Could not open $ARGV[0]: $!\n"; 

seek (F, 0, SEEK_END) or die "$0: Could not SEEK_END $ARGV[0]: $!\n"; 
my $max = tell(F); 

my %seen; 
for (my $i=0; $i < $n; ++$i) 
{ 
    my $offset = int(rand($max))-75; 
    my $first = 0; 
    if ($offset < 0) 
    { 
     $offset = 0; 
     $first = 1; 
    } 
    seek (F, $offset, SEEK_SET) 
     or die "$0: Could not SEEK_SET $ARGV[0]: $!\n"; 
    <F> unless $first; 
    redo if eof (F); # Cheap trick, just retry if at eof 
    redo if $seen{tell(F)}++; 
    print scalar(<F>); 
} 

我添加了代碼以避免重複;這是%seen散列。

+0

如果您請求的文件中包含的行數超過文件中的行數,這將運行無限循環。我想不出一種非常優雅的方式來檢測這種情況。也許作爲一個粗略的解決方法計算迭代次數,並以1000或2 * n或更小者爲上限。 – tripleee