هناك نوعان من أشكال إنشاء كائنات السلسلة في Java، أحدهما شكل حرفي، مثل String str = "droid"؛ والآخر هو استخدام طريقة قياسية جديدة لإنشاء الكائنات، مثل String str = new String( "droid")؛ غالبًا ما يتم استخدام هاتين الطريقتين عند كتابة التعليمات البرمجية، وخاصة الطريقة الحرفية. ومع ذلك، توجد بالفعل بعض الاختلافات في الأداء واستخدام الذاكرة بين هذين التطبيقين. كل هذا يرجع إلى حقيقة أنه من أجل تقليل الإنشاء المتكرر لكائنات السلسلة، يحتفظ JVM بذاكرة خاصة تسمى هذه الذاكرة تجمع سلسلة ثابتة أو تجمع سلسلة حرفية.
مبدأ العمل
عندما يتم إنشاء كائن سلسلة في شكل حرفي في التعليمات البرمجية، سيتحقق JVM أولاً من الحرفي. إذا كان هناك مرجع لكائن سلسلة له نفس المحتوى في تجمع ثابت السلسلة، فسيتم إرجاع المرجع، وإلا سيتم إنشاء سلسلة جديدة، ويتم وضع هذا المرجع في تجمع ثابت السلسلة، ويتم إرجاع المرجع.
أعط مثالا
شكل الخلق الحرفي
انسخ رمز الكود كما يلي:
سلسلة str1 = "الروبوت";
يكتشف JVM هذا الحرفي. هنا نعتقد أنه لا يوجد كائن يكون محتواه droid. لا يمكن لـ JVM العثور على كائن سلسلة بمحتوى droid من خلال تجمع ثابت السلسلة، ثم سيقوم بإنشاء كائن سلسلة، ثم يضع مرجع الكائن الذي تم إنشاؤه حديثًا في تجمع ثابت السلسلة، ويعيد المرجع إلى متغير str1 .
إذا كان هناك قطعة من التعليمات البرمجية مثل هذا بعد ذلك
انسخ رمز الكود كما يلي:
سلسلة str2 = "الروبوت";
وبالمثل، لا يزال JVM بحاجة إلى اكتشاف هذا الحرفي، حيث يبحث JVM في تجمع ثابت السلسلة ويجد أن كائن السلسلة الذي يحتوي على محتوى "droid" موجود، لذلك يقوم بإرجاع مرجع كائن السلسلة الموجود إلى المتغير str2. لاحظ أنه لا يتم إعادة إنشاء كائن سلسلة جديد هنا.
للتحقق مما إذا كان str1 وstr2 يشيران إلى نفس الكائن، يمكننا استخدام هذا الرمز
انسخ رمز الكود كما يلي:
System.out.println(str1 == str2);
والنتيجة صحيحة.
إنشاء باستخدام جديد
انسخ رمز الكود كما يلي:
String str3 = new String("droid");
عندما نستخدم new لإنشاء كائن سلسلة، سيتم إنشاء كائن سلسلة جديد بغض النظر عما إذا كان هناك مرجع لكائن له نفس المحتوى في تجمع ثابت السلسلة. لذلك نستخدم الكود التالي لاختباره،
انسخ رمز الكود كما يلي:
String str3 = new String("droid");
System.out.println(str1 == str3);
والنتيجة خاطئة كما اعتقدنا، مما يدل على أن المتغيرين يشيران إلى كائنات مختلفة.
المتدرب
بالنسبة لكائن السلسلة الذي تم إنشاؤه باستخدام الجديد أعلاه، إذا كنت تريد إضافة مرجع هذا الكائن إلى تجمع ثابت السلسلة، فيمكنك استخدام الأسلوب الداخلي.
بعد استدعاء المتدرب، تحقق أولاً مما إذا كان هناك مرجع للكائن في تجمع ثابت السلسلة، إذا كان موجودًا، قم بإرجاع المرجع إلى المتغير.
انسخ رمز الكود كما يلي:
سلسلة str4 = str3.intern();
System.out.println(str4 == str1);
نتيجة الإخراج صحيحة.
أسئلة صعبة
شرط أساسي؟
الشرط الأساسي لتنفيذ تجمع ثابت السلسلة هو أن كائن السلسلة في Java غير قابل للتغيير، مما يضمن بأمان مشاركة متغيرات متعددة في نفس الكائن. إذا كان كائن السلسلة في Java قابلاً للتغيير وتغير قيمة الكائن من خلال عملية مرجعية، فمن الواضح أن هذا غير معقول.
مرجع أو كائن
المشكلة الأكثر شيوعًا هي ما إذا كانت المراجع أو الكائنات مخزنة في تجمع ثابت السلسلة. يقوم تجمع ثابت السلسلة بتخزين مراجع الكائنات، وليس الكائنات. في Java، يتم إنشاء الكائنات في ذاكرة الكومة.
التحقق من التحديث، العديد من التعليقات الواردة تناقش هذه المشكلة أيضًا، لقد قمت ببساطة بالتحقق منها. بيئة التحقق:
انسخ رمز الكود كما يلي:
22:18:54-androidyue~/Videos$ cat /etc/os-release
الاسم = فيدورا
الإصدار = "17 (معجزة سمين)"
المعرف=فيدورا
VERSION_ID=17
PRETTY_NAME="فيدورا 17 (معجزة بيفي)"
ANSI_COLOR = "0;34"
CPE_NAME="cpe:/o:fedoraproject:fedora:17"
22:19:04-androidyue~/Videos$ java -version
نسخة جافا "1.7.0_25"
بيئة تشغيل OpenJDK (fedora-2.3.12.1.fc17-x86_64)
OpenJDK 64-Bit Server VM (النسخة 23.7-b01، الوضع المختلط)
فكرة التحقق: يقوم برنامج Java التالي بقراءة ملف فيديو بحجم 82M وتنفيذ عمليات التدريب على شكل سلاسل.
انسخ رمز الكود كما يلي:
22:01:17-androidyue~/Videos$ ll -lh |
-rw-rw-r--. 1 androidyue androidyue 82M 20 أكتوبر 2013 لماذا_to_learn.mp4
رمز التحقق
انسخ رمز الكود كما يلي:
import java.io.BufferedReader;
import java.io.FileNotFoundException;
استيراد java.io.FileReader؛
import java.io.IOException;
الطبقة العامة TestMain {
محتوى ملف سلسلة ثابت خاص؛
public static void main(String[] args) {
fileContent = readFileToString(args[0]);
إذا (فارغة! = fileContent) {
fileContent = fileContent.intern();
System.out.println("ليست فارغة");
}
}
سلسلة ثابتة خاصة readFileToString (ملف سلسلة) {
قارئ BufferedReader = null;
يحاول {
Reader = new BufferedReader(new FileReader(file));
StringBuffer buff = new StringBuffer();
خط السلسلة؛
بينما ((line = Reader.readLine()) != null) {
buff.append(line);
}
إرجاع buff.toString();
} قبض على (FileNotFoundException ه) {
printStackTrace();
} قبض (IOException ه) {
printStackTrace();
} أخيراً {
إذا (فارغة! = القارئ) {
يحاول {
Reader.Close();
} قبض (IOException ه) {
printStackTrace();
}
}
}
عودة فارغة؛
}
}
نظرًا لوجود تجمع ثابت للسلسلة في التوليد الدائم في ذاكرة الكومة، فهو قابل للتطبيق قبل Java8. لقد تحققنا من ذلك من خلال ضبط الجيل الدائم على قيمة صغيرة جدًا. إذا كان كائن السلسلة موجودًا في تجمع ثابت السلسلة، فسيتم حتماً طرح خطأ مساحة java.lang.OutOfMemoryError.
انسخ رمز الكود كما يلي:
java -XX:PermSize=6m TestMain ~/Videos/why_to_learn.mp4
لم يؤدي تشغيل برنامج الإثبات إلى ظهور OOM. في الواقع، لا يمكن لهذا أن يثبت بشكل جيد ما إذا كان يتم تخزين الكائنات أو المراجع.
ولكن هذا يثبت على الأقل أن كائن المحتوى الفعلي char[] للسلسلة لا يتم تخزينه في تجمع ثابت السلسلة. في هذه الحالة، ليس من المهم في الواقع ما إذا كان تجمع السلسلة الثابتة يخزن كائنات السلسلة أو يشير إلى كائنات السلسلة. لكن شخصياً ما زلت أفضل تخزينه كمرجع.
المزايا والعيوب
تتمثل ميزة تجمع ثابت السلسلة في تقليل إنشاء سلاسل بنفس المحتوى وتوفير مساحة الذاكرة.
إذا أصررنا على الحديث عن العيوب، فهو أنه يتم التضحية بوقت حوسبة وحدة المعالجة المركزية مقابل المساحة. يتم استخدام وقت حساب وحدة المعالجة المركزية بشكل أساسي لمعرفة ما إذا كان هناك مرجع لكائن له نفس المحتوى في تجمع ثابت السلسلة. ومع ذلك، فإن تنفيذه الداخلي هو HashTable، وبالتالي فإن تكلفة الحساب منخفضة.
إعادة تدوير جي سي؟
نظرًا لأن تجمع ثابت السلسلة يحتوي على مراجع لكائنات السلسلة المشتركة، فهل يعني هذا أنه لا يمكن إعادة تدوير هذه الكائنات؟
أولًا، الكائنات المشتركة في السؤال صغيرة نسبيًا بشكل عام. على حد علمي، كانت هذه المشكلة موجودة في الإصدارات السابقة، ولكن مع إدخال مراجع ضعيفة، يجب أن تختفي هذه المشكلة الآن.
فيما يتعلق بهذه المشكلة، يمكنك معرفة المزيد حول هذه المقالة السلاسل المتدربة: Java Glossary
استخدام المتدرب؟
الشرط الأساسي لاستخدام المتدرب هو أن تعرف أنك تحتاج حقًا إلى استخدامه. على سبيل المثال، لدينا ملايين السجلات هنا، وقيمة معينة في السجل هي كاليفورنيا بالولايات المتحدة الأمريكية عدة مرات، ولا نريد إنشاء الملايين من كائنات السلسلة هذه. يمكننا استخدام المتدرب للاحتفاظ بنسخة واحدة فقط في الذاكرة. يستطيع. للحصول على فهم أكثر تعمقًا للمتدرب، يرجى الرجوع إلى التحليل المتعمق لـ String#intern.
هل هناك دائما استثناءات؟
هل تعلم أن التعليمة البرمجية التالية ستنشئ عدة كائنات سلسلة وتحفظ عدة مراجع في تجمع ثابت السلسلة؟
انسخ رمز الكود كما يلي:
اختبار السلسلة = "أ" + "ب" + "ج"؛
الجواب هو أنه يتم إنشاء كائن واحد فقط ويتم حفظ مرجع واحد فقط في التجمع الثابت. يمكننا معرفة ذلك باستخدام javap للفك وإلقاء نظرة.
انسخ رمز الكود كما يلي:
17:02 $ javap -c TestInternedPoolGC
تم تجميعها من "TestInternedPoolGC.java"
الطبقة العامة TestInternedPoolGC تمتد java.lang.Object{
اختبار عام TestInternedPoolGC();
شفرة:
0: التحميل_0
1: استدعاء خاص #1; //الطريقة java/lang/Object."<init>":()V
4: العودة
public static void main(java.lang.String[]) يلقي java.lang.Exception؛
شفرة:
0: LDC #2; //سلسلة ABC
2:المتجر_1
3: العودة
هل رأيت أنه أثناء التجميع، تم دمج هذه الأحرف الثلاثة في حرف واحد؟ يعد هذا في الواقع تحسينًا يتجنب إنشاء كائنات سلسلة زائدة عن الحاجة ولا يسبب مشاكل في ربط السلسلة. فيما يتعلق بربط السلسلة، يمكنك عرض تفاصيل Java: ربط السلسلة.