内存高效替换功能

时间:2017-10-20 07:00:57

标签: java string

我正在处理一个代表html页面的大字符串,然后进行处理。我所做的是:

String data = <HTML PAGE CONTENT>;

// remove first/last appostrove
data = data.substring(1, data.length() - 1);
data = StringUtils.replace(data, "\\u003C", "<");
data = StringUtils.replace(data, "\\u003E", ">");
data = StringUtils.replace(data, "\\\"", "\"");
// the head html element is not needed, so I remove it beforehand
data = removeTag(data, "head", true);
// format the data if necessary in utf8 
// => necessary, otherwise I see unwanted characters in my data
data = cleanString(data);

// continue... here I only parse out a list of all relevant tags I'm interested in
// from here on I use a html parser, which is memory efficient...

问题

对于某些人,我得到OOM异常,大多数介于我的字符串处理函数之间,所以我希望改进它们。我感谢任何提高内存效率代码的建议(速度并不重要!)。

功能

private static String removeTag(String html, String tag, boolean replaceWithEmpty) {
    String regex = "<" + tag + ">.*?</" + tag + ">";
    return StringUtils.replaceAll(html, regex, replaceWithEmpty ? "<" + tag + "></" + tag + ">" : "");
}

private static String cleanString(String s) {
    try {
        // Convert from Unicode to UTF-8
        byte[] utf8 = s.getBytes("UTF-8");
        // Convert from UTF-8 to Unicode
        s = new String(utf8, "UTF-8");
    } catch (UnsupportedEncodingException e) {
        L.e(e);
    }

    return s;
}

StringUtils的

public class StringUtils {

    // compile each pattern once only!
    private static HashMap<String, Pattern> COMPILED_PATTERNS = new HashMap<>();

    private static Pattern getPattern(String regex) {
        if (COMPILED_PATTERNS.containsKey(regex)) {
            return COMPILED_PATTERNS.get(regex);
        }
        Pattern p = Pattern.compile(regex);
        COMPILED_PATTERNS.put(regex, p);
        return p;
    }

    public static Matcher match(String regex, String data) {
        Pattern p = getPattern(regex);
        return p.matcher(data);
    }

    public static String replace(final String str, final CharSequence searchChars, CharSequence replaceChars) {
        return str.replace(searchChars, replaceChars);
    }

    public static String replaceAll(final String str, final String regex, String replacement) {
        Pattern p = getPattern(regex);
        return p.matcher(str).replaceAll(replacement);
    }

    public static String findContentBetween(String content, String prefix, String postfix) {
        return findContentBetween(content, prefix, postfix, false);
    }

    public static String findContentBetween(String content, String prefix, String postfix, boolean searchEndFirst) {
        if (content == null || content.length() == 0) {
            return null;
        }

        if (searchEndFirst) {
            int index = content.indexOf(postfix);
            if (index >= 0) {
                int end = -1;
                int start = -1;
                String s;
                while (index >= 0) {
                    s = content.substring(index, index + 1);
                    if (s.equals("?")) {
                        end = index;
                    } else if (s.equals("/")) {
                        start = index + 1;
                    }
                    if (end != -1 && start != -1) {
                        break;
                    }

                    index--;
                }
                if (end > start && end >= 0) {
                    return content.substring(start, end);
                }
            }
        } else {
            int end;
            int start = content.indexOf(prefix);
            if (start > 0) {
                start += prefix.length();
                end = content.indexOf(postfix, start + 1);
                if (end > start) {
                    return content.substring(start, end);
                }
            }
        }
        return null;
    }
}

2 个答案:

答案 0 :(得分:4)

这个答案正在解决使用常规字符串时的问题。如果您正在使用HTML,那么有更好的解决方案。

data = data.substring(1, data.length() - 1);
data = StringUtils.replace(data, "\\u003C", "<");
data = StringUtils.replace(data, "\\u003E", ">");
data = StringUtils.replace(data, "\\\"", "\"");

String是不可变的,因此每个字符串都必须创建一个新的String(或者,它没有做任何事情)。因此,如果这些行中的每一行在很大程度上保持字符串不变,那么基本上只是复制该字符串。

相反,在StringBuilder中累积更新的字符串,一次性完成所有替换:

StringBuilder sb = new StringBuilder(data.length());
Map<String, String> replacements = Map.of("\\u003C", "<", "\\u003E", ">" /* etc */);
for (int i = 1; i < data.length() - 1; ++i) {
  sb.append(data.charAt(i));

  for (Map.Entry<String, String> entry : replacements.entrySet()) {
    String search = entry.getKey();

    // This is basically checking "endsWith".
    int endIndex = sb.length() - search.length();
    if (endIndex >= 0 && sb.indexOf(search, endIndex) == endIndex) {
      sb.delete(endIndex, sb.length());
      sb.append(entry.getValue());
    }
   }
}
data = sb.toString();

请注意,这是记忆效率,就像你要求的那样;有办法让这个时间更有效率。

例如,您可以编译与您要替换的内容匹配的Pattern

Pattern p = Pattern.compile(
    replacements.keySet()
        .stream()
        .map(Pattern::quote)
        .collect(Collectors.joining("|")));

然后使用Matcher API,它非常适合此任务:

Matcher m = p.matcher(data);
int prev = 1;
while (m.find()) {
  sb.append(data, prev, m.start());
  sb.append(replacements.get(m.group()));
  prev = m.end();
}
sb.append(data, prev, data.length() - 1);

Ideone demo

如果您希望扩展Pattern / Matcher方法以涵盖head替换,您可以将"|<head>[\s\S]*?</head>"附加到模式,然后专门处理循环:

if (!m.group().startsWith("<head>")) {
  sb.append(replacements.get(m.group()));
}

但是当你开始尝试使用HTML的正则表达式时,你会很快发现它的缺点......

答案 1 :(得分:1)

正则表达式与大字符串组合通常不是一个好主意。更强,you shouldn't parse [X]HTML with regex。特别是当模式使用捕获组时,它必须照顾很多。此外,<div>内的<div>会破坏代码。

当然可以获取一个StringBuilder,它可以节省一部分内存,但是仍然存在使用正则表达式解析HTML的问题。

修改

如果在文本的大部分内部应用替换是正确的,则可能会创建目标文本的许多修改副本。但是,您的一些要求可以由解析器处理。

  • 删除代码
    你应该可以这样做:

    Elements selector = docsoup.select("<your tag>");
    for (Element element : selector) {
        element.remove();
    }