我对使用对象完全没有经验,所以我真的希望有一些输入。
我正在尝试从其中包含某些“不需要的单词”的列表中删除注释,这些注释和“不需要的单词”列表都在ArrayList对象中。
这在名为FormHelper
的类中,该类包含私有成员comments
作为ArrayList,auditList
ArrayList在名为populateComments()
的成员函数中本地创建,然后调用此函数(如下)。 PopulateComments()
由构造函数调用,因此,在创建FormHelper
的实例时,此函数仅被调用一次。
private void filterComments(ArrayList <String> auditList) {
for(String badWord : auditList) {
for (String thisComment : this.comments) {
if(thisComment.contains(badWord)) {
int index = this.comments.indexOf(thisComment);
this.comments.remove(index);
}
}
}
}
关于实现此方法的某些方法感觉不正确,我还担心我没有有效地使用ArrayList函数。我的怀疑正确吗?
答案 0 :(得分:2)
这不是特别有效。但是,找到更有效的解决方案并非易事。
让我们回到一个更简单的问题。
private void findBadWords(List <String> wordList, List <String> auditList) {
for(String badWord : auditList) {
for (String word : wordList) {
if (word.equals(badWord)) {
System.err.println("Found a bad word");
}
}
}
}
假设wordList
包含N
个单词,auditList
包含M
个单词。一些简单的分析将显示内部循环执行了N x M
次。 N
因素是不可避免的,但是M
因素却令人不安。这意味着您需要检查的“坏”字越多,检查所需的时间也就越长。
有一种更好的方法:
private void findBadWords(List <String> wordList, HashSet<String> auditWords) {
for (String word : wordList) {
if (auditWords.contains(word))) {
System.err.println("Found a bad word");
}
}
}
那为什么更好?更好(更快),因为HashSet::contains
不需要一次检查所有审核词。实际上,在最佳情况下,它将不检查任何一个(!),而通常情况仅检查其中的一两个。 (我不会讨论为什么,但是如果您想了解,请阅读哈希表上的Wikipedia页面。)
但是您的问题更加复杂。您正在使用String::contains
测试每个注释是否包含每个坏词。那不是一个简单的字符串相等性测试(按照我的简化版本)。
该怎么办?
一种可能的解决方案是将注释分成单词数组(例如,使用String::split
,然后使用HashSet
查找方法。但是:
这将更改代码的行为。 (实际上,这是一种很好的方式:请阅读Scunthorpe problem!)现在,您将只匹配审核词,因为它们是注释文本中的实际单词。
将字符串拆分为单词并不便宜。如果使用String::split
,则需要创建并使用Pattern
对象来查找单词边界,为每个单词创建子字符串并将它们放入数组中。您可能可以做得更好,但这始终是不平凡的计算。
因此,真正的问题将是优化是否会奏效。这最终将取决于M
的值;即您要寻找的坏词数量。 M
越大,将注释分解为单词并使用HashSet
测试单词的可能性就越大。
可能还有另一种不涉及拆分注释的方法。您可以将审核词的列表像这样\b(word-1|word-2|...|word-n)\b
组合成单个正则表达式:然后使用Matcher::find
搜索每个注释字符串。性能将取决于Java平台中正则表达式引擎的优化能力。这有可能比拆分更快。
我的建议是在开始运行 之前对整个应用程序进行基准测试和配置。仅优化:
当基准测试表明发生注释检查的请求的 overall 性能与之有关。 (如果可以,请不要浪费时间进行优化。)
当分析表明 this 方法是性能热点时。 (很有可能真正的热点位于其他地方。如果是这样,则应优化 them 而不是此方法。)
请注意,假设您已经(足够)完成了您的应用程序,并在考虑进行优化之前 为它创建了一个现实的基准。 (过早的优化不是一个好主意……除非您真的知道自己在做什么。)
答案 1 :(得分:1)
通常,从循环中从ArrayList中删除单个元素的效率很低,因为它需要沿数组中的一个位置移动所有“跟随”元素。
A B C D E
^ if you remove this
^---^ you have to shift these 3 along by one
/ / /
A C D E
如果删除很多元素,这将对时间复杂度产生重大影响。最好先确定要删除的元素,然后立即将其全部删除。
我建议使用一种更整洁的方法来使用removeIf
,该方法(至少对于ArrayList
这样的集合实现而言)可以“一次性”删除:
this.comments.removeIf(
c -> auditList.stream().anyMatch(c::contains));
这很简洁,但是可能很慢,因为它必须不断检查整个注释字符串以查看它是否包含每个坏词。
一种可能更快的方法是使用正则表达式:
Pattern p = Pattern.compile(
auditList.stream()
.map(Pattern::quote)
.collect(joining("|")));
this.comments.removeIf(
c -> p.matcher(c).find());
这会更好,因为编译后的正则表达式会在每个注释中一次通过搜索所有不好的词。
基于正则表达式的方法的另一个优点是,通过在编译正则表达式时提供适当的标志,您可以不区分大小写地进行检查。
答案 2 :(得分:0)
在Java 8+中,您可以像下面这样
this.comments = this.comments.stream().filter(comment -> !auditList.contains(comment)).collect(Collectors.toList());
我认为最好在populateComments本身中对其进行过滤,而不是仅执行一次代码时调用此函数。
更有效的方法可能如下所示
Collections.sort(auditList);
this.comments = this.comments.stream().filter(comment -> Collections.binarySearch(comment) == -1).collect(Collectors.toList());
binarySearch方法仅在使用排序列表进行搜索时有效。那里有Collections.sort(auditList)
答案 3 :(得分:0)
您正在尝试修改要迭代的列表。您的内部循环(超过注释)将抛出ConcurrentModificationException
。因此,我建议进行以下更改。
public void filterComment(List<String> auditList) {
Iterator<String> it = this.comments.iterator();
while ((it.hasNext())){
String comment = it.next();
for(String word : auditList){
if(comment.contains(word)){
it.remove();
break;
}
}
}
}