سؤال

بافتراض السلسلة أ و ب:

a += b
a = a.concat(b)

تحت غطاء محرك السيارة، هل هم نفس الشيء؟

هنا تم فك ترجمتها concat كمرجع.أود أن أكون قادرًا على فك + المشغل كذلك لنرى ماذا يفعل.

public String concat(String s) {

    int i = s.length();
    if (i == 0) {
        return this;
    }
    else {
        char ac[] = new char[count + i];
        getChars(0, count, ac, 0);
        s.getChars(0, i, ac, count);
        return new String(0, count + i, ac);
    }
}
هل كانت مفيدة؟

المحلول

لا، ليس تماما.

أولا، هناك اختلاف طفيف في الدلالات.لو a يكون null, ، ثم a.concat(b) يلقي أ NullPointerException لكن a+=b سوف تعامل القيمة الأصلية لل a كما لو كان null.علاوة على ذلك، concat() يقبل الأسلوب فقط String القيم بينما + سيقوم عامل التشغيل بتحويل الوسيطة بصمت إلى سلسلة (باستخدام toString() طريقة للكائنات).لذلك concat() الطريقة أكثر صرامة فيما تقبله.

للنظر تحت الغطاء، اكتب فصلًا بسيطًا به a += b;

public class Concat {
    String cat(String a, String b) {
        a += b;
        return a;
    }
}

الآن تفكيك مع javap -c (متضمن في Sun JDK).يجب أن تشاهد قائمة تتضمن:

java.lang.String cat(java.lang.String, java.lang.String);
  Code:
   0:   new     #2; //class java/lang/StringBuilder
   3:   dup
   4:   invokespecial   #3; //Method java/lang/StringBuilder."<init>":()V
   7:   aload_1
   8:   invokevirtual   #4; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   11:  aload_2
   12:  invokevirtual   #4; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   15:  invokevirtual   #5; //Method java/lang/StringBuilder.toString:()Ljava/lang/    String;
   18:  astore_1
   19:  aload_1
   20:  areturn

لذا، a += b هو ما يعادل

a = new StringBuilder()
    .append(a)
    .append(b)
    .toString();

ال concat يجب أن تكون الطريقة أسرع.ومع ذلك، مع المزيد من السلاسل StringBuilder أسلوب يفوز، على الأقل من حيث الأداء.

الكود المصدري ل String و StringBuilder (والفئة الأساسية للحزمة الخاصة بها) متاحة في src.zip الخاص بـ Sun JDK.يمكنك أن ترى أنك تقوم ببناء مصفوفة أحرف (تغيير حجمها حسب الضرورة) ثم التخلص منها عند إنشاء النسخة النهائية String.في الممارسة العملية، يكون تخصيص الذاكرة سريعًا بشكل مدهش.

تحديث: وكما لاحظ باول أدامسكي، فقد تغير الأداء في HotSpot الأحدث. javac لا يزال ينتج نفس الكود تمامًا، لكن مترجم الكود الثانوي يغش.يفشل الاختبار البسيط تمامًا لأنه يتم التخلص من نص التعليمات البرمجية بالكامل.الجمع System.identityHashCode (لا String.hashCode) ويبين StringBuffer الكود له ميزة طفيفة.عرضة للتغيير عند إصدار التحديث التالي، أو إذا كنت تستخدم JVM مختلفًا.من @لوكاسيدر, قائمة بمبادئ HotSpot JVM.

نصائح أخرى

نياز صحيح، ولكن تجدر الإشارة أيضًا إلى أنه يمكن تحويل عامل التشغيل الخاص + إلى شيء أكثر كفاءة بواسطة مترجم Java.تحتوي Java على فئة StringBuilder التي تمثل سلسلة غير آمنة وقابلة للتغيير.عند تنفيذ مجموعة من سلاسل السلسلة، يقوم مترجم Java بالتحويل بصمت

String a = b + c + d;

داخل

String a = new StringBuilder(b).append(c).append(d).toString();

وهو أكثر كفاءة بشكل ملحوظ بالنسبة للسلاسل الكبيرة.بقدر ما أعرف، هذا لا يحدث عند استخدام طريقة concat.

ومع ذلك، فإن طريقة concat تكون أكثر كفاءة عند تسلسل سلسلة فارغة إلى سلسلة موجودة.في هذه الحالة، لا يحتاج JVM إلى إنشاء كائن سلسلة جديد ويمكنه ببساطة إرجاع الكائن الموجود.يرى وثائق المترابطة لتأكيد هذا.

لذا، إذا كنت مهتمًا جدًا بالكفاءة، فيجب عليك استخدام طريقة concat عند ربط سلاسل ربما تكون فارغة، واستخدام + بخلاف ذلك.ومع ذلك، يجب أن يكون فرق الأداء ضئيلًا وربما لا داعي للقلق بشأن هذا الأمر على الإطلاق.

لقد أجريت اختبارًا مشابهًا مثل @marcio ولكن باستخدام الحلقة التالية بدلاً من ذلك:

String c = a;
for (long i = 0; i < 100000L; i++) {
    c = c.concat(b); // make sure javac cannot skip the loop
    // using c += b for the alternative
}

فقط لحسن التدبير، رميت StringBuilder.append() أيضًا.تم إجراء كل اختبار 10 مرات، مع 100 ألف تكرار لكل جولة.وهنا النتائج:

  • StringBuilder يفوز.كانت نتيجة وقت الساعة 0 لمعظم عمليات التشغيل، واستغرقت أطولها 16 مللي ثانية.
  • a += b يستغرق حوالي 40000 مللي ثانية (40 ثانية) لكل تشغيل.
  • concat يتطلب فقط 10000 مللي ثانية (10 ثوانٍ) لكل تشغيل.

لم أقم بفك ترجمة الفصل لرؤية الأجزاء الداخلية أو تشغيله من خلال ملف التعريف حتى الآن، ولكن أظن a += b يقضي الكثير من الوقت في إنشاء كائنات جديدة StringBuilder ومن ثم تحويلهم مرة أخرى إلى String.

معظم الإجابات هنا تعود إلى عام 2008.ويبدو أن الأمور تغيرت مع مرور الوقت.تُظهر أحدث المعايير التي قمت بها مع JMH ذلك في Java 8 + أسرع بحوالي مرتين من concat.

المعيار الخاص بي:

@Warmup(iterations = 5, time = 200, timeUnit = TimeUnit.MILLISECONDS)
@Measurement(iterations = 5, time = 200, timeUnit = TimeUnit.MILLISECONDS)
public class StringConcatenation {

    @org.openjdk.jmh.annotations.State(Scope.Thread)
    public static class State2 {
        public String a = "abc";
        public String b = "xyz";
    }

    @org.openjdk.jmh.annotations.State(Scope.Thread)
    public static class State3 {
        public String a = "abc";
        public String b = "xyz";
        public String c = "123";
    }


    @org.openjdk.jmh.annotations.State(Scope.Thread)
    public static class State4 {
        public String a = "abc";
        public String b = "xyz";
        public String c = "123";
        public String d = "!@#";
    }

    @Benchmark
    public void plus_2(State2 state, Blackhole blackhole) {
        blackhole.consume(state.a+state.b);
    }

    @Benchmark
    public void plus_3(State3 state, Blackhole blackhole) {
        blackhole.consume(state.a+state.b+state.c);
    }

    @Benchmark
    public void plus_4(State4 state, Blackhole blackhole) {
        blackhole.consume(state.a+state.b+state.c+state.d);
    }

    @Benchmark
    public void stringbuilder_2(State2 state, Blackhole blackhole) {
        blackhole.consume(new StringBuilder().append(state.a).append(state.b).toString());
    }

    @Benchmark
    public void stringbuilder_3(State3 state, Blackhole blackhole) {
        blackhole.consume(new StringBuilder().append(state.a).append(state.b).append(state.c).toString());
    }

    @Benchmark
    public void stringbuilder_4(State4 state, Blackhole blackhole) {
        blackhole.consume(new StringBuilder().append(state.a).append(state.b).append(state.c).append(state.d).toString());
    }

    @Benchmark
    public void concat_2(State2 state, Blackhole blackhole) {
        blackhole.consume(state.a.concat(state.b));
    }

    @Benchmark
    public void concat_3(State3 state, Blackhole blackhole) {
        blackhole.consume(state.a.concat(state.b.concat(state.c)));
    }


    @Benchmark
    public void concat_4(State4 state, Blackhole blackhole) {
        blackhole.consume(state.a.concat(state.b.concat(state.c.concat(state.d))));
    }
}

نتائج:

Benchmark                             Mode  Cnt         Score         Error  Units
StringConcatenation.concat_2         thrpt   50  24908871.258 ± 1011269.986  ops/s
StringConcatenation.concat_3         thrpt   50  14228193.918 ±  466892.616  ops/s
StringConcatenation.concat_4         thrpt   50   9845069.776 ±  350532.591  ops/s
StringConcatenation.plus_2           thrpt   50  38999662.292 ± 8107397.316  ops/s
StringConcatenation.plus_3           thrpt   50  34985722.222 ± 5442660.250  ops/s
StringConcatenation.plus_4           thrpt   50  31910376.337 ± 2861001.162  ops/s
StringConcatenation.stringbuilder_2  thrpt   50  40472888.230 ± 9011210.632  ops/s
StringConcatenation.stringbuilder_3  thrpt   50  33902151.616 ± 5449026.680  ops/s
StringConcatenation.stringbuilder_4  thrpt   50  29220479.267 ± 3435315.681  ops/s

توم محق في وصف ما يفعله عامل التشغيل + بالضبط.يخلق مؤقتا StringBuilder, ، يُلحق الأجزاء، وينتهي بـ toString().

ومع ذلك، فإن جميع الإجابات حتى الآن تتجاهل تأثيرات تحسينات وقت تشغيل HotSpot.على وجه التحديد، يتم التعرف على هذه العمليات المؤقتة كنمط شائع ويتم استبدالها برمز آلة أكثر كفاءة في وقت التشغيل.

@مارسيو:لقد قمت بإنشاء المعيار الصغير;مع JVM الحديثة، هذه ليست طريقة صالحة لملف التعريف الرمزي.

السبب وراء أهمية تحسين وقت التشغيل هو أن العديد من هذه الاختلافات في التعليمات البرمجية - حتى بما في ذلك إنشاء الكائنات - تكون مختلفة تمامًا بمجرد بدء تشغيل HotSpot.الطريقة الوحيدة لمعرفة ذلك على وجه اليقين هي تحديد ملف التعريف الخاص بك فى الموقع.

وأخيرا، كل هذه الأساليب هي في الواقع سريعة بشكل لا يصدق.قد تكون هذه حالة من التحسين المبكر.إذا كان لديك تعليمات برمجية تقوم بتسلسل السلاسل كثيرًا، فمن المحتمل أن لا علاقة لطريقة الحصول على السرعة القصوى بالمشغلين الذين تختارهم وبدلاً من ذلك بالخوارزمية التي تستخدمها!

ماذا عن بعض الاختبارات البسيطة؟استخدم الكود أدناه:

long start = System.currentTimeMillis();

String a = "a";

String b = "b";

for (int i = 0; i < 10000000; i++) { //ten million times
     String c = a.concat(b);
}

long end = System.currentTimeMillis();

System.out.println(end - start);
  • ال "a + b" تم تنفيذ الإصدار في 2500 مللي ثانية.
  • ال a.concat(b) أعدم في 1200 مللي ثانية.

تم اختباره عدة مرات.ال concat() استغرق تنفيذ الإصدار نصف الوقت في المتوسط.

هذه النتيجة فاجأتني لأن concat() تقوم الطريقة دائمًا بإنشاء سلسلة جديدة (ترجع "new String(result)".ومن المعروف أن:

String a = new String("a") // more than 20 times slower than String a = "a"

لماذا لم يكن المترجم قادرًا على تحسين إنشاء السلسلة في الكود "a + b"، مع العلم أنه يؤدي دائمًا إلى نفس السلسلة؟يمكن أن يتجنب إنشاء سلسلة جديدة.إذا كنت لا تصدق العبارة أعلاه، اختبر بنفسك.

في الأساس، هناك اختلافان مهمان بين + و concat طريقة.

  1. إذا كنت تستخدم com.concat الطريقة، فلن تتمكن إلا من ربط السلاسل في حالة + عامل التشغيل، يمكنك أيضًا ربط السلسلة بأي نوع بيانات.

    على سبيل المثال:

    String s = 10 + "Hello";
    

    في هذه الحالة يجب أن يكون الإخراج 10مرحبا.

    String s = "I";
    String s1 = s.concat("am").concat("good").concat("boy");
    System.out.println(s1);
    

    في الحالة المذكورة أعلاه، يجب عليك توفير سلسلتين إلزاميتين.

  2. الفرق الثاني والرئيسي بين + و com.concat هل هذا:

    حالة 1:لنفترض أنني قمت بربط نفس السلاسل مع com.concat المشغل بهذه الطريقة

    String s="I";
    String s1=s.concat("am").concat("good").concat("boy");
    System.out.println(s1);
    

    في هذه الحالة، إجمالي عدد الكائنات التي تم إنشاؤها في التجمع هو 7 مثل هذا:

    I
    am
    good
    boy
    Iam
    Iamgood
    Iamgoodboy
    

    الحالة 2:

    الآن سأقوم بتسلسل نفس السلاسل عبر + المشغل أو العامل

    String s="I"+"am"+"good"+"boy";
    System.out.println(s);
    

    في الحالة المذكورة أعلاه، إجمالي عدد الكائنات التي تم إنشاؤها هو 5 فقط.

    في الواقع عندما نقوم بتسلسل السلاسل عبر + المشغل ثم يحتفظ بفئة StringBuffer لأداء نفس المهمة على النحو التالي: -

    StringBuffer sb = new StringBuffer("I");
    sb.append("am");
    sb.append("good");
    sb.append("boy");
    System.out.println(sb);
    

    بهذه الطريقة سيتم إنشاء خمسة كائنات فقط.

يا شباب هذه هي الاختلافات الأساسية بين + و ال com.concat طريقة.يتمتع :)

من أجل الاكتمال، أردت أن أضيف أنه يمكن العثور على تعريف عامل التشغيل "+" في ملف جي إل إس SE8 15.18.1:

إذا كان تعبير معامل واحد فقط من نوع سلسلة ، فسيتم إجراء تحويل السلسلة (§5.1.11) على المعامل الآخر لإنتاج سلسلة في وقت التشغيل.

نتيجة تسلسل السلسلة هي إشارة إلى كائن سلسلة يمثل تسلسل السلاسل المعاملة.تسبق أحرف اليد اليسرى أحرف المعامل الأيمن في السلسلة التي تم إنشاؤها حديثًا.

يتم إنشاء كائن السلسلة حديثًا (§12.5) ما لم يكن التعبير تعبيرًا ثابتًا (§15.28).

حول تنفيذ JLS يقول ما يلي:

قد يختار التنفيذ إجراء التحويل والتسلسل في خطوة واحدة لتجنب إنشاء كائن سلسلة وسيطة ثم التخلص منها.لزيادة أداء تسلسل السلسلة المتكررة ، قد يستخدم برنامج التحويل البرمجي Java فئة StringBuffer أو تقنية مماثلة لتقليل عدد كائنات السلسلة الوسيطة التي يتم إنشاؤها عن طريق تقييم التعبير.

بالنسبة للأنواع البدائية ، قد يؤدي التنفيذ أيضًا إلى تحسين إنشاء كائن غلاف عن طريق التحويل مباشرة من نوع بدائي إلى سلسلة.

لذا انطلاقًا من "قد يستخدم مترجم Java فئة StringBuffer أو تقنية مشابهة للتقليل"، يمكن للمترجمين المختلفين إنتاج كود بايت مختلف.

ال + المشغل يمكن أن تعمل بين قيمة نوع البيانات سلسلة وسلسلة أو حرف أو عدد صحيح أو مزدوج أو عائم.إنه يقوم فقط بتحويل القيمة إلى تمثيل السلسلة الخاص بها قبل التسلسل.

ال مشغل متزامن لا يمكن القيام به إلا باستخدام الخيوط.فهو يتحقق من توافق نوع البيانات ويلقي خطأً في حالة عدم تطابقهما.

باستثناء هذا، فإن الكود الذي قدمته يفعل نفس الأشياء.

أنا لا أعتقد ذلك.

a.concat(b) تم تنفيذه في String وأعتقد أن التنفيذ لم يتغير كثيرًا منذ ظهور آلات جافا المبكرة.ال + يعتمد تنفيذ العملية على إصدار Java والمترجم.حالياً + يتم تنفيذه باستخدام StringBuffer لجعل العملية في أسرع وقت ممكن.ربما في المستقبل سيتغير هذا.في الإصدارات السابقة من Java + كانت العملية على السلاسل أبطأ بكثير لأنها أنتجت نتائج متوسطة.

أعتقد ذلك += يتم تنفيذه باستخدام + والأمثل بالمثل.

عند استخدام +، تنخفض السرعة مع زيادة طول السلسلة، ولكن عند استخدام concat، تكون السرعة أكثر استقرارًا، والخيار الأفضل هو استخدام فئة StringBuilder التي تتمتع بسرعة ثابتة للقيام بذلك.

أعتقد أنه يمكنك فهم السبب.لكن أفضل طريقة على الإطلاق لإنشاء سلاسل طويلة هي استخدام StringBuilder() وappend()، وأي من السرعتين ستكون غير مقبولة.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top