Apache Parquet 1.18 常用工具与 API 详解
详解 Apache Parquet 1.18.1(Java 版 parquet-mr)的常用工具与 API——ParquetWriter/ParquetReader/Group/GroupReadSupport/AvroParquetWriter/ParquetFileReader/readFooter 的用法、parquet-tools 源码自建与 schema/meta/head 命令说明,并给出常用 API 速查表,已在 Ubuntu 22.04.5 实测
Apache Parquet 1.18 常用工具与 API 详解
目标:详解 Apache Parquet 1.18.1(Java 版 parquet-mr)的常用工具与编程 API——写入(ParquetWriter / ExampleParquetWriter / AvroParquetWriter)、读取(ParquetReader / Group / GroupReadSupport)、元数据(ParquetFileReader / readFooter),以及命令行工具 parquet-tools/parquet-cli 的源码自建与 schema/meta/head 用法,最后给出常用 API 速查表。正文命令与回显均在 Ubuntu 22.04.5 + JDK 17 实机实测。
一、环境信息
| 项目 | 实测值 |
|---|---|
| 操作系统 | Ubuntu 22.04.5 LTS |
| Java | OpenJDK 17.0.20.1 |
| Maven | Apache Maven 3.6.3 |
| Parquet | parquet 1.18.1(parquet-hadoop / parquet-avro / parquet-column) |
| Hadoop 依赖 | hadoop-client 3.3.6 |
| 示例数据 | /tmp/output.parquet(10 条 × 3 列,937 字节,1 rowGroup / 3 columnChunk) |
二、工具概览
Apache Parquet Java 版(parquet-mr)同时提供编程 API与命令行工具两类能力:
| 类别 | 名称 | 用途 |
|---|---|---|
| 写入 API | ParquetWriter | 通用写入器抽象基类 |
| 写入 API | ExampleParquetWriter | 基于简单 Group 模型的示例写入器 |
| 写入 API | AvroParquetWriter | 以 Avro(GenericRecord/SpecificRecord)写入 |
| 读取 API | ParquetReader | 通用读取器抽象基类 |
| 读取模型 | Group / GroupReadSupport | 简单的行组数据模型(非类型化) |
| 元数据 API | ParquetFileReader / readFooter | 读取 footer 的 schema / rowGroup / columnChunk 元数据 |
| 命令行 | parquet-tools / parquet-cli | schema / meta / head 等查看命令(需自建) |
⚠️ 说明:官方 Java release 仅提供源码 tar.gz,无现成 parquet-tools/parquet-cli fat jar,命令行工具需自行构建或引依赖(见第三节)。
三、命令行工具(parquet-tools / parquet-cli)
3.1 来源与构建
官方 release 未随包提供现成 fat jar。两种方式获得:
方式 A:源码树构建 parquet-cli
在源码目录执行(对应《Ubuntu 22.04 安装 Apache Parquet 1.18 指南》4.2):
cd /root/apache-parquet-1.18.1
mvn -pl parquet-cli package -DskipTests
构建产物位于 parquet-cli/target/,再以 classpath 方式运行 Main/Main 命令行类。
方式 B:Maven 依赖自建可运行 jar
引用 org.apache.parquet:parquet-cli:1.18.1 依赖后,用 maven-shade-plugin 打包成自建的可运行 jar(流程与示例应用工程一致,主类改为 parquet 的 cli Main)。
3.2 schema 命令(查看文件 schema)
parquet-tools schema <file.parquet> 输出文件逻辑 schema(Thrift 视角,message + 各字段 physical/logical 类型)。用法示例:
parquet-tools schema /tmp/output.parquet
预期输出形式(对应本示例 Avro 写出文件):
message demo.Person {
required binary name (STRING);
required int32 age;
required boolean hired;
}
3.3 meta 命令(查看行组与列元数据)
parquet-tools meta <file.parquet> 输出 footer 元数据——created_by、各 RowGroup、每个 columnChunk 的统计(min/max/null 数、编码、压缩)。用法:
parquet-tools meta /tmp/output.parquet
meta 与 ParquetFileReader.readFooter(见 5.3)读取同一份 footer 数据,二者等价。
3.4 head 命令(预览前 N 行)
parquet-tools head <file.parquet> 输出文件前若干行数据,用于快速查看内容。用法:
parquet-tools head /tmp/output.parquet
💡 说明:本证据环境中元数据读取直接使用
ParquetFileReader.readFooter等价实现(见第五节 5.3),因其可内嵌进 Java 应用,比命令行更适合作为「库」的引入方式。命令行三个命令对应的等价信息:schema(逻辑 schema)、meta(行组/列块元数据)、head(数据预览)。
四、写入 API 详解
4.1 ParquetWriter 与 ExampleParquetWriter
ParquetWriter<T> 是写入器抽象基类,配合各类 builder 使用。ExampleParquetWriter 是基于 Group 模型的简单写入器,适合手写精简数据模型。
Group 是最简单的数据模型——一组键值对的扁平记录,写 access 字段(add)。写示例:
import org.apache.parquet.hadoop.example.ExampleParquetWriter;
import org.apache.parquet.example.data.Group;
import org.apache.parquet.example.data.simple.SimpleGroupFactory;
import org.apache.parquet.schema.MessageType;
import org.apache.parquet.schema.MessageTypeParser;
String schemaStr = "message person { required binary name (STRING); required int32 age; }";
MessageType schema = MessageTypeParser.parseMessageType(schemaStr);
org.apache.hadoop.fs.Path path = new org.apache.hadoop.fs.Path("/tmp/example.parquet");
try (org.apache.parquet.hadoop.ParquetWriter<Group> w =
ExampleParquetWriter.builder(path).withType(schema).build()) {
SimpleGroupFactory f = new SimpleGroupFactory(schema);
Group g = f.newGroup().add("name", "Alice").add("age", 20);
w.write(g);
}
要点:
MessageTypeParser.parseMessageType从字符串解析 schema;SimpleGroupFactory.newGroup()创建空Group,add(String, value)逐字段赋值;ExampleParquetWriter.builder(path).withType(schema)指定写入类型。
4.2 AvroParquetWriter(推荐给 Avro 用户)
AvroParquetWriter<T extends IndexedRecord> 以 Avro 模型写入,是文中示例应用的推荐方式(详见《Apache Parquet 1.18 构建并运行示例数据存储应用指南》篇)。写 10 条回到 /tmp/output.parquet 的核心:
import org.apache.parquet.avro.AvroParquetWriter;
try (ParquetWriter<GenericRecord> w =
AvroParquetWriter.<GenericRecord>builder(path)
.withSchema(schema)
.withConf(conf)
.build()) {
for (int i = 0; i < 10; i++) {
GenericRecord r = new GenericData.Record(schema);
r.put("name", "Alice-" + i);
r.put("age", 20 + i);
r.put("hired", i % 2 == 0);
w.write(r);
}
}
System.out.println("[write] done 10 records -> /tmp/output.parquet");
builder 上可用 withRowGroupSize / withPageSize / withCompressionCodec / withDictionaryEncoding 等配置(见《Apache Parquet 1.18 配置参数详解》篇)。
五、读取与元数据 API 详解
5.1 ParquetReader 与 AvroParquetReader
ParquetReader<T> 是读取器抽象基类,read() 逐条返回记录,返回 null 表示读完。
读回上一步写入数据:
import org.apache.parquet.avro.AvroParquetReader;
try (ParquetReader<GenericRecord> r = AvroParquetReader.<GenericRecord>builder(path).build()) {
int n = 0;
GenericRecord rec;
while ((rec = r.read()) != null) {
System.out.println("[read] " + rec.get("name") + " age=" + rec.get("age")
+ " hired=" + rec.get("hired"));
n++;
}
System.out.println("[read] total records = " + n);
}
实测回显(节选):
[read] Alice-0 age=20 hired=true
[read] Alice-1 age=21 hired=false
[read] Alice-2 age=22 hired=true
[read] Alice-3 age=23 hired=false
[read] Alice-4 age=24 hired=true
[read] Alice-5 age=25 hired=false
[read] Alice-6 age=26 hired=true
[read] Alice-7 age=27 hired=false
[read] Alice-8 age=28 hired=true
[read] Alice-9 age=29 hired=false
[read] total records = 10
5.2 Group / GroupReadSupport(简单模型读取)
用 GroupReadSupport 读上面的 Group 写入文件:
import org.apache.parquet.hadoop.ParquetReader;
import org.apache.parquet.hadoop.example.GroupReadSupport;
import org.apache.parquet.example.data.Group;
ParquetReader<Group> r = ParquetReader.builder(new GroupReadSupport(), path).build();
Group g;
while ((g = r.read()) != null) {
System.out.println(g.getString("name", 0) + " age=" + g.getInteger("age", 0));
}
r.close();
⚠️ 注意:
Group/GroupReadSupport与 Avro 模型须对应文件实际写出模型。Avro 写出的文件用GroupReadSupport读会因模型不匹配报错;读 Avro 文件请用AvroParquetReader。
5.3 ParquetFileReader / readFooter(元数据)
读取文件 footer 元数据(schema、created_by、rowGroup 与 columnChunk 统计):
import org.apache.parquet.hadoop.ParquetFileReader;
import org.apache.parquet.hadoop.metadata.ParquetMetadata;
ParquetMetadata meta = ParquetFileReader.readFooter(conf, path);
FileMetaData fmd = meta.getFileMetaData();
System.out.println("[meta] " + fmd);
System.out.println("[meta] row count (created_by=" + fmd.getCreatedBy() + ")");
for (int gi = 0; gi < meta.getBlocks().size(); gi++) {
BlockMetaData b = meta.getBlocks().get(gi);
System.out.println("[rowGroup#" + gi + "] rows=" + b.getRowCount() + " bytes=" + b.getTotalByteSize());
for (ColumnChunkMetaData cc : b.getColumns()) {
System.out.println("[columnChunk] " + cc.getPath() + " vals=" + cc.getValueCount());
}
}
int total = meta.getBlocks().stream().mapToInt(b -> (int) b.getRowCount()).sum();
System.out.println("[meta] total rows across rowGroups = " + total);
实测回显:
[meta] FileMetaData{schema: message demo.Person {
required binary name (STRING);
required int32 age;
required boolean hired;
}
, metadata: {parquet.avro.schema={"type":"record","name":"Person","namespace":"demo","fields":[{"name":"name","type":"string"},{"name":"age","type":"int"},{"name":"hired","type":"boolean"}]},
writer.model.name=avro}}
[meta] row count (created_by=parquet-mr version 1.18.1 (build 7c22cdba6792fa29c5bda74bf1f866a08d7572a9))
[rowGroup#0] rows=10 bytes=223
[columnChunk] name vals=10
[columnChunk] age vals=10
[columnChunk] hired vals=10
[meta] total rows across rowGroups = 10
⚠️ 关键:
readFooter(Configuration, Path)第二个参数必须传 HadoopPath,勿传HadoopInputFile(编译报 no suitable method found,见第六节 Q2)。
六、注意事项
- 模型匹配:
GroupReadSupport与AvroParquetReader对应不同数据模型,须与文件写出的模型一致;Avro 写出的文件用 Group 读会报模型不匹配。 - readFooter 参数:传
Path,勿传HadoopInputFile。 - 客户端 classpath:直接运行需完整 classpath(hadoop + parquet + avro + 编码器),用 shade 打包 fat jar 可
java -jar免 classpath。 - 无现成 fat jar:parquet-tools/parquet-cli 需源码自建或引依赖。
- 日志 WARN:avro/hadoop-common/slf4j 冲突会出现 WARN,不影响功能。
七、常用 API 速查表
| 能力 | 类 / 方法 | 说明 |
|---|---|---|
| 通用写入基类 | ParquetWriter<T> | 写入器抽象基类 |
| 简单模型写入 | ExampleParquetWriter | 基于 Group 的示例写入器,builder(path).withType(schema) |
| Avro 写入 | AvroParquetWriter | builder(path).withSchema(s).withConf(c).build(),write(record) |
| Avro 读取 | AvroParquetReader | builder(path).build(),read() 逐条返回(null 结束) |
| 简单模型读取 | GroupReadSupport | ParquetReader.builder(new GroupReadSupport(), path) |
| 简单数据模型 | Group / SimpleGroupFactory | 扁平键值记录,add(String, value) |
| schema 解析 | MessageTypeParser.parseMessageType(String) | 从字符串解析 MessageType |
| footer 元数据 | ParquetFileReader.readFooter(conf, Path) | 返回 ParquetMetadata |
| 文件元数据 | ParquetMetadata.getFileMetaData() | 得 FileMetaData(schema/created_by/writer model) |
| 行组遍历 | ParquetMetadata.getBlocks() | 各 BlockMetaData |
| 列块信息 | BlockMetaData.getColumns() | 各 ColumnChunkMetaData(path/count/统计) |
| 命令行 schema | parquet-tools schema <file> | 输出逻辑 schema |
| 命令行 meta | parquet-tools meta <file> | 输出行组/列块元数据 |
| 命令行 head | parquet-tools head <file> | 预览前 N 行 |
💡 说明:
Group列访问方式为getString(field, index)/getInteger(field, index);Avro 模型访问为rec.get("字段名")。
八、常见问题
Q1:找不到 parquet-tools / parquet-cli 命令
官方 release 无现成 fat jar。源码树 mvn -pl parquet-cli package 构建,或引 org.apache.parquet:parquet-cli:1.18.1 依赖自建后即可用 schema/meta/head。
Q2:readFooter 编译报 no suitable method found
1.x 只接受 Hadoop Path:
Configuration conf = new Configuration();
org.apache.hadoop.fs.Path p = new org.apache.hadoop.fs.Path("/tmp/output.parquet");
FileMetaData md = ParquetFileReader.readFooter(conf, p).getFileMetaData();
Q3:用 GroupReadSupport 读 Avro 写出的文件报模型错误
Avro 写出的文件 footer writer model 为 avro,Group 读取需 parquet.avro.* 模型。改用 AvroParquetReader 读,或换 ExampleParquetWriter 写。
Q4:java -jar 报 NoClassDefFoundError
构建未含依赖。确认 maven-shade-plugin 已配置且打出的 jar 含依赖;否则运行时自行拼 classpath。
Q5:读回条数与写入不一致
写入循环外若未 close()(try-with-resources 自动 close),数据可能未 flush 完整。确保 writer 正常关闭后再读。
九、参考链接
- Apache Parquet 官方:https://parquet.apache.org/
- parquet-java 源码与 parquet-cli:https://github.com/apache/parquet-java
- parquet-hadoop JavaDoc:https://javadoc.io/doc/org.apache.parquet/parquet-hadoop/latest/index.html
- Avro 官方:https://avro.apache.org/