Java中,spark提供的Dataset详解

小巫程序Demo日记:SparkDataset使用全解

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()或直接传入列名字符串。

最后,我们需要自定义检查是否有遗漏的方法或需要注意的细节,比如异常处理、资源管理等。


一、创建和基本操作

1. 创建 Dataset
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; }
}

二、基本操作

2. 查看数据
// 显示前 20 行
ds.show();

// 打印 Schema
ds.printSchema();

三、转换操作(Transformations)

3. 选择列(Select)
Dataset<Row> selected = ds.select(col("name"), col("age").plus(1));
selected.show();
4. 过滤(Filter)
// 使用表达式
Dataset filtered = ds.filter(col("age").gt(30));

// 使用 Lambda 表达式(需 Java 8+)
Dataset filtered2 = ds.filter((FilterFunction) p -> p.getAge() > 30);

四、聚合操作(Aggregations)

5. 分组统计
Dataset aggregated = ds.groupBy("name")
    .agg(
        sum("age").alias("total_age"),
        avg("age").alias("avg_age")
    );
aggregated.show();

五、类型转换

6. 转换为强类型 Dataset
// 如果已有 Dataset,转换为 Java Bean 类型
Dataset typedDS = df.as(Encoders.bean(PPerson.class));

六、行动操作(Actions)

7. 收集数据
List collected = ds.collectAsList();
for (Person p : collected) {
    System.out.println(p.getName() + ": " + p.getAge());
}
8. 计数
long count = ds.count();
System.out.println("Total records: " + count);

七、其他操作

9. 去重
Dataset distinctDS = ds.distinct();
10. Join 操作
Dataset otherDS = ...; // 另一个 Dataset
Dataset joined = ds.join(otherDS, ds.col("name").equalTo(otherDS.col("name")), "inner");

关键注意事项:

  1. Java Bean 类:必须包含无参构造函数和所有字段的 Getter/Setter。
  2. Encoder:在 Java 中必须显式指定 Encoders.bean(...)
  3. Lambda 表达式:Java 8+ 支持,但需显式转换(如 FilterFunction)。

小巫程序Demo日记:一、基于SparkDataset数据清洗核心方法

1. 处理缺失值
方法 说明 示例代码
na().drop() 删除包含空值的行 Dataset cleaned = dataset.na().drop();
na().fill(value) 用指定值填充所有空值 Dataset filled = dataset.na().fill(0);
na().fill(Map) 按列填充不同值 Map fills = new HashMap<>();
fills.put("age", 0);
dataset.na().fill(fills);
2. 去重处理
方法 说明 示例代码
dropDuplicates() 删除完全重复的行 Dataset unique = dataset.dropDuplicates();
dropDuplicates(colNames) 根据指定列去重 Dataset unique = dataset.dropDuplicates(new String[]{"id"});
3. 过滤无效数据
方法 说明 示例代码
filter(condition) 根据条件过滤行 Dataset valid = dataset.filter(col("age").gt(0));
where(condition) filter Dataset valid = dataset.where("salary > 1000");
4. 类型转换
方法 说明 示例代码
withColumn(colName, expr) 转换列类型或计算新列 Dataset converted = dataset.withColumn("age", col("age").cast("int"));
cast(DataType) 强制类型转换 col("timestamp").cast(DataTypes.TimestampType)
5. 列操作
方法 说明 示例代码
select(cols) 选择特定列 Dataset selected = dataset.select(col("name"), col("age"));
withColumnRenamed(old, new) 重命名列 Dataset renamed = dataset.withColumnRenamed("oldName", "newName");
drop(colName) 删除列 Dataset reduced = dataset.drop("unusedColumn");
6. 字符串处理
方法 说明 示例代码
regexp_replace() 正则替换 functions.regexp_replace(col("email"), "@.*", "")
trim() 去除首尾空格 col("name").trim()
substr(start, length) 截取子字符串 col("phone").substr(0, 3)

二、Dataset 核心操作方法

1. 数据加载与保存
方法 说明 示例代码
read().csv(path) 读取 CSV 文件 Dataset df = spark.read().csv("hdfs:///input.csv");
write().parquet(path) 保存为 Parquet 格式 df.write().parquet("hdfs:///output.parquet");
write().jdbc(...) 写入关系型数据库 .option("url", "jdbc:mysql://...")
.option("dbtable", "table")
2. 聚合操作
方法 说明 示例代码
groupBy(cols) 按列分组 GroupedData grouped = df.groupBy("department");
agg(exprs) 聚合计算 grouped.agg(avg("salary"), max("age"));
3. 数据转换
方法 说明 示例代码
join(otherDataset, condition) 表连接 df1.join(df2, df1.col("id").equalTo(df2.col("id")));
union(otherDataset) 合并数据集 Dataset combined = df1.union(df2);
4. 数据探查
方法 说明 示例代码
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();
    }
}

四、性能优化技巧

  1. 分区策略

    // 调整读取分区数
    spark.read().option("basePath", "/data")
        .csv("hdfs:///input_*.csv")
        .repartition(200);
    
    // 按列分区保存
    cleanedData.write()
        .partitionBy("year", "month")
        .parquet("hdfs:///partitioned_data");
    
  2. 缓存机制

    Dataset<Row> cachedDF = df.cache(); // MEMORY_AND_DISK
    
  3. 广播变量

    List<String> validCountries = Arrays.asList("US", "CN", "EU");
    Broadcast<List<String>> broadcastVar = spark.sparkContext().broadcast(validCountries);
    df.filter(col("country").isin(broadcastVar.value()));
    
  4. 并行度控制

    spark.conf().set("spark.sql.shuffle.partitions", "200");
    

五、常见问题处理

问题 解决方案
内存不足 (OOM) 增加 Executor 内存:spark.executor.memory=8g
数据倾斜 使用 repartitionsalt 技术分散热点数据
类型转换失败 使用 try_cast 或先过滤无效数据
字符串编码问题 指定编码格式:.option("encoding", "UTF-8")

你可能感兴趣的:(Spark+Hadoop学习,Java,java,spark,开发语言)