Android切词工具——BreakIterator(3)

来源:互联网 发布:m258 施耐德软件 编辑:程序博客网 时间:2024/05/20 18:51

5.ICU BreakIterator实现原理分析

上一篇性能测试,看到耗时和内存占用上的一些现象。当然,对于一个开源的东西,最高效的方式还是研究源代码了。接下来我们会深入到ICU源代码简要看看分词的实现方法。

5.1初始化:获取BreakIterator实例

以我们例子中用到的获取当前默认Locale的WordInstance为例:

java.text.BreakIterator.getWordInstance() ->java.text.BreakIterator.getWordInstance(Locale.getDefault()) ->java.text.IcuIteratorWrapper new IcuIteratorWrapper() ->android.icu.text.BreakIterator.getWordInstance(ULocale where) ->android.icu.text.BreakIterator.getBreakInstance(where, KIND_WORD)

OK,到了第一个关键逻辑:
android.icu.text.BreakIterator

    /**     * Returns a particular kind of BreakIterator for a locale.     * Avoids writing a switch statement with getXYZInstance(where) calls.     * @internal     * @deprecated This API is ICU internal only.     */    @Deprecated    public static BreakIterator getBreakInstance(ULocale where, int kind) {        if (where == null) {            throw new NullPointerException("Specified locale is null");        }        if (iterCache[kind] != null) {            BreakIteratorCache cache = (BreakIteratorCache)iterCache[kind].get();            if (cache != null) {                if (cache.getLocale().equals(where)) {                    return cache.createBreakInstance();                }            }        }        // sigh, all to avoid linking in ICULocaleData...        BreakIterator result = getShim().createBreakIterator(where, kind);        BreakIteratorCache cache = new BreakIteratorCache(where, result);        iterCache[kind] = new SoftReference<BreakIteratorCache>(cache);        if (result instanceof RuleBasedBreakIterator) {            RuleBasedBreakIterator rbbi = (RuleBasedBreakIterator)result;            rbbi.setBreakType(kind);        }        return result;    }

从上面初始化的逻辑可以看到几点:
(1)使用一个软引用数组iterCache来缓存:

    /**     * {@icu}     * @stable ICU 2.4     */    public static final int KIND_CHARACTER = 0;    /**     * {@icu}     * @stable ICU 2.4     */    public static final int KIND_WORD = 1;    /**     * {@icu}     * @stable ICU 2.4     */    public static final int KIND_LINE = 2;    /**     * {@icu}     * @stable ICU 2.4     */    public static final int KIND_SENTENCE = 3;    /**     * {@icu}     * @stable ICU 2.4     */    public static final int KIND_TITLE = 4;    /**     * @since ICU 2.8     */    private static final int KIND_COUNT = 5;    private static final SoftReference<?>[] iterCache = new SoftReference<?>[5];

我们知道,在虚拟机堆内存充裕的情况下软引用对象可以被使用,如果内存不充裕,软引用的对象会被GC回收。
(2)缓存的对象对应的类是BreakIteratorCache,这是BreakIterator的一个静态私有内部类:

    private static final class BreakIteratorCache {        private BreakIterator iter;        private ULocale where;        BreakIteratorCache(ULocale where, BreakIterator iter) {            this.where = where;            this.iter = (BreakIterator) iter.clone();        }        ULocale getLocale() {            return where;        }        BreakIterator createBreakInstance() {            return (BreakIterator) iter.clone();        }    }

(3)缓存没有命中的情况。通过如下代码生成并保存一个缓存对象:

        BreakIterator result = getShim().createBreakIterator(where, kind);        BreakIteratorCache cache = new BreakIteratorCache(where, result);        iterCache[kind] = new SoftReference<BreakIteratorCache>(cache);

从缓存类源代码可以看到,缓存中的iter引用由getShim().createBreakIterator()得到的BreakIterator对象,这个BreakIterator对象也会直接返回给初始化方法的调用者。
(4)缓存命中的情况。如果kind和Locale都命中,那么会使用缓存中的BreakIteratorCache对象通过BreakIteratorCache.createBreakInstance(),其实也就是(BreakIterator) iter.clone(),克隆一个BreakIterator对象返回。

到这里,这段代码实际上是有很多疑问的,为什么写成这样?带着疑问继续研究。接下来看看初始化中的干货代码:

getShim().createBreakIterator(where, kind);

先看一下getShim():

    private static BreakIteratorServiceShim shim;    private static BreakIteratorServiceShim getShim() {        // Note: this instantiation is safe on loose-memory-model configurations        // despite lack of synchronization, since the shim instance has no state--        // it's all in the class init.  The worst problem is we might instantiate        // two shim instances, but they'll share the same state so that's ok.        if (shim == null) {            try {                Class<?> cls = Class.forName("com.ibm.icu.text.BreakIteratorFactory");                shim = (BreakIteratorServiceShim)cls.newInstance();            }            catch (MissingResourceException e)            {                throw e;            }            catch (Exception e) {                ///CLOVER:OFF                if(DEBUG){                    e.printStackTrace();                }                throw new RuntimeException(e.getMessage());                ///CLOVER:ON            }        }        return shim;    }

这是一个懒惰式初始化,有两个特点:第一,没有做同步以实现线程安全;第二,使用反射获取类BreakIteratorFactory并创建对象。
没有实现线程安全已经有注释来解释:初始化的逻辑实现于类加载(类初始化中),对象本身是无状态的,状态都在类中,即使最差情况下非单例,也没关系。也就是说,关键的逻辑是放在类加载中来间接实现线程安全。
为什么使用反射(类BreakIteratorFactory就位于同一个package中)?因为初始化的逻辑放到了BreakIteratorFactory的类加载中,同时,需要实现懒惰式初始化,所以不能够允许类BreakIteratorFactory在第一次调用getShim()之前被加载。我们知道,JVM规范对于类加载的时机没有硬性规定,只要求在使用的必须加载完毕(当然,类初始化也就完成了)。时候所以不同的JVM完全可以根据自身的策略来选择加载时机,也就有可能会被预加载,这就不能满足懒惰式初始化了。而使用反射,JVM预先并不知道需要使用这个类,所以只有在getShim()运行时加载类。在IDE中查看BreakIteratorFactory的使用情况为never used,也是佐证。

接下来分析BreakIteratorFactory。BreakIteratorFactory类初始化逻辑有哪些?
(1)初始化常量数组

    /** KIND_NAMES are the resource key to be used to fetch the name of the     *             pre-compiled break rules.  The resource bundle name is "boundaries".     *             The value for each key will be the rules to be used for the     *             specified locale - "word" -> "word_th" for Thai, for example.     */    private static final String[] KIND_NAMES = {            "grapheme", "word", "line", "sentence", "title"    };

(2)初始化service

static final ICULocaleService service = new BFService();

service这个静态成员变量很重要,前文分析过,初始化中的干货代码:

getShim().createBreakIterator(where, kind);

而从BreakIteratorFactory来看,service与创建实例有关:

    public BreakIterator createBreakIterator(ULocale locale, int kind) {    // TODO: convert to ULocale when service switches over        if (service.isDefault()) {            return createBreakInstance(locale, kind);        }        ULocale[] actualLoc = new ULocale[1];        BreakIterator iter = (BreakIterator)service.get(locale, kind, actualLoc);        iter.setLocale(actualLoc[0], actualLoc[0]); // services make no distinction between actual & valid        return iter;    }

看一下静态私有内部类BFService:

    private static class BFService extends ICULocaleService {        BFService() {            super("BreakIterator");            class RBBreakIteratorFactory extends ICUResourceBundleFactory {                protected Object handleCreate(ULocale loc, int kind, ICUService srvc) {                    return createBreakInstance(loc, kind);                }            }            registerFactory(new RBBreakIteratorFactory());            markDefault();        }        /**         * createBreakInstance() returns an appropriate BreakIterator for any locale.         * It falls back to root if there is no specific data.         *         * <p>Without this override, the service code would fall back to the default locale         * which is not desirable for an algorithm with a good Unicode default,         * like break iteration.         */        @Override        public String validateFallbackLocale() {            return "";        }    }

在他的构造方法中,把一个包装了BreakIteratorFactory.createBreakInstance()方法的局部类RBBreakIteratorFactory的实例注册到自身的工厂集合中,并标记为默认。这样如果没有其他的工厂实例注册进来,默认就会使用BreakIteratorFactory.createBreakInstance()。详见前面介绍的BreakIteratorFactory.createBreakIterator()方法。
再补充一点,前面提到的注释中“对象本身是无状态的,状态都在类中”,应该即指service。

阅读全文
0 0
原创粉丝点击