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,代表记录的类型。因此,将剩余字符拆分成的字段数以及每个字段的宽度取决于第一个字符。
我想到了几个办法:
- 将
之后的每个字符串转换成某种流并消耗第一个字符,switch-cases根据数字,然后根据情况消耗适当长度的字符。
- 拆分行并使用 String.split 保留
之后的第一个子字符串,然后使用 String.charAt(int i) 遍历每个字符,switch-case 在 i==0 ,然后迭代并存储适当的编号。字符数。
- 使用 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 你的替代方案对我来说似乎很有用,因为它们都专注于你必须迭代或流过字符串的想法。
每种记录类型都可以以不同的方式处理并生成不同类型的对象,具体取决于格式。我们了解的不够多,无法提出更具体的建议。
我必须构建一个 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>
...
我只需要考虑
我想到了几个办法:
- 将
之后的每个字符串转换成某种流并消耗第一个字符,switch-cases根据数字,然后根据情况消耗适当长度的字符。 - 拆分行并使用 String.split 保留
之后的第一个子字符串,然后使用 String.charAt(int i) 遍历每个字符,switch-case 在 i==0 ,然后迭代并存储适当的编号。字符数。 - 使用 String.charAt(int i)、switch-case at i==27 等遍历完整行中的每个字符。
这些方法中的哪一个应该产生最易于维护的代码,同时又不影响 speed/resources?
如果我应该选择 1,对于我的用例,将字符串转换为流并使用它的适当方法是什么?例如:转换为 ByteArrayInputStream
,或转换为 Stream<Character>
(函数式编程)。
当前日志文件有 70,000 行,并且还在增加。
编辑:日志文件是 Windows 上的文本文件,以 CRLF 结尾。
我用过下面的代码
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 你的替代方案对我来说似乎很有用,因为它们都专注于你必须迭代或流过字符串的想法。
每种记录类型都可以以不同的方式处理并生成不同类型的对象,具体取决于格式。我们了解的不够多,无法提出更具体的建议。