Java案例如何实现数据清洗?

wen python案例 3

本文目录导读:

Java案例如何实现数据清洗?

  1. 核心思路与常用库
  2. 案例一:使用 Stream API 清洗内存中的数据(去空、去重、过滤)
  3. 案例二:使用正则表达式清洗文本格式(手机号、身份证、邮箱)
  4. 案例三:清洗 CSV 文件(读取 → 清洗 → 写出)
  5. 案例四:复杂清洗 — 填充缺失值与异常值
  6. 案例五:使用 Apache Commons Lang 处理字符串清洗
  7. 总结与最佳实践

Java中实现数据清洗通常涉及对原始数据进行过滤、转换、去重、填充缺失值、格式标准化等操作,数据清洗是数据预处理的关键步骤,通常在大数据分析、ETL(数据抽取、转换、加载)或数据迁移项目中使用。

下面我将通过几个典型的 Java 案例,展示如何实现常见的数据清洗任务。


核心思路与常用库

在 Java 中实现数据清洗,主要依赖以下几种方式:

  1. 纯 Java + 集合框架:适合中小规模数据,使用 ListMapStream API 进行过滤和映射。
  2. 正则表达式:用于清洗文本格式(如电话号码、邮箱、身份证)。
  3. Apache Commons Lang / BeanUtils:处理字符串、Bean属性。
  4. OpenCSV / Apache POI:读取 CSV、Excel 文件进行清洗。
  5. Java 8+ Stream API:链式操作,简洁高效。

使用 Stream API 清洗内存中的数据(去空、去重、过滤)

假设我们从用户注册日志中获取了一个用户列表,需要清洗掉无效数据。

import java.util.*;
import java.util.stream.*;
public class StreamCleanExample {
    public static void main(String[] args) {
        // 模拟脏数据:包含 null、空字符串、重复、非法年龄
        List<User> rawUsers = Arrays.asList(
            new User("Alice", 25, "alice@example.com"),
            new User("Bob", -5, "bob@test"),       // 年龄非法
            new User("", 30, ""),                  // 姓名为空
            new User("Charlie", null, null),       // 年龄为null,邮箱为null
            new User("Alice", 25, "alice@example.com"), // 重复
            new User("David", 150, "david@example.com") // 年龄过大
        );
        // 清洗逻辑:年龄在0-120之间,姓名非空,邮箱非空且格式简单校验
        List<User> cleanedUsers = rawUsers.stream()
            .filter(Objects::nonNull)                         // 过滤null
            .filter(u -> u.getName() != null && !u.getName().trim().isEmpty()) // 姓名非空
            .filter(u -> u.getAge() != null && u.getAge() >= 0 && u.getAge() <= 120) // 年龄合法
            .filter(u -> u.getEmail() != null && u.getEmail().contains("@")) // 邮箱包含@
            .distinct()                                   // 去重(需重写equals/hashCode)
            .collect(Collectors.toList());
        System.out.println("清洗前数量: " + rawUsers.size());
        System.out.println("清洗后数量: " + cleanedUsers.size());
        cleanedUsers.forEach(System.out::println);
    }
}
class User {
    private String name;
    private Integer age;
    private String email;
    public User(String name, Integer age, String email) {
        this.name = name;
        this.age = age;
        this.email = email;
    }
    // getter / setter / equals / hashCode / toString 省略
}

关键点:

  • distinct() 依赖 equals() / hashCode(),如果User未重写则无法正确去重(默认比较对象引用)。
  • 使用 filter() 链式过滤规则。

使用正则表达式清洗文本格式(手机号、身份证、邮箱)

通常用于从非结构化文本或CSV字段中提取/验证数据。

import java.util.regex.Pattern;
import java.util.ArrayList;
import java.util.List;
public class RegexCleanExample {
    // 预编译正则
    private static final Pattern PHONE_PATTERN = Pattern.compile("^1[3-9]\\d{9}$");
    private static final Pattern EMAIL_PATTERN = Pattern.compile("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$");
    public static void main(String[] args) {
        List<String> rawData = new ArrayList<>();
        rawData.add("13800138000");   // 正确
        rawData.add("123456");        // 太短
        rawData.add("abc@def");       // 无域名后缀
        rawData.add("test@example.com"); // 正确
        rawData.add(" 13800138001 "); // 含空格
        rawData.add(null);            // null
        List<String> cleaned = new ArrayList<>();
        for (String data : rawData) {
            if (data == null) continue;
            String trimmed = data.trim();
            // 判断是手机号还是邮箱
            if (PHONE_PATTERN.matcher(trimmed).matches() || EMAIL_PATTERN.matcher(trimmed).matches()) {
                cleaned.add(trimmed);
            }
        }
        System.out.println("清洗后有效数据: " + cleaned);
    }
}

