问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

Jsoup V的幕后秘密:优化的技巧和窍门

创作时间:
作者:
@小白创作中心

Jsoup V的幕后秘密:优化的技巧和窍门

引用
CSDN
1.
https://blog.csdn.net/dnc8371/article/details/106810110

我们做对了,现在是时候更快地做事了。 我们会牢记Donald Knuth的警告:“大约97%的时间,我们应该忘记效率低下:过早的优化是万恶之源”。

根据Jonathan Hedley的介绍,他使用YourKit Java Profiler来测量内存使用情况并找到性能热点。 使用此类工具的统计结果对于优化的成功至关重要,它将阻止您花费时间思考和进行无用的调优,这不会提高性能,但也会使代码不必要地变得复杂且难以维护。 乔纳森(Jonathan)在“科隆(Colophon)”中也谈到了这一点。

我们将列出Jsoup中使用的一些技巧和窍门,它们目前是随机排序的,将来会重新组织。

1.缩进填充

// memoised padding up to 21, from "", " ", "  " to "                   "
static final String[] padding = {......};
public static String padding(int width) {
    if (width < 0)
        throw new IllegalArgumentException("width must be > 0");
    if (width < padding.length)
        return padding[width];
    char[] out = new char[width];
    for (int i = 0; i < width; i++)
        out[i] = ' ';
    return String.valueOf(out);
}
protected void indent(Appendable accum, int depth, Document.OutputSettings out) throws IOException {
    accum.append('\n').append(StringUtil.padding(depth * out.indentAmount()));
}

很聪明吧? 它保留了不同长度的填充缓存,可以覆盖80%的情况-我认为这是基于作者的经验和统计数据。

2.是否上课?

Element#hasClass
被标记为对性能敏感的,例如,我们要检查

是否具有类 production ,将类按空格分割为一个数组,然后循环并进行搜索,但深入了解这将是无效的。 Jsoup首先在这里介绍了**Early Exit**,方法是将长度与目标类名进行比较,以避免不必要的扫描和搜索,这也将是有益的。 然后,它使用一个检测空白的指针并执行regionMatches-坦白地说,这是我第一次了解方法 String#regionMatches &#55357;&#56904;&#55357;&#56837;。
public boolean hasClass(String className) {
    final String classAttr = attributes().getIgnoreCase("class");
    final int len = classAttr.length();
    final int wantLen = className.length();
    if (len == 0 || len < wantLen) {
        return false;
    }
    // if both lengths are equal, only need compare the className with the attribute
    if (len == wantLen) {
        return className.equalsIgnoreCase(classAttr);
    }
    // otherwise, scan for whitespace and compare regions (with no string or arraylist allocations)
    boolean inClass = false;
    int start = 0;
    for (int i = 0; i < len; i++) {
        if (Character.isWhitespace(classAttr.charAt(i))) {
            if (inClass) {
                // white space ends a class name, compare it with the requested one, ignore case
                if (i - start == wantLen && classAttr.regionMatches(true, start, className, 0, wantLen)) {
                    return true;
                }
                inClass = false;
            }
        } else {
            if (!inClass) {
                // we're in a class name : keep the start of the substring
                inClass = true;
                start = i;
            }
        }
    }
    // check the last entry
    if (inClass && len - start == wantLen) {
        return classAttr.regionMatches(true, start, className, 0, wantLen);
    }
    return false;
}

3.标签名称是否存在?

正如我们在之前的文章中所分析的那样,
HtmlTreeBuilderState
将通过检查某个集合中的标记名称是否正确来验证嵌套的正确性。 我们可以比较1.7.3之前和之后的实现以进行检查。

// 1.7.2
} else if (StringUtil.in(name, "base", "basefont", "bgsound", "command", "link", "meta", "noframes", "script", "style", "title")) {
    return tb.process(t, InHead);
}
// 1.7.3
static final String[] InBodyStartToHead = new String[]{"base", "basefont", "bgsound", "command", "link", "meta", "noframes", "script", "style", "title"};
...
} else if (StringUtil.inSorted(name, Constants.InBodyStartToHead)) {
    return tb.process(t, InHead);
}

