草庐IT

java - 任何人都可以建议一个更快的替代这个正则表达式算法吗?

coder 2023-12-13 原文

我需要经常调用这个函数。基本上,它将所有带重音的字符替换为其无重音的等效字符,将空格更改为“_”,转换为小写,并去除其余的外来代码,因此可以安全地用作文件名/url 路径/等。问题是,如您所见,从性能的角度来看,它看起来很糟糕。谁能想到更清洁、更快的替代方案?

public static String makeValidPathName(String rawString) {
    if (rawString==null) return null;
    rawString = rawString.replaceAll("[ÁÀÂÄáàäaàáâãäå]","a");
    rawString = rawString.replaceAll("æ","ae");
    rawString = rawString.replaceAll("çÇ","c");
    rawString = rawString.replaceAll("[ÈÉÊËèéêë]","e");
    rawString = rawString.replaceAll("[ìíîïÍÌÎÏ]","i");
    rawString = rawString.replaceAll("ñÑ","n");                            
    rawString = rawString.replaceAll("[ÓÓÖÔòóôõö]","o");
    rawString = rawString.replaceAll("œ","oe");
    rawString = rawString.replaceAll("[ÙÚÛÜùúûü]", "u");
    rawString = rawString.replaceAll("[ýÿ]","y");
    rawString= rawString.replaceAll("[^a-z A-Z 0-9 \\_ \\+]","");
    rawString = rawString.replaceAll(" ","_");
    return rawString.toLowerCase();
}

--- 编辑

好的,伙计们......我对所有 4 种情况都进行了性能测试:

  • 案例 1) 此处发布的原始例程。
  • 案例2)@WChargin建议的改进
  • 案例 3)@devconsole 建议的查找表以及我对 SparseArray 的优化
  • 案例 4)@Erik Pragt 建议的 Normalizer 方法

然后...获胜者是...TADAAA .....

D/REPLACEMENT_TEST(18563): *** Running Tests (1000 iterations)
D/REPLACEMENT_TEST(18563): Original REGEX  : 13533 ms
D/REPLACEMENT_TEST(18563): Compiled REGEX  : 12563 ms
D/REPLACEMENT_TEST(18563): Character LUT   : 1840 ms
D/REPLACEMENT_TEST(18563): Java NORMALIZER : 2416 ms
  • 有趣的是,模式编译优化并没有多大帮助。
  • 我发现我对 REGEXES 速度的假设也完全错误,而 devconsole 对 Normalizer 优于正则表达式的有根据的猜测是正确的。
  • REGEXES 的速度之慢令人惊讶。一个数量级的差异真的让我感到惊讶。我会尽量避免在 Java 上使用它们。
  • 查找表是最快的选项。我会坚持使用这个解决方案,因为使用 Normalizer 时,我仍然需要手动替换一些字符(将空格替换为“_”),然后转换为小写。

测试是在 Samsung Galaxy Tab v1 10.1 中完成的。

请在附件中找到测试用例的源代码。

public class Misc { 

    /* Test 2 (@WCChargin's Regex compilation) initialization */

static Map<Pattern, String> patterns = new HashMap<Pattern, String>();

static {
        patterns.put(Pattern.compile("[ÁÀÂÄáàäaàáâãäå]") ,"a");
        patterns.put(Pattern.compile("æ"),"ae");
        patterns.put(Pattern.compile("çÇ"),"c");
        patterns.put(Pattern.compile("[ÈÉÊËèéêë]"),"e");
        patterns.put(Pattern.compile("[ìíîïÍÌÎÏ]"),"i");
        patterns.put(Pattern.compile("ñÑ"),"n");                            
        patterns.put(Pattern.compile("[ÓÓÖÔòóôõö]"),"o");
        patterns.put(Pattern.compile("œ"),"oe");
        patterns.put(Pattern.compile("[ÙÚÛÜùúûü]"), "u");
        patterns.put(Pattern.compile("[ýÿ]"),"y");
        patterns.put(Pattern.compile("[^a-z A-Z 0-9 \\_ \\+]"),"");
        patterns.put(Pattern.compile(" "),"_");
}

