【问题标题】:String split and compare - fastest method字符串拆分和比较 - 最快的方法
【发布时间】:2013-11-30 22:21:24
【问题描述】:

我有一个类似的字符串:

1,2,3:3,4,5

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

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

我是如何做到的:

  1. 读取标准输入。
  2. 使用 string.split 进行拆分并将左侧部分存储在 HashSet 中。
  3. 将正确的部分存储在ArrayList 中。
  4. 遍历数组列表,使用contains()检查元素是否存在于HashSet中。

【问题讨论】:

  • 关于在 RHS 上重复您所说的关于 HashSet 的内容对我来说毫无意义。无论如何,IIUYC 重复项都会被忽略,因为您不计算出现次数。
  • 数字可以有多大?
  • 好的,所以它没有意义的原因是因为我不能忽略右侧的重复项,当我遇到一个重复项时,我必须标记它或者简单地说,用它做一些其他的事情。一个数字可以是 10 到 15 位之间的任何数字。
  • @Subashini P:那么您还必须为 RHS 使用 Set,因为 set.add 会告诉您重复的情况。

标签: java string performance split


【解决方案1】:
  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. 使用int[] 足够大的2 次幂长度作为哈希:

    // 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
    

【讨论】:

  • 你不是要使用掩码而不是模数吗?
  • @maaartinus 是的,还添加了有意义的代码来破坏哈希集中的元素簇。
  • 这看起来不错。我想用int i = h &gt;&gt;&gt; -hashSizePower 替换h ^= h &gt;&gt; (32 - hashSizePower); int i = h &amp; (hashSize - 1); 可以让它更快一点。我现在懒得尝试了,但我很好奇它比其他答案快了多少。
  • 谢谢大家 - 无意冒犯,但我是一个真正的新手,不知道如何使用你的代码 - 有点混乱
【解决方案2】:

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

  1. java.util.Scanner
  2. java.io.BufferedReader#readLine & java.util.StringTokenizer
  3. java.io.BufferedReader#readLine & java.lang.String#split

我不清楚哪种方法最适合这个问题,所以我决定尝试一下。我生成了测试数据,为每种方法实现了一个解析器,并对结果进行计时。

测试数据

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

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

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

测试代码

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

扫描码

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 毫秒
  • testdata_10k.txt - 39.5036 毫秒
  • testdata_100k.txt - 240.5626 毫秒
  • testdata_1000k.txt - 2671.5132 毫秒

字符串标记器

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

字符串.split

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

结论

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-03-06
    • 2011-01-04
    • 1970-01-01
    • 2018-03-14
    • 1970-01-01
    相关资源
    最近更新 更多