Java:使用流与迭代字符来解析文件中的原始数据

Java: using streams vs. iterating over chars for parsing raw data from file

我必须构建一个 JAVA 应用程序来解析存储在日志文件中的数据。文件 reader 代码我使用 returns 行作为字符串数组,如下所示。

R:16-08-2021 18:32:09 <STX>1120007 - 01           <ETX>0x3A
S:16-08-2021 18:32:09 <ACK>
R:16-08-2021 18:32:09 <STX>2xxxxxxxxxx<ETX>0x3F
S:16-08-2021 18:32:09 <ACK>
R:16-08-2021 18:32:09 <STX>300 15.3  0.6  1.0  0.6  3.0 13.7 83.8  0.0  0.0  0.0  0.0  910<ETX>0x35
S:16-08-2021 18:32:09 <ACK>
R:16-08-2021 18:32:09 <STX>4   02 6 651<ETX>0x11
S:16-08-2021 18:32:09 <ACK>
R:16-08-2021 18:32:09 <STX>5 1A1A  B  15 650  15  76  87    7.16  0.6<ETX>0x7F
S:16-08-2021 18:32:09 <ACK>
R:16-08-2021 18:32:09 <STX>5 2A1B  V  15 650  87 101 123   10.66  1.0<ETX>0x78
S:01-09-2021 16:06:08 <ACK>
R:01-09-2021 16:06:08 <STX>6<ETX>0x35
S:01-09-2021 16:06:08 <ACK>
R:01-09-2021 16:06:08 <STX>7  1   71.350   71.300   71.340   71.338   71.346   71.347   71.348   71.349   71.350   71.350<ETX>0xF
S:01-09-2021 16:06:08 <ACK>
R:01-09-2021 16:06:09 <STX>7  2   71.350   71.350   71.350   71.350   71.350   71.350   71.350   71.350   71.350   71.350<ETX>0x6
S:01-09-2021 16:06:09 <ACK>
R:01-09-2021 16:06:09 <STX>7  3   71.350   71.350   71.350   71.350   71.350   71.350   71.350   71.350   71.350   71.350<ETX>0x7
S:01-09-2021 16:06:09 <ACK>
R:01-09-2021 16:06:09 <STX>7  4   71.350   71.350   71.350   71.350   71.350   71.350   71.350   71.350   71.350   71.350<ETX>0x0
S:01-09-2021 16:06:09 <ACK>
...

我只需要考虑 之间的子字符串。第一个字符(从 1 到 7 的整数),也称为 header,代表记录的类型。因此,将剩余字符拆分成的字段数以及每个字段的宽度取决于第一个字符。

我想到了几个办法:

  1. 之后的每个字符串转换成某种流并消耗第一个字符,switch-cases根据数字,然后根据情况消耗适当长度的字符。
  2. 拆分行并使用 String.split 保留 之后的第一个子字符串,然后使用 String.charAt(int i) 遍历每个字符,switch-case 在 i==0 ,然后迭代并存储适当的编号。字符数。
  3. 使用 String.charAt(int i)、switch-case at i==27 等遍历完整行中的每个字符。

这些方法中的哪一个应该产生最易于维护的代码,同时又不影响 speed/resources?

如果我应该选择 1,对于我的用例,将字符串转换为流并使用它的适当方法是什么?例如:转换为 ByteArrayInputStream,或转换为 Stream<Character>(函数式编程)。

当前日志文件有 70,000 行,并且还在增加。

编辑:日志文件是 Windows 上的文本文件,以 CRLF 结尾。 是从文本文件中原样读取的(5 个字符),它不是日志文件中的控制字节。

我用过下面的代码

public class LogParser {

private static long lineNo = 69408;
private static String fileLocation = "C:\xxx\log\log_00.log";

public static void main(String[] args) {
    parseRawData();
}

private static void parseRawData() {
    try {
        Stream<String> nextLinesStream = Files.lines(Paths.get(fileLocation), StandardCharsets.UTF_8)
                .skip(lineNo - 1).filter(s -> s.contains("<STX>"));

        nextLinesStream.forEachOrdered(s -> {
            parseLine(s);
        });

    } catch (IOException e) {
        e.printStackTrace();
    }
}

private static void parseLine(String s) {

    String inter = s.split("\>", 3)[1];
    String line = inter.substring(0, inter.length() - 4);

    System.out.println(line);
    
}

}

获取输出

1120006 - 04           
209010023
300 12.6  0.7  1.0  0.8  3.7 11.0 85.4  0.0  0.0  0.0  0.0  956
4   02 6 651
5 1A1A  B  15 650  15  77  89    8.39  0.7
5 2A1B  V  15 650  89 104 121   13.01  1.0
5 3F    V  15 650 121 139 151   10.55  0.8
5 4LA1C+V  15 650 151 166 186   47.41  3.7
5 5SA1C V  15 650 186 203 256  118.68 11.0
5 6A0   V  15 650 256 308 650 1101.05 85.4
6
7  1   71.350   71.300   71.340   71.338   71.346   71.347   71.348   71.349   71.350   71.350
7  2   71.350   71.350   71.350   71.350   71.350   71.350   71.350   71.350   71.350   71.350
7  3   71.350   71.350   71.350   71.350   71.350   71.350   71.350   71.350   71.350   71.350
...
7 29   75.540   75.673   75.813   75.957   76.114   76.274   76.448   76.634   76.896   77.296
7 30   77.929   78.745   79.635   80.516   81.720   84.037   88.321   94.472  101.119  106.648
7 31  110.383  114.136  128.814  178.810  275.595  399.911  502.001  562.222  571.605  551.175
7 32  505.796  453.784  400.256  348.288  299.139  254.605  216.039  184.069  158.402  138.224
7 33  122.565  110.508  101.285   94.268   88.971   84.988   82.012   79.790   78.130   76.884
7 34   75.943   75.225   74.674   74.230   73.881   73.592   73.365   73.161   72.985   72.839
...
7 64   71.350   71.350   71.350   71.350   71.350   71.350   71.350   71.350   71.350   71.350
7 65   71.350   71.350   71.350   71.350   71.350   71.350   71.350   71.350   71.350   71.350
7 66   71.350    0.000    0.000    0.000    0.000    0.000    0.000    0.000    0.000    0.000

