字符串拆分和比较 - 最快的方法

时间:2013-11-17 21:29:33

标签: java string performance split

我有一个字符串:

1,2,3:3,4,5

分隔符左侧的字符串需要与分隔符右侧的字符串(:)进行比较。现在,当我的意思是比较时,我实际上是要找出右侧部分(3,4,5)中的元素是否存在于左侧部分(1,2,3)的元素中。正确的部分可以包含重复项,这很好(显然我不能使用HashSet)。我已经完成了这个(详情如下),但我需要以最快的方式来分割和比较上面提到的字符串。

这纯粹是一个基于性能的问题,以找出哪个方法可以更快,因为我将使用的实际输入是巨大的(在任何一方)。只有一行,它将通过 stdin 读取。

我是如何做到这一点的:

  1. 阅读 stdin
  2. 使用string.split拆分并将左侧部分存储在HashSet
  3. 将正确的部分存储在ArrayList
  4. 使用contains()遍历数组列表,检查HashSet中是否存在该元素。

2 个答案:

答案 0 :(得分:2)

  1. 将输入读入byte[]数组,以将指针放在代码的一侧。

  2. 逐字节读取,在路上计算整数元素:

    int b = inputBytes[p++];
    int d = b - '0';
    if (0 <= d) {
        if (d <= 9) {
            element = element * 10 + d;
        } else {
            // b == ':'
        }
    } else {
        // b == ','
        // add element to the hash; element = 0;
        ...
    }
    if (p == inputBytesLength) {
        inputBytesLength = in.read(inputBytes);
        if (inputBytesLength == 0) { ... }
        p = 0;
    }
    
  3. 使用长度为2的足够大的int[]作为哈希:

    // as add()
    int h = element * 0x9E3779B9;
    int i = h >>> (32 - hashSizePower);
    while (hash[i] != 0) {
        if (--i < 0) i += hashSize;
    }
    hash[i] = element;
    
    // contains() similarly
    

答案 1 :(得分:2)

假设一行输入符合JVM堆,那么从Java中的输入解析字符串的三种常用方法是:

  1. java.util.Scanner
  2. java.io.BufferedReader#readLine&amp; java.util.StringTokenizer
  3. java.io.BufferedReader#readLine&amp; java.lang.String#split
  4. 对我而言,哪种方法最适合这个问题并不明显,所以我决定尝试一下。我生成了测试数据,为每种方法实现了解析器,并对结果进行了定时。

    测试数据

    我生成了4个测试数据文件:

    • testdata_1k.txt - 大小20KB
    • testdata_10k.txt - 大小205KB
    • testdata_100k.txt - 大小2MB
    • testdata_1000k.txt - 尺寸20M

    我生成的文件与您描述的格式相匹配。每个,分隔元素都是一个随机整数。如果:,文件名中的数字描述了每一侧的元素数量。例如,testdata_1k.txt左侧有1,000个元素,右侧有1,000个元素。

    测试代码

    这是我用来测试每​​种方法的代码。请注意,这些不是生产质量代码的示例。

    扫描程序代码

    public Map<String, Boolean> scanner(InputStream stream) {
        final Scanner in = new Scanner(new BufferedInputStream(stream));
        final HashMap<String, Boolean> result = new HashMap<String, Boolean>();
        final HashSet<String> left = new HashSet<String>();
    
        in.useDelimiter(",");
        boolean leftSide = true;
        while (in.hasNext()) {
            String token = in.next();
            if (leftSide) {
                int delim = token.indexOf(':');
                if (delim >= 0) {
                    left.add(token.substring(0, delim));
                    String rightToken = token.substring(delim + 1, token.length());
                    result.put(rightToken, left.contains(rightToken));
                    leftSide = false;
                } else {
                    left.add(token);
                }
            } else {
                result.put(token, left.contains(token));
            }
        }
        return result;
    }
    

    StringTokenizer代码

    public Map<String, Boolean> stringTokenizer(InputStream stream) throws IOException {
        final BufferedReader in = new BufferedReader(new InputStreamReader(stream));
        final HashMap<String, Boolean> result = new HashMap<String, Boolean>();
    
        final StringTokenizer lineTokens = new StringTokenizer(in.readLine(), ":");
        final HashSet<String> left = new HashSet<String>();
        if (lineTokens.hasMoreTokens()) {
            final StringTokenizer leftTokens = new StringTokenizer(lineTokens.nextToken(), ",");
            while (leftTokens.hasMoreTokens()) {
                left.add(leftTokens.nextToken());
            }
        }
        if (lineTokens.hasMoreTokens()) {
            final StringTokenizer rightTokens = new StringTokenizer(lineTokens.nextToken(), ",");
            while (rightTokens.hasMoreTokens()) {
                String token = rightTokens.nextToken();
                result.put(token, left.contains(token));
            }
        }
        return result;
    }
    

    String.split代码

    public Map<String, Boolean> split(InputStream stream) throws IOException {
        final BufferedReader in = new BufferedReader(new InputStreamReader(stream));
        final HashMap<String, Boolean> result = new HashMap<String, Boolean>();
    
        final String[] splitLine = in.readLine().split(":");
        final HashSet<String> left = new HashSet<String>(Arrays.asList(splitLine[0].split(",")));
    
        for (String element : splitLine[1].split(",")) {
            result.put(element, left.contains(element));
        }
        return result;
    }
    

    时序

    我针对每个文件运行了6次。我扔掉了第一个样品。以下代表剩余5个样本的平均值。

    扫描仪

    • testdata_1k.txt - 23.2948 millis
    • testdata_10k.txt - 39.5036毫升
    • testdata_100k.txt - 240.5626 millis
    • testdata_1000k.txt - 2671.5132 millis

    的StringTokenizer

    • testdata_1k.txt - 31.2344 millis
    • testdata_10k.txt -14.7926 millis
    • testdata_100k.txt - 102.6412 millis
    • testdata_1000k.txt - 1353.073 millis

    String.split

    • testdata_1k.txt - 8.9596 millis
    • testdata_10k.txt - 7.8396 millis
    • testdata_100k.txt - 63.4854 millis
    • testdata_1000k.txt - 947.8384 millis

    结论

    假设您的数据适合JVM堆,与String.splitStringTokenizer相比,很难超过Scanner的解析速度。