使用缓冲区 reader 读取给定输入的数据块

Using a buffer reader to read blocks of data for a given input

这是我正在读取的文件的结构:

[MESSAGE BEGIN]
uan:123
messageID: 111
[MESSAGE END]
[MESSAGE BEGIN]
uan:123
status:test
[MESSAGE END]

我想要做的是,对于给定的 uan,return 它的所有细节,同时保持块结构“MESSAGE BEGIN”“MESSAGE END”。

这是我写的代码:

startPattern= "uan:123"
endPattern= "[MESSAGE END]"
 System.out.println("Matching: " + this.getStartPattern());
        List<String> desiredLines = new ArrayList<>();

        try (BufferedReader buff = Files.newBufferedReader(getPath())) {
            String line = "";
            while ((line = buff.readLine()) != null) {

                if (line.contains(this.getStartPattern())) {
                    desiredLines.add(line);
                    System.out.println(" \nMatch Found! ");
                    buff.lines().forEach(streamElement -> {
                        if (!streamElement.contains(this.getEndPattern())) {
                            desiredLines.add(streamElement);
                        } else if (streamElement.contains(this.getEndPattern())) {
                            throw new IndexOutOfBoundsException("Exit Status 0");
                        }
                    });

                }

现在的问题是,while 条件在看到第一个“uan”时中断,只捕获消息 ID。我希望代码在通过 uan 时也包含“状态”。

有人可以帮忙吗?

编辑

这是我的预期输出:

 uan:123
 messageID: 111
 uan:123
 status:test
     

应捕获 uan:123 的所有实例

只需使用简单的解析逻辑,仅在看到匹配项时才输出数据uan。我使用一个布尔变量来跟踪我们是否在给定块内命中了匹配的 uan。如果是这样,那么我们输出所有行,否则我们不操作并跳过所有内容。

try (BufferedReader buff = Files.newBufferedReader(getPath())) {
    String line = "";
    String uan = "uan:123";
    String begin = "[MESSAGE BEGIN]";
    String end = "[MESSAGE END]";
    boolean match = false;

    while ((line = buff.readLine()) != null) {
        if (uan.equals(line)) {
            match = true;
        }
        else if (end.equals(line)) {
            match = false;
        }
        else if (!begin.equals(line) && match) {
            System.out.println(line);
        }
    }
}

请注意,我没有做任何验证来检查是否每个 BEGIN 都被正确的关闭 END 所反映。如果你需要这个,你可以在上面的代码中添加额外的逻辑。

对过滤后的邮件进行分组

您的总体方法似乎不错。我会将其分解为更简单、更直接的逻辑,而不是嵌套循环,例如:

String needle = "uan:123";

String startPattern = "[MESSAGE BEGIN]";
String endPattern = "[MESSAGE END]";

List<List<String>>> result = new ArrayList<>();
try (BufferedReader buff = Files.newBufferedReader(getPath())) {
    // Lines and flag for current message
    List<String> currentMessage = new ArrayList<>();
    boolean messageContainedNeedle = false;

    // Read all lines
    while (true) {
        String line = buff.readLine();
        if (line == null) {
            break;
        }

        // Collect current line to message, ignore indicator
        if (!line.equals(endPattern) && !line.equals(startPattern)) {
            currentMessage.add(line);
        }

        // Set flag if message contains needle
        if (!messageContainedNeedle && line.equals(needle)) {
            messageContainedNeedle = true;
        }

        // Message ends
        if (line.equals(endPattern)) {
            // Collect if needle was contained
            if (messageContainedNeedle) {
                result.add(currentMessage);
            }

            // Prepare for next message
            messageContainedNeedle = false;
            currentMessage = new ArrayList<>();
        }
    }
}

更容易阅读和理解。它支持您的消息项以任意顺序出现。此外,生成的 result 仍然将消息分组在 List<List<String>> 中。如果您仍然想要 List<String>.

,您可以轻松地绘制平面图

结果结构是:

[
    ["uan:123", "messageID: 111"],
    ["uan:123", "status: test"]
]

现在很容易准确地实现您想要的输出:

// Variant 1: Nested for-each
result.forEach(message -> message.forEach(System.out::println));

// Variant 2: Flat-map
result.stream().flatMap(List::stream).forEach(System.out::println));

// Variant 3: Without streams
for (List<String> message : result) {
    for (String line : message) {
        System.out.println(line);
    }
}

对所有消息进行分组

如果您省略标志部分,您可以将所有消息解析为该结构,然后轻松地在其上流式传输:

public static List<List<String>> parseMessages(Path path) {
    String startPattern = "[MESSAGE BEGIN]";
    String endPattern = "[MESSAGE END]";

    List<List<String>>> result = new ArrayList<>();
    try (BufferedReader buff = Files.newBufferedReader(path)) {
        // Data for current message
        List<String> currentMessage = new ArrayList<>();

        // Read all lines
        while (true) {
            String line = buff.readLine();
            if (line == null) {
                break;
            }

            // Collect current line to message, ignore indicator
            if (!line.equals(endPattern) && !line.equals(startPattern)) {
                currentMessage.add(line);
            }

            // Message ends
            if (line.equals(endPattern)) {
                // Collect message
                result.add(currentMessage);

                // Prepare for next message
                currentMessage = new ArrayList<>();
            }
        }
    }

    return result;
}

使用简单明了。例如,过滤 "uan:123":

的消息
List<List<String>> messages = parseMessages(getPath());

String needle = "uan:123";
List<List<String>> messagesWithNeedle = messages.stream()
    .filter(message -> message.contains(needle))
    .collect(Collectors.toList());

结果结构又是:

[
    ["uan:123", "messageID: 111"],
    ["uan:123", "status: test"]
]

可以直接在流级联上实现您想要的输出:

messages.stream()  // Stream<List<String>>
    .filter(message -> message.contains(needle))
    .flatMap(List::stream)  // Stream<String>
    .forEach(System.out::println);

消息容器

一个自然的想法是将消息数据分组到指定的 Message 容器 class 中。类似的东西:

public class Message {
    private final Map<String, String> mProperties;

    public Message() {
        mProperties = new HashMap<>();
    }

    public String getValue(String key) {
        return mProperties.get(key);
    }

    public void put(String key, String value) {
        mProperties.put(key, value);
    }

    public static Message fromLines(List<String> lines) {
        Message message = new Message();
        for (String line : lines) {
            String[] data = line.split(":");
            message.put(data[0].trim(), data[1].trim());
        }
        return message;
    }

    // Other methods ...
}

注意方便的 Message#fromLines 方法。使用它你会得到一个 List<Message> 并且处理数据会更方便。

如何创建例如Data class,包含给定 uan 的所有字段?我可以看到你有一个带有 id(即 uan)的对象,并且有很多消息是针对这个对象的。

我提议使用这种方法并在同一实例中收集所有相关信息(与uan属于同一对象):

这是Data class:

final class Data {

    private String uan;
    private final List<Map<String, String>> events = new LinkedList<>();

    public Data(String uan) {
        this.uan = uan;
    }

    public String getUan() {
        return uan;
    }

    public boolean hasUan() {
        return uan != null && !uan.isEmpty();
    }

    public void set(Data data) {
        if (data != null)
            events.addAll(data.events);
    }

    public void addEvent(String key, String value) {
        if ("uan".equalsIgnoreCase(key))
            uan = value;
        else
            events.add(Collections.singletonMap(key, value));
    }
}

这是读取给定文件并检索 Map<String, Data> 的方法,键为 uan,值是该对象的所有数据:

private static final String BEGIN = "[MESSAGE BEGIN]";
private static final String END = "[MESSAGE END]";
private static final Pattern KEY_VALUE_PATTERN = Pattern.compile("\s*(?<key>[^:]+)\s*:\s*(?<value>[^:]+)\s*");

private static Map<String, Data> readFile(Reader reader) throws IOException {
    try (BufferedReader br = new BufferedReader(reader)) {
        Data data = null;
        Map<String, Data> map = new TreeMap<>();

        for (String str; (str = br.readLine()) != null; ) {
            if (str.equalsIgnoreCase(BEGIN))
                data = new Data(null);
            else if (str.equalsIgnoreCase(END)) {
                if (data != null && data.hasUan()) {
                    String uan = data.getUan();
                    map.putIfAbsent(uan, new Data(uan));
                    map.get(uan).set(data);
                }

                data = null;
            } else if (data != null) {
                Matcher matcher = KEY_VALUE_PATTERN.matcher(str);

                if (matcher.matches())
                    data.addEvent(matcher.group("key"), matcher.group("value"));
            }
        }

        return map;
    }
}

最后,客户端的样子是这样的:

Map<String, Data> map = readFile(new FileReader("data.txt"));