此数据用于绘制 HPLC(色谱)图。在 header 中每次出现“1”时,我将创建一个新的自定义 object 类型的 MyGraph 来保存后续记录的数据。记录“7”中的数据(例如 71.350 等)是将存储在 ArrayList 中的原始数据点(总共约 600 个,每个 9 个字符长),该图已根据最小值和最大值进行缩放和归一化原始数字的值,以及来自“5”条记录的一些值。缩放后的点将存储在另一个 ArrayList 中。我计划将这些点发送到绘制 REST API 的图表。从 API 接收到的图像数据将与“2”记录中的 HPLC 样品编号一起发送到数据库。

你可以这样做。

String data = """
R:16-08-2021 18:32:09 <STX>1120007 - 01           <ETX>0x3A
S:16-08-2021 18:32:09 <ACK>
R:16-08-2021 18:32:09 <STX>2xxxxxxxxxx<ETX>0x3F
S:16-08-2021 18:32:09 <ACK>
R:16-08-2021 18:32:09 <STX>300 15.3  0.6  1.0  0.6  3.0 13.7 83.8  0.0  0.0  0.0  0.0  910<ETX>0x35
S:16-08-2021 18:32:09 <ACK>
R:16-08-2021 18:32:09 <STX>4   02 6 651<ETX>0x11
S:16-08-2021 18:32:09 <ACK>
R:16-08-2021 18:32:09 <STX>5 1A1A  B  15 650  15  76  87    7.16  0.6<ETX>0x7F
S:16-08-2021 18:32:09 <ACK>
R:16-08-2021 18:32:09 <STX>5 2A1B  V  15 650  87 101 123   10.66  1.0<ETX>0x78
S:01-09-2021 16:06:08 <ACK>
R:01-09-2021 16:06:08 <STX>6<ETX>0x35
S:01-09-2021 16:06:08 <ACK>
R:01-09-2021 16:06:08 <STX>7  1   71.350   71.300   71.340   71.338   71.346   71.347   71.348   71.349   71.350   71.350<ETX>0xF
S:01-09-2021 16:06:08 <ACK>
R:01-09-2021 16:06:09 <STX>7  2   71.350   71.350   71.350   71.350   71.350   71.350   71.350   71.350   71.350   71.350<ETX>0x6
S:01-09-2021 16:06:09 <ACK>
R:01-09-2021 16:06:09 <STX>7  3   71.350   71.350   71.350   71.350   71.350   71.350   71.350   71.350   71.350   71.350<ETX>0x7
S:01-09-2021 16:06:09 <ACK>
R:01-09-2021 16:06:09 <STX>7  4   71.350   71.350   71.350   71.350   71.350   71.350   71.350   71.350   71.350   71.350<ETX>0x0
S:01-09-2021 16:06:09 <ACK>
    """;
        
Pattern p = Pattern.compile(".*?\<STX\>(.*)\<ETX\>.*");

Arrays.stream(data.split("\n"))
        .map(p::matcher).filter(Matcher::find).map(mr->whatType(mr.group(1))).
        .forEach(System.out::println);


public static String whatType(String v) {
    char header = v.charAt(0);
    return "header type " + header + " -> " + v.substring(1);
}

打印

header type 1 -> 120007 - 01           
header type 2 -> xxxxxxxxxx
header type 3 -> 00 15.3  0.6  1.0  0.6  3.0 13.7 83.8  0.0  0.0  0.0  0.0  910
header type 4 ->    02 6 651
header type 5 ->  1A1A  B  15 650  15  76  87    7.16  0.6
header type 5 ->  2A1B  V  15 650  87 101 123   10.66  1.0
header type 6 -> 
header type 7 ->   1   71.350   71.300   71.340   71.338   71.346   71.347   71.348   71.349   71.350   71.350
header type 7 ->   2   71.350   71.350   71.350   71.350   71.350   71.350   71.350   71.350   71.350   71.350
header type 7 ->   3   71.350   71.350   71.350   71.350   71.350   71.350   71.350   71.350   71.350   71.350
header type 7 ->   4   71.350   71.350   71.350   71.350   71.350   71.350   71.350   71.350   71.350   71.350

然后您可以发送它并package/process根据类型。

当源是一个可能很大的文件时,使用 Scanner。例如

try(Scanner sc = new Scanner(path)) {
    sc.findAll("<STX>(.)(.*)<ETX>")
        .map(mr -> {
            char header = mr.group(1).charAt(0);
            String recordData = mr.group(2);
            return "type " + header + ", data " + recordData;
        })
        .forEach(System.out::println);
}

None 你的替代方案对我来说似乎很有用,因为它们都专注于你必须迭代或流过字符串的想法。

每种记录类型都可以以不同的方式处理并生成不同类型的对象,具体取决于格式。我们了解的不够多,无法提出更具体的建议。