2012-02-16 61 views
6

我想修改Scala中的大PostScript文件(有些大小大到1GB)。該文件是一組批次,用含有一個表示批號,頁數一個代碼等修改Scala中的大文件

我需要每批:

  1. 搜索批處理碼的文件(其總是以文件中的同一行開始)
  2. 計算直到下一個批次代碼的頁數
  3. 修改批次代碼以包括每批次中的頁數。
  4. 將新文件保存在其他位置。

我當前的解決方案使用兩個迭代(iterAiterB和),從Source.fromFile("file.ps").getLines創建。第一個迭代器(iterA)在while循環中遍歷批處理代碼的開頭(每次都調用iterB.next)。 iterB然後繼續搜索,直到下一個批處理代碼(或文件的結尾),計算它在通過時所經過的頁數。然後,它在iterA的位置更新批次代碼,該過程重複進行。

這看起來非常不像Scala一樣,我仍然沒有設計一種將這些更改保存到新文件的好方法。

什麼是解決此問題的好方法?我應該完全拋棄迭代器嗎?我最好喜歡這樣做,而不必將整個輸入或輸出一次寫入內存。

謝謝!

回答

2

你可以用Scala的Stream類來實現它。我假設你不介意 一次在內存中保存一個「批處理」。

import scala.annotation.tailrec 
import scala.io._ 

def isBatchLine(line:String):Boolean = ... 

def batchLine(size: Int):String = ... 

val it = Source.fromFile("in.ps").getLines 
// cannot use it.toStream here because of SI-4835 
def inLines = Stream.continually(i).takeWhile(_.hasNext).map(_.next) 

// Note: using `def` instead of `val` here means we don't hold 
// the entire stream in memory 
def batchedLinesFrom(stream: Stream[String]):Stream[String] = { 
    val (batch, remainder) = stream span { !isBatchLine(_) } 
    if (batch.isEmpty && remainder.isEmpty) { 
    Stream.empty 
    } else { 
    batchLine(batch.size) #:: batch #::: batchedLinesFrom(remainder.drop(1)) 
    } 
} 

def newLines = batchedLinesFrom(inLines dropWhile isBatchLine) 

val ps = new java.io.PrintStream(new java.io.File("out.ps")) 

newLines foreach ps.println 

ps.close() 
+1

我的猜測是這個解決方案將保存整個文件在內存中,因爲在2.9.x這個模式'Source.fromFile(「in。ps「)。getLines.toStream'保持流的頭部。請參閱http://stackoverflow.com/a/8640680/257449和https://issues.scala-lang.org/browse/SI-4835。 – huynhjl 2012-02-17 06:59:43

+0

huynhjl,我已更新代碼示例以修復您找到的(煩人)錯誤。謝謝。 – stephenjudkins 2012-02-17 22:32:45

0

可能是你可以有效地使用spanduplicate。假設迭代器位於批處理的開始位置,您可以在下一個批處理之前使用該批處理,將其複製以便可以對頁面進行計數,編寫修改的批處理行,然後使用重複的迭代器編寫頁面。然後處理遞歸下一批......

def batch(i: Iterator[String]) { 
    if (i.hasNext) { 
    assert(i.next() == "batch") 
    val (current, next) = i.span(_ != "batch") 
    val (forCounting, forWriting) = current.duplicate 
    val count = forCounting.filter(_ == "p").size 
    println("batch " + count) 
    forWriting.foreach(println) 
    batch(next) 
    } 
} 

假設下面的輸入:

val src = Source.fromString("head\nbatch\np\np\nbatch\np\nbatch\np\np\np\n") 

您在批量的開始位置的迭代器,然後處理該批次:

val (head, next) = src.getLines.span(_ != "batch") 
head.foreach(println) 
batch(next) 

這打印:

head 
batch 2 
p 
p 
batch 1 
p 
batch 3 
p 
p 
p 
1

如果你不追求功能性的斯卡拉啓蒙運動,我會推薦使用java.util.Scanner#findWithinHorizon更強制性的風格。我的例子是非常天真的,迭代通過輸入兩次。

val scanner = new Scanner(inFile) 

val writer = new BufferedWriter(...) 

def loop() = { 
    // you might want to limit the horizon to prevent OutOfMemoryError 
    Option(scanner.findWithinHorizon(".*YOUR-BATCH-MARKER", 0)) match { 
    case Some(batch) => 
     val pageCount = countPages(batch) 
     writePageCount(writer, pageCount) 
     writer.write(batch)   
     loop() 

    case None => 
    } 
} 

loop() 
scanner.close() 
writer.close()