النسخ المتماثل string.split مع stringtokenizer
-
13-09-2019 - |
سؤال
بتشجيع من هذه, ، وحقيقة أن لدي مليارات من السلسلة لتحليلها، حاولت تعديل التعليمات البرمجية الخاصة بي stringtokenizer. بدلاً من سلسلة[
الشيء الوحيد المتبقي بيني والحصول على تعزيز أداء X2 لذيذ هو حقيقة أنه عندما تفعل
"dog,,cat".split(",")
//output: ["dog","","cat"]
StringTokenizer("dog,,cat")
// nextToken() = "dog"
// nextToken() = "cat"
كيف يمكنني تحقيق نتائج مماثلة مع stringtokenizer؟ هل هناك طرق أسرع للقيام بذلك؟
المحلول
هل أنت فقط تتكامل في الفواصل؟ إذا كان الأمر كذلك، فسوف أكتب خصمها الخاص - قد ينتهي الأمر بكونه أكثر كفاءة من StringTokenizer أكثر من الأغراض العامة التي يمكن أن تبحث عن رموز متعددة، ويمكنك أن تتصرف بذلك. لمثل هذه الحالة الاستخدام البسيطة، يمكن أن يكون تطبيق بسيط.
إذا كان من المفيد، فيمكنك التنفيذ Iterable<String>
والحصول على دعم محسن لمدة حلقة مع الكتابة القوية بدلا من Enumeration
الدعم المقدم من قبل StringTokenizer
. وبعد اسمحوا لي أن أعرف إذا كنت تريد أي مساعدة ترميز مثل هذا الوحش - لا ينبغي أن يكون صعبا للغاية.
بالإضافة إلى ذلك، سأحاول تشغيل اختبارات الأداء على بياناتك الفعلية قبل القفز بعيدا عن الحل الحالي. هل لديك أي فكرة عن مقدار وقت التنفيذ الخاص بك فعلا أمضى في String.split
ب أعلم أن لديك الكثير من السلاسل لتحليلها، ولكن إذا كنت تفعل أي شيء مهم معهم بعد ذلك، فإنني أتوقع أن يكون أكثر أهمية بكثير من الانقسام.
نصائح أخرى
بعد العبث مع StringTokenizer
فئة، لم أجد طريقة لإرضاء المتطلبات للعودة ["dog", "", "cat"]
.
علاوة على ذلك، و StringTokenizer
يتم ترك الطبقة فقط لأسباب التوافق، واستخدام String.split
هو مغرور. من مواصفات API ل StringTokenizer
:
StringTokenizer
هي فئة قديمة يتم الاحتفاظ بها لأسباب التوافق على الرغم من إحباط استخدامها في التعليمات البرمجية الجديدة. يوصى بأن يستخدم أي شخص يسعى هذه الوظيفةsplit
طريقةString
أو الjava.util.regex
حزمة بدلا من ذلك.
منذ القضية هي الأداء الضعيف المفترض String.split
الطريقة، نحن بحاجة إلى العثور على بديل.
ملاحظة: أقول "أداء ضعيف يفترض" لأنه من الصعب تحديد أن كل حالة استخدام ستؤدي إلى StringTokenizer
أن تكون متفوقة على String.split
طريقة. علاوة على ذلك، في كثير من الحالات، ما لم تكن خصم السلاسل بالفعل عنق الزجاجة للتطبيق الذي يحدده التنميط السليم، أشعر أنه سينتهي الأمر بكونه الأمثل المبكر، إذا كان أي شيء. سأمبل أن أقول رمز الكتابة ذات مغزى وسهل الفهم قبل مغامرة على التحسين.
الآن، من المتطلبات الحالية، من المحتمل أن تتدحرج ممتلكاتنا الخاصة لن تكون صعبة للغاية.
لفة tokenzier الخاصة بنا!
ما يلي هو مثمر مملز بسيط كتبته. يجب أن ألاحظ أنه لا توجد تحسينات السرعة، ولا توجد شيكات خطأ لمنع الذهاب في نهاية السلسلة - وهذا تنفيذ سريع وقذر:
class MyTokenizer implements Iterable<String>, Iterator<String> {
String delim = ",";
String s;
int curIndex = 0;
int nextIndex = 0;
boolean nextIsLastToken = false;
public MyTokenizer(String s, String delim) {
this.s = s;
this.delim = delim;
}
public Iterator<String> iterator() {
return this;
}
public boolean hasNext() {
nextIndex = s.indexOf(delim, curIndex);
if (nextIsLastToken)
return false;
if (nextIndex == -1)
nextIsLastToken = true;
return true;
}
public String next() {
if (nextIndex == -1)
nextIndex = s.length();
String token = s.substring(curIndex, nextIndex);
curIndex = nextIndex + 1;
return token;
}
public void remove() {
throw new UnsupportedOperationException();
}
}
ال MyTokenizer
سوف يستغرق String
لتكييف و String
كمسؤول، واستخدام String.indexOf
طريقة لتنفيذ البحث عن محددات. يتم إنتاج الرموز من قبل String.substring
طريقة.
أود أن أشك في أن هناك بعض التحسينات الأداء من خلال العمل على السلسلة في char[]
مستوى بدلا من في String
مستوى. لكنني سأترك ذلك بمثابة تمرين للقارئ.
فئة تنفذ أيضا Iterable
و Iterator
من أجل الاستفادة من for-each
بناء الحلقة التي تم تقديمها في جافا 5. StringTokenizer
هو Enumerator
, ، ولا يدعم for-each
بناء.
هل هو أسرع؟
من أجل معرفة ما إذا كان هذا أسرع، كتبت برنامجا لمقارنة السرعات بالطرق الأربعة التالية:
- استخدام
StringTokenizer
. - استخدام الجديد
MyTokenizer
. - استخدام
String.split
. - استخدام التعبير العادي المعاد
Pattern.compile
.
في الأساليب الأربعة، السلسلة "dog,,cat"
تم فصله إلى الرموز. على الرغم من أن StringTokenizer
يتم تضمينها في المقارنة، تجدر الإشارة إلى أنه لن يرجع النتيجة المرجوة لل ["dog", "", "cat]
.
تم تكرار التزخم لمدة 1 مليون مرة لإعطاء ما يكفي من الوقت لإشعار الفرق في الأساليب.
كان التعليمات البرمجية المستخدمة في المعيار البسيط ما يلي:
long st = System.currentTimeMillis();
for (int i = 0; i < 1e6; i++) {
StringTokenizer t = new StringTokenizer("dog,,cat", ",");
while (t.hasMoreTokens()) {
t.nextToken();
}
}
System.out.println(System.currentTimeMillis() - st);
st = System.currentTimeMillis();
for (int i = 0; i < 1e6; i++) {
MyTokenizer mt = new MyTokenizer("dog,,cat", ",");
for (String t : mt) {
}
}
System.out.println(System.currentTimeMillis() - st);
st = System.currentTimeMillis();
for (int i = 0; i < 1e6; i++) {
String[] tokens = "dog,,cat".split(",");
for (String t : tokens) {
}
}
System.out.println(System.currentTimeMillis() - st);
st = System.currentTimeMillis();
Pattern p = Pattern.compile(",");
for (int i = 0; i < 1e6; i++) {
String[] tokens = p.split("dog,,cat");
for (String t : tokens) {
}
}
System.out.println(System.currentTimeMillis() - st);
النتائج
تم تشغيل الاختبارات باستخدام Java SE 6 (بناء 1.6.0_12-B04)، وكانت النتائج ما يلي:
تشغيل 1 تشغيل 2 تشغيل 2 Run 3 Run 4 Run 5 ----- ----- ----- ----- ----- stringtokenizer 172 188 187 172 172 mytokenizer 234 234 234 234 235 string.split 1172 1156 1171 1172 1156 نمط.com 906 891 891 907 906
لذلك، كما يمكن أن نرى من الاختبار المحدود وخمس فقط أشواط، StringTokenizer
هل في الواقع يخرج الأسرع، ولكن MyTokenizer
جاء في كمغلق 2nd. ثم، String.split
كان أبطأ، وكان التعبير المنتظم العمود أسرع قليلا من split
طريقة.
كما هو الحال مع أي معيار صغير، ربما لم يكن ممثلا للغاية لظروف الحياة الحقيقية، لذلك يجب أن تؤخذ النتائج مع الحبوب (أو تل) من الملح.
ملاحظة: بعد أن قام ببعض المعايير السريعة، يتحول الماسح الضوئي إلى أن تكون أبطأ أربع مرات من String.Split. وبالتالي، لا تستخدم الماسح الضوئي.
(سأترك النشر لأعلى لتسجيل حقيقة أن الماسح الضوئي فكرة سيئة في هذه الحالة. (اقرأ كما: لا تقم الهبوط لي لاقتراح الماسح الضوئي، يرجى ...))
على افتراض أنك تستخدم Java 1.5 أو أعلى، حاول الماسح الضوئي, ، والتي تنفذ Iterator<String>
, ، كما يحدث:
Scanner sc = new Scanner("dog,,cat");
sc.useDelimiter(",");
while (sc.hasNext()) {
System.out.println(sc.next());
}
يعطي
dog
cat
اعتمادا على نوع السلاسل التي تحتاج إلى تتكاملها، يمكنك كتابة الفاصل الخاص بك بناء على String.indexof () على سبيل المثال. يمكنك أيضا إنشاء حل متعدد الأساس لتحسين الأداء أكثر من ذلك، حيث أن خصوم السلاسل مستقل عن بعضها البعض. العمل على دفعات القول - 100 سلاسل لكل كور. هل string.split () أو Water آخر.
بدلا من stringtokenizer، يمكنك تجربة فئة Strtokenizer من Apache Commons Lang، التي اقتبسها:
يمكن أن تقسم هذه الفئة سلسلة في العديد من السلاسل الأصغر. يهدف إلى القيام بعمل مماثل ل Stringtokenizer، ومع ذلك، فإنه يوفر المزيد من السيطرة والمرونة بما في ذلك تنفيذ واجهة List Listitorator.
قد تتم إزالة الرموز الفارغة أو إرجاعها ك NULL.
هذا يبدو وكأنه ما تحتاجه، وأعتقد؟
يمكنك أن تفعل شيئا من هذا القبيل. انها ليست مثالية، ولكن قد تعمل من أجلك.
public static List<String> find(String test, char c) {
List<String> list = new Vector<String>();
start;
int i=0;
while (i<=test.length()) {
int start = i;
while (i<test.length() && test.charAt(i)!=c) {
i++;
}
list.add(test.substring(start, i));
i++;
}
return list;
}
إذا كان ذلك ممكنا، فيمكنك Ommit على قائمة القائمة وأعلم شيئا مباشرة إلى Substring:
public static void split(String test, char c) {
int i=0;
while (i<=test.length()) {
int start = i;
while (i<test.length() && test.charAt(i)!=c) {
i++;
}
String s = test.substring(start,i);
// do something with the string here
i++;
}
}
على نظامي، الطريقة الأخيرة أسرع من الحل stringtokenizer، ولكن قد ترغب في اختبار كيفية عملك. (بالطبع يمكنك جعل هذه الطريقة أقصر قليلا من خلال ommite {} في الثانية الثانية أثناء البحث وبالطبع يمكنك استخدام حلقة واحدة بدلا من الحلقة الخارجية، بما في ذلك أخيرا I ++ في ذلك، لكنني لم أفعل ذلك أفعل ذلك هنا لأنني أعتبر أن النمط السيئ.
حسنا، أسرع شيء يمكنك القيام به هو اجتياز السلسلة يدويا، على سبيل المثال
List<String> split(String s) {
List<String> out= new ArrayList<String>();
int idx = 0;
int next = 0;
while ( (next = s.indexOf( ',', idx )) > -1 ) {
out.add( s.substring( idx, next ) );
idx = next + 1;
}
if ( idx < s.length() ) {
out.add( s.substring( idx ) );
}
return out;
}
يبدو أن هذا (اختبار غير رسمي) شيء يشبه ضعف التقسيم. ومع ذلك، فهو خطير بعض الشيء لتكرار هذه الطريقة، على سبيل المثال، وسوف ينكسر بفواصل هربا، وإذا انتهى بك الأمر إلى الحاجة إلى التعامل مع ذلك في مرحلة ما (لأن قائمتك مليار سلاسل لديها 3 فواصل 3 فاصلة) بحلول وقت لك لقد سمحت بذلك، ربما تنتهي بك الأمر بفقدان بعض فائدة السرعة.
في النهاية، ربما لا يستحق عناء.
أود أن أوصي جوجا جوجل Splitter
.
قارنت ذلك كوه اختبار وحصلت على النتائج التالية:
Stringtokenizer 104.
Google Guava Splitter 142
string.split 446.
Regexp 299.
إذا تم تنظيم إدخالك، فيمكنك إلقاء نظرة على محمول Javacc. يولد فئة Java يقرأ المدخلات الخاصة بك. انها تبدو مثل هذا:
TOKEN { <CAT: "cat"> , <DOG:"gog"> }
input: (cat() | dog())*
cat: <CAT>
{
animals.add(new Animal("Cat"));
}
dog: <DOG>
{
animals.add(new Animal("Dog"));
}