本文目录导读:

- 核心思路与常用库
- 案例一:使用 Stream API 清洗内存中的数据(去空、去重、过滤)
- 案例二:使用正则表达式清洗文本格式(手机号、身份证、邮箱)
- 案例三:清洗 CSV 文件(读取 → 清洗 → 写出)
- 案例四:复杂清洗 — 填充缺失值与异常值
- 案例五:使用 Apache Commons Lang 处理字符串清洗
- 总结与最佳实践
Java中实现数据清洗通常涉及对原始数据进行过滤、转换、去重、填充缺失值、格式标准化等操作,数据清洗是数据预处理的关键步骤,通常在大数据分析、ETL(数据抽取、转换、加载)或数据迁移项目中使用。
下面我将通过几个典型的 Java 案例,展示如何实现常见的数据清洗任务。
核心思路与常用库
在 Java 中实现数据清洗,主要依赖以下几种方式:
- 纯 Java + 集合框架:适合中小规模数据,使用
List、Map、Stream API进行过滤和映射。 - 正则表达式:用于清洗文本格式(如电话号码、邮箱、身份证)。
- Apache Commons Lang / BeanUtils:处理字符串、Bean属性。
- OpenCSV / Apache POI:读取 CSV、Excel 文件进行清洗。
- 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 数据清洗的通用步骤:
- 解析:读取原始数据(文件、数据库、消息队列)。
- 过滤:剔除 null、空值、非法记录。
- 转换:统一格式(日期、大小写、编码)、去除空格、替换异常值。
- 验证:正则校验(手机号、邮箱)、范围校验(年龄0-120)。
- 去重:基于业务主键或 equals/hashCode。
- 写出:输出到清洗后的目标存储。
如果你有具体的场景(如清洗特定的日志格式、金融交易数据),可以提供更多细节,我可以帮你定制更精确的实现方案。