常见清洗模式:

  • 空格清理:str.trim()str.replaceAll("[\\s]+", "")
  • 特殊符号:str.replaceAll("[^a-zA-Z0-9\\u4e00-\\u9fa5]", "")(保留中文)
  • 统一大小写:str.toLowerCase()

清洗 CSV 文件(读取 → 清洗 → 写出)

使用 OpenCSV 进行文件级清洗是非常常见的场景。

Maven 依赖:

<dependency>
    <groupId>com.opencsv</groupId>
    <artifactId>opencsv</artifactId>
    <version>5.8</version>
</dependency>

代码示例:

import com.opencsv.*;
import java.io.*;
import java.util.*;
public class CsvCleanExample {
    public static void main(String[] args) throws Exception {
        String inputFile = "data.csv";
        String outputFile = "cleaned_data.csv";
        try (
            Reader reader = new FileReader(inputFile);
            Writer writer = new FileWriter(outputFile);
            CSVReader csvReader = new CSVReaderBuilder(reader).withSkipLines(1).build(); // 跳过标题头
            CSVWriter csvWriter = new CSVWriterBuilder(writer).build()
        ) {
            String[] line;
            // 写标题头
            csvWriter.writeNext(new String[]{"姓名", "年龄", "邮箱", "手机号"});
            while ((line = csvReader.readNext()) != null) {
                if (line.length < 4) continue; // 字段不足
                String name = line[0].trim();
                String ageStr = line[1].trim();
                String email = line[2].trim();
                String phone = line[3].trim();
                // 规则清洗
                if (name.isEmpty()) continue;
                int age;
                try {
                    age = Integer.parseInt(ageStr);
                    if (age < 0 || age > 120) continue;
                } catch (NumberFormatException e) {
                    continue; // 年龄非数字
                }
                if (!email.contains("@")) continue;
                if (!phone.matches("^1[3-9]\\d{9}$")) continue;
                csvWriter.writeNext(new String[]{name, String.valueOf(age), email, phone});
            }
        }
        System.out.println("清洗完成,输出至: " + outputFile);
    }
}

复杂清洗 — 填充缺失值与异常值

通常用于数值型数据(如传感器数据、销售数据)。

import java.util.*;
import java.util.stream.*;
public class FillMissingExample {
    public static void main(String[] args) {
        List<Double> readings = Arrays.asList(23.5, null, 25.0, null, -999.0, 24.0, null);
        // 统计有效值的均值(忽略异常值 -999)
        double sum = 0;
        long count = 0;
        for (Double v : readings) {
            if (v != null && v != -999.0) {
                sum += v;
                count++;
            }
        }
        double mean = count > 0 ? sum / count : 0;
        // 清洗:null 或 -999 替换为均值
        List<Double> cleaned = readings.stream()
            .map(v -> {
                if (v == null || v == -999.0) {
                    return mean;
                }
                return v;
            })
            .collect(Collectors.toList());
        System.out.println("原始: " + readings);
        System.out.println("清洗后: " + cleaned);
        System.out.println("填充均值: " + mean);
    }
}

使用 Apache Commons Lang 处理字符串清洗

import org.apache.commons.lang3.StringUtils;
public class StringCleanExample {
    public static void main(String[] args) {
        String raw = "  Hello,  World!!  ";
        // 常用清洗
        String cleaned = raw
            .trim()
            .replaceAll("[^a-zA-Z0-9\\u4e00-\\u9fa5 ]", "")  // 只保留中文、英文、数字、空格
            .replaceAll("\\s+", " ");                  // 多个空格合并为一个
        System.out.println("'" + cleaned + "'");
        // 判断空
        if (StringUtils.isBlank(cleaned)) {
            System.out.println("数据为空");
        }
    }
}

总结与最佳实践

场景 推荐技术
内存中小批量数据 Java 8+ Stream API + 正则
文件(CSV/Excel) OpenCSV / Apache POI + 流式处理
大数据量(千万+) Apache Spark / Flink(Java API),并行清洗
实时数据流 Apache Flink / Kafka Streams
数据质量规则引擎 使用策略模式 + Drools 规则引擎

Java 数据清洗的通用步骤:

  1. 解析:读取原始数据(文件、数据库、消息队列)。
  2. 过滤:剔除 null、空值、非法记录。
  3. 转换:统一格式(日期、大小写、编码)、去除空格、替换异常值。
  4. 验证:正则校验(手机号、邮箱)、范围校验(年龄0-120)。
  5. 去重:基于业务主键或 equals/hashCode。
  6. 写出:输出到清洗后的目标存储。

如果你有具体的场景(如清洗特定的日志格式、金融交易数据),可以提供更多细节,我可以帮你定制更精确的实现方案。

抱歉,评论功能暂时关闭!