从字符串中提取数据的快速方法

时间:2017-05-18 08:37:20

标签: java regex parsing arraylist

我的OkHttpClient响应如下:

{"CUSTOMER_ID":"928941293291"}
{"CUSTOMER_ID":"291389218398"}
{"CUSTOMER_ID":"1C4DC4FC-02Q9-4130-S12B-762D97FS43C"}
{"CUSTOMER_ID":"219382198"}
{"CUSTOMER_ID":"282828"}
{"CUSTOMER_ID":"21268239813"}
{"CUSTOMER_ID":"1114445184"}
{"CUSTOMER_ID":"2222222222"}
{"CUSTOMER_ID":"99218492183921"}

我想在minId和maxId之间提取所有类型为long的customerId(然后跳过1C4DC4FC-02Q9-4130-S12B-762D97FS43C)。 这是我的实施:

final List<String> customerIds = Arrays.asList(response.body().string()
                    .replace("CUSTOMER_ID", "")
                    .replace("\"", "")
                    .replace("{", "").replace(":", "")
                    .replace("}", ",").split("\\s*,\\s*"));
for (final String id : customerIds) {
    try {
        final Long idParsed = Long.valueOf(id);
        if (idParsed > minId && idParsed < maxId) {
            ids.add(idParsed);
        }
    } catch (final NumberFormatException e) {
        logger.debug("NumberFormatException", e);
    }
}

我有一长串customerId(大约1M),那么性能非常重要。这是我行为的最佳实现吗?

6 个答案:

答案 0 :(得分:1)

由于你有一个大文件,然后逐行阅读内容可能是一种方法,不要替换CUSTOMER_ID,而是定义一个更好的正则表达式模式。

按照您的方法:替换USER_ID并使用正则表达式:

String x = "{\"CUSTOMER_ID\":\"928941293291\"}{\"CUSTOMER_ID\":\"291389218398\"}{\"CUSTOMER_ID\":\"1C4DC4FC-02Q9-4130-S12B-762D97FS43C\"}"
            + "{\"CUSTOMER_ID\":\"99218492183921\"}";

x = x.replaceAll("\"CUSTOMER_ID\"", "");
Pattern p = Pattern.compile("\"([^\"]*)\"");
Matcher m = p.matcher(x);
while (m.find()) {
    System.out.println(m.group(1));
}

或实现与:“”} 之间的所有匹配的正则表达式

String x = "{\"CUSTOMER_ID\":\"928941293291\"}{\"CUSTOMER_ID\":\"291389218398\"}{\"CUSTOMER_ID\":\"1C4DC4FC-02Q9-4130-S12B-762D97FS43C\"}"
            + "{\"CUSTOMER_ID\":\"99218492183921\"}";

Pattern p = Pattern.compile(":\"([^\"]*)\"}");
Matcher m = p.matcher(x);
while (m.find()) {
     System.out.println(m.group(1));
}

所以无需替换 CUSTOMER_ID

答案 1 :(得分:1)

我会使用BufferedReader逐行读取字符串 https://www.mkyong.com/java/how-to-read-file-from-java-bufferedreader-example/

每行我会减少替换量

