الاختلافات في مكدس جافا
الكاتب:Eve Cole
وقت التحديث:2009-11-30 17:08:19
-
1. المكدس والكومة هما مكانان تستخدمهما Java لتخزين البيانات في ذاكرة الوصول العشوائي. على عكس C++، تقوم Java تلقائيًا بإدارة المكدس والكومة، ولا يمكن للمبرمجين تعيين المكدس أو الكومة مباشرة.
2. ميزة المكدس هي أن سرعة الوصول أسرع من الكومة، وتأتي في المرتبة الثانية بعد السجلات الموجودة مباشرة في وحدة المعالجة المركزية. لكن العيب هو أنه يجب تحديد حجم وعمر البيانات المخزنة في المكدس، كما أن هناك نقصًا في المرونة. بالإضافة إلى ذلك، يمكن مشاركة بيانات المكدس، راجع النقطة 3 للحصول على التفاصيل. تتمثل ميزة الكومة في أنها يمكنها تخصيص حجم الذاكرة ديناميكيًا، ولا يحتاج المترجم إلى إخبار المترجم مسبقًا بعمرها، وسيقوم جامع البيانات المهملة في Java تلقائيًا بجمع البيانات التي لم تعد مستخدمة. لكن العيب هو أنه نظرًا للحاجة إلى تخصيص الذاكرة ديناميكيًا في وقت التشغيل، فإن سرعة الوصول تكون بطيئة.
3. هناك نوعان من أنواع البيانات في Java.
الأول هو الأنواع البدائية، وهناك 8 أنواع في المجموع، وهي int، وshort، وlong، وbyte، وfloat، وdouble، وboolean، وchar (لاحظ أنه لا يوجد نوع أساسي من السلسلة). يتم تعريف هذا النوع من التعريف في شكل مثل int a = 3; long b = 255L; تجدر الإشارة إلى أن المتغيرات التلقائية تخزن قيمًا حرفية، وليس مثيلات للفئات، أي أنها ليست مراجع للفئات. على سبيل المثال، int a = 3؛ حيث a هو مرجع يشير إلى النوع int، ويشير إلى القيمة الحرفية 3. بيانات هذه القيم الحرفية معروفة من حيث الحجم والعمر (يتم تحديد هذه القيم الحرفية بشكل ثابت في كتلة برنامج معينة، وتختفي قيم الحقل بعد خروج كتلة البرنامج). موجودة على المكدس.
بالإضافة إلى ذلك، تحتوي المكدس على ميزة خاصة مهمة جدًا، وهي أنه يمكن مشاركة البيانات المخزنة في المكدس. لنفترض أننا نحدد أيضًا:
كثافة العمليات = 3؛
كثافة العمليات ب = 3؛
يقوم المترجم أولاً بمعالجة int a = 3؛ أولاً يقوم بإنشاء مرجع للمتغير a على المكدس، ثم يبحث عن عنوان بقيمة حرفية 3. إذا لم يعثر عليه، فإنه يفتح عنوانًا لتخزين القيمة الحرفية. القيمة 3، ثم تشير إلى العنوان 3. بعد ذلك، تتم معالجة int b = 3؛ بعد إنشاء المتغير المرجعي b، نظرًا لوجود قيمة حرفية بالفعل تبلغ 3 في المكدس، تتم الإشارة b مباشرةً إلى العنوان 3. بهذه الطريقة، هناك موقف حيث يشير كل من a وb إلى 3 في نفس الوقت.
من المهم ملاحظة أن مرجع هذه القيمة الحرفية يختلف عن مرجع كائن الفئة. افترض أن مراجع كائنين من الفئة تشير إلى نفس الكائن في نفس الوقت، إذا قام متغير مرجع كائن واحد بتعديل الحالة الداخلية للكائن، فإن متغير مرجع الكائن الآخر سيعكس التغيير على الفور. وفي المقابل، لا يؤدي تعديل قيمة المرجع الحرفي إلى تغيير قيمة مرجع آخر إلى المرجع الحرفي أيضًا. كما في المثال أعلاه، بعد أن حددنا قيمتي a وb، قمنا بتعيين a=4؛ داخل المترجم، عندما يواجه = 4؛، سيعيد البحث عما إذا كانت هناك قيمة حرفية 4 في المكدس، وإذا لم يكن الأمر كذلك، فسوف يعيد فتح عنوان لتخزين القيمة 4 إذا كانت موجودة بالفعل ، فسوف يشير مباشرة إلى هذا العنوان. ولذلك، فإن التغييرات في قيمة أ لن تؤثر على قيمة ب.
والآخر هو بيانات فئة المجمع، مثل عدد صحيح، سلسلة، مزدوجة وغيرها من الفئات التي تغلف أنواع البيانات الأساسية المقابلة. كل هذه الأنواع من البيانات موجودة في الكومة. تستخدم Java العبارة الجديدة () لتخبر المترجم بوضوح أنه سيتم إنشاؤها ديناميكيًا حسب الحاجة في وقت التشغيل، لذا فهي أكثر مرونة، ولكن العيب هو أنها تستغرق وقتًا أطول. 4. السلسلة عبارة عن بيانات من نوع مجمّع خاص. أي أنه يمكن إنشاؤه في شكل String str = new String( "abc" ); أو يمكن إنشاؤه في شكل String str = "abc" (للمقارنة، قبل JDK 5.0، لم يكن لديك مطلقًا رأيت تعبيرًا صحيحًا i = 3؛ لأنه لا يمكن استخدام الفئات والقيم الحرفية بالتبادل، باستثناء السلسلة في JDK 5.0، لأن المترجم يقوم بتحويل Integer i = new Integer(3). خلفية) . الأول عبارة عن عملية إنشاء فئة موحدة، أي أن كل شيء في Java عبارة عن كائن، والكائنات هي مثيلات للفئات، وكلها تم إنشاؤها في شكل جديد (). يمكن لبعض الفئات في Java، مثل فئة DateFormat، إرجاع فئة تم إنشاؤها حديثًا من خلال طريقة getInstance () للفئة، والتي يبدو أنها تنتهك هذا المبدأ. ليس حقيقيًا. تستخدم هذه الفئة النمط المفرد لإرجاع مثيل للفئة، ولكن يتم إنشاء هذا المثيل داخل الفئة من خلال new ()، ويقوم getInstance() بإخفاء هذه التفاصيل من الخارج. إذن لماذا في String str = "abc"؛، لا يتم إنشاء المثيل من خلال new (). هل ينتهك المبدأ المذكور أعلاه؟ في الواقع لا.
5. حول الأعمال الداخلية للسلسلة Str = "abc". تقوم Java بتحويل هذا البيان داخليًا إلى الخطوات التالية:
(1) قم أولاً بتعريف متغير مرجع كائن يسمى str إلى فئة السلسلة: String str;
(2) ابحث في المكدس لمعرفة ما إذا كان هناك عنوان بقيمة "abc"، وإذا لم يكن الأمر كذلك، فافتح عنوانًا بقيمة حرفية "abc"، ثم قم بإنشاء كائن جديد من فئة السلسلة، وأضف العنصر. أحرف o تشير قيمة السلسلة إلى هذا العنوان، ويتم تسجيل الكائن المشار إليه o بجوار هذا العنوان على المكدس. إذا كان هناك بالفعل عنوان بالقيمة "abc"، فسيتم العثور على الكائن o وإرجاع عنوان o.
(3) أشر إلى عنوان الكائن o.
تجدر الإشارة إلى أنه بشكل عام يتم تخزين قيم السلسلة في فئة السلسلة مباشرة. ولكن مثل String str = "abc"؛ في هذه الحالة، تحفظ قيمة السلسلة مرجعًا للبيانات المخزنة على المكدس!
ولتوضيح هذه المشكلة بشكل أفضل يمكننا التحقق منها من خلال الأكواد التالية.
سلسلة str1 = "اي بي سي"؛
سلسلة str2 = "اي بي سي"؛
System.out.println(str1==str2);
لاحظ أننا لا نستخدم str1.equals(str2) هنا، لأن هذا سيقارن ما إذا كانت قيم السلسلتين متساوية. == علامة، وفقًا لتعليمات JDK، تُرجع صحيحًا فقط عندما يشير كلا المرجعين إلى نفس الكائن. ما نريد رؤيته هنا هو ما إذا كان كل من str1 وstr2 يشيران إلى نفس الكائن.
تظهر النتائج أن JVM أنشأ مرجعين str1 وstr2، لكنه أنشأ كائنًا واحدًا فقط، ويشير كلا المرجعين إلى هذا الكائن.
دعنا نتقدم خطوة أخرى ونغير الكود أعلاه إلى:
سلسلة str1 = "اي بي سي"؛
سلسلة str2 = "اي بي سي"؛
str1 = "bcd" ;
System.out.println(str1 + "،" + str2)؛ //bcd، abc
System.out.println(str1==str2);
هذا يعني أن التغيير في المهمة يؤدي إلى تغيير في مرجع كائن الفئة، ويشير str1 إلى كائن جديد آخر! ولا يزال str2 يشير إلى الكائن الأصلي. في المثال أعلاه، عندما قمنا بتغيير قيمة str1 إلى "bcd"، وجد JVM أنه لا يوجد عنوان لتخزين هذه القيمة على المكدس، لذلك فتح هذا العنوان وأنشأ كائنًا جديدًا تشير قيمة سلسلته إلى هذا العنوان .
في الواقع، تم تصميم فئة السلسلة لتكون غير قابلة للتغيير. إذا كنت ترغب في تغيير قيمته، يمكنك ذلك، لكن JVM يقوم بهدوء بإنشاء كائن جديد بناءً على القيمة الجديدة في وقت التشغيل، ثم يقوم بإرجاع عنوان هذا الكائن إلى مرجع للفئة الأصلية. على الرغم من أن عملية الإنشاء هذه تتم تلقائيًا بالكامل، إلا أنها تستغرق وقتًا أطول على كل حال. وفي بيئة حساسة لمتطلبات الوقت، سيكون لها آثار سلبية معينة.
قم بتعديل الكود الأصلي مرة أخرى:
سلسلة str1 = "اي بي سي"؛
سلسلة str2 = "اي بي سي"؛
str1 = "bcd" ;
سلسلة str3 = str1؛
System.out.println(str3); //bcd
سلسلة str4 = "bcd"؛
System.out.println(str1 == str4);
تشير الإشارة إلى الكائن str3 مباشرةً إلى الكائن المشار إليه بواسطة str1 (لاحظ أن str3 لا يُنشئ كائنًا جديدًا). بعد أن تغير str1 قيمتها، قم بإنشاء مرجع سلسلة str4 وأشر إلى الكائن الجديد الذي تم إنشاؤه لأن str1 قام بتعديل القيمة. يمكن العثور على أن str4 لم يقم بإنشاء كائن جديد هذه المرة، وبالتالي تحقيق مشاركة البيانات في المكدس مرة أخرى.
دعونا نلقي نظرة على الكود التالي مرة أخرى.
String str1 = new String( "abc");
سلسلة str2 = "اي بي سي"؛
System.out.println(str1==str2);
يتم إنشاء مرجعين. يتم إنشاء كائنين. يشير المرجعان إلى كائنين مختلفين على التوالي.
سلسلة str1 = "اي بي سي"؛
String str2 = new String( "abc");
System.out.println(str1==str2);
يتم إنشاء مرجعين. يتم إنشاء كائنين. يشير المرجعان إلى كائنين مختلفين على التوالي.
يوضح الجزءان أعلاه من التعليمات البرمجية أنه طالما تم استخدام new () لإنشاء كائن جديد، فسيتم إنشاؤه في الكومة، ويتم تخزين قيمة السلسلة الخاصة به بشكل منفصل حتى لو كانت هي نفس البيانات الموجودة في المكدس لن تتم مشاركته مع البيانات الموجودة في المكدس.
6. لا يمكن تعديل قيمة فئة غلاف نوع البيانات. لا يمكن تعديل قيمة فئة السلسلة فحسب، بل لا يمكن لجميع فئات غلاف أنواع البيانات تغيير قيمها الداخلية. 7. الخاتمة والمقترحات:
(1) عندما نحدد فئة باستخدام تنسيق مثل String str = "abc";، فإننا نعتبر دائمًا أنه من المسلم به أننا نقوم بإنشاء كائن str من فئة String. تقلق بشأن الفخاخ! ربما لم يتم إنشاء الكائن! الشيء الوحيد المؤكد هو أنه تم إنشاء مرجع لفئة السلسلة. أما فيما يتعلق بما إذا كان هذا المرجع يشير إلى كائن جديد، فيجب أخذه في الاعتبار وفقًا للسياق، ما لم تقم بشكل صريح بإنشاء كائن جديد من خلال الطريقة الجديدة (). لذلك، العبارة الأكثر دقة هي أننا نقوم بإنشاء متغير مرجعي str يشير إلى كائن من فئة السلسلة. يشير متغير مرجع الكائن هذا إلى فئة سلسلة بقيمة "abc". إن الفهم الواضح لهذا مفيد جدًا في القضاء على الأخطاء التي يصعب العثور عليها في البرنامج.
(2) يمكن أن يؤدي استخدام String str = "abc" إلى تحسين سرعة تشغيل البرنامج إلى حد ما، لأن JVM سيحدد تلقائيًا ما إذا كان من الضروري إنشاء كائن جديد بناءً على الوضع الفعلي للبيانات الموجودة في المكدس. . أما بالنسبة لكود String str = new String("abc"); فسيتم دائمًا إنشاء كائنات جديدة في الكومة، بغض النظر عما إذا كانت قيم السلسلة الخاصة بها متساوية أو ما إذا كان من الضروري إنشاء كائنات جديدة، وبالتالي زيادة العبء على البرنامج. يجب أن تكون هذه الفكرة هي فكرة وضع وزن الذبابة، لكن من غير المعروف ما إذا كان التنفيذ الداخلي لـ JDK يطبق هذا الوضع.
(3) عند مقارنة ما إذا كانت القيم في فئة التغليف متساوية، استخدم طريقة يساوي () ؛ عند اختبار ما إذا كانت مراجع فئتي التغليف تشير إلى نفس الكائن، استخدم ==.
(4) نظرًا لطبيعة فئة السلسلة غير القابلة للتغيير، عندما يحتاج متغير السلسلة إلى تغيير قيمته بشكل متكرر، يجب عليك التفكير في استخدام فئة StringBuffer لتحسين كفاءة البرنامج.