    /* Test 3 (@devconsole's Lookup table) initialization */
static SparseArray<String> homeBrewPatterns=new SparseArray<String>();
/** helper function to fill the map where many different chars map to the same replacement */
static void fillMap(String chars, String replacement) { for (int i=0,len=chars.length(); i<len; i++) homeBrewPatterns.put(chars.charAt(i), replacement); }
static {
    // fill the sparsearray map with all possible substitutions: Any char code gets its equivalent, ie, ä->a. a->a. A->a
    // this also does the toLowerCase automatically. If a char is not in the list, it is forbidden and we skip it.
    fillMap("ÁÀÂÄáàäaàáâãäå","a");
    fillMap("æ","ae");
    fillMap("çÇ","c");
    fillMap("ÈÉÊËèéêë","e");
    fillMap("ìíîïÍÌÎÏ","i");
    fillMap("ñÑ","n");
    fillMap("ÓÓÖÔòóôõö","o");
    fillMap("œ","oe");
    fillMap("ÙÚÛÜùúûü","u");
    fillMap("ýÿ","y");
    fillMap(" ","_");
    for (char c='a'; c<='z'; c++) fillMap(""+c,""+c); // fill standard ASCII lowercase -> same letter 
    for (char c='A'; c<='Z'; c++) fillMap(""+c,(""+c).toLowerCase()); // fill standard ASCII uppercase -> uppercase
    for (char c='0'; c<='9'; c++) fillMap(""+c,""+c); // fill numbers
}

    /** FUNCTION TO TEST #1: Original function, no pattern compilation */ 
public static String makeValidPathName(String rawString) {
    if (rawString==null) return null;
    rawString = rawString.replaceAll("[ÁÀÂÄáàäaàáâãäå]","a");
    rawString = rawString.replaceAll("æ","ae");
    rawString = rawString.replaceAll("çÇ","c");
    rawString = rawString.replaceAll("[ÈÉÊËèéêë]","e");
    rawString = rawString.replaceAll("[ìíîïÍÌÎÏ]","i");
    rawString = rawString.replaceAll("ñÑ","n");                            
    rawString = rawString.replaceAll("[ÓÓÖÔòóôõö]","o");
    rawString = rawString.replaceAll("œ","oe");
    rawString = rawString.replaceAll("[ÙÚÛÜùúûü]", "u");
    rawString = rawString.replaceAll("[ýÿ]","y");
    rawString = rawString.replaceAll("[^a-z A-Z 0-9 \\_ \\+]","");
    rawString = rawString.replaceAll(" ","_");
    return rawString.toLowerCase();
}
/** FUNCTION TO TEST #2: @WCChargin's suggestion: Compile patterns then iterate a map */
public static String makeValidPathName_compiled(String rawString) {
    for (Map.Entry<Pattern, String> e : patterns.entrySet()) {
        rawString=e.getKey().matcher(rawString).replaceAll(e.getValue());
    }
    return rawString.toLowerCase();
}

    /** FUNCTION TO TEST #3: @devconsole's suggestion: Create a LUT with all equivalences then append to a stringbuilder */
public static String makeValidPathName_lut(String rawString) {
    StringBuilder purified=new StringBuilder(rawString.length()); // to avoid resizing
    String aux; // to avoid creating objects inside the for
    for (int i=0, len=rawString.length(); i<len; i++) {
        aux=homeBrewPatterns.get(rawString.charAt(i));
        if (aux!=null) purified.append(aux);
    }
    return purified.toString();
}

    /** FUNCTION TO TEST #4: @Erik Pragt's suggestion on the use of a Normalizer */
public static String makeValidPathName_normalizer(String rawString) {
        return rawString == null ? null
            : Normalizer.normalize(rawString, Form.NFD)
                .replaceAll("\\p{InCombiningDiacriticalMarks}+", "");
}

    /** Test Runner as a Runnable, just do a Handler.post() to run it */

public static Runnable msStringReplacementTest=new Runnable() {

    public void run() {


    String XTAG="REPLACEMENT_TEST";

    Log.d(XTAG, "*** Running Tests");

    int ITERATIONS=1000;

    String[] holder=new String[4];

            /* http://www.adhesiontext.com/ to generate dummy long text ... its late n im tired :) */

    String tester="e arte nací valse ojales i demediada cesé entrañan domó reo ere fiaréis cinesiterapia fina pronto mensuraré la desatufases adulantes toree fusca ramiro hez apolíneo insalvable atas no enorme mí ojo trola chao fas eh borda no consignataria uno ges no arenque ahuyento y daca pío veló tenle baúl ve birria filo rho fui tañe mean taz raicita alimentarías ano defunciones u reabráis repase apreciaran cantorales ungidas naftalina ex guió abomba o escribimos consultarás des usó saudí mercó yod aborrecieses guiri lupia ña adosado jeringara fe cabalgadura tú descasar diseñe amar limarme escobero latamente e oreó lujuria niñez fabularios da inviernen vejé estañarán dictará sil mírales emoción zar claudiquéis ó e ti ñ veintén dañen ríase esmeraras acató noté as mancharlos avisen chocarnos divertidas y relata nuera usé fié élitro baba upa cu enhornan da toa hechizase genesíacos sol fija aplicó gafa pi enes fin asé deal rolar recurran cacen ha id pis pisó democristiano oes eras lañó ch pino fijad ñ quita hondazos ñ determinad vid corearan corrompimiento pamema meso fofas ocio eco amagados pian bañarla balan cuatrimestrales pijojo desmandara merecedor nu asimiladores denunciándote jada ñudos por descifraseis oré pelote ro botó tu sí mejorado compatibilizaciones enyerba oyeses atinado papa borbón pe dé ñora semis prosada luces leí aconteciesen doy colmará o ve te modismos virola garbillen apostabas abstenido ha bajá le osar cima ají adormecéis ñu mohecí orden abrogándote dan acanilladas uta emú ha emporcara manila arribeña hollejo ver puntead ijadeáis chalanesca pechugón silbo arabescos e i o arenar oxidas palear ce oca enmaderen niñez acude topó aguanieves i aconsejaseis lago él roía grafito ceñir jopo nitritos mofe botáis nato compresores ñu asilo amerizan allanándola cuela ó han ice puya alta lío son de sebo antieconómicas alá aceza latitud faca lavé colocándolos concebirlo miserea ñus gro mearé enchivarse";

    long time0=System.currentTimeMillis();
    for (int i=0; i<ITERATIONS; i++) holder[0]=makeValidPathName(tester); // store in an array to avoid possible JIT optimizations
    long elapsed0=System.currentTimeMillis()-time0;

    Log.d(XTAG, "Original REGEX  : "+elapsed0+" ms");

    long time1=System.currentTimeMillis();
    for (int i=0; i<ITERATIONS; i++) holder[1]=makeValidPathName_compiled(tester); // store in an array to avoid possible JIT optimizations
    long elapsed1=System.currentTimeMillis()-time1;

    Log.d(XTAG, "Compiled REGEX  : "+elapsed1+" ms");

    long time2=System.currentTimeMillis();
    for (int i=0; i<ITERATIONS; i++) holder[2]=makeValidPathName_lut(tester); // store in an array to avoid possible JIT optimizations
    long elapsed2=System.currentTimeMillis()-time2;

    Log.d(XTAG, "Character LUT   : "+elapsed2+" ms");

    long time3=System.currentTimeMillis();
    for (int i=0; i<ITERATIONS; i++) holder[3]=makeValidPathName_normalizer(tester); // store in an array to avoid possible JIT optimizations
    long elapsed3=System.currentTimeMillis()-time3;

    Log.d(XTAG, "Java NORMALIZER : "+elapsed3+" ms");

    Log.d(XTAG, "Output 0: "+holder[0]);
    Log.d(XTAG, "Output 1: "+holder[1]);
    Log.d(XTAG, "Output 2: "+holder[2]);
    Log.d(XTAG, "Output 3: "+holder[3]);
    }
};

伙计们,非常感谢您的建议 :) 我喜欢 stackoverflow :)

最佳答案

构建静态 Map<Character,String>将一个字符映射到它的替换字符串,即将“Á”映射到“a”,“ä”映射到“a”等。还包括一对一的对应关系,即映射“a”到“a”,等等。您得到的 map 最多包含几百个条目。

现在遍历输入字符串的字符并在静态映射中查找替换字符串。如果映射不包含条目,则跳过该字符,否则将替换追加到 StringBuilder。

关于java - 任何人都可以建议一个更快的替代这个正则表达式算法吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/16451654/