String id= line.replace({"CUSTOMER_ID":", "");
id = id.substring(0, id.length-2); //to avoid one more replace

然后应用尝试解析长逻辑,将成功尝试添加到列表中。

答案 2 :(得分:1)

尽量避免异常!当10%-20%的数字解析失败时,它需要多10倍的时间来执行它(你可以为它编写一个litte测试)。

如果您的输入与您展示的完全相同,则应使用便宜的操作: 使用BufferedReader逐行读取文件(如前所述)或(如果您将整个数据作为字符串)我们StringTokenizer来处理每行分隔。 每行以{"CUSTOMER_ID":"开头,以"}结尾。不要使用replace或正则表达式(更糟糕的是)删除它!只需使用一个简单的substring

String input = line.substring(16, line.length() - 2)

为避免异常,您需要找到用于区分id和UUID(?)的度量标准,以便您的解析无异常。例如,您的ID将是positiv,但您的UUID包含减号,或者long只能包含20个数字,但您的UUID包含35个字符。所以这是一个简单的if-else而不是try-catch。

对于那些认为在解析数字时没有捕获NumberFormatException的人:如果有一个无法解析的id,整个文件就会损坏,这意味着你不应该继续尝试但是要努力失败。

这是一个小小的测试,可以看到捕获异常和测试输入之间的性能差异:

long REPEATS = 1_000_000, startTime;
final String[] inputs = new String[]{"0", "1", "42", "84", "168", "336", "672", "a-b", "1-2"};
for (int r = 0; r < 1000; r++) {
    startTime = System.currentTimeMillis();
    for (int i = 0; i < REPEATS; i++) {
        try {
            Integer.parseInt(inputs[i % inputs.length]);
        } catch (NumberFormatException e) { /* ignore */ }
    }
    System.out.println("Try: " + (System.currentTimeMillis() - startTime) + " ms");
    startTime = System.currentTimeMillis();
    for (int i = 0; i < REPEATS; i++) {
        final String input = inputs[i % inputs.length];
        if (input.indexOf('-') == -1)
            Integer.parseInt(inputs[i % inputs.length]);
    }
    System.out.println("If:  " + (System.currentTimeMillis() - startTime) + " ms");
}

我的结果是:

  • ~20ms(测试)和~200ms(捕获),20%无效输入。
  • ~22ms(测试)和~130ms(捕获),10%无效输入。

由于JIT或其他优化,这些类型的性能测试很容易做到。但我认为你可以看到一个方向。

答案 3 :(得分:0)

您可以忽略所有非数字字段

long[] ids = 
    Stream.of(response.body().string().split("\""))
          .mapToLong(s -> parseLong(s))
          .filter(l -> l > minId && i < maxId)
          .toArray();

static long parseLong(String s) {
    try {
        if (!s.isEmpty() && Character.isDigit(s.charAt(0)))
            return Long.parseLong(s);
    } catch (NumberFormatException expected) {
    }
    return Long.MIN_VALUE
}

或者如果您使用的是Java 7

List<Long> ids = new ArrayList<>();
for (String s : response.body().string().split("\"")) {
    long id = parseLong(s);
    if (id > minId && id < maxId)
        ids.add(id);
}

答案 4 :(得分:0)

您可以使用Files.lines()来传输文件中的数据。在这里,我演示了使用stream中的List

List<String> sample = Arrays.asList(
        "{\"CUSTOMER_ID\":\"928941293291\"}",
        "{\"CUSTOMER_ID\":\"291389218398\"}",
        "{\"CUSTOMER_ID\":\"1C4DC4FC-02Q9-4130-S12B-762D97FS43C\"}",
        "{\"CUSTOMER_ID\":\"219382198\"}",
        "{\"CUSTOMER_ID\":\"282828\"}",
        "{\"CUSTOMER_ID\":\"21268239813\"}",
        "{\"CUSTOMER_ID\":\"1114445184\"}",
        "{\"CUSTOMER_ID\":\"2222222222\"}",
        "{\"CUSTOMER_ID\":\"99218492183921\"}"
);

static final long MIN_ID = 1000000L;
static final long MAX_ID = 1000000000000000000L;

public void test() {
    sample.stream()
            // Extract CustomerID
            .map(s -> s.substring("{\"CUSTOMER_ID\":\"".length(), s.length() - 2))
            // Remove any bad ones - such as UUID.
            .filter(s -> s.matches("[0-9]+"))
            // Convert to long - assumes no number too big, add a further filter for that.
            .map(s -> Long.valueOf(s))
            // Apply limits.
            .filter(l -> MIN_ID <= l && l <= MAX_ID)
            // For now - just print them.
            .forEach(s -> System.out.println(s));
}

答案 5 :(得分:0)

首先,您应该尝试逐行读取文件。然后从每一行中提取id,如果它与模式匹配并将其收集到一个数组中。这是在python中实现的类似解决方案。

import re
# Open the file
with open('cids.json') as f:
    # Read line by line
    for line in f:
       try:
           # Try to extract matching id with regex pattern
           _id = re.search('^{[\w\W]+:"([A-Z\d]+-[A-Z\d]+-[A-Z\d]+-[A-Z\d]+-[A-Z\d]+)"}', line).group(1)
           customer_ids.append(_id)
       except:
           print('No match')