【问题标题】:remove certain lines from a StringBuffer从 StringBuffer 中删除某些行
【发布时间】:2024-01-22 20:09:01
【问题描述】:

旧版应用程序有一个巨大的字符串缓冲区(有时大小高达 1 Mb),它会按顺序处理以修改内容。我必须实现一个更改,其中我需要更新字符串缓冲区以删除以某些特定单词开头的一些行。有哪些可能的实现方式?

例如:

ABC:djfk kdjf kdsjfk#
ABC:jfue eijf iefe# 
DEL:kdjfi efe eei # 
DEL:ieeif dfddf dfdf#
HJU:heuir fwer ouier# 
ABC:dsf erereree ererre #

我需要删除以 DEL 开头的行。将字符串缓冲区拆分为字符串、处理行并再次连接字符串以创建字符串缓冲区会有点昂贵。请告诉我可能的解决方案。

谢谢

【问题讨论】:

  • 您提到了 legacy:您可以使用什么? java.util.Scanner? StringBuilder?
  • 该应用程序最近迁移到 JDK 1.5 - 我们刚刚使用 1.5 JDK 编译并运行该应用程序。所以,我相信我可以同时使用 Scanner 和 StringBuilder。

标签: java split stringbuffer


【解决方案1】:

将字符串缓冲区拆分为字符串, 处理线并再次加入 创建字符串缓冲区的字符串 会有点贵。

删除这些行实际上会更多成本,因为你最终会为你删除的每一行复制缓冲区的其余部分。

最快的方法可能是java.util.regex.Matcher.replaceAll() 来获取缓冲区的副本,而不包含您不想要的所有行。

【讨论】:

  • 链接已损坏。
【解决方案2】:

如果字符串缓冲区中的行由换行符分隔,您可以将其读入并创建一个新缓冲区。对于 1 meg 的缓冲区,这在几十毫秒内完成,并且比 Regex 更快。您可以创建一个自定义版本的 StringReader 来直接读取 StringBuffer 而不是转换为字符串以节省更多时间。


final String NEWLINE = System.getProperty("line.separator");
StringBuffer nuBuffer = new StringBuffer();
BufferedReader br = new BufferedReader(new StringReader(sbData.toString()));
String line;
while ( (line = br.readLine()) != null) {
    if (!line.startsWith("DEL:")) {  // don't copy lines starting with DEL:
        nuBuffer.append(line).append(NEWLINE);
    }
}
br.close();

【讨论】:

    【解决方案3】:

    可以有效地就地执行此操作。您必须以适当的时间间隔覆盖缓冲区中的字符,然后在逻辑上使用setLength 缩短缓冲区。这将是相当复杂的,但它会是就地和O(N)

    您之所以要覆盖而不是使用delete/insert,是因为那将是O(N^2),因为事情需要不必要地改变。

    在原地执行此操作非常简单,O(N) 但需要辅助缓冲区,从而使空间需求增加一倍。


    概念验证

    这是一个简单的概念验证。 removeIntervals 采用 StringBufferint[][] intervals。每个int[] 都假定为一对{ start, end } 值(半开范围,独占上限)。在线性时间和原地,这些间隔通过简单的overwriteStringBuffer 中删除。这适用于对间隔进行排序且不重叠并从左到右处理的情况。

    然后使用setLength 缩短缓冲区,截断尽可能多的已删除字符。

    static void overwrite(StringBuffer sb, int dst, int srcFrom, int srcTo) {
        for (int i = srcFrom; i < srcTo; i++) {
            sb.setCharAt(dst++, sb.charAt(i));
        }
    }
    static int safeGet(int[][] arr, int index, int defaultValue) {
        return (index < arr.length) ? arr[index][0] : defaultValue;
    }
    static void removeIntervals(StringBuffer sb, int[][] intervals) {
        int dst = safeGet(intervals, 0, 0);
        int removed = 0;
        for (int i = 0; i < intervals.length; i++) {
            int start = intervals[i][0];
            int end   = intervals[i][1];
            int nextStart = safeGet(intervals, i+1, sb.length());
            overwrite(sb, dst, end, nextStart);
            removed += end - start;
            dst += nextStart - end;
        }
        sb.setLength(sb.length() - removed);
    }
    

    那么我们可以如下测试:

        String text = "01234567890123456789";
        int[][][] tests = {
            { { 0, 5, },
            }, // simple test, removing prefix
            { { 1, 2, }, { 3, 4, }, { 5, 6, }
            }, // multiple infix removals
            { { 3, 7, }, { 18, 20, },
            }, // suffix removal
            {
            }, // no-op
            { { 0, 20 },
            }, // remove whole thing
            { { 7, 10 }, { 10, 13 }, {15, 15 }, 
            }, // adjacent intervals, empty intervals
        };
    
        for (int[][] test : tests) {
            StringBuffer sb = new StringBuffer(text);
            System.out.printf("> '%s'%n", sb);
            System.out.printf("- %s%n", java.util.Arrays.deepToString(test));
            removeIntervals(sb, test);
            System.out.printf("= '%s'%n%n", sb);
        }
    

    这会打印 (as seen on ideone.com):

    > '01234567890123456789'
    - [[0, 5]]
    = '567890123456789'
    
    > '01234567890123456789'
    - [[1, 2], [3, 4], [5, 6]]
    = '02467890123456789'
    
    > '01234567890123456789'
    - [[3, 7], [18, 20]]
    = '01278901234567'
    
    > '01234567890123456789'
    - []
    = '01234567890123456789'
    
    > '01234567890123456789'
    - [[0, 20]]
    = ''
    
    > '01234567890123456789'
    - [[7, 10], [10, 13], [15, 15]]
    = '01234563456789'
    

    获取间隔

    在这种特定情况下,间隔可以在初步通道中构建(使用indexOf),或者如果绝对需要,整个过程可以一次性完成。关键是,这绝对可以在线性时间内就地完成(如果绝对必要,可以一次性完成)。


    一个不合时宜的解决方案

    使用辅助缓冲区和正则表达式是不合适的。由于其简单性,它被提供以供考虑。除非可证明需要进一步优化(在证据分析结果之后),否则这应该足够了:

        String text =
            "DEL: line1\n" +
            "KEP: line2\r\n" +
            "DEL: line3\n" +
            "KEP: line4\r" +
            "DEL: line5\r" +
            "DEL: line6\r" +
            "KEP: line7\n" +
            "DEL: line8";
        StringBuffer sb = new StringBuffer(text);
        Pattern delLine = Pattern.compile("(?m)^DEL:.*$");
        String cleanedUp = delLine.matcher(sb).replaceAll("<deleted!>");
        System.out.println(cleanedUp);
    

    这打印(as seen on ideone.com):

    <deleted!>
    KEP: line2
    <deleted!>
    KEP: line4
    <deleted!>
    <deleted!>
    KEP: line7
    <deleted!>
    

    参考文献

    【讨论】:

      最近更新 更多