有关java - 任何人都可以建议一个更快的替代这个正则表达式算法吗?的更多相关文章

  1. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

    类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

  2. ruby - 如何将脚本文件的末尾读取为数据文件(Perl 或任何其他语言) - 2

    我正在寻找执行以下操作的正确语法(在Perl、Shell或Ruby中):#variabletoaccessthedatalinesappendedasafileEND_OF_SCRIPT_MARKERrawdatastartshereanditcontinues. 最佳答案 Perl用__DATA__做这个:#!/usr/bin/perlusestrict;usewarnings;while(){print;}__DATA__Texttoprintgoeshere 关于ruby-如何将脚

  3. ruby - 使用 Vim Rails,您可以创建一个新的迁移文件并一次性打开它吗? - 2

    使用带有Rails插件的vim,您可以创建一个迁移文件,然后一次性打开该文件吗?textmate也可以这样吗? 最佳答案 你可以使用rails.vim然后做类似的事情::Rgeneratemigratonadd_foo_to_bar插件将打开迁移生成的文件,这正是您想要的。我不能代表textmate。 关于ruby-使用VimRails,您可以创建一个新的迁移文件并一次性打开它吗?,我们在StackOverflow上找到一个类似的问题: https://sta

  4. ruby-on-rails - Rails - 一个 View 中的多个模型 - 2

    我需要从一个View访问多个模型。以前,我的links_controller仅用于提供以不同方式排序的链接资源。现在我想包括一个部分(我假设)显示按分数排序的顶级用户(@users=User.all.sort_by(&:score))我知道我可以将此代码插入每个链接操作并从View访问它,但这似乎不是“ruby方式”,我将需要在不久的将来访问更多模型。这可能会变得很脏,是否有针对这种情况的任何技术?注意事项:我认为我的应用程序正朝着单一格式和动态页面内容的方向发展,本质上是一个典型的网络应用程序。我知道before_filter但考虑到我希望应用程序进入的方向,这似乎很麻烦。最终从任何

  5. ruby-on-rails - 渲染另一个 Controller 的 View - 2

    我想要做的是有2个不同的Controller,client和test_client。客户端Controller已经构建,我想创建一个test_clientController,我可以使用它来玩弄客户端的UI并根据需要进行调整。我主要是想绕过我在客户端中内置的验证及其对加载数据的管理Controller的依赖。所以我希望test_clientController加载示例数据集,然后呈现客户端Controller的索引View,以便我可以调整客户端UI。就是这样。我在test_clients索引方法中试过这个:classTestClientdefindexrender:template=>

  6. ruby - 我可以使用 Ruby 从 CSV 中删除列吗? - 2

    查看Ruby的CSV库的文档,我非常确定这是可能且简单的。我只需要使用Ruby删除CSV文件的前三列,但我没有成功运行它。 最佳答案 csv_table=CSV.read(file_path_in,:headers=>true)csv_table.delete("header_name")csv_table.to_csv#=>ThenewCSVinstringformat检查CSV::Table文档:http://ruby-doc.org/stdlib-1.9.2/libdoc/csv/rdoc/CSV/Table.html

  7. ruby - 我可以使用 aws-sdk-ruby 在 AWS S3 上使用事务性文件删除/上传吗? - 2

    我发现ActiveRecord::Base.transaction在复杂方法中非常有效。我想知道是否可以在如下事务中从AWSS3上传/删除文件:S3Object.transactiondo#writeintofiles#raiseanexceptionend引发异常后,每个操作都应在S3上回滚。S3Object这可能吗?? 最佳答案 虽然S3API具有批量删除功能,但它不支持事务,因为每个删除操作都可以独立于其他操作成功/失败。该API不提供任何批量上传功能(通过PUT或POST),因此每个上传操作都是通过一个独立的API调用完成的

  8. ruby-on-rails - 如果 Object::try 被发送到一个 nil 对象,为什么它会起作用? - 2

    如果您尝试在Ruby中的nil对象上调用方法,则会出现NoMethodError异常并显示消息:"undefinedmethod‘...’fornil:NilClass"然而,有一个tryRails中的方法,如果它被发送到一个nil对象,它只返回nil:require'rubygems'require'active_support/all'nil.try(:nonexisting_method)#noNoMethodErrorexceptionanymore那么try如何在内部工作以防止该异常? 最佳答案 像Ruby中的所有其他对象

  9. ruby - 为什么 SecureRandom.uuid 创建一个唯一的字符串? - 2

    关闭。这个问题需要detailsorclarity.它目前不接受答案。想改进这个问题吗?通过editingthispost添加细节并澄清问题.关闭8年前。Improvethisquestion为什么SecureRandom.uuid创建一个唯一的字符串?SecureRandom.uuid#=>"35cb4e30-54e1-49f9-b5ce-4134799eb2c0"SecureRandom.uuid方法创建的字符串从不重复?

  10. java - 等价于 Java 中的 Ruby Hash - 2

    我真的很习惯使用Ruby编写以下代码:my_hash={}my_hash['test']=1Java中对应的数据结构是什么? 最佳答案 HashMapmap=newHashMap();map.put("test",1);我假设? 关于java-等价于Java中的RubyHash,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/22737685/

随机推荐