spark读取保存gbk的hadoop文件

本文介绍了一种在Spark中处理GBK编码文件的方法,通过自定义的工具类实现了RDD[String].saveAsGBKTextFile和sc.gbkTextFile的功能,解决了跨部门间文件格式不匹配的问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最近工作遇到一个小问题。隔壁部门非要输出gbk格式的hadoop file。虽然这个要求很奇怪,但是仔细搞了搞发现也没有那么容易。翻了翻书,对implicit这个关键字理解的更深入了一些。编写了这么一个小工具类,可以实现sc.gbkTextFile(path)来读取gbk文件。RDD[String].saveAsGBKTextFile(path)来写入gbk文件。

import java.io.{DataOutputStream, IOException}
import org.apache.hadoop.fs.FileSystem
import org.apache.hadoop.io._
import org.apache.hadoop.io.compress.GzipCodec
import org.apache.hadoop.mapred._
import org.apache.hadoop.util.{Progressable, ReflectionUtils}
import org.apache.spark.SparkContext
import org.apache.spark.rdd.RDD

object RDDTextEncoding {

  //隐式转换值类。可以实现RDD[String].saveAsGBKTextFile
  implicit class GBKSaver(val rdd: RDD[String]) extends AnyVal {
    def saveAsGBKTextFile(path: String): Unit = {
      rdd.map(row => (org.apache.hadoop.io.NullWritable.get(), row))
        .saveAsHadoopFile[GBKTextOutputFormat[org.apache.hadoop.io.NullWritable, String]](path)
    }
  }

  //隐式转换值类。可以实现sc.gbkTextFile
  implicit class GBKReader(val sc: SparkContext) extends AnyVal {
    def gbkTextFile(path: String): RDD[String] = {
      sc.hadoopFile(path,classOf[TextInputFormat],classOf[LongWritable],classOf[Text],1).map(p => new String(p._2.getBytes, 0, p._2.getLength, "GBK"))
    }
  }

  //自定义的GBK的OutputFormat
  class GBKTextOutputFormat[K, V] extends FileOutputFormat[K, V] {
    val encoding = "GBK"

    class LineRecordWriter[K, V](var out: DataOutputStream) extends RecordWriter[K, V] {
      @throws[IOException]
      def writeObject(o: Any): Unit = {
        out.write(o.toString.getBytes(encoding))
      }

      @throws[IOException]
      override def write(key: K, value: V): Unit = {
        val nullKey = key == null || key.isInstanceOf[NullWritable]
        val nullValue = value == null || value.isInstanceOf[NullWritable]
        if (nullKey && !nullValue) {
          writeObject(value)
          out.write("\n".getBytes(encoding))
        }
      }

      @throws[IOException]
      override def close(reporter: Reporter): Unit = {
        out.close()
      }
    }

    override def getRecordWriter(ignored: FileSystem, job: JobConf, name: String, progress: Progressable): RecordWriter[K, V] = {
      if (FileOutputFormat.getCompressOutput(job)) {
        val codecClass = FileOutputFormat.getOutputCompressorClass(job, classOf[GzipCodec])
        val codec = ReflectionUtils.newInstance(codecClass, job)
        val file = FileOutputFormat.getTaskOutputPath(job, name + codec.getDefaultExtension)
        val fs = file.getFileSystem(job)
        val fileOut = fs.create(file, progress)
        new LineRecordWriter[K, V](new DataOutputStream(codec.createOutputStream(fileOut)))
      } else {
        val file = FileOutputFormat.getTaskOutputPath(job, name)
        val fs = file.getFileSystem(job)
        val fileOut = fs.create(file, progress)
        new LineRecordWriter[K, V](fileOut)
      }
    }
  }

}

 

<think>我们正在解决Spark读取CSV文件时出现中文乱码的问题。根据引用内容,乱码的主要原因是文件编码Spark默认的UTF-8编码不一致,例如文件可能是GBK或GB2312编码。 解决方案总结: 1. 在读取CSV文件时,通过`.option("encoding", "gbk")`(或其他正确编码)指定文件编码。 2. 如果使用`spark.read.textFile`读取文本文件,也需要指定编码,但注意`textFile`方法没有直接提供设置编码的option,可能需要使用其他方(如使用`spark.read.option("encoding", "gbk").text(path)`)。 3. 如果上述方法无效,可以尝试使用Hadoop API读取,然后手动转换编码(如引用[4]所示)。 具体步骤: 方法一:使用Spark DataFrame API读取CSV时指定编码(推荐) ```scala val df = spark.read .option("header", "true") // 如果第一行是标题 .option("encoding", "gbk") // 根据文件实际编码设置,如GBK, GB2312等 .option("delimiter", ",") // 分隔符,根据实际情况调整 .csv("path/to/your/file.csv") ``` 方法二:读取文本文件(非结构化)时指定编码 ```scala val textDF = spark.read .option("encoding", "gbk") .text("path/to/your/file.txt") ``` 方法三:使用Hadoop API读取转换编码(当上述方法无效时) 这种方法适用于需要逐行处理且编码问题复杂的情况。注意,这种方法会得到RDD,然后可以转换为DataFrame。 ```scala import org.apache.hadoop.io.{LongWritable, Text} import org.apache.hadoop.mapreduce.lib.input.TextInputFormat import org.apache.spark.rdd.RDD // 使用newAPIHadoopFile读取,指定编码 val rdd: RDD[String] = spark.sparkContext .newAPIHadoopFile( "path/to/your/file.csv", classOf[TextInputFormat], classOf[LongWritable], classOf[Text], spark.sparkContext.hadoopConfiguration ) .map { case (_, text) => // 这里将Text转换为String,并指定编码转换 new String(text.getBytes, 0, text.getLength, "GBK") } // 将RDD转换为DataFrame val df = spark.createDataFrame(rdd.map(Tuple1.apply)).toDF("column_name") ``` 注意:在方法三中,我们通过`new String(text.getBytes, 0, text.getLength, "GBK")`将原始字节按指定编码转换为字符串。这里需要确保指定的编码与实际文件编码一致。 为什么有时指定encoding选项无效? 根据引用[4]中的描述,可能在某些情况下(如使用某些Hadoop版本或文件系统)Spark的CSV读取器对编码的支持不够好。此时使用Hadoop API直接读取转换编码是更可靠的方法。 另外,还需要注意: - 如果文件是BOM头(如UTF-8 with BOM),可能需要先去除BOM头,或者使用支持BOM的编码(如"UTF-8-BOM"),但Java标准库并不直接支持,可能需要手动处理前几个字节。 - 如果文件混合了多种编码,则处理起来会更复杂,可能需要逐行判断或使用工具统一编码。 建议:在读取前,尽量将文件转换为UTF-8编码(无BOM),这样可以避免很多编码问题。 相关问题: 1. Spark读取CSV文件时如何自动检测文件编码? 2. 如何处理包含多种编码的CSV文件? 3. 如何解决Spark读取Excel文件时的中文乱码问题? 4. 如何将GBK编码的CSV文件批量转换为UTF-8编码?</think>### Spark 读取 CSV 文件乱码原因及解决方法 #### 一、乱码原因分析 1. **编码不匹配**(核心原因) Spark 默认使用 `UTF-8` 编码读取文件,当 CSV 文件使用 `GBK`/`GB2312` 等中文编码时,会导致乱码[^1][^2][^3]。 $$ \text{乱码} = \text{解码器}(\text{GBK字节流} \mid \text{UTF-8规则}) $$ 2. **Hadoop 配置问题** 某些 Hadoop 环境默认使用 `org.apache.hadoop.io.Text` 的默认编码(通常为 UTF-8),无法自动识别源文件编码[^4]。 3. **特殊字符处理** 包含 BOM 头(如 `EF BB BF`)或混合编码文件可能导致解析异常。 --- #### 二、解决方案(按优先级排序) ##### ✅ 方案1:直接指定编码(推荐) 在 `spark.read` 时显声明文件编码: ```scala // Scala 示例 val df = spark.read .option("header", "true") // 包含标题行 .option("encoding", "gbk") // ⭐关键参数:指定GBK编码 .option("delimiter", ",") // 分隔符(根据实际情况调整) .csv("hdfs:///path/to/file.csv") ``` ```python # PySpark 示例 df = spark.read \ .option("header", "true") \ .option("encoding", "gbk") \ # 关键参数 .option("sep", ",") \ .csv("hdfs:///path/to/file.csv") ``` ##### ✅ 方案2:使用二进制读取 + 手动解码 当方案1失效时(如旧版本 Spark),使用 Hadoop API: ```scala import org.apache.hadoop.io.{LongWritable, Text} import org.apache.spark.rdd.RDD val rawRDD: RDD[String] = spark.sparkContext .hadoopFile("hdfs:///path.csv", classOf[TextInputFormat], classOf[LongWritable], classOf[Text]) .map { case (_, text) => new String(text.getBytes, 0, text.getLength, "GBK") // ⭐手动GBK解码 } // 转换为DataFrame val df = spark.read.option("delimiter", ",").csv(rawRDD.toDS()) ``` ##### ✅ 方案3:全局编码配置(集群环境) 在 `spark-submit` 中设置默认编码: ```bash spark-submit \ --conf spark.hadoop.hadoop.tmp.dir=/tmp \ --conf spark.hadoop.mapreduce.input.fileinputformat.input.dir.recursive=true \ --conf spark.hadoop.fs.file.impl.disable.cache=true \ --conf spark.hadoop.fs.defaultFS=hdfs://cluster \ --conf spark.executor.extraJavaOptions="-Dfile.encoding=GBK" \ # ⭐全局编码 --conf spark.driver.extraJavaOptions="-Dfile.encoding=GBK" \ your_app.jar ``` --- #### 三、编码检测技巧 1. **Linux 文件编码检测** ```bash file -i input.csv # 输出示例:input.csv: text/plain; charset=gbk ``` 2. **Python 自动检测** ```python import chardet with open("local.csv", "rb") as f: result = chardet.detect(f.read(10000)) print(result["encoding"]) # 输出:GBK ``` --- #### 四、避免 BOM 问题 在方案2基础上添加 BOM 头处理: ```scala rawRDD.map { line => if(line.startsWith("\uFEFF")) line.substring(1) // 移除UTF-8 BOM头 else line } ``` --- #### 五、根本解决方案建议 1. **源文件转码**: 使用 `iconv` 批量转换编码(适合静态数据): ```bash iconv -f GBK -t UTF-8 source.csv > target_utf8.csv ``` 2. **统一数据管道**: 在数据接入层强制所有 CSV 转为 UTF-8 编码存储(推荐生产环境使用)。 --- ### 相关问题 1. Spark 读取 JSON 文件时出现乱码如何解决? 2. 如何批量检测 HDFS 上文件编码类型? 3. Spark 读取 Excel 文件时如何避免中文乱码? 4. 分布环境下如何统一所有节点的文件编码配置?[^2][^3]
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值