检查非常长的String时出现Java堆空间错误

时间:2018-01-22 16:30:12

标签: java string heap

我有一个指定的String,我需要检查此String是否有指定长度的相同部分。例如,如果字符串是" abcdab"并且长度指定为2,该字符串中的相同部分是" ab" (总是寻找最重复的一个)。为了获得最佳性能,我将算法重新设计了4-5次,但最后,如果String的长度为1m +,则会抛出Java堆空间错误。

所以我的问题是:如何解决错误,也许还有另一种检查相同部分的方法,或者可能还有其他一些方法来构建整个算法。我想出了一个可能的解决方案,但它的工作速度非常慢,所以我只要求解决方案的速度和我当前的算法一样快,或者甚至更快。这是当前的代码:

int length = 2;
String str = "ababkjdklfhcjacajca";
ArrayList<String> h = new ArrayList<String>(); 
h.add(str.substring(0, length));
ArrayList<Integer> contains = new ArrayList<Integer>();
contains.add(1);

String c;
for (int g = 1; g < str.length()-length+1; g++) {
    c = str.substring(g, length+g);
    for (int e = 0; e < h.size(); e++) {
        if (h.get(e).charAt(0) == c.charAt(0) && h.get(e).charAt(length-1) == c.charAt(length-1)) {
            if (h.get(e).equals(c)) {
                contains.set(e, contains.get(e)+1);
                break;
            }
        }
        else if (e+1 == h.size()) {
            h.add(c);
            contains.add(1);
            break;
        }
    }

}

ArrayList h存储String的每个唯一部分,ArrayList包含表示该字符串的每个唯一部分的重复数量。 字符串c是主要问题(这里是java堆空间点)。它逐渐表示字符串的每个部分,然后将其存储在ArrayList h中(如果c是唯一的)。 之后,我会使用ArrayLists找到最重复的内容并打印出来。

4 个答案:

答案 0 :(得分:1)

如果你想有效地进行搜索,时间和记忆,我建议你:

  1. 首先,创建一个包含每个字符出现次数的简单字符直方图。如果子串的第一个字符的出现次数少于我们到目前为止找到的最常见的子字符串,我们可以跳过这个子字符串。

  2. 我们不是创建包含字符内容副本的子字符串,而是使用CharBuffer wraps字符串并调整其positionlimit来表示一个子序列。当然,一旦缓冲区作为键存储在地图中,我们就不能修改缓冲区,因此我们在每个键存储在地图中时为每个键创建一个新的缓冲区。因此,我们为每个不同的子字符串创建最多一个CharBuffer,这些缓冲区仍然只包装String而不是复制任何字符数据

  3. public static Map<String,Integer> mostCommonSubstring(String s, int len) {
        int[] charHistogram = new int[Character.MAX_VALUE+1];
        s.chars().forEach(ch -> charHistogram[ch]++);
        int most = 0;
        HashMap<Buffer, Integer> subStrings = new HashMap<>();
        CharBuffer cb = CharBuffer.wrap(s);
        for(int ix = 0, e = s.length()-len; ix <= e; ix++) {
            if(charHistogram[s.charAt(ix)] < most) continue;
            int num = subStrings.merge(cb.limit(ix+len).position(ix), 1, Integer::sum);
            if(num == 1) cb = CharBuffer.wrap(s);
            if(num > most) most = num;
        }
        final int mostOccurences = most;
        return subStrings.entrySet().stream()
            .filter(e -> e.getValue() == mostOccurences)
            .collect(Collectors.toMap(e -> e.getKey().toString(), Map.Entry::getValue));
    }
    

    前两行创建直方图

        int[] charHistogram = new int[Character.MAX_VALUE+1];
        s.chars().forEach(ch -> charHistogram[ch]++);
    

    在循环中

            if(charHistogram[s.charAt(ix)] < most) continue;
    

    检查当前子字符串的第一个字符是否比我们到目前为止找到的最常见的字符串少,并在这种情况下跳过后续测试。

    下一行调整当前缓冲区以表示子字符串,并更新映射,将缓冲区与1相关联(如果不存在)或将1添加到现有映射的计数中。

            int num = subStrings.merge(cb.limit(ix+len).position(ix), 1, Integer::sum);
    

    我们使用返回的值来检测merge操作是否在地图中创建了一个新的关联,只有在结果为1的情况下才会这样。在这种情况下,我们不能在之后修改缓冲区,因此,创建一个新的

            if(num == 1) cb = CharBuffer.wrap(s);
    

    然后,我们使用结果来跟踪最高出现次数

            if(num > most) most = num;
    

    循环后的最后一步很简单。我们已经拥有最多的出现次数,过滤地图以保持条目具有匹配的数字(可能存在平局)并创建新地图,现在将缓冲区转换为String实例,因为我们不希望保留对原始String的引用,它只影响少数结果子串。

        final int mostOccurences = most; // needed because most is not “effectively final”
        return subStrings.entrySet().stream()
            .filter(e -> e.getValue() == mostOccurences)
            .collect(Collectors.toMap(e -> e.getKey().toString(), Map.Entry::getValue));
    

答案 1 :(得分:0)

您可以尝试使用Map来跟踪子字符串的出现次数,例如:

public class Test {
    public static void main(String[] args) {
        String test = "asdasdagagsjug8afhnqh3gbq29873brfuysbf78sdgy0yg7483wthsddbfahbfasfga78dftg78VGFIBDVGIUASF8928HWEAWD";
        int substringLength = 2;

        Map<String, Integer> tracker = new HashMap<>();

        for(int i = 0; i < test.length() - substringLength + 1; i ++) {
            String subString = test.substring(i, substringLength + i);
            tracker.compute(subString, (k,v) -> v == null ? 1 : v + 1);
        }

        for(String key : tracker.keySet()) {
            System.out.println("Substring: " + key + " has " + tracker.get(key) + " occurences.");
        }
    }
}

在您的示例中,您将分别跟踪每个事件。例如,在字符串中:

ababababababababababababa

您可以将每个子字符串分别存储在列表h中。上面的代码将在1映射中跟踪String ab。事实上,它打印:

Substring: ab has 12 occurences.
Substring: ba has 12 occurences.

我希望这能给你一个开始的地方。

答案 2 :(得分:0)

您可以通过将位置g添加到g + 1到散列表来迭代字符串一次,然后使用if来检查出现是否在哈希表中。例如,表中abcab为ab - > 2,bc-> 1,ca-> 1。

int length = 2;
String str = "ababkjdklfhcjacajca";
Hashtable<String, Integer> identicalStrings = new Hashtable<String, Integer>();
h.add(str.substring(0, length));

for (int i = 0; i < str.length() - 1; i++) {
   if(!identicalStrings.contains(str.substring(i, i+2)) {
        identicalStrings.numbers.put(str.substring(i, i+2), 1);
   } else {
       identicalStrings.put(str.substring(i, i+2), identicalStrings.get(str.substring(i, i+2)) + 1);
   }

}

我写得很快,所以我不确定它是否编译,但类似的东西应该有效。

答案 3 :(得分:0)

使用Map(表示每个子字符串的出现次数),PatternMatcher类是一个有趣的研究案例。

对我来说另一件有趣的事情是,例如子串aaaaa中出现2次;而不是我最初计算的一次,使用replaceAll方法(用于计算单个字符)。


我的解决方案
(我用10 ^ 8 = 100,000,000个字符长String对它进行了测试,效果很好。似乎存在的唯一边界是the length of the String输入)

public static Map<String, Integer> getMostRepeatedSubstring(int length, String str) {
    HashSet<String> possibleSubstrings = new HashSet<>();
    Map<String, Integer> ans = new HashMap<>();
    int max = 0;

    // Create a list of all the unique substrings of "str" with a certain length
    for(int i=0; i<str.length()-length; i++) {
        String curr = str.substring(i, i+length);
        possibleSubstrings.add(curr);
        // "curr" is added only if it doesn't already appear within "possibleSubstrings"
    }

    for(String sub : possibleSubstrings) {
        Pattern pattern = Pattern.compile(sub, Pattern.LITERAL);
        Matcher matcher = pattern.matcher(str);
        int currentOccurrences = 0;
        while (matcher.find())
            currentOccurrences ++;

        if(currentOccurrences > max) {           // We have a new winner!
            max = currentOccurrences;
            ans.clear();
            ans.put(sub, currentOccurrences);
        }
        else if (currentOccurrences == max) {    // We have a tide
            ans.put(sub, currentOccurrences);
        }
    }

    return ans;
}

编辑:感谢 @Holger 进行重要改进!