簡體   English   中英

讀取文件時間過長

[英]Reading file takes too long

我的應用程序首先從SD卡中解析了一個約100MB的文件,然后花了幾分鍾時間。 為了弄清楚這一點,在我的PC上,解析同一文件需要幾秒鍾。

我首先使用MatcherPattern天真地實現了解析器,但是DDMS告訴我90%的時間都花在了計算正則表達式上。 而且花了半個多小時來解析文件。 該模式非常簡單,一行包括:

ID (a number) <TAB> LANG (a 3-to-5 character string) <TAB> DATA (the rest)

我決定嘗試使用String.split 它沒有顯示出明顯的改進,可能是因為此函數本身可能使用了正則表達式。 到那時,我決定完全重寫解析器,最終得到如下結果:

protected Collection<Sentence> doInBackground( Void... params ) {
    BufferedReader reader = new BufferedReader( new FileReader( sentenceFile ) );

    String currentLine = null;
    while ( (currentLine = reader.readLine()) != null ) {
        treatLine( currentLine, allSentences );
    }

    reader.close();
    return allSentences;
}

private void treatLine( String line, Collection<Sentence> allSentences ) {
    char[] str = line.toCharArray();

    // ...
    // treat the array of chars into an id, a language and some data

    allSentences.add( new Sentence( id, lang, data ) );
}

我注意到了巨大的推動力。 用了幾分鍾而不是半個小時。 但是我對此並不滿意,因此我分析並意識到瓶頸是BufferedReader.readLine 我想知道:這可能是受IO約束的,但也可能是花了很多時間來填充我真正不需要的中間緩沖區。 因此,我直接使用FileReader重寫了整個過程:

protected Collection<Sentence> doInBackground( Void... params ) {
    FileReader reader = new FileReader( sentenceFile );
    int currentChar;
    while ( (currentChar = reader.read()) != -1 ) {
        // parse an id
        // ...            

        // parse a language
        while ( (currentChar = reader.read()) != -1 ) {
            // do some parsing stuff
        }

        // parse the sentence data
        while ( (currentChar = reader.read()) != -1 ) {
            // parse parse parse
        }

        allSentences.add( new Sentence( id, lang, data ) );
    }

    reader.close();
}

當我意識到性能非常糟糕時,我感到非常驚訝。 顯然,大部分時間都花在FileReader.read上 我想只讀一個字符會花很多錢。

現在我有點靈感了。 有小費嗎?

這可能會提高性能的另一種選擇是使用InputStreamReader周圍FileInputStream 您必須自己進行緩沖,但這絕對可以提高性能。 有關更多信息,請參見本教程 -但是不要盲目地跟隨它。 例如,當您使用char數組時,可以將char數組用作緩沖區(並在到達treatLine()時將其發送到treatLine() )。

另一個建議是直接使用Thread 關於AsyncTask 文檔說(我的語氣):

AsyncTask被設計為圍繞Thread和Handler的幫助器類,並且不構成通用的線程框架。 理想情況下,應將AsyncTasks用於較短的操作(最多幾秒鍾)。如果需要使線程長時間運行,則強烈建議您使用java.util.concurrent pacakge提供的各種API,例如執行程序,ThreadPoolExecutor和FutureTask。

另外,獲得更快的SD卡肯定會有所幫助-這可能是其比台式機慢得多的主要原因。 普通的HD可以讀取60 MB / s,而慢速的SD卡可以讀取2 MB / s。

我想您需要保留BufferedReader,但可能不使用readline。 FileReader從最慢的SD卡讀取內容。 BufferredReader從內存中讀取,效果更好。 第二種方法增加了您訪問Filereader.read()的時間,我想這行不通。

如果readline()很耗時,請嘗試以下操作:

   reader.read(char[] cbuf, int off, int len) 

嘗試一次獲取大量數據。

刪除BufferedReader會使情況變得更糟。 當然。 確實需要“填滿中間緩沖區”。 使用FileReader目錄每個字符時,它可以節省8191個系統調用中的8191個。 緩沖的I / O總是更快。 我不知道你為什么會想到別的。

如@EJP所述,您應該使用BufferedReader。 但從根本上講,您正在移動設備上運行,而不是PC。 閃存的讀取速度遠遠不能與PC相比,其計算能力僅是運行於3.5 GHz的4核8線程i7的一小部分,我們甚至都沒有考慮過以全速運行閃存和CPU的情況。會影響設備的電池壽命。

因此,您應該問自己的真正問題是,為什么您的應用程序需要解析100 MB的數據? 而且,如果每次啟動時都需要對其進行解析,那么為什么不可以僅在PC上對其進行解析,而不必讓用戶這樣做呢?

allSentences是ArrayList嗎? 如果是這樣,則其中的項目數量可能很多,並且必須多次調整大小。 嘗試初始化大容量的陣列。

每個ArrayList實例都有一個容量。 容量是用於在列表中存儲元素的數組的大小。 它總是至少與列表大小一樣大。 將元素添加到ArrayList后,其容量會自動增長。 除了添加元素具有固定的攤銷時間成本外,沒有指定增長策略的詳細信息。

應用程序可以使用sureCapacity操作在添加大量元素之前增加ArrayList實例的容量。 這可以減少增量重新分配的數量。 數組列表

其他人認為您可以嘗試:

  • 使用NDK。
  • 正如@Anson Yao所說的,嘗試增加緩沖區的大小
  • 刪除調用函數的TreatLine函數,以減少開銷

關於文件讀取

從上到下,讀取字符看起來像這樣:

  1. 在Java中,您要求讀取字符;
  2. 它轉換為從InputStream讀取一個字節(通常取決於編碼);
  3. 然后轉到本機代碼,在本機代碼中將其轉換為類似的操作系統命令,以從打開的文件中讀取一個字節;
  4. 然后這一個字節以相同的方式返回。

當您讀入緩沖區時 ,會發生相同的事件序列,但是一次傳遞了數千個字節。

由此,您當然可以建立一個直覺,為什么一次從一個文件讀取一個字符很慢。

關於正則表達式

我看不到使用Pattern and Matcher方法有什么問題:如果表達式編寫正確,並且Patern僅編譯一次並重用,那么它應該非常快。

您懷疑String#split也使用一個正則表達式,並在每次調用它時對其進行重新編譯。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM