Scala中的文件读写-全文单词统计(一)

26 阅读2分钟

1. 基本文件读写

读取文件

import scala.io.Source
import java.io.{File, PrintWriter}
import scala.collection.mutable

// 方法1: 使用Source读取整个文件
val source = Source.fromFile("input.txt")
val content = source.mkString  // 读取全部内容
source.close()

// 方法2: 逐行读取(推荐,节省内存)
val lines = Source.fromFile("input.txt").getLines().toList

// 方法3: 使用try-with-resources风格
val content2 = using(Source.fromFile("input.txt")) { source =>
  source.mkString
}

// 辅助函数:资源管理
def using[A <: { def close(): Unit }, B](resource: A)(f: A => B): B = {
  try {
    f(resource)
  } finally {
    resource.close()
  }
}

写入文件

// 方法1: 使用PrintWriter
val writer = new PrintWriter(new File("output.txt"))
writer.write("Hello, World!")
writer.close()

// 方法2: 使用Java NIO(更现代的方式)
import java.nio.file.{Files, Paths, StandardOpenOption}
val data = "Hello, World!".getBytes
Files.write(Paths.get("output.txt"), data)

// 方法3: 使用scala.util.Using(Scala 2.13+)
import scala.util.Using
Using(new PrintWriter(new File("output.txt"))) { writer =>
  writer.write("Hello, World!")
}

2. 完整单词统计示例

方案1:基本版本

import scala.io.Source
import scala.collection.mutable.Map

object WordCountBasic {
  def main(args: Array[String]): Unit = {
    // 读取文件
    val source = Source.fromFile("input.txt")
    val lines = try {
      source.getLines().toList
    } finally {
      source.close()
    }
    
    // 单词统计
    val wordCount = mutable.Map[String, Int]().withDefaultValue(0)
    
    for {
      line <- lines
      word <- line.split("\\W+")  // 按非单词字符分割
      if word.nonEmpty
    } {
      val normalizedWord = word.toLowerCase()
      wordCount(normalizedWord) += 1
    }
    
    // 输出结果
    wordCount.toSeq
      .sortBy(-_._2)  // 按频率降序排序
      .foreach { case (word, count) =>
        println(s"$word: $count")
      }
  }
}

方案2:函数式编程风格

import scala.io.Source
import scala.util.Using

object WordCountFunctional {
  def countWords(filePath: String): Map[String, Int] = {
    Using.resource(Source.fromFile(filePath)) { source =>
      source.getLines()
        .flatMap(_.split("\\W+"))          // 分割单词
        .filter(_.nonEmpty)                 // 过滤空字符串
        .map(_.toLowerCase)                 // 转为小写
        .toList                             // 转为List
        .groupBy(identity)                  // 按单词分组
        .view
        .mapValues(_.size)                  // 计算每组数量
        .toMap
    }
  }
  
  def main(args: Array[String]): Unit = {
    val wordCount = countWords("input.txt")
    
    // 排序并输出
    wordCount.toSeq
      .sortWith { case ((w1, c1), (w2, c2)) =>
        c1 > c2 || (c1 == c2 && w1 < w2)
      }
      .foreach { case (word, count) =>
        println(f"$word%-15s $count%d")
      }
    
    // 统计总数
    val totalWords = wordCount.values.sum
    println(s"\nTotal unique words: ${wordCount.size}")
    println(s"Total words: $totalWords")
  }
}

方案3:使用Spark(处理大文件)

import org.apache.spark.sql.SparkSession

object WordCountSpark {
  def main(args: Array[String]): Unit = {
    val spark = SparkSession.builder()
      .appName("WordCount")
      .master("local[*]")  // 本地模式
      .getOrCreate()
      
    import spark.implicits._
    
    // 读取文件
    val textFile = spark.read.textFile("input.txt")
    
    // 单词统计
    val wordCounts = textFile
      .flatMap(_.split("\\W+"))
      .filter(_.nonEmpty)
      .map(_.toLowerCase)
      .groupBy("value")
      .count()
      .orderBy($"count".desc)
    
    // 显示结果
    wordCounts.show(20, truncate = false)
    
    // 保存结果到文件
    wordCounts
      .coalesce(1)  // 合并为一个文件
      .write
      .csv("word_count_output")
    
    spark.stop()
  }
}

3. 高级功能示例

支持多种文件编码

import scala.io.{Codec, Source}

// 指定编码读取文件
val source = Source.fromFile("input.txt")(Codec.UTF8)
val content = source.mkString
source.close()

// 读取多个文件
def countWordsInDirectory(dirPath: String): Map[String, Int] = {
  import java.io.File
  
  val dir = new File(dirPath)
  val files = dir.listFiles().filter(_.isFile).filter(_.getName.endsWith(".txt"))
  
  files.foldLeft(Map.empty[String, Int]) { (acc, file) =>
    val fileWordCount = countWords(file.getPath)
    // 合并两个Map
    (acc.keySet ++ fileWordCount.keySet).map { key =>
      key -> (acc.getOrElse(key, 0) + fileWordCount.getOrElse(key, 0))
    }.toMap
  }
}

带标点符号处理的单词统计

import scala.io.Source

object AdvancedWordCount {
  def cleanWord(word: String): String = {
    // 移除开头和结尾的标点符号
    word
      .replaceAll("^[^a-zA-Z0-9]+", "")  // 开头非字母数字
      .replaceAll("[^a-zA-Z0-9]+$", "")  // 结尾非字母数字
      .toLowerCase
  }
  
  def countWordsWithContext(filePath: String): Map[String, (Int, List[String])] = {
    Using.resource(Source.fromFile(filePath)) { source =>
      val lines = source.getLines().toList
      
      lines.zipWithIndex
        .flatMap { case (line, lineNum) =>
          line.split("\\s+").zipWithIndex.map { case (word, wordPos) =>
            (cleanWord(word), (lineNum + 1, wordPos + 1, line.trim))
          }
        }
        .filter(_._1.nonEmpty)
        .groupBy(_._1)  // 按单词分组
        .map { case (word, contexts) =>
          val count = contexts.size
          // 获取前3个出现位置作为示例
          val examples = contexts.take(3).map { case (_, (line, pos, text)) =>
            s"Line $line, Position $pos: ...${text.take(50)}..."
          }
          word -> (count, examples)
        }
    }
  }
}

4. 测试示例文件

创建 input.txt

Hello World! This is a test file.
Hello again, world. Testing, testing, 1, 2, 3.
Scala is great for data processing.
Hello Scala World!

运行结果示例:

hello: 3
world: 2
testing: 2
scala: 2
is: 2
a: 1
again: 1
data: 1
file: 1
for: 1
great: 1
processing: 1
test: 1
this: 1
1: 1
2: 1
3: 1

5. 性能优化建议

  1. 大文件处理:使用流式处理,避免一次性加载整个文件
  2. 内存管理:及时关闭文件资源
  3. 并行处理:对于大文件,可以使用并行集合
  4. 缓存中间结果:如果多次统计,缓存清洗后的数据
// 并行处理版本
def parallelWordCount(filePath: String): Map[String, Int] = {
  Using.resource(Source.fromFile(filePath)) { source =>
    source.getLines()
      .toVector  // 转为Vector支持并行
      .par  // 转为并行集合
      .flatMap(_.split("\\W+"))
      .filter(_.nonEmpty)
      .map(_.toLowerCase)
      .groupBy(identity)
      .mapValues(_.size)
      .seq  // 转回顺序集合
      .toMap
  }
}