摘要:有沒有更快的方法如果分隔符不是單字符而且也不需要按正則分隔的話,使用的方法還會和一樣使用正則表達式。使用分隔字符串,針對不需要按正則分隔的場景提供更好的實現,分隔符支持字符串。
String.split 是Java里很常用的字符串操作,在普通業務操作里使用的話并沒有什么問題,但如果需要追求高性能的分割的話,需要花一點心思找出可以提高性能的方法。
String.split方法的分割參數regex實際不是字符串,而是正則表達式,就是說分隔字符串支持按正則進行分割,雖然這個特性看上去非常好,但從另一個角度來說也是性能殺手。
在Java6的實現里,String.split每次調用都直接新建Pattern對象對參數進行正則表達式的編譯,再進行字符串分隔,而正則表達式的編譯從字面上看就知道需要耗不少時間,并且實現中也沒有對Pattern進行緩存,因此多次頻繁調用的使用場景下性能很差,如果是要使用正則表達式分隔的話,應該自行對Pattern進行緩存。
public String[] split(String regex, int limit) { return Pattern.compile(regex).split(this, limit); }
但很多時候我們并不會真的想使用正則表達式分隔字符串,我們其實想的只是用一個簡單的字符比如空格、下劃線分隔字符串而已,為了需要是滿足這個需求卻要背上正則表達式支持的性能損耗,非常不值得。
因此在Java7的實現里,針對單字符的分隔進行了優化,對這種場景實現了更合適的方法。單字符不走正則表達式的實現,直接利用indexOf快速定位分隔位置,提高性能。
/* fastpath if the regex is a (1)one-char String and this character is not one of the RegEx"s meta characters ".$|()[{^?*+", or (2)two-char String and the first char is the backslash and the second is not the ascii digit or ascii letter. */ char ch = 0; if (((regex.value.length == 1 && ".$|()[{^?*+".indexOf(ch = regex.charAt(0)) == -1) || (regex.length() == 2 && regex.charAt(0) == "" && (((ch = regex.charAt(1))-"0")|("9"-ch)) < 0 && ((ch-"a")|("z"-ch)) < 0 && ((ch-"A")|("Z"-ch)) < 0)) && (ch < Character.MIN_HIGH_SURROGATE || ch > Character.MAX_LOW_SURROGATE)) { int off = 0; int next = 0; boolean limited = limit > 0; ArrayListlist = new ArrayList<>(); while ((next = indexOf(ch, off)) != -1) { if (!limited || list.size() < limit - 1) { list.add(substring(off, next)); off = next + 1; } else { // last one //assert (list.size() == limit - 1); list.add(substring(off, value.length)); off = value.length; break; } } // If no match was found, return this if (off == 0) return new String[]{this}; // Add remaining segment if (!limited || list.size() < limit) list.add(substring(off, value.length)); // Construct result int resultSize = list.size(); if (limit == 0) while (resultSize > 0 && list.get(resultSize - 1).length() == 0) resultSize--; String[] result = new String[resultSize]; return list.subList(0, resultSize).toArray(result); }
有沒有更快的方法?如果分隔符不是單字符而且也不需要按正則分隔的話,使用split的方法還會和Java6一樣使用正則表達式。這里還有其他備用手段:
使用StringTokenizer,StringTokenizer沒有正則表達式分隔的功能,單純的根據分隔符逐次返回分隔的子串,默認按空格分隔,性能比String.split方法稍好,但這個類實現比較老,屬于jdk的遺留類,而且注釋上也說明不建議使用這個類。
使用org.apache.commons.lang3.StringUtils.split分隔字符串,針對不需要按正則分隔的場景提供更好的實現,分隔符支持字符串。
還能有更快的方法么?注意到String.split和StringUtils.split方法返回值是String[], 原始數組的大小是固定的,而在分隔字符串不可能提前知道分隔了多少個子串,那這個數組肯定藏了貓膩,看看是怎么實現的。
定位String.split單字符實現,發現分隔的子串其實保存在ArrayList里,并沒有高深的技巧,直到路徑的最后一行,代碼對存儲了子串的ArrayList再轉成數組,而toArray的實現里對數組進行了復制。
return list.subList(0, resultSize).toArray(result);
StringUtils.split方法里同樣也是這樣。
return list.toArray(new String[list.size()]);
因此這里可以做一個優化,把代碼實現復制過來,然后將方法參數返回類型改為List,減少數組復制的內存消耗。
還能有更快的方法么?其實很多時候我們需要對分隔后的字符串進行遍歷訪問做一些操作,并不是真的需要這個數組,這和文件讀取是一樣的道理,讀文件不需要把整個文件讀入到內存中再使用,完全可以一次讀取一行進行處理,因此還可以做一個優化,增加參數作為子串處理方法的回調,在相應地方改為對回調的調用,這樣能完全避免數組的創建。也就是說,把字符串分隔看做一個流。
private static void splitWorker(final String str, final String separatorChars, final int max, final boolean preserveAllTokens, ConsumeronSplit) { if (str == null) { return; } final int len = str.length(); if (len == 0) { return; } int sizePlus1 = 1; int i = 0, start = 0; boolean match = false; boolean lastMatch = false; if (separatorChars == null) { // Null separator means use whitespace while (i < len) { if (Character.isWhitespace(str.charAt(i))) { if (match || preserveAllTokens) { lastMatch = true; if (sizePlus1++ == max) { i = len; lastMatch = false; } onSplit.accept(str.substring(start, i)); match = false; } start = ++i; continue; } lastMatch = false; match = true; i++; } } else if (separatorChars.length() == 1) { // Optimise 1 character case final char sep = separatorChars.charAt(0); while (i < len) { if (str.charAt(i) == sep) { if (match || preserveAllTokens) { lastMatch = true; if (sizePlus1++ == max) { i = len; lastMatch = false; } onSplit.accept(str.substring(start, i)); match = false; } start = ++i; continue; } lastMatch = false; match = true; i++; } } else { // standard case while (i < len) { if (separatorChars.indexOf(str.charAt(i)) >= 0) { if (match || preserveAllTokens) { lastMatch = true; if (sizePlus1++ == max) { i = len; lastMatch = false; } onSplit.accept(str.substring(start, i)); match = false; } start = ++i; continue; } lastMatch = false; match = true; i++; } } if (match || preserveAllTokens && lastMatch) { onSplit.accept(str.substring(start, i)); } } public static void split(final String str, final String separatorChars, Consumer onSplit) { splitWorker(str, separatorChars, -1, false, onSplit); } // 使用方法 public void example() { split("Hello world", " ", System.out::println); }
還能有更快的方法么?也有更極端的優化方法,因為在拿子串(substring方法)時實際發生了一次字符串復制,因此可以把回調函數改為傳入子串在字符串的區間start、end,回調再根據區間讀取子串進行處理,但并不是很通用,這里就不展示代碼了,有興趣的可以試一下。
還能有更快的方...
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/72025.html
摘要:中的算法附道面試常見算法題解決方法和思路關注每日一道面試題詳解面試過程通常從最初的電話面試開始,然后是現場面試,檢查編程技能和文化契合度。值得記住的數組方法有和。一個好的解決方案是使用內置的方法。 JavaScript中的算法(附10道面試常見算法題解決方法和思路) 關注github每日一道面試題詳解 Introduction 面試過程通常從最初的電話面試開始,然后是現場面試,檢查編程...
摘要:的構造器經過重載可以接受多種輸出目的地,不過最常用的還是和。組號為表示整個表達式,組號表示被第一對括號括起的組,依此類推。有多個重載的構造器,可以接受和對象。 點擊進入我的博客 字符串操作是計算機程序設計中最常見的行為 13.1 不可變String String底層是由char[]實現的,是不可變的。看起來會改變String的方法,實際上都是創建了一個新的String對象,任何指向它...
摘要:快速寫入和讀取文件話不多說,先看題隨機生成的記錄,如,每行一條記錄,總共萬記錄,寫入文本文件編碼,然后讀取文件,的前兩個字符相同的,其年薪累加,比如,萬,個人,最后做排序和分組,輸出年薪總額最高的組萬,人萬,人位隨機,隨機隨機,年薪總 JAVA8快速寫入和讀取文件? 話不多說,先看題: 隨機生成 Salary {name, baseSalary, bonus }的記錄,如wxxx,1...
摘要:高性能代碼的最佳實踐前言在這篇文章中,我們將討論幾個有助于提升應用程序性能的方法。要獲得有關應用程序需求的最好最可靠的方法是對應用程序執行實際的負載測試,并在運行時跟蹤性能指標。 showImg(https://segmentfault.com/img/bVbtgk4?w=256&h=254); 高性能Java代碼的最佳實踐前言 在這篇文章中,我們將討論幾個有助于提升Java應用程序性...
閱讀 1394·2021-11-08 13:14
閱讀 747·2021-09-23 11:31
閱讀 1038·2021-07-29 13:48
閱讀 2781·2019-08-29 12:29
閱讀 3371·2019-08-29 11:24
閱讀 1899·2019-08-26 12:02
閱讀 3688·2019-08-26 10:34
閱讀 3435·2019-08-23 17:07