في الآونة الأخيرة ، عندما كنت أبحث عن وظيفة ، سألني الفاحص سؤالًا بسيطًا: "الفرق بين StringBuffer و StringBuilder ، ما هي سيناريوهات التطبيق الخاصة بهم؟" تتم مشاركة إجابة المحرر معك أدناه ، بحيث يمكن أن يكون من المناسب أن يتعلم الجميع في المستقبل ، وذلك لتحقيق سجل.
في الواقع ، فقط ابحث عن Google Master وستكون لديك الإجابة: StringBuffer مكافئ تمامًا للطرق والوظائف في StringBuilder ، ولكن معظم الطرق في StringBuffer تستخدم الكلمة الرئيسية المتزامنة للتعديل ، بحيث تكون آمنة مؤشرات الترابط. بدون هذا التعديل ، يمكن اعتبار StringBuilder مؤشر ترابط غير آمن.
من أجل فهم الإجابة أعلاه بشكل أفضل ، من الأفضل أن نرى مباشرة أن تنفيذ رمز المصدر لـ StringBuffer و StringBuilder أكثر واقعية. كمبرمج ، "إذا كان لديك أي أسئلة ، فابحث عن رمز المصدر" هي الطريقة الصحيحة. أستطيع أن أقول بمسؤولية ، بالطبع ، يجب أن يكون لديك شروط!
في تطبيق JDK ، يتم توريث كل من StringBuffer و StringBuilder من AbstractStringBuilder. من أجل الأمن وغير السلامة من متعدد الخيوط ، سيكون لديك فهم تقريبي للطرق المتزامنة في StringBuffer.
هنا سأتحدث بإيجاز عن مبدأ التنفيذ لـ AbstractStringBuilder: نحن نعلم أن استخدام StringBuffer ليس أكثر من تحسين كفاءة اتصال السلسلة في Java ، لأنه إذا كنت تستخدم + مباشرة لاتصال السلسلة ، فإن JVM ستقوم بإنشاء كائنات سلسلة متعددة ، والتي ستسبب بعض الشيء. يستخدم AbstractStringBuilder صفيف char لحفظ السلسلة التي يجب إلحاقها. صفيف char لها حجم أولي. عندما يتجاوز طول سلسلة سلسلة الإلحاق سعة صفيف Char الحالية ، يتم توسيع صفيف Char بشكل ديناميكي ، أي إعادة تطبيق مساحة ذاكرة أكبر ، ثم نسخ صفيف Char الحالي إلى موقع جديد. نظرًا لأن النفقات العامة لإعادة تخصيص الذاكرة والنسخ كبيرة نسبيًا ، في كل مرة تقوم فيها إعادة تطبيق مساحة الذاكرة بالطريقة التي تكون بها مساحة الذاكرة أكبر من التيار المطلوب ، وهو مرتين.
بعد ذلك ، استمتع ببعض المرح!
فيما يلي بعض المعلومات في Google:
【
بدأ StringBuffer مع JDK 1.0
بدأ StringBuilder مع JDK 1.5
بدءًا من JDK 1.5 ، يتم استخدام عملية الاتصال (+) مع متغيرات السلسلة داخليًا بواسطة JVM
يتم تنفيذ StringBuilder ، وتم تنفيذ هذه العملية باستخدام StringBuffer.
】
نحن ننظر إلى عملية التنفيذ من خلال برنامج بسيط:
قائمة 1 Buffer.java
الفئة العامة العازلة {public static void main (string [] args) {String S1 = "AAAAA" ؛ السلسلة S2 = "bbbbbb" ؛ سلسلة r = فارغة ؛ int i = 3694 ؛ r = s1 + i + s2 ؛ لـ (int j = 0 ؛ i <10 ؛ j ++) {r+= "23124" ؛ }}}استخدم الأمر javap -c buffer لعرض تطبيق Bytecode:
إدراج 2 bytecode فئة المخزن المؤقت
القائمة المقابلة 1 والقائمة 2 ، تقوم تعليمات LDC في القائمة 2 بتحميل سلسلة "AAAA" من المجموعة الثابتة إلى أعلى المكدس ، ومتاجر ISTORE_1 "AAAAA" في المتغير 1. فيما يلي هو نفسه. يدفع Sipush قيمة ثابتة عدد ثابتة (-32768 ~ 32767) إلى الجزء العلوي من المكدس. هنا هو الثابت "3694". لمزيد من مجموعات تعليمات Java ، يرجى التحقق من مقال آخر "مجموعة تعليمات Java".
دعنا نرى مباشرة أن 13 ، 13 ~ 17 جديد في كائن StringBuffer ونطلق على طريقة التهيئة الخاصة به. 20 ~ 21 هو الضغط الأول على المتغير 1 إلى أعلى المكدس من خلال aload_1. كما ذكرنا سابقًا ، يتم وضع المتغير 1 في سلسلة "AAAAA" ، ثم اتصل بطريقة إلحاق StringBuffer من خلال تعليمات invokevirtual لربط "AAAAA" معًا. ما يلي 24 ~ 30 هو نفسه. أخيرًا ، في 33 ، يتم استدعاء وظيفة ToString لـ StringBuffer للحصول على نتيجة السلسلة وتخزينها في المتغير 3 من خلال المتجر.
عندما نرى هذا ، قد يقول أحدهم ، "نظرًا لأن JVM يستخدم StringBuffer لتوصيل السلاسل ، فإننا لا نحتاج إلى استخدام StringBuffer بأنفسنا ، فقط استخدم"+"فقط!" هل هذا صحيح؟ بالطبع لا. كما يقول المثل ، "هناك سبب للوجود" ، دعنا نستمر في النظر إلى رمز Bytecode المقابل للحلقة اللاحقة.
37 ~ 42 كلها بعض الاستعدادات قبل الدخول إلى الحلقة. 37 ، 38 SET J إلى 1. خلاف ذلك ، فإنه يدخل الحلقة ، أي رمز Bytecode من 47 ~ 66. هنا نحتاج فقط إلى النظر إلى 47 إلى 51 لنعرف لماذا نستخدم StringBuffer في الكود الخاص بنا للتعامل مع اتصالات السلسلة ، لأنه في كل مرة نقوم فيها بعملية "+" ، فإن JVM لديه كائن جديد للسلسلة للتعامل مع اتصالات السلسلة ، والتي ستكون باهظة الثمن عند إشراك العديد من عمليات اتصال السلسلة.