有没有更快的方法来解析Java中有效整数的字符串?

时间:2019-01-08 16:54:26

标签: json regex string performance java-8

我的应用程序期望json请求包含仅包含整数和可能为null值的(可能的多维)未排序数组。像[6, 2, [4, 3],[[[5], nil], 1]]

由于我无法解析无效的json,所以我不得不求助于使用正则表达式来完成肮脏的工作,而且它非常慢。

例如,上面的测试用例大约需要1.xx seconds才能完成,而包含10000个元素的平面数组要少于1 second

当前,我以字符串形式获取请求正文,然后应用正则表达式。

static ArrayList<Integer> getIntegers(String requestData) {
    // Apply a regex to the request body
    final String regularExpression = "([^\\d])+";
    // to get all the nested arrays
    Pattern pattern = Pattern.compile(regularExpression);
    String[] results = pattern.split(requestData);
    ArrayList<Integer> numbers = new ArrayList<>();
    // loop over the results and add to numbers array
    for (String result : results) {
        try {
            numbers.add(Integer.valueOf(result));
        } catch (NumberFormatException e) {
            // Catch and skip any non integers
        }

    }
    return numbers;
}

}

无论如何,我是否可以加快速度?或者是否有另一种性能更好的替代方法?如果我需要处理包含20000个元素的多维数组,那就太慢了。

5 个答案:

答案 0 :(得分:3)

我做了一些修改,并创建了以下课程:

class JsonNumberParser {
    private final String json;
    private final int length;
    private final List<Integer> result;
    private final char[] buffer = new char[64];
    private int bufferIndex = 0;

    public JsonNumberParser(String json) {
        this.json = json;
        length = json.length();
        result = new ArrayList<>(length);
    }

    public List<Integer> parse() {
        char c;
        for (int i = 0; i < length; i++) {
            c = json.charAt(i);
            // if we encounter a comma and the buffer contains data
            if (c == ',' && bufferIndex > 0) {
                // then we add the new number
                addBuffer();
                // and reset the buffer
                while (bufferIndex > 0) {
                    buffer[--bufferIndex] = '\0';
                }
            } else if (c == '-' || (c >= '0' && c <= '9')) {
                buffer[bufferIndex++] = c;
            }
        }
        // add the last possible number, if there was any
        if (bufferIndex > 0) {
            addBuffer();
        }

        // return the result
        return result;
    }

    private void addBuffer() {
        result.add(Integer.valueOf(new String(buffer, 0, bufferIndex)));
    }
}

当然,您可以将所有这些放到一个方法中,但是随后您会得到一些有关添加Integers的代码的重复。

此解析器的工作方式是,它使用缓冲区来缓冲数字,直到遇到逗号为止。这样,我们可以在json中包含大数字(在此实现中,最大为64位)。

您可以像以下示例中所示使用它:

List<Integer> integers = new JsonNumberParser(jsonRequest).parse();

关于性能,我希望它比使用Regex快得多。但是可惜我手头没有基准测试设置


  

请记住,这不是验证程序,因此json字符串:[[,,,]}]只会产生一个空的List


(也许)改进:我已经考虑并搜索了更多内容。以下是一些可以改善性能的改进:

1。。只需为buffer分配一个new int[64]即可重置extension MyHelpTableViewCell { override class var nib: UINib { return UINib(nibName: "MyHelpTableViewCell", bundle: nil) } ,这会产生更多的垃圾,但最终可能会更快。

2。。通过使用建议的答案here,可以改善数字的解析。它仅使用简单的旧数学,而没有创建字符串和解析整数。

答案 1 :(得分:2)

This answer已经指向正确的方向。重要的第一步是将昂贵的Pattern.compile操作移出该方法,因为Pattern实例可以重复使用。

此外,遍历数字匹配项可以使您免于创建split的数组。现在,您也可以跳过子String的创建:

static final Pattern NUMBER = Pattern.compile("\\d+");
static ArrayList<Integer> getIntegers(String requestData) {
    ArrayList<Integer> numbers = new ArrayList<>();
    Matcher m = NUMBER.matcher(requestData);
    while(m.find()) numbers.add(Integer.parseInt(requestData, m.start(), m.end(), 10));
    return numbers;
}

Integer.parseInt(CharSequence s, int beginIndex, int endIndex, int radix)已在Java 9中添加。如果您使用的是旧版本,则可以创建自己的变体。为了简化,现在仅支持10的基数:

static final Pattern NUMBER = Pattern.compile("-?\\d+");
static ArrayList<Integer> getIntegers(String requestData) {
    ArrayList<Integer> numbers = new ArrayList<>();
    Matcher m = NUMBER.matcher(requestData);
    while(m.find()) numbers.add(parseInt(requestData, m.start(), m.end()));
    return numbers;
}

static int parseInt(CharSequence cs, int start, int end) {
    int pos = start;
    if(pos >= end) throw format(cs, start, end);
    boolean negative = cs.charAt(pos) == '-';
    if((negative || cs.charAt(pos) == '+') && ++pos==end)
        throw format(cs, start, end);
    int value = 0;
    for(; pos < end; pos++) {
        int next = cs.charAt(pos) - '0';
        if(next < 0 || next > 9) throw format(cs, start, end);
        if(value < Integer.MIN_VALUE/10) throw size(cs, start, pos, end);
        value = value * 10 - next;
    }
    if(value > 0 || !negative && value == Integer.MIN_VALUE)
        throw size(cs, start, pos, end);
    return negative? value: -value;
}
private static RuntimeException format(CharSequence cs, int start, int end) {
    return start > end? new IndexOutOfBoundsException(end+" < "+start):
        new NumberFormatException(start == end?
            "empty string": cs.subSequence(start, end).toString());
}
private static RuntimeException size(CharSequence cs, int start, int pos, int end) {
    for(; pos < end; pos++) 
        if(cs.charAt(pos) < '0' || cs.charAt(pos) > '9') return format(cs, start, end);
    return new NumberFormatException(cs.subSequence(start, end)+" outside the int range");
}

答案 2 :(得分:0)

如果您遇到的问题是性能,那么我认为流API不是一个好的解决方案。

static ArrayList<Integer> getIntegers(String requestData) {
            char[] charArray = requestData.toCharArray();
             ArrayList<Integer> numbers = new ArrayList<>();
            for(char c : charArray) {

                if(Character.isDigit(c)) {
                    numbers.add(Integer.valueOf(c) - 48);
                }
            }
            return numbers;
        }

答案 3 :(得分:0)

如何使用堆栈?

我们可以升级balanced braces问题。

在迭代字符串时,如果字符为notBracket(),则它应该是一个数字。不用说,您将忽略所有逗号。同时,它还将验证数组结构。

这具有O(n)的摊销复杂度。

答案 4 :(得分:0)

通过解析正向模式(例如\d+)而不是负向模式([^\d]+),可以获得更好的性能。

private static final Pattern NUMBER = Pattern.compile("\\d+");

List<Integer> extractNumbersRegex(String str) throws IOException {
    Matcher m = NUMBER.matcher(str);
    ArrayList<Integer> numbers = new ArrayList<>();
    while (m.find()) {
        numbers.add(Integer.parseInt(m.group()));
    }
    return numbers;
}

这对于从字符串中提取是可以的,但是对于大数据,可能会切换到更高效的方式,而这种方式不依赖于正则表达式而是直接匹配字符:

List<Integer> extractNumbersHandcoded(String str) throws IOException {
    ArrayList<Integer> numbers = new ArrayList<>();
    int start = 0;
    while (start < str.length()) {
        if (Character.isDigit(str.charAt(start))) {
            break;
        } 
        start++;
    }
    int bufferedInt = 0;
    for (int i = start; i < str.length(); i++) {
        char c = str.charAt(i);
        if (Character.isDigit(c)) {
            bufferedInt = bufferedInt * 10 + (c - '0');
        } else {
            numbers.add(bufferedInt);
            bufferedInt = 0;
        }
    }
    return numbers;
}

如果您的数据与流一样大,则可以考虑使用Streamtokenizer的解决方案:

List<Integer> extractNumbersStreamTokenizer(String str) throws IOException {
    StreamTokenizer s = new StreamTokenizer(new StringReader(str));
    ArrayList<Integer> numbers = new ArrayList<>();
    int token;
    while ((token = s.nextToken()) != StreamTokenizer.TT_EOF) {
        if (token == StreamTokenizer.TT_NUMBER) {
            numbers.add((int) s.nval);
        }
    }
    return numbers;
}

所有解决方案均假定数据仅包含整数文字(不包含浮点文字)。