逗号分隔值(CSV)文件每行都有固定数目的字段,字段间用逗号隔开(在制表符分隔值文件,即TSV文件中用制表符隔开)。记录通常是一行一条,不过也不总是这样,有时也可以跨行。CSV文件和TSV文件有时支持的标准不一致,主要是在处理换行符、转义字符、非ASCII字符、非整数值等方面。CSV原生并不支持嵌套字段,所以需要手动组合和分解特定的字段。
与JSON中的字段不一样的是,这里的每条记录都没有相关联的字段名,只能得到对应的序号。常规做法是使用第一行中每列的值作为字段名。
读取CSV/TSV数据和读取JSON数据相似,都需要先把文件当作普通文本文件读取数据,再对数据进行处理。由于格式标准的确实,同一个库的不同版本有时也会用不同的方式处理输入数据。
与JSON一样,CSV也有很多不同的库,但是只在每种语言中使用一个库。Java中我们使用opencsv库。
Hadoop InputFormat中的CSVInputFormat也可以使用于在Scala和Java中读取CSV数据。不过它不支持包含换行符的记录。
如果恰好你的CSV的所有数据字段均没有包含换行符,你也可以使用textFile()读取并解析数据。
/**
* @author DKing
* @description
* @date 2019/6/19
*/
public class CSVProcessor {
public static void main(String[] args) {
SparkConf conf = new SparkConf().setMaster("local").setAppName("TextFile");
JavaSparkContext sc = new JavaSparkContext(conf);
String inputFile = "";
JavaRDD csvFile1 = sc.textFile(inputFile);
JavaRDD csvData = csvFile1.map(new ParseLine());
}
public static class ParseLine implements Function {
@Override
public String[] call(String line) throws Exception {
CSVReader reader = new CSVReader(new StringReader(line));
return reader.readNext();
}
}
}
如果在字段中嵌有换行符,就需要完整读入每个文件,然后解析各段。如果每个文件都很大,读取和解析的过程可能会很不幸的成为性能瓶颈。
Java中完整的读取CSV
/**
* @author DKing
* @description
* @date 2019/6/19
*/
public class CSVProcessor {
public static void main(String[] args) {
SparkConf conf = new SparkConf().setMaster("local").setAppName("TextFile");
JavaSparkContext sc = new JavaSparkContext(conf);
String inputFile = "";
JavaPairRDD csvData2 = sc.wholeTextFiles(inputFile);
JavaRDD keyedRDD = csvData2.flatMap(new ParseLine2());
}
public static class ParseLine2 implements FlatMapFunction, String[]> {
@Override
public Iterator call(Tuple2 stringStringTuple2) throws Exception {
CSVReader reader = new CSVReader(new StringReader(stringStringTuple2._2()));
return reader.readAll().iterator();
}
}
}
如果只有一小部分输入文件,你需要使用wholeFile()方法,可能还需要对输入数据进行重新分区是的Spark能够高效地并行化执行后续操作。
和JSON数据一样,写出CSV/TSV数据相当简单,同样可以通过重用输出编码器来加速。由于在CSV中我们不会在每条记录中输出字段名,因此为了使输出保持一致,需要创建一种映射关系。一种简单做法是写一个函数,用于将字段转为指定顺序的数据。在Python中,如果输出字典,CSV输出其会根据创建输出器时给定的fieldnames的顺序帮我们完成这一行为。
我们所使用的CSV库要输出到文件或者输出器,所以可以使用StringWriter或StringIO来将结果放到RDD中。
在Scala中写CSV
pandaLovers.map(person => List(person.name, person.favoriteAnimal).toArray)
.mapPartitions{people =>
val stringWriter = new StringWriter();
val csvWriter = new CSVWriter(stringWriter);
csvWriter.writeAll(people.toList)
Iterator(stringWriter.toString)
}.saveAsTextFile(outFile)
你可能已经注意到,上面的例子只能在我们知道所要输出的所有字段时使用。然而,如果一些字段名是在运行时由用户输入决定的,就要使用别的方法了。最简单的方法是遍历所有的数据,提取不同的键,然后分别输出。
SequenceFile是由没有相对关系结构的键值对文件组成的常用Hadoop格式。SequenceFile文件由同步标记,Spark可以用它来定位到文件中的某个点,然后再与记录的边界对齐。这可以让Spark使用多个节点高效地并行读取SequenceFile文件。SequenceFile也是Hadoop MapReduce作业中常用地输入输出格式,所以如果你在使用一个已有地Hadoop系统,数据很有可能是以SequenceFile地格式供你使用的。
由于Hadoop使用了一套自定义的序列化框架,因此SequenceFile是由实现Hadoop的Writable接口的元素组成。标准的经验法则是尝试再类名的后面加上Writable这个词,然后检查它是否是org.apache.hadoop.io.Writable已知的子类。也可以通过重载Writable类中的readfields和write来实现自己的Writable类。
Spark有专门用来读取SequenceFile的接口。在SparkContext中,可以调用sequenceFile(path,keyClass,valueClass,minPartitions)。前面提到过,SequenceFile使用Writable类,因此keyClass和valueClass参数都必须使用正确的Writable类。
Java中读取SequenceFile
/**
* @author DKing
* @description
* @date 2019/6/19
*/
public class SequenceFileProcessor {
public static void main(String[] args) {
SparkConf conf = new SparkConf().setMaster("local").setAppName("TextFile");
JavaSparkContext sc = new JavaSparkContext(conf);
String fileName = "";
JavaPairRDD input = sc.sequenceFile(fileName, Text.class,
IntWritable.class);
JavaPairRDD result = input.mapToPair(
(PairFunction, String, Integer>) record
-> new Tuple2(record._1.toString(), record._2.get())
);
}
}
SequenceFile存储的是键值对,所以需要创建一个由可以写出到SequenceFile的类型构成的PairRDD。
话不多讲,api没找到 呵呵呵呵呵。。。
对象文件看起来就像是对SequenceFile的简单封装,它允许存储只包含值的RDD。和SequenceFile不一样的是,对象文件是使用Java序列化写出的。
如果你修改了你的类——比如增减了几个字段——已经生成的对象文件就不再可读了。对象文件使用Java序列化,它对兼容他同一个类的不同版本有一定程度的支持,但是需要自行实现。
对对象文件使用Java序列化有几个需要注意的地方。首先,和普通的SequenceFile不同,对于同样的对象,对象文件的输出和Hadoop的输出不一样。其次,与其他文件格式不同的是,对象文件通常用于Spark作业间的通信。最后,Java序列化有可能相当慢。
要保存对象文件,只需在RDD上调用saveAsObjectFile就行了。读回对象文件也相当简单:用SparkContext中的objectFile()函数接收一个路径,返回对应的RDD。
了解了关于使用对象文件的这些注意事项,你可能像知道为什么会有人要用它。使用对象文件的主要原因是他们可以用来保存几乎任意对象而不需要额外的工作。