طريقة متساوية وطريقة HashCode في Java في كائن ، لذلك كل كائن لديه هاتين الطريقتين. في بعض الأحيان نحتاج إلى تنفيذ احتياجات محددة وقد تضطر إلى إعادة كتابة هاتين الطريقتين. اليوم ، سوف نقدم وظائف هاتين الطريقتين.
تُستخدم طرق متساوية () و hashcode () للمقارنة في نفس الفئة ، خاصة عند تخزين كائن الفئة نفسه في الحاوية مثل تعيين الكائنات في نفس الفئة.
هنا نحتاج أولاً إلى فهم مشكلة:
يجب أن يكون كائنان ذوو المساواة () متساويًا ، وتجزئة الرمز () متساويًا ، ولا يمكن أن يثبت كائنان مع متساوي () لا يساوي ، أن Hashcode () ليس متساويًا. بمعنى آخر ، بالنسبة لكائنين لا يساويهم طريقة () ، قد يكون Hashcode () متساويًا. (فهمي ناتج عن تعارضات رمز التجزئة عند توليدها)
هنا يشبه Hashcode فهرس كل حرف في القاموس ، ويساوي () مثل مقارنة الكلمات المختلفة تحت نفس الحرف في القاموس. كما هو الحال في القاموس ، البحث عن الكلمتين "الذات" و "عفوية" تحت كلمة "الذات" في القاموس ، إذا تم استخدام متساوي () لتحديد المساواة في الاستعلام عن الكلمات ، فهي نفس الكلمة. على سبيل المثال ، فإن الكلمتين المقارنتين بـ equals () هما "الذات" ، ثم يجب أن تكون القيم التي تم الحصول عليها بواسطة طريقة HashCode () متساوية في هذا الوقت ؛ إذا كانت الطريقة المتساوية () تقارن عبارة "الذات" و "عفوية" ، فإن النتيجة هي أنك لا تريد الانتظار ، لكن كلتا هذه الكلمتين تنتميان إلى الكلمات "ذاتية" وهكذا عند البحث عن الفهارس ، أي ، Hashcode () هو نفسه. إذا كان متساوي () يقارن عبارة "الذات" و "هم" ، فإن النتائج تختلف أيضًا ، والنتائج التي تم الحصول عليها بواسطة HashCode () تختلف أيضًا في هذا الوقت.
على العكس: Hashcode () مختلفة ، ويمكن تقديم متساوٍ () ؛ HashCode () متساوية ، قد يكون متساويًا () متساويًا أو قد لا يكون متساويًا. في فئة الكائن ، فإن طريقة HashCode () هي طريقة محلية ، والتي تُرجع قيمة العنوان للكائن. تقارن طريقة تساوي () في فئة الكائن أيضًا قيم العنوان للكائنين. إذا كانت المساواة () متساوية ، فهذا يعني أن قيم العنوان للكائنين متساوية أيضًا ، بالطبع hashcode () متساوية ؛
في الوقت نفسه ، توفر خوارزمية التجزئة كفاءة عالية لإيجاد العناصر
إذا كنت ترغب في معرفة ما إذا كان الكائن موجودًا في مجموعة ، فكيف تكتب رمز البرنامج التقريبي؟
عادةً ما تخرج كل عنصر واحد تلو الآخر لمقارنته بالكائن الذي تبحث عنه. عندما تجد أن نتيجة مقارنة طريقة متساوٍ بين عنصر والكائن الذي تبحث عنه ، توقف عن البحث وإرجاع المعلومات الإيجابية. خلاف ذلك ، إرجاع المعلومات السلبية. إذا كان هناك العديد من العناصر في المجموعة ، مثل 10000 عنصر ولا تحتوي على الكائن الذي تبحث عنه ، فهذا يعني أن برنامجك يحتاج إلى الحصول على 10000 عنصر من المجموعة ومقارنة واحدة تلو الأخرى للحصول على استنتاج.
اخترع شخص ما خوارزمية التجزئة لتحسين كفاءة العثور على عناصر من مجموعة. بهذه الطريقة ، تنقسم المجموعة إلى عدة مناطق تخزين. يمكن لكل كائن حساب رمز التجزئة ، ويمكن تجميع رمز التجزئة (محسوب باستخدام وظائف التجزئة المختلفة). كل مجموعة تتوافق مع منطقة تخزين معينة. وفقًا لتجزئة كائن ما ، يمكنه تحديد المساحة التي يجب تخزين الكائن فيها. يستخدم Hashset خوارزمية تجزئة للوصول إلى مجموعة الكائنات. يستخدم داخليًا طريقة أخذ ما تبقى من عدد معين N (هذه وظيفة التجزئة هي الأسهل) لتجميع وتقسيم رموز التجزئة. تحدد فئة الكائن طريقة hashcode () لإرجاع رمز التجزئة لكل كائن Java. عند البحث عن كائن من مجموعة hashset ، يقوم نظام Java أولاً باستدعاء طريقة HashCode () للكائن للحصول على جدول رمز التجزئة للكائن. ، ثم ابحث عن منطقة التخزين المقابلة بناءً على التجزئة ، وأخيراً الحصول على كل عنصر في منطقة التخزين وقارنه باستخدام الكائن لطريقة متساوية ؛ وبهذه الطريقة ، يمكنك الحصول على الاستنتاج دون اجتياز جميع العناصر في المجموعة. يمكن ملاحظة أن مجموعة hashset لها أداء جيد لاسترجاع الكائنات ، ولكن كفاءة تخزين الكائنات في مجموعة hashset منخفضة نسبيًا ، لأنه عند إضافة كائن إلى مجموعة التجزئة ، يجب أولاً حساب رمز التجزئة للكائن وتحديد موقع تخزين الكائن في المجموعة بناءً على رمز التجزئة. من أجل التأكد من أنه يمكن تخزين كائنات المثيل للفئة بشكل طبيعي في hashset ، يجب أن تكون نتائج كائدي المثيلين في هذه الفئة متساوية أيضًا عندما تكون النتائج مقارنة بالطريقة المتساوية () متساوية ؛ أي إذا كانت نتيجة OBJ1.equals (OBJ2) صحيحة ، فيجب أن تكون نتيجة التعبير التالي صحيحة أيضًا:
OBJ1.HashCode () == OBJ2.HASHCODE ()
بمعنى آخر: عندما نعيد كتابة طريقة متساوية في الكائن ، يجب علينا إعادة كتابة طريقة Hashcode الخاصة به. ومع ذلك ، إذا لم نعيد كتابة طريقة HashCode الخاصة بها ، فإن طريقة HashCode في كائن الكائن تُرجع دائمًا عنوان التجزئة لكائن ما ، وهذا العنوان لا يساوي أبدًا. لذا ، حتى إذا تم إعادة كتابة طريقة متساوية في هذا الوقت ، فلن يكون هناك أي تأثير محدد ، لأنه إذا كانت طريقة Hashcode لا تريد الانتظار ، فلن تسمي طريقة متساوية للمقارنة ، لذلك فهي لا معنى لها.
إذا كانت طريقة hashcode () للفئة لا تتبع المتطلبات المذكورة أعلاه ، فعندما تكون نتائج المقارنة بين كائنين مثيل اثنين من هذه الفئة متساوية مع طريقة متساوية () ، لا ينبغي تخزينها في المجموعة في نفس الوقت. ومع ذلك ، إذا تم تخزينها في مجموعة التجزئة ، نظرًا لأن قيمة الإرجاع لطريقة HashCode () مختلفة (تكون قيمة إرجاع طريقة Hashcode في الكائن دائمًا مختلفًا) ، فقد يتم وضع الكائن الثاني في منطقة مختلفة من الكائن الأول أولاً وفقًا لحساب رمز التجزئة ، لذلك لا يمكن أن يكون بمقارنة طريقة المساواة مع الكائن الأول ، فقد يتم تسجيله في مجموعة التجزئة. لا يمكن أن تلبي طريقة HashCode () في فئة الكائن متطلبات الكائن الذي يتم تخزينه في مجموعة التجزئة ، لأن قيمة الإرجاع الخاصة بها محسوبة من عنوان ذاكرة الكائن. لا تتغير قيمة التجزئة التي يتم إرجاعها بواسطة نفس الكائن في أي وقت أثناء تشغيل البرنامج. لذلك ، طالما أنه كائنان مثيلان مختلفان ، حتى لو كانت نتائج المقارنة لطريقة متساوية متساوية ، فإن قيمة إرجاع طريقة HashCode الافتراضية الخاصة بها مختلفة.
دعنا نلقي نظرة على مثال محدد:
كائن RectObject: package com.weijia.demo ؛ الطبقة العامة rectobject {public int x ؛ الجمهور int y ؛ RectObject (int x ، int y) {this.x = x ؛ this.y = y ؛ } Override public int hashcode () {Final int prime = 31 ؛ int النتيجة = 1 ؛ النتيجة = prime * النتيجة + x ؛ النتيجة = prime * النتيجة + y ؛ نتيجة العودة } Override Public Boolean يساوي (كائن OBJ) {if (this == obj) إرجاع true ؛ إذا (OBJ == NULL) عودة خطأ ؛ if (getClass ()! = obj.getClass ()) return false ؛ المستقيم النهائي الآخر = (المستقيم) OBJ ؛ if (x! = other.x) {return false ؛ } if (y! = other.y) {return false ؛ } إعادة صواب ؛ }} لقد تجاوزنا الأساليب التجزئة ومساواة الأساليب في كائن الفئة الأصل ونرى أنه في الأساليب ومساواة الرمز المتساوي ، إذا كانت قيم x و y لكائنين اثنين من المستقيم متساوية ، فإن قيم hashcode الخاصة بهم متساوية ، وتساوي الإرجاع صحيحًا ؛
هنا هو رمز الاختبار:
حزمة com.weijia.demo ؛ استيراد java.util.hashset ؛ demo class public {public static void main (string [] args) {hashset <RestoBject> set = new hashset <rectObject> () ؛ RectObject R1 = RectObject (3،3) ؛ RectObject R2 = مستقيم جديد (5،5) ؛ RectObject R3 = RectObject (3،3) ؛ set.add (r1) ؛ set.add (r2) ؛ set.add (r3) ؛ set.add (r1) ؛ system.out.println ("size:"+set.size ()) ؛ }} قمنا بتخزين أربعة كائنات في hashset وطبعنا حجم مجموعة مجموعة. ما هي النتيجة؟
النتيجة الجارية: الحجم: 2
لماذا هو 2؟ هذا بسيط للغاية ، لأننا نعيد كتابة طريقة Hashcode لفئة RectObject. طالما أن قيم سمة X و Y لكائن RectObject متساوي ، فإن قيم Hashcode الخاصة بها متساوية أيضًا. لذلك قارن أولاً قيم Hashcode. لا تساوي قيم سمة X و Y لكائنات R1 و R2 ، لذا فإن ترميزات HashCodes مختلفة ، لذلك يمكن وضع كائن R2 ، ولكن قيم سمة X و Y لكائن R3 هي نفس قيم السمة لكائن R1 ، وبالتالي فإن رمز Hashcode متساوي. في هذا الوقت ، نقوم بمقارنة طريقة متساوية من R1 و R3 ، لأن قيم X و Y للاثنين متساوية ، لذلك لا تتم إضافة كائنات R1 و R3 ، لذلك لا يمكن وضع R3. أيضًا ، لا تتم إضافة R1 في النهاية ، لذلك لا يوجد سوى كائنان واحد ، R1 و R2 في المجموعة المحددة.
بعد ذلك ، نعلق على طريقة HashCode في كائن RectObject ، أي أننا لا نتجاوز طريقة Hashcode في كائن الكائن ، وتشغيل الرمز:
النتيجة الجارية: الحجم: 3
هذه النتيجة هي أيضا بسيطة للغاية. أولاً ، احكم على علامة التجزئة لكائن R1 وكائن R2. نظرًا لأن طريقة HashCode في الكائن تُرجع نتيجة تحويل عنوان الذاكرة المحلية للكائن ، فإن رمز التجزئة لكائنات المثيلات المختلفة مختلفة. وبالمثل ، نظرًا لأن رمز التجزئة لـ R3 و R1 غير متكافئ أيضًا ، ولكن R1 == R1 ، لذلك في المجموعة النهائية ، لا يوجد سوى ثلاثة كائنات R1 و R2 و R3 ، وبالتالي فإن الحجم 3
بعد ذلك ، نعلق على محتويات طريقة متساوية في كائن RectObject ، وإرجاع خطأ مباشرة ، دون التعليق على طريقة Hashcode ، وقم بتشغيل الرمز:
النتيجة الجارية: الحجم: 3
هذه النتيجة غير متوقعة بعض الشيء ، دعنا نحللها:
أولاً ، قارن كائنات R1 و R2 ، والتي ليست متساوية ، لذلك يتم وضع R2 في مجموعة ، ثم انظر إلى طرق Hashcode لـ R3 ومقارنة R1 و R3 ، والتي تكون متساوية ، ثم تقارن طرقها المتساوية. نظرًا لأن طريقة متساوية تُرجع دائمًا false ، فإن R1 و R3 غير متكافئين أيضًا ، وليس هناك حاجة إلى ذكر R3 و R2. لا تتساوى ترميزات الاثنين ، لذلك يتم وضع R3 في مجموعة ، ثم انظر إلى R4 ، وقارن R1 و R4 أن الرموز المتساوية متساوية. عند مقارنة طريقة متساوٍ ، نظرًا لأن العوائد المساوية ، فإن R1 و R4 غير متساوين ، فإن نفس R2 و R4 غير متكافئين أيضًا ، كما أن R3 و R4 غير متكافئين أيضًا ، لذلك يمكن وضع R4 في المجموعة المحددة ، لذلك يجب أن تكون النتيجة الحجم: 4 ، فلماذا 3؟
في هذا الوقت ، نحتاج إلى التحقق من رمز المصدر لشركة Hashset. فيما يلي رمز المصدر لطريقة إضافة في hashset:
/*** يضيف العنصر المحدد إلى هذه المجموعة إذا لم يكن موجودًا بالفعل. * بشكل أكثر رسمية ، يضيف العنصر المحدد <tt> e </tt> إلى هذه المجموعة إذا كانت هذه المجموعة تحتوي على أي عنصر <tt> e2 </tt> بحيث * <tt> (e == null؟ e2 == null: E.equals (e2)) </tt>. * إذا كانت هذه المجموعة تحتوي بالفعل على العنصر ، فإن المكالمة تترك المجموعة * دون تغيير وتُرجع <tt> false </tt>. * * param e element لإضافته إلى هذه المجموعة * return <tt> true </tt> إذا لم تحتوي هذه المجموعة بالفعل على العنصر */ boolean العام add (e e) {return map.put (e ، present) == null ؛ } هنا يمكننا أن نرى أن Hashset يتم تنفيذها فعليًا على أساس HashMap. نحن نضغط على طريقة وضع hashmap ، رمز المصدر هو كما يلي:
/*** يربط القيمة المحددة بالمفتاح المحدد في هذه الخريطة. * إذا كانت الخريطة تحتوي سابقًا على رسم خرائط للمفتاح ، فسيتم استبدال القيمة القديمة *. * * مفتاح مفتاح param الذي يمكن أن تتمثل فيه القيمة المحددة * قيمة قيمة param المراد ارتباطها بالمفتاح المحدد * @RETURN القيمة السابقة المرتبطة بمفتاح <tt> </tt> ، أو * <tt> null </tt> إذا لم يكن هناك رسم خرائط <tt> مفتاح </tt>. * (A <tt> null </tt> يمكن أن تشير الإرجاع أيضًا إلى أن الخريطة * المرتبطة مسبقًا <tt> null </tt> مع <tt> مفتاح </tt>.) */public v put (k key ، v value) {if (key == null) return putfornullkey (value) ؛ int hash = hash (مفتاح) ؛ int i = indexfor (hash ، table.length) ؛ لـ (الإدخال <k ، v> e = table [i] ؛ e! = null ؛ e = e.next) {object k ؛ if ( e.value = القيمة ؛ eRecordAccess (هذا) ؛ إرجاع Oldvalue ؛ }} modcount ++ ؛ addentry (التجزئة ، المفتاح ، القيمة ، i) ؛ العودة لاغية. } دعونا نلقي نظرة أساسا على شروط الحكم إذا.
أولاً ، نحدد ما إذا كان الرمز المتساوي متساويًا. إذا لم يكن متساويًا ، فاخفقه مباشرة. إذا كان ذلك متساويًا ، فقم بمقارنة ما إذا كان هذان الكائنان متساويان أو طريقة متساوية لهذين الكائنين. نظرًا لأنه يتم تنفيذه أو تشغيله ، طالما أن أحدهم صحيح ، يمكننا أن نشرح ذلك هنا. في الواقع ، فإن حجم المجموعة أعلاه هو 3 ، لأنه لم يتم وضع آخر R1 ، وكان يعتقد أن R1 == R1 عاد صحيحًا ، لذلك لم يتم وضعه. وبالتالي فإن حجم المجموعة هو 3.
أخيرًا ، دعونا نلقي نظرة على تسرب الذاكرة الناجم عن Hashcode: انظر إلى الرمز:
حزمة com.weijia.demo ؛ استيراد java.util.hashset ؛ demo class public {public static void main (string [] args) {hashset <RestoBject> set = new hashset <rectObject> () ؛ RectObject R1 = RectObject (3،3) ؛ RectObject R2 = مستقيم جديد (5،5) ؛ RectObject R3 = RectObject (3،3) ؛ set.add (r1) ؛ set.add (r2) ؛ set.add (r3) ؛ r3.y = 7 ؛ System.out.println ("الحجم قبل الحذف:"+set.size ()) ؛ set.remove (R3) ؛ System.out.println ("الحجم بعد الحذف:"+set.size ()) ؛ }} نتائج التشغيل:
الحجم قبل الحذف: 3
الحجم المحذوف: 3
راش ، وجدت مشكلة ، وكانت مشكلة كبيرة. أطلقنا على الإزالة لحذف كائن R3 ، معتقدين أنه تم حذفه ، ولكن في الواقع لم يتم حذفه. وهذا ما يسمى تسرب الذاكرة ، وهو كائن غير مستخدم ، لكنه لا يزال في الذاكرة. لذلك بعد تشغيل هذا عدة مرات ، تنفجر الذاكرة. ألقِ نظرة على رمز الإزالة المصدر:
/*** يزيل العنصر المحدد من هذه المجموعة في حالة وجوده. * بشكل أكثر رسميًا ، يزيل عنصرًا <tt> e </tt> بحيث * <tt> (o == null؟ e == null: o.equals (e)) </tt> ، * إذا كانت هذه المجموعة تحتوي على مثل هذا العنصر. إرجاع <tt> true </tt> إذا كانت هذه المجموعة تحتوي على العنصر (أو ما يعادلها ، إذا تم تغيير هذه المجموعة * نتيجة للمكالمة). (لن تحتوي هذه المجموعة على العنصر * بمجرد إرجاع المكالمات.) * * param o الكائن المراد إزالته من هذه المجموعة ، إذا كانت موجودة * @tt> true </tt> إذا كانت المجموعة تحتوي على العنصر المحدد */ public boolean remove (Object o) {return map.remove (o) == الحاضر ؛ } ثم انظر إلى الكود المصدري لطريقة إزالة:
/*** يزيل التعيين للمفتاح المحدد من هذه الخريطة إذا كانت موجودة. * * مفتاح مفتاح Param الذي سيتم إزالته من الخريطة * @Return القيمة السابقة المرتبطة بمفتاح <tt> </tt> ، أو * <tt> null </tt> إذا لم يكن هناك رسم خرائط لـ <tt> مفتاح </tt>. * (A <tt> null </tt> يمكن أن تشير الإرجاع أيضًا إلى أن الخريطة * المرتبطة مسبقًا <tt> null </tt> مع <tt> مفتاح </tt>.) */public v إزالة (مفتاح الكائن) {إدخال <k ، v> e = removeentryforkey (مفتاح) ؛ العودة (e == null؟ null: e.value) ؛ } دعنا نلقي نظرة على رمز مصدر طريقة removeentryforkey:
/** * يزيل وإرجاع الإدخال المرتبط بالمفتاح المحدد * في hashmap. إرجاع NULL إذا كان hashmap لا يحتوي على رسم الخرائط * لهذا المفتاح. */ الإدخال النهائي <k ، v> removeentryforkey (مفتاح الكائن) {int hash = (key == null)؟ 0: التجزئة (المفتاح) ؛ int i = indexfor (hash ، table.length) ؛ الدخول <k ، v> prev = table [i] ؛ الدخول <k ، v> e = prev ؛ بينما (e! = null) {entry <k ، v> next = e.next ؛ كائن ك ؛ if ( مقاس--؛ if (prev == e) table [i] = next ؛ آخر prev.next = التالي ؛ e.RecordRemoval (هذا) ؛ إرجاع ه ؛ } prev = e ؛ ه = التالي ؛ } إرجاع e ؛ } نرى أنه عند استدعاء طريقة إزالة ، سنستخدم أولاً قيمة Hashcode للكائن للعثور على الكائن ثم حذفه. هذه المشكلة لأننا قمنا بتعديل قيمة سمة Y لكائن R3. ولأن طريقة HashCode لكائن RectObject لها قيمة y المشاركة في العملية ، فقد تغير رمز التجزئة لكائن R3 ، لذلك لم يتم العثور على R3 في طريقة إزالة ، لذلك فشل الحذف. بمعنى أن رمز التجزئة لـ R3 قد تغير ، ولكن لم يتم تحديث الموقع الذي يخزنه ولا يزال في موقعه الأصلي ، لذلك عندما نستخدم رمز الهاود الجديد للعثور عليه ، لن نجدها بالتأكيد.
في الواقع ، فإن الطريقة أعلاه بسيطة للغاية في التنفيذ: كما هو موضح في الشكل:
جدول التجزئة الخطي البسيط للغاية ، وظيفة التجزئة المستخدمة هي mod ، والرمز المصدر هو كما يلي:
/*** إرجاع الفهرس لرمز التجزئة ح. */ static int indexfor (int h ، int) {return h & (length-1) ؛ } هذه في الواقع عملية وزارة الدفاع ، ولكن هذا النوع من التشغيل أكثر كفاءة من التشغيل ٪.
1،2،3،4،5 يعني نتيجة MOD ، وكل عنصر يتوافق مع بنية قائمة مرتبطة. لذلك ، إذا كنت ترغب في حذف إدخال <k ، v> ، فستحصل أولاً على Hashcode ، حتى تحصل على عقدة الرأس للقائمة المرتبطة ، ثم تكرارها من خلال القائمة المرتبطة. إذا كانت Hashcode و Equals متساوية ، فقم بحذف هذا العنصر.
يخبرني تسرب الذاكرة أعلاه رسالة: إذا شاركنا في تشغيل HashCode لقيمة سمة الكائن ، فلا يمكننا تعديل قيمة السمة الخاصة به عند حذفها ، وإلا ستحدث مشاكل خطيرة.
في الواقع ، يمكننا أيضًا النظر إلى طريقة HashCode وطريقة أنواع الكائنات المقابلة لأنواع البيانات الأساسية 8.
من بينها ، هو رمز التجزئة من النوع الأساسي في 8 بسيط للغاية لإعادة حجمها العددي مباشرة. يكون كائن السلسلة من خلال طريقة حساب معقدة ، ولكن يمكن أن تضمن طريقة الحساب هذه أنه إذا كانت قيم هذه السلسلة متساوية ، فستكون Hashcode متساوية. تتمثل الأنواع الأساسية الثمانية للطرق المتساوية في مقارنة القيم الرقمية مباشرة ، ويقارن نوع السلسلة المتساوي قيم السلاسل.
ما سبق هو كل محتوى هذه المقالة. آمل أن يكون ذلك مفيدًا لتعلم الجميع وآمل أن يدعم الجميع wulin.com أكثر.