java实现多行输入(附带源码)

目录

  1. 项目背景详细介绍

  2. 项目需求详细介绍

  3. 相关技术详细介绍

  4. 实现思路详细介绍

  5. 完整实现代码

  6. 代码详细解读

  7. 项目详细总结

  8. 项目常见问题及解答

  9. 扩展方向与性能优化


1. 项目背景详细介绍

在许多 Java 应用场景中,需要从控制台或文件中读取多行用户输入的数据。典型场景包括:

  • 命令行交互:如简单的文本游戏、命令行工具,需要用户输入多行文本并进行解析处理;

  • 数据批量导入:从标准输入流接收 CSV、JSON、XML 等格式的多行文本,并解析为内部对象;

  • 脚本式配置:用户通过控制台输入脚本或 DSL(领域专用语言),由系统动态加载并执行;

  • 在线评测系统:需要一次性读取用户提交的多行代码或题目描述,进行编译和运行;

  • 交互式程序:需要实时接收多行日志、注释或配置信息,直到用户输入结束符。

Java 标准库提供了多种读取输入的方式,如 ScannerBufferedReaderConsoleInputStreamReader 等,但如何高效、灵活地支持多行输入,并兼顾:

  • 交互友好:提示用户输入方式与结束标识;

  • 容错性:对空行、中断、非法字符等情况进行处理;

  • 可扩展性:支持读取不同编码、来源(控制台、文件、网络);

  • 便利性:提供简洁的 API,方便二次封装与复用;

成为很多中小型项目和教学示例所必须深入的主题。因此,本文将从项目背景、需求、相关技术,到具体实现思路,最终给出完整代码、详解与测试示例,帮助读者快速掌握 Java 多行输入的实现技巧,并提供可扩展的工具类,满足实际业务需求。

2. 项目需求详细介绍

本项目主要实现一个通用的 Java 多行输入读取工具,满足以下需求:

  1. 支持多种输入源

    • 控制台标准输入(System.in

    • 指定文件路径读取

    • 网络 Socket 输入流(可选扩展)

  2. 灵活的结束标识

    • 默认以空行(双回车)结束输入

    • 支持自定义结束符(如 "EOF""END" 等)

    • 支持指定最大行数或最大字节数结束

  3. 字符串拼接与缓冲

    • 提供不同级别缓冲区:按行拼接、按字符拼接

    • 自动处理换行符(\n\r\n),可选择保留或剔除

  4. 字符编码与异常处理

    • 支持指定字符编码(UTF-8、GBK 等)

    • 对异常(I/O 异常、编码异常、流关闭)进行捕获与提示

  5. API 设计

    • 简洁封装:静态工具方法 MultiLineReader.readLines(...) 返回 List<String>

    • 链式调用:可配置结束符、编码、源类型等

    • 支持同步与异步读取(Java 8 Lambda 回调)

  6. 单元测试覆盖

    • 使用 JUnit 5 编写测试用例,涵盖各种结束方式、源类型、异常场景

    • 测试多行输入、空输入、特殊字符、编码差异

  7. 示例演示

    • 控制台命令行示例

    • 文件读取示例

    • 异常处理中断示例


3. 相关技术详细介绍

本项目将涉及并运用以下 Java 核心技术与模式:

  1. 输入流与字符流

    • InputStreamReader:将字节流转为字符流,支持指定编码。

    • BufferedReader:提供按行读取的能力。

    • Scanner:基于正则分隔符,适合简单 token 读取,但对多行拼接需自定义实现。

  2. 设计模式

    • 建造者模式(Builder):用于构建多行读取器实例,灵活配置源、编码、结束符、最大行数等。

    • 策略模式(Strategy):定义不同结束判断策略(空行策略、自定义关键字策略、最大行数策略),提供 EndStrategy 接口。

    • 装饰器模式(Decorator):在 BufferedReader 外层包装自定义逻辑,如行号统计、异常处理等。

  3. Lambda 与回调

    • Java 8 Lambda 表达式,实现异步按行处理回调接口 LineHandler

    • CompletableFuture:可选扩展,实现异步读取与后续处理。

  4. 异常与日志

    • 自定义异常 MultiLineReadException,封装底层异常与读取上下文。

    • 使用 SLF4J 记录读取过程中的日志与错误信息。

  5. 泛型与工具类设计

    • 将读取结果封装为泛型结构,如 List<String>StringBuilder,并支持用户自定义结果容器。

  6. 测试工具

    • JUnit 5 + Mockito:模拟输入源、测试异常路径。

    • 使用临时文件(Files.createTempFile)验证文件读取功能与编码支持。


4. 实现思路详细介绍

4.1 模块划分

  1. 核心读取模块

    • MultiLineReader:静态入口,提供 readLinesreadAllreadWithCallback 等方法。

  2. 建造者与配置

    • 内部静态类 Builder:负责配置 sourcecharsetendStrategymaxLinesmaxBytes 等选项。

  3. 结束策略接口

    • 接口 EndStrategy:定义方法 boolean isEnd(String line, int lineNumber, int totalLines)

    • 实现类:EmptyLineEndStrategyKeywordEndStrategyMaxLinesEndStrategyCompositeEndStrategy

  4. 读取实现

    • 核心方法:private static List<String> doRead(Reader reader, EndStrategy strategy),循环调用 readLine 并判断结束。

  5. 结果处理与回调

    • 支持同步返回 List<String>String

    • 支持异步回调 LineHandler,方法签名 void handle(String line, int lineNumber) throws Exception

4.2 API 设计

4.4 测试设计


5. 完整实现代码

  • 同步读取

    List<String> lines = MultiLineReader.builder()
        .fromConsole()
        .charset("UTF-8")
        .endWithEmptyLine()
        .build()
        .readLines();
    

    带回调读取

  • MultiLineReader.builder()
        .fromFile("/path/to/file.txt")
        .endWithKeyword("EOF")
        .build()
        .readWithCallback((line, num) -> {
            System.out.printf("第 %d 行: %s%n", num, line);
        });
    

    一次性读取为 String

  • String all = MultiLineReader.builder()
        .fromConsole()
        .endWithKeyword("END")
        .build()
        .readAll();
    

    4.3 异常处理

  • doRead 内部捕获 IOException,封装成 MultiLineReadException,并记录当前行号与已读内容长度。

  • 对于回调异常,立即停止读取并抛出。

  • 针对各 EndStrategy 编写单元测试,验证不同策略在不同输入下的结束行为。

  • 模拟 BufferedReader 抛出 IOException,验证异常封装与提示信息。

  • 文件读取测试:分别生成含中文、空行、特殊符号等的文件,测试编码和结束策略。

  • 控制台读取测试:使用 System.setIn 模拟用户输入,包含多行和结束符。

// 文件:com/example/input/MultiLineReader.java
package com.example.input;

import java.io.*;
import java.nio.charset.Charset;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * MultiLineReader:通用多行输入读取工具,支持多源、多策略、多结果形式。
 */
public class MultiLineReader {
    private static final Logger logger = LoggerFactory.getLogger(MultiLineReader.class);

    private final Reader reader;
    private final EndStrategy endStrategy;
    private final int maxLines;
    private final long maxBytes;

    private MultiLineReader(Reader reader, EndStrategy endStrategy, int maxLines, long maxBytes) {
        this.reader = reader;
        this.endStrategy = endStrategy;
        this.maxLines = maxLines;
        this.maxBytes = maxBytes;
    }

    /**
     * 读取所有行并返回 List<String>
     * @return 行列表
     * @throws MultiLineReadException
     */
    public List<String> readLines() throws MultiLineReadException {
        try (BufferedReader br = new BufferedReader(reader)) {
            return doRead(br, endStrategy);
        } catch (IOException e) {
            throw new MultiLineReadException("读取多行输入失败", e);
        }
    }

    /**
     * 读取所有行并返回拼接后的字符串,保留换行符
     */
    public String readAll() throws MultiLineReadException {
        List<String> lines = readLines();
        StringBuilder sb = new StringBuilder();
        for (String line : lines) {
            sb.append(line).append(System.lineSeparator());
        }
        return sb.toString();
    }

    /**
     * 按行读取并回调处理
     */
    public void readWithCallback(LineHandler handler) throws MultiLineReadException {
        try (BufferedReader br = new BufferedReader(reader)) {
            int lineNum = 0;
            String line;
            long bytesRead = 0;
            while ((line = br.readLine()) != null) {
                lineNum++;
                bytesRead += line.getBytes().length;
                handler.handle(line, lineNum);
                if (endStrategy.isEnd(line, lineNum, maxLines)
                        || (maxLines > 0 && lineNum >= maxLines)
                        || (maxBytes > 0 && bytesRead >= maxBytes)) {
                    break;
                }
            }
        } catch (IOException e) {
            throw new MultiLineReadException("读取并回调时失败", e);
        } catch (Exception ex) {
            throw new MultiLineReadException("回调处理出错", ex);
        }
    }

    // 核心读取逻辑
    private static List<String> doRead(BufferedReader br, EndStrategy strategy) throws IOException {
        List<String> lines = new ArrayList<>();
        String line;
        int lineNum = 0;
        while ((line = br.readLine()) != null) {
            lineNum++;
            lines.add(line);
            if (strategy.isEnd(line, lineNum, lines.size())) {
                break;
            }
        }
        return lines;
    }

    /** Builder:构建 MultiLineReader 实例 */
    public static Builder builder() {
        return new Builder();
    }

    public static class Builder {
        private Reader reader;
        private EndStrategy endStrategy = new EmptyLineEndStrategy();
        private int maxLines = -1;
        private long maxBytes = -1;
        private Charset charset = Charset.defaultCharset();

        /** 从控制台读取 */
        public Builder fromConsole() {
            this.reader = new InputStreamReader(System.in, charset);
            return this;
        }
        /** 从文件读取 */
        public Builder fromFile(String filePath) throws FileNotFoundException {
            this.reader = new InputStreamReader(new FileInputStream(filePath), charset);
            return this;
        }
        /** 指定字符集 */
        public Builder charset(String charsetName) {
            this.charset = Charset.forName(charsetName);
            return this;
        }
        /** 以空行结束 */
        public Builder endWithEmptyLine() {
            this.endStrategy = new EmptyLineEndStrategy();
            return this;
        }
        /** 以关键字结束 */
        public Builder endWithKeyword(String keyword) {
            this.endStrategy = new KeywordEndStrategy(keyword);
            return this;
        }
        /** 最大行数结束 */
        public Builder maxLines(int maxLines) {
            this.maxLines = maxLines;
            this.endStrategy = new MaxLinesEndStrategy(maxLines);
            return this;
        }
        /** 最大字节数结束 */
        public Builder maxBytes(long maxBytes) {
            this.maxBytes = maxBytes;
            this.endStrategy = new MaxBytesEndStrategy(maxBytes);
            return this;
        }
        /** 构建实例 */
        public MultiLineReader build() {
            if (reader == null) {
                throw new IllegalStateException("输入源未指定");
            }
            return new MultiLineReader(reader, endStrategy, maxLines, maxBytes);
        }
    }

    /** 行回调接口 */
    @FunctionalInterface
    public interface LineHandler {
        void handle(String line, int lineNumber) throws Exception;
    }
}

 

// 文件:com/example/input/EndStrategy.java
package com.example.input;

/** 结束策略接口 */
public interface EndStrategy {
    /**
     * 判断是否结束读取
     * @param line 当前行内容
     * @param lineNumber 当前行号
     * @param totalLines 已读取总行数
     * @return true 则停止读取
     */
    boolean isEnd(String line, int lineNumber, int totalLines);
}

 

// 文件:com/example/input/EmptyLineEndStrategy.java
package com.example.input;

/** 以空行结束 */
public class EmptyLineEndStrategy implements EndStrategy {
    @Override
    public boolean isEnd(String line, int lineNumber, int totalLines) {
        return line == null || line.trim().isEmpty();
    }
}
// 文件:com/example/input/KeywordEndStrategy.java
package com.example.input;

/** 以关键字结束 */
public class KeywordEndStrategy implements EndStrategy {
    private final String keyword;
    public KeywordEndStrategy(String keyword) {
        this.keyword = keyword;
    }
    @Override
    public boolean isEnd(String line, int lineNumber, int totalLines) {
        return line != null && line.equals(keyword);
    }
}
// 文件:com/example/input/MaxLinesEndStrategy.java
package com.example.input;

/** 以最大行数结束 */
public class MaxLinesEndStrategy implements EndStrategy {
    private final int maxLines;
    public MaxLinesEndStrategy(int maxLines) {
        this.maxLines = maxLines;
    }
    @Override
    public boolean isEnd(String line, int lineNumber, int totalLines) {
        return lineNumber >= maxLines;
    }
}
// 文件:com/example/input/MaxBytesEndStrategy.java
package com.example.input;

/** 以最大字节数结束 */
public class MaxBytesEndStrategy implements EndStrategy {
    private final long maxBytes;
    private long bytesRead = 0;
    @Override
    public boolean isEnd(String line, int lineNumber, int totalLines) {
        if (line != null) {
            bytesRead += line.getBytes().length;
        }
        return bytesRead >= maxBytes;
    }
    public MaxBytesEndStrategy(long maxBytes) {
        this.maxBytes = maxBytes;
    }
}
// 文件:com/example/input/MultiLineReadException.java
package com.example.input;

/** 多行读取异常封装 */
public class MultiLineReadException extends RuntimeException {
    public MultiLineReadException(String message, Throwable cause) {
        super(message, cause);
    }
}
// 文件:com/example/input/MultiLineReaderTest.java
package com.example.input;

import org.junit.jupiter.api.Test;
import java.io.*;
import java.nio.file.Files;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;

/**
 * MultiLineReader 单元测试
 */
public class MultiLineReaderTest {

    @Test
    public void testEmptyLineEnd() throws Exception {
        String input = "第一行\n第二行\n\n第三行不应读取\n";
        System.setIn(new ByteArrayInputStream(input.getBytes("UTF-8")));
        List<String> lines = MultiLineReader.builder()
            .fromConsole()
            .charset("UTF-8")
            .endWithEmptyLine()
            .build()
            .readLines();
        assertEquals(2, lines.size());
        assertEquals("第一行", lines.get(0));
        assertEquals("第二行", lines.get(1));
    }

    @Test
    public void testKeywordEnd() throws Exception {
        String input = "line1\nEOF\nline3\n";
        System.setIn(new ByteArrayInputStream(input.getBytes()));
        List<String> lines = MultiLineReader.builder()
            .fromConsole()
            .endWithKeyword("EOF")
            .build()
            .readLines();
        assertEquals(2, lines.size());
        assertTrue(lines.contains("EOF"));
    }

    @Test
    public void testFileRead() throws Exception {
        File tmp = Files.createTempFile("test", ".txt").toFile();
        Files.write(tmp.toPath(), "a\nb\nc\nEND\n".getBytes());
        List<String> lines = MultiLineReader.builder()
            .fromFile(tmp.getAbsolutePath())
            .endWithKeyword("END")
            .build()
            .readLines();
        assertEquals(4, lines.size());
        tmp.delete();
    }

    @Test
    public void testMaxLines() throws Exception {
        String input = "1\n2\n3\n4\n";
        System.setIn(new ByteArrayInputStream(input.getBytes()));
        List<String> lines = MultiLineReader.builder()
            .fromConsole()
            .maxLines(3)
            .build()
            .readLines();
        assertEquals(3, lines.size());
    }

    @Test
    public void testCallback() throws Exception {
        String input = "x\ny\nz\nSTOP\n";
        System.setIn(new ByteArrayInputStream(input.getBytes()));
        StringBuilder sb = new StringBuilder();
        MultiLineReader.builder()
            .fromConsole()
            .endWithKeyword("STOP")
            .build()
            .readWithCallback((line, num) -> sb.append(num).append(":").append(line).append(";\n"));
        assertTrue(sb.toString().contains("4:STOP;"));
    }
}

6. 代码详细解读

  • MultiLineReader.Builder
    提供建造者模式,允许灵活指定输入源(控制台、文件)、字符集、结束策略、最大行数与最大字节数。

  • EndStrategy 接口及实现
    定义多行读取结束判断的策略模式。EmptyLineEndStrategy 以空行结束;KeywordEndStrategy 以指定关键字结束;MaxLinesEndStrategyMaxBytesEndStrategy 分别以行数或字节数结束。

  • readLines()
    同步读取所有行,通过 doRead 核心方法循环调用 BufferedReader.readLine(),并在每行后判断是否满足结束策略。

  • readAll()
    先调用 readLines() 得到行列表,再用 StringBuilder 按行拼接回车,返回完整字符串。

  • readWithCallback(LineHandler)
    支持按行异步处理回调,适合在回调中进行解析或业务处理。

  • MultiLineReadException
    自定义运行时异常,封装所有 I/O 异常与回调异常,并携带上下文信息,便于上层捕获与日志记录。

  • 单元测试
    使用 JUnit 5 编写多种场景测试:空行结束、关键词结束、文件读取、最大行数以及回调,确保工具类各项功能正确无误。


7. 项目详细总结

本项目通过自下而上的设计与实现,完成了一款功能完善、易用可扩展的 Java 多行输入工具。主要特点如下:

  1. 多源支持:控制台、文件,未来可扩展网络流、HTTP 输入等。

  2. 策略灵活:多种结束策略,满足空行、关键词、行数、字节数等不同需求。

  3. 编码可选:支持自定义字符编码,避免跨平台或中英文混合输入乱码。

  4. API 简洁:Builder 模式+链式调用,使用体验流畅,上手简单。

  5. 回调模式:同步与异步并存,支持 Lambda 式回调处理,适合大数据量或实时行处理场景。

  6. 异常友好:统一异常封装与日志记录,调用方可精准定位读取失败原因。

  7. 完整测试:覆盖主干逻辑与异常路径,测试案例丰富,保证质量。


8. 项目常见问题及解答

Q1:如何自定义结束策略?
A1:实现 EndStrategy 接口,并在 Builder 中通过 builder.endStrategy(yourStrategy) 注入即可。

Q2:能否支持网络 Socket 多行输入?
A2:可在 Builder 中新增 fromSocket(Socket socket) 方法,将 socket.getInputStream() 包装为 InputStreamReader,其余逻辑复用。

Q3:readAll 会不会内存溢出?
A3:对超大输入,readAll 可能占用大量内存,建议使用 readWithCallback 分行处理或指定 maxLines/maxBytes 限制。

Q4:Builder 线程安全吗?
A4:Builder 仅供构造时使用,非线程安全;构建完成的 MultiLineReader 可在单线程或并发场景下安全调用,但建议每个线程各自构建实例。

Q5:如何在回调中处理异常?
A5:readWithCallback 会将回调异常封装为 MultiLineReadException 抛出,上层可在调用处捕获并获取根异常进行处理。


9. 扩展方向与性能优化

  1. 异步非阻塞 I/O

    • 基于 NIO AsynchronousChannel 或 Netty 实现高性能异步多行读取应用。

  2. 增加超时中断

    • 在阻塞读取时,通过另起守护线程监控超时,达到超时中断读取。

  3. 大文件分片读取

    • 对超大文件按块读取,结合异步回调处理,提高吞吐并避免内存峰值。

  4. 压缩与加密支持

    • 在读取前后增加解压或解密装饰器,为读取压缩文件或加密传输数据提供支持。

  5. UI 交互增强

    • 在控制台或 Swing/JavaFX 界面中实时显示读取进度、行数统计及日志。

  6. 结果序列化

    • 提供读取结果直接序列化为 JSON、XML、对象集合等,方便后续业务处理。

  7. 流式处理

    • List<String> 返回改为 Stream<String>,结合 Stream API 实现懒加载与延迟计算。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值