Java和scala的Dataset的创建方式有所不同,因为Java的API和类型系统与Scala不同。比如,Scala中的隐式转换和case类在Java中并不适用,需要用Java Bean或Encoders来明确指定类型。基本操作部分,比如show()和printSchema(),这些方法在Java中应该是一样的,因为Spark的DataFrame/Dataset API在Scala和Java中是统一的。我们可能需要知道如何在Java中调用这些方法,尤其是链式调用时是否需要分号。
然后是转换操作,如select()和filter()。在Java中,这些方法同样存在,但列表达式需要用到org.apache.spark.sql.functions中的静态方法。需要注意Java中使用col()函数时需要导入相关的静态方法,并且lambda表达式在Java中的语法与Scala不同,比如filter条件的使用。
聚合操作部分,比如groupBy和agg,Java中同样需要依赖functions中的方法,比如sum()、avg()等。它是需要确保用户了解如何在Java中链式调用这些方法,并且正确使用别名alias。
类型转换方面,Java中没有as方法,但可以使用as(Encoders.bean())来指定类型。我们需要定义Java Bean类,并确保字段名称和类型与Dataset匹配。
行动操作如count()和collect()在Java中也是类似的,但要注意collect()返回的是对象列表,需要处理可能的类型转换和异常。
其他操作如join和distinct,在Java中调用方式类似,但同样需要注意列引用的方式,使用functions.col()或直接传入列名字符串。
最后,我们需要自定义检查是否有遗漏的方法或需要注意的细节,比如异常处理、资源管理等。
import org.apache.spark.sql.Dataset;
import org.apache.spark.sql.Row;
import org.apache.spark.sql.SparkSession;
import static org.apache.spark.sql.functions.*;
// 初始化 SparkSession
SparkSession spark = SparkSession.builder()
.appName("JavaDatasetExample")
.master("local[*]")
.getOrCreate();
// 从集合创建 Dataset(需定义 Java Bean)
List<Person> people = Arrays.asList(
new Person("Alice", 34),
new Person("Bob", 45)
);
// 使用 Encoder 转换为 Dataset
Dataset<Row> df = spark.createDataFrame(people, Person.class);
Dataset<Person> ds = df.as(Encoders.bean(Person.class));
// 定义 Java Bean 类
public static class Person implements Serializable {
private String name;
private int age;
// 必须有无参构造函数
public Person() {}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// Getter 和 Setter 必须存在
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
}
// 显示前 20 行
ds.show();
// 打印 Schema
ds.printSchema();
Dataset<Row> selected = ds.select(col("name"), col("age").plus(1));
selected.show();
// 使用表达式
Dataset filtered = ds.filter(col("age").gt(30));
// 使用 Lambda 表达式(需 Java 8+)
Dataset filtered2 = ds.filter((FilterFunction) p -> p.getAge() > 30);
Dataset aggregated = ds.groupBy("name")
.agg(
sum("age").alias("total_age"),
avg("age").alias("avg_age")
);
aggregated.show();
// 如果已有 Dataset,转换为 Java Bean 类型
Dataset typedDS = df.as(Encoders.bean(PPerson.class));
List collected = ds.collectAsList();
for (Person p : collected) {
System.out.println(p.getName() + ": " + p.getAge());
}
long count = ds.count();
System.out.println("Total records: " + count);
Dataset distinctDS = ds.distinct();
Dataset otherDS = ...; // 另一个 Dataset
Dataset joined = ds.join(otherDS, ds.col("name").equalTo(otherDS.col("name")), "inner");
Encoders.bean(...)
。FilterFunction
)。方法 | 说明 | 示例代码 |
---|---|---|
na().drop() |
删除包含空值的行 | Dataset |
na().fill(value) |
用指定值填充所有空值 | Dataset |
na().fill(Map |
按列填充不同值 | Map fills.put("age", 0); dataset.na().fill(fills); |
方法 | 说明 | 示例代码 |
---|---|---|
dropDuplicates() |
删除完全重复的行 | Dataset |
dropDuplicates(colNames) |
根据指定列去重 | Dataset |
方法 | 说明 | 示例代码 |
---|---|---|
filter(condition) |
根据条件过滤行 | Dataset |
where(condition) |
同 filter |
Dataset |
方法 | 说明 | 示例代码 |
---|---|---|
withColumn(colName, expr) |
转换列类型或计算新列 | Dataset |
cast(DataType) |
强制类型转换 | col("timestamp").cast(DataTypes.TimestampType) |
方法 | 说明 | 示例代码 |
---|---|---|
select(cols) |
选择特定列 | Dataset |
withColumnRenamed(old, new) |
重命名列 | Dataset |
drop(colName) |
删除列 | Dataset |
方法 | 说明 | 示例代码 |
---|---|---|
regexp_replace() |
正则替换 | functions.regexp_replace(col("email"), "@.*", "") |
trim() |
去除首尾空格 | col("name").trim() |
substr(start, length) |
截取子字符串 | col("phone").substr(0, 3) |
方法 | 说明 | 示例代码 |
---|---|---|
read().csv(path) |
读取 CSV 文件 | Dataset |
write().parquet(path) |
保存为 Parquet 格式 | df.write().parquet("hdfs:///output.parquet"); |
write().jdbc(...) |
写入关系型数据库 | .option("url", "jdbc:mysql://...") .option("dbtable", "table") |
方法 | 说明 | 示例代码 |
---|---|---|
groupBy(cols) |
按列分组 | GroupedData grouped = df.groupBy("department"); |
agg(exprs) |
聚合计算 | grouped.agg(avg("salary"), max("age")); |
方法 | 说明 | 示例代码 |
---|---|---|
join(otherDataset, condition) |
表连接 | df1.join(df2, df1.col("id").equalTo(df2.col("id"))); |
union(otherDataset) |
合并数据集 | Dataset |
方法 | 说明 | 示例代码 |
---|---|---|
show() |
打印前 N 行数据 | df.show(10); |
printSchema() |
打印 Schema | df.printSchema(); |
describe(cols) |
统计数值列的基本信息 | df.describe("age", "salary").show(); |
import org.apache.spark.sql.Dataset;
import org.apache.spark.sql.Row;
import org.apache.spark.sql.SparkSession;
import static org.apache.spark.sql.functions.*;
public class DataCleaningExample {
public static void main(String[] args) {
SparkSession spark = SparkSession.builder()
.appName("Data Cleaning Demo")
.master("local[*]")
.getOrCreate();
// 1. 读取原始数据
Dataset<Row> rawData = spark.read()
.option("header", true)
.option("inferSchema", true)
.csv("hdfs:///input.csv");
// 2. 数据清洗流程
Dataset<Row> cleanedData = rawData
// 处理空值: 删除全空行
.na().drop("all")
// 填充特定列空值
.na().fill(0, new String[]{"age"})
// 过滤无效年龄
.filter(col("age").geq(0).and(col("age").leq(100)))
// 类型转换
.withColumn("birth_year",
functions.lit(2023).minus(col("age")).cast("int"))
// 字符串处理
.withColumn("name", trim(col("name")))
// 去重
.dropDuplicates(new String[]{"id"})
// 选择最终列
.select("id", "name", "age", "birth_year");
// 3. 保存清洗结果
cleanedData.write()
.mode("overwrite")
.parquet("hdfs:///cleaned_data");
spark.stop();
}
}
分区策略
// 调整读取分区数
spark.read().option("basePath", "/data")
.csv("hdfs:///input_*.csv")
.repartition(200);
// 按列分区保存
cleanedData.write()
.partitionBy("year", "month")
.parquet("hdfs:///partitioned_data");
缓存机制
Dataset<Row> cachedDF = df.cache(); // MEMORY_AND_DISK
广播变量
List<String> validCountries = Arrays.asList("US", "CN", "EU");
Broadcast<List<String>> broadcastVar = spark.sparkContext().broadcast(validCountries);
df.filter(col("country").isin(broadcastVar.value()));
并行度控制
spark.conf().set("spark.sql.shuffle.partitions", "200");
问题 | 解决方案 |
---|---|
内存不足 (OOM) | 增加 Executor 内存:spark.executor.memory=8g |
数据倾斜 | 使用 repartition 或 salt 技术分散热点数据 |
类型转换失败 | 使用 try_cast 或先过滤无效数据 |
字符串编码问题 | 指定编码格式:.option("encoding", "UTF-8") |