0

0

Java 文件解析与数据处理:解决 ArrayList 空值及排序问题

心靈之曲

心靈之曲

发布时间:2025-11-10 17:15:24

|

764人浏览过

|

来源于php中文网

原创

Java 文件解析与数据处理:解决 ArrayList 空值及排序问题

在 Java 应用开发中,从外部文件读取结构化数据并进行处理是常见的任务。然而,这一过程常常伴随着各种潜在的问题,尤其是在数据格式不严格时。本文将聚焦于一个典型的场景:从 CSV 格式文件读取电影信息,将其分类存储到 `ArrayList` 中,并按年份排序。我们将分析导致 `ArrayList` 为空和数据解析异常的常见原因,并提供专业的解决方案。

1. 问题分析:数据解析的陷阱

在处理文件输入时,开发者经常会遇到两种主要问题:

  1. NumberFormatException: 当尝试将包含非数字字符的字符串转换为数字类型(如 int 或 double)时,会抛出此异常。这通常是由于输入字符串中包含意外的空格、换行符或其他不可见字符。
  2. 数据分类失败: 即使数据被成功读取,如果分类逻辑(例如,基于字符串比较)不够健壮,也可能导致数据无法正确匹配到预期的类别,从而使相应的 ArrayList 保持为空。

在给定的电影信息处理案例中,原始代码在尝试解析电影年份和流派时遇到了上述问题。例如,当文件中的一行数据为 Schindler's List, 1994, War, R 时:

  • movieInfo[1] 得到的是 " 1994",而非 "1994"。直接使用 Integer.parseInt(" 1994") 会导致 NumberFormatException。
  • movieInfo[2] 得到的是 " War",而非 "War"。因此,genre.equals("War") 的判断始终为 false,导致所有电影都无法被正确分类到任何流派的 ArrayList 中。

这些问题的根源在于文件数据中的额外空格,而默认的 String.split(",") 方法无法自动处理这些空格。

2. 解决方案:数据清洗与健壮解析

为了解决上述问题,我们需要在数据读取和解析阶段进行必要的清洗。有两种主要方法可以实现这一点:

2.1 使用 String.trim() 方法

String.trim() 方法可以移除字符串两端的空白字符(包括空格、制表符、换行符等)。这是最直接且易于理解的方法。在解析每个字段后,立即调用 trim() 方法。

立即学习Java免费学习笔记(深入)”;

// 原始代码:
// String[] movieInfo = movie.split(",");
// String title = movieInfo[0];
// int year = Integer.parseInt(movieInfo[1]);
// String genre = movieInfo[2];

// 改进后:
String[] movieInfo = movie.split(",");
String title = movieInfo[0].trim(); // 移除标题两端空格
int year = Integer.parseInt(movieInfo[1].trim()); // 移除年份两端空格后再转换
String genre = movieInfo[2].trim(); // 移除流派两端空格
String rating = movieInfo[3].trim(); // 移除评分两端空格

通过在每个字段上调用 trim(),可以确保 Integer.parseInt() 接收到纯数字字符串,并且流派字符串与预定义的常量完全匹配。

2.2 使用正则表达式优化 String.split()

更优雅且推荐的方法是利用正则表达式来增强 split() 方法。我们可以让 split() 不仅在逗号处分割,还能自动忽略逗号后的任意数量的空白字符。

绘蛙
绘蛙

电商场景的AI创作平台,无需高薪聘请商拍和文案团队,使用绘蛙即可低成本、批量创作优质的商拍图、种草文案

下载
// 改进后:
// 使用正则表达式 ",\s*"。
// "," 表示匹配一个逗号。
// "\s*" 表示匹配零个或多个空白字符(空格、制表符、换行符等)。
String[] movieInfo = movie.split(",\s*");
String title = movieInfo[0]; // 无需再trim,因为split已经处理了
int year = Integer.parseInt(movieInfo[1]); // 无需再trim
String genre = movieInfo[2]; // 无需再trim
String rating = movieInfo[3]; // 无需再trim

这种方法简化了后续代码,因为每个 movieInfo 数组元素在被访问时已经去除了前导空白。

3. 数据排序:实现 Comparator

为了实现按年份对电影进行排序,我们需要定义一个 Comparator。Comparator 是一个函数式接口,用于定义两个对象之间的比较规则。

import java.util.Comparator;

public class MovieComparator implements Comparator<Movie> {
    @Override
    public int compare(Movie m1, Movie m2) {
        // 按照年份升序排序
        return Integer.compare(m1.getYearReleased(), m2.getYearReleased());
    }
}

有了 MovieComparator,就可以使用 ArrayList 的 sort() 方法对电影列表进行排序:

adventure.sort(new MovieComparator());
drama.sort(new MovieComparator());
// ... 对其他所有流派列表进行排序

4. 完整示例代码与改进

以下是整合了数据清洗和排序功能的完整 Java 代码示例。

4.1 MovieListing.txt 文件示例

假设 MovieListing.txt 文件内容如下(请注意逗号后的空格):

Steven Spielberg
John Williams
Schindler's List, 1994, War, R
Amistad, 1997, Drama, R
The Post, 2017, Drama, PG-13
E.T. the Extra-Terrestrial, 1982, Sci Fi, PG
Jurassic Park, 1993, Sci Fi, PG-13

4.2 Director.java

public class Director {
    private String directorName;
    private String composerName;

    public Director(String d, String c) {
        this.directorName = d;
        this.composerName = c;
    }

    public String getDirectorName() {
        return directorName;
    }

    public void setDirectorName(String directorName) {
        this.directorName = directorName;
    }

    public String getComposerName() {
        return composerName;
    }

    public void setComposerName(String composerName) {
        this.composerName = composerName;
    }
}

4.3 Movie.java

public class Movie extends Director { // 注意:Movie继承Director在面向对象设计上可能不合理,但此处保留原结构
    private String title;
    private int yearReleased;
    private String genre;
    private String rating;

    public Movie(String title, int yearReleased, String genre, String rating, String directorName, String composerName) {
        super(directorName, composerName);
        this.title = title;
        this.yearReleased = yearReleased;
        this.genre = genre;
        this.rating = rating;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public int getYearReleased() {
        return yearReleased;
    }

    public void setYearReleased(int yearReleased) {
        this.yearReleased = yearReleased;
    }

    public String getGenre() {
        return genre;
    }

    public void setGenre(String genre) {
        this.genre = genre;
    }

    public String getRating() {
        return rating;
    }

    public void setRating(String rating) {
        this.rating = rating;
    }

    @Override
    public String toString() { // 重写toString方便调试输出
        return "Movie{" +
               "title='" + title + ''' +
               ", yearReleased=" + yearReleased +
               ", genre='" + genre + ''' +
               ", rating='" + rating + ''' +
               ", director='" + getDirectorName() + ''' +
               ", composer='" + getComposerName() + ''' +
               '}';
    }
}

4.4 MovieComparator.java

import java.util.Comparator;

public class MovieComparator implements Comparator<Movie> {
    @Override
    public int compare(Movie m1, Movie m2) {
        // 按照年份升序排序
        return Integer.compare(m1.getYearReleased(), m2.getYearReleased());
    }
}

4.5 Driver.java (核心逻辑改进)

import java.util.ArrayList;
import java.util.Scanner;
import javax.swing.JOptionPane;
import java.io.*;

public class Driver {

    public void start() throws FileNotFoundException {
        // Initialize arraylists
        ArrayList<Movie> adventure = new ArrayList<>();
        ArrayList<Movie> drama = new ArrayList<>();
        ArrayList<Movie> fantasy = new ArrayList<>();
        ArrayList<Movie> romance = new ArrayList<>();
        ArrayList<Movie> sciFi = new ArrayList<>();
        ArrayList<Movie> thriller = new ArrayList<>();
        ArrayList<Movie> war = new ArrayList<>();

        String directorName = "";
        String composerName = "";

        File myObj = new File("MovieListing.txt");
        try (Scanner myReader = new Scanner(myObj)) { // 使用try-with-resources确保Scanner关闭
            if (myReader.hasNextLine()) {
                directorName = myReader.nextLine().trim(); // 读取并trim导演名
            }
            if (myReader.hasNextLine()) {
                composerName = myReader.nextLine().trim(); // 读取并trim作曲家名
            }

            while (myReader.hasNextLine()) {
                String movieLine = myReader.nextLine();
                // 改进点:使用正则表达式 ",\s*" 分割字符串,自动处理逗号后的空格
                String[] movieInfo = movieLine.split(",\s*");

                if (movieInfo.length < 4) { // 简单的数据完整性检查
                    System.err.println("Skipping malformed line: " + movieLine);
                    continue;
                }

                String title = movieInfo[0];
                int year = Integer.parseInt(movieInfo[1]);
                String genre = movieInfo[2];
                String rating = movieInfo[3];

                Movie movie1 = new Movie(title, year, genre, rating, directorName, composerName);

                // sort movies into arraylists
                if (genre.equals("Adventure")) {
                    adventure.add(movie1);
                } else if (genre.equals("Drama")) {
                    drama.add(movie1);
                } else if (genre.equals("Fantasy")) {
                    fantasy.add(movie1);
                } else if (genre.equals("Romance")) {
                    romance.add(movie1);
                } else if (genre.equals("Sci Fi")) {
                    sciFi.add(movie1);
                } else if (genre.equals("Thriller")) {
                    thriller.add(movie1);
                } else if (genre.equals("War")) {
                    war.add(movie1);
                }
            }
        } catch (FileNotFoundException e) {
            System.err.println("Error: MovieListing.txt not found. " + e.getMessage());
            throw e; // 重新抛出异常,让调用者处理
        } catch (NumberFormatException e) {
            System.err.println("Error parsing number: " + e.getMessage());
            // 可以选择跳过该行或进行其他错误处理
        }

        // 调试输出,检查列表是否已填充
        System.out.println("Adventure Movies: " + adventure);
        System.out.println("War Movies: " + war); // 示例

        // Ask the user which genre they would like to view
        String genreChoice = JOptionPane.showInputDialog("Director: " + directorName + "
" +
                "Composer: " + composerName + "
" +
                "Which genre would you like? 
" +
                "1. Adventure 
" +
                "2. Drama 
" +
                "3. Fantasy 
" +
                "4. Romance 
" +
                "5. SciFi 
" +
                "6. Thriller 
" +
                "7. War 
" +
                "Your choice: ");

        // Sort all movie lists by year released
        MovieComparator movieComparator = new MovieComparator();
        adventure.sort(movieComparator);
        drama.sort(movieComparator);
        fantasy.sort(movieComparator);
        romance.sort(movieComparator);
        sciFi.sort(movieComparator);
        thriller.sort(movieComparator);
        war.sort(movieComparator);

        // Display the output dialog box
        StringBuilder output = new StringBuilder();
        output.append("Director: ").append(directorName).append("
");
        output.append("Composer: ").append(composerName).append("

");
        output.append("Genre: ");

        ArrayList<Movie> selectedGenreList = null;
        String genreName = "";

        switch (genreChoice) {
            case "1":
                selectedGenreList = adventure;
                genreName = "Adventure";
                break;
            case "2":
                selectedGenreList = drama;
                genreName = "Drama";
                break;
            case "3":
                selectedGenreList = fantasy;
                genreName = "Fantasy";
                break;
            case "4":
                selectedGenreList = romance;
                genreName = "Romance";
                break;
            case "5":
                selectedGenreList = sciFi;
                genreName = "Sci Fi";
                break;
            case "6":
                selectedGenreList = thriller;
                genreName = "Thriller";
                break;
            case "7":
                selectedGenreList = war;
                genreName = "War";
                break;
            default:
                JOptionPane.showMessageDialog(null, "Invalid genre choice.");
                return;
        }

        output.append(genreName).append("

");
        output.append("Movie Title	Year Released	Rating
");

        if (selectedGenreList != null) {
            for (Movie movie : selectedGenreList) {
                output.append(String.format("%-20s	%-15d	%s
",
                                             movie.getTitle(),
                                             movie.getYearReleased(),
                                             movie.getRating()));
            }
        }
        JOptionPane.showMessageDialog(null, output.toString());
    }
}

4.6 Main.java

import java.io.FileNotFoundException;

public class Main {
    public static void main(String[] args) throws FileNotFoundException {
        Driver driver = new Driver();
        driver.start();
    }
}

5. 注意事项与总结

  1. 数据清洗的重要性: 在从外部源(如文件、网络)读取数据时,始终假定数据可能不完全符合预期格式。对输入数据进行严格的验证和清洗是避免运行时错误的关键。String.trim() 和正则表达式是处理这类问题的强大工具
  2. 异常处理: 在文件 I/O 和数据类型转换时,FileNotFoundException 和 NumberFormatException 是常见的受检异常。使用 try-catch 块进行适当的异常处理至关重要,这可以使程序更加健壮,并提供有用的错误信息。使用 try-with-resources 语句(如 try (Scanner myReader = new Scanner(myObj)))可以确保资源(如 Scanner)在使用完毕后自动关闭,避免资源泄漏。
  3. 面向对象设计: 原始代码中 Movie extends Director 的继承关系在语义上可能不完全符合现实世界模型(电影通常“有”一个导演,而不是“是”一个导演)。在实际项目中,更好的设计可能是让 Movie 类包含一个 Director 类型的成员变量(组合关系),而不是继承关系。然而,本文主要关注数据解析和排序,因此保留了原有结构。
  4. 格式化输出: 使用 String.format() 可以更灵活地控制输出字符串的格式,例如对齐文本、设置宽度等,以生成更美观的报告。
  5. 代码可读性: 适当的注释、有意义的变量名和清晰的代码结构对于维护和理解代码至关重要。

通过上述改进,我们不仅解决了 ArrayList 为空和 NumberFormatException 的问题,还使文件数据处理流程更加健壮和专业。这对于任何需要处理外部数据的 Java 应用来说都是基本且重要的实践。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

WorkBuddy
WorkBuddy

腾讯云推出的AI原生桌面智能体工作台

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
js正则表达式
js正则表达式

php中文网为大家提供各种js正则表达式语法大全以及各种js正则表达式使用的方法,还有更多js正则表达式的相关文章、相关下载、相关课程,供大家免费下载体验。

530

2023.06.20

正则表达式不包含
正则表达式不包含

正则表达式,又称规则表达式,,是一种文本模式,包括普通字符和特殊字符,是计算机科学的一个概念。正则表达式使用单个字符串来描述、匹配一系列匹配某个句法规则的字符串,通常被用来检索、替换那些符合某个模式的文本。php中文网给大家带来了有关正则表达式的相关教程以及文章,希望对大家能有所帮助。

258

2023.07.05

java正则表达式语法
java正则表达式语法

java正则表达式语法是一种模式匹配工具,它非常有用,可以在处理文本和字符串时快速地查找、替换、验证和提取特定的模式和数据。本专题提供java正则表达式语法的相关文章、下载和专题,供大家免费下载体验。

766

2023.07.05

java正则表达式匹配字符串
java正则表达式匹配字符串

在Java中,我们可以使用正则表达式来匹配字符串。本专题为大家带来java正则表达式匹配字符串的相关内容,帮助大家解决问题。

219

2023.08.11

正则表达式空格
正则表达式空格

正则表达式空格可以用“s”来表示,它是一个特殊的元字符,用于匹配任意空白字符,包括空格、制表符、换行符等。本专题为大家提供正则表达式相关的文章、下载、课程内容,供大家免费下载体验。

357

2023.08.31

Python爬虫获取数据的方法
Python爬虫获取数据的方法

Python爬虫可以通过请求库发送HTTP请求、解析库解析HTML、正则表达式提取数据,或使用数据抓取框架来获取数据。更多关于Python爬虫相关知识。详情阅读本专题下面的文章。php中文网欢迎大家前来学习。

293

2023.11.13

正则表达式空格如何表示
正则表达式空格如何表示

正则表达式空格可以用“s”来表示,它是一个特殊的元字符,用于匹配任意空白字符,包括空格、制表符、换行符等。想了解更多正则表达式空格怎么表示的内容,可以访问下面的文章。

245

2023.11.17

正则表达式中如何匹配数字
正则表达式中如何匹配数字

正则表达式中可以通过匹配单个数字、匹配多个数字、匹配固定长度的数字、匹配整数和小数、匹配负数和匹配科学计数法表示的数字的方法匹配数字。更多关于正则表达式的相关知识详情请看本专题下面的文章。php中文网欢迎大家前来学习。

547

2023.12.06

C# ASP.NET Core微服务架构与API网关实践
C# ASP.NET Core微服务架构与API网关实践

本专题围绕 C# 在现代后端架构中的微服务实践展开,系统讲解基于 ASP.NET Core 构建可扩展服务体系的核心方法。内容涵盖服务拆分策略、RESTful API 设计、服务间通信、API 网关统一入口管理以及服务治理机制。通过真实项目案例,帮助开发者掌握构建高可用微服务系统的关键技术,提高系统的可扩展性与维护效率。

76

2026.03.11

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Kotlin 教程
Kotlin 教程

共23课时 | 4.4万人学习

C# 教程
C# 教程

共94课时 | 11.2万人学习

Java 教程
Java 教程

共578课时 | 81.2万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号