我正在处理一个代表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;
}
}
答案 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);
如果您希望扩展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();
}