根据作者的评论,“这里有点难读,但与动态varargs相比,GC更少。 贡献了大约10%的解析GC负载。 必须确保将这些排序,如在findSorted中使用的一样。 简单地使用
static final
常量数组,也可以对其进行排序,以便二进制搜索也可以从O(n)改进为O(log(n)),在这里性价比非常好。

但是,“如果添加更多的数组,则必须更新HtmlTreebuilderStateTest”不是同步恕我直言的好方法,而不是复制和粘贴,我将使用反射来检索这些常量。 您可以在Pull Request#1157中找到我的建议:“简化状态排序状态单元测试–避免在HtmlTreeBuilderStateTest.java中重复代码”。

4.轻量级模式

您知道
Integer.valueOf(i)
的技巧吗? 如果已配置(
java.lang.Integer.IntegerCache.high
),则它将维护从-128到127或更高的
IntegerCache
缓存,结果,当值位于不同范围时,

equals
结果将有所不同(经典Java面试问题?)。 这实际上是一个轻量级模式的示例。 对于Jsoup,应用此模式还将减少对象创建的时间并提高性能。

/**
 * Caches short strings, as a flywheel pattern, to reduce GC load. Just for this doc, to prevent leaks.
 * <p />
 * Simplistic, and on hash collisions just falls back to creating a new string, vs a full HashMap with Entry list.
 * That saves both having to create objects as hash keys, and running through the entry list, at the expense of
 * some more duplicates.
 */
private static String cacheString(final char[] charBuf, final String[] stringCache, final int start, final int count) {
    // limit (no cache):
    if (count > maxStringCacheLen)
        return new String(charBuf, start, count);
    if (count < 1)
        return "";
    // calculate hash:
    int hash = 0;
    int offset = start;
    for (int i = 0; i < count; i++) {
        hash = 31 * hash + charBuf[offset++];
    }
    // get from cache
    final int index = hash & stringCache.length - 1;
    String cached = stringCache[index];
    if (cached == null) { // miss, add
        cached = new String(charBuf, start, count);
        stringCache[index] = cached;
    } else { // hashcode hit, check equality
        if (rangeEquals(charBuf, start, count, cached)) { // hit
            return cached;
        } else { // hashcode conflict
            cached = new String(charBuf, start, count);
            stringCache[index] = cached; // update the cache, as recently used strings are more likely to show up again
        }
    }
    return cached;
}

还有另一种情况,可以使用相同的想法来最小化新的StringBuilder GC。

private static final Stack<StringBuilder> builders = new Stack<>();
/**
 * Maintains cached StringBuilders in a flyweight pattern, to minimize new StringBuilder GCs. The StringBuilder is
 * prevented from growing too large.
 * <p>
 * Care must be taken to release the builder once its work has been completed, with {@see #releaseBuilder}
*/
public static StringBuilder borrowBuilder() {
    synchronized (builders) {
        return builders.empty() ?
            new StringBuilder(MaxCachedBuilderSize) :
            builders.pop();
    }
}

实际上,
CharacterReader

StringUtil
值得越来越消化,因为有许多有用的提示和技巧会激发您的灵感。

5.其他改善方法

  • 使用RandomAccessFile读取文件,使文件读取时间缩短了2倍。 查看#248了解更多详情
  • 节点层次结构重构。 查看#911了解更多详细信息
  • “在很大程度上基于对各种网站的分析重新排序HtmlTreeBuilder方法而带来的改进” –我在此列出了这一点,因为它非常实用。 更深入地了解和观察代码的运行方式也将为您提供一些见解
  • 调用
    list.toArray(0)
    而不是
    list.toArray(list.size())
    –已在某些开源项目(例如h2database)中使用,因此我也在另一个Pull Request#1158中提出了此要求

6.未知数

优化永无止境。 我目前还没有发现很多提示和技巧。 如果您能在Jsoup中找到更多启发性的想法,请与我分享,我将不胜感激。 您可以在本网站的左侧栏中找到我的联系信息,或者直接通过
ny83427 at gmail.com
发送电子邮件至
ny83427 at gmail.com

-未完待续-

© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号