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 ParquetApache Parquet2026/10/11

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
JavaOpenJDK 17.0.20.1
MavenApache Maven 3.6.3
Parquetparquet 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与命令行工具两类能力:

类别名称用途
写入 APIParquetWriter通用写入器抽象基类
写入 APIExampleParquetWriter基于简单 Group 模型的示例写入器
写入 APIAvroParquetWriter以 Avro(GenericRecord/SpecificRecord)写入
读取 APIParquetReader通用读取器抽象基类
读取模型Group / GroupReadSupport简单的行组数据模型(非类型化)
元数据 APIParquetFileReader / readFooter读取 footer 的 schema / rowGroup / columnChunk 元数据
命令行parquet-tools / parquet-clischema / 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) 第二个参数必须传 Hadoop Path,勿传 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 写入AvroParquetWriterbuilder(path).withSchema(s).withConf(c).build(),write(record)
Avro 读取AvroParquetReaderbuilder(path).build(),read() 逐条返回(null 结束)
简单模型读取GroupReadSupportParquetReader.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/统计)
命令行 schemaparquet-tools schema <file>输出逻辑 schema
命令行 metaparquet-tools meta <file>输出行组/列块元数据
命令行 headparquet-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 正常关闭后再读。

九、参考链接