أعتقد أن معظم الناس على دراية بهيكل بيانات جدول التجزئة ، وفي العديد من الأماكن ، يتم استخدام جداول التجزئة لتحسين كفاءة البحث. هناك طريقة في فئة كائن Java:
Hashcode الأصلي العام () ؛
وفقًا لإعلان هذه الطريقة ، يمكن ملاحظة أن الطريقة تُرجع قيمة رقمية للنوع الباحث وهي طريقة محلية ، لذلك لا يوجد تنفيذ محدد في فئة الكائن.
لماذا تحتاج فئة الكائن إلى مثل هذه الطريقة؟ ما هي وظيفتها؟ اليوم سنناقش طريقة Hashcode بالتفصيل.
1. وظيفة طريقة Hashcode
بالنسبة لغات البرمجة التي تحتوي على أنواع الحاويات ، يشارك HashCode بشكل أساسي. وينطبق الشيء نفسه في جافا. تتمثل الوظيفة الرئيسية لطريقة HashCode في العمل بشكل طبيعي مع مجموعات قائمة على التجزئة ، وتشمل مجموعات التجزئة Hashset و HashMap و Hashtable.
لماذا تقول ذلك؟ فكر في موقف حيث يتم إدراج كائن في مجموعة ، وكيفية تحديد ما إذا كان الكائن موجود بالفعل في المجموعة؟ (ملاحظة: لا يُسمح بالعناصر المكررة في المجموعة)
ربما يفكر معظم الناس في استدعاء طريقة متساوية لمقارنة واحدة تلو الأخرى ، وهذه الطريقة ممكنة بالفعل. ومع ذلك ، إذا كان هناك بالفعل عشرة آلاف قطعة من البيانات أو المزيد من البيانات في المجموعة ، إذا تم استخدام طريقة متساوية لمقارنة واحدة تلو الأخرى ، فستكون الكفاءة مشكلة. في هذا الوقت ، تنعكس وظيفة طريقة Hashcode. عندما يتم إضافة كائن جديد إلى المجموعة ، يتم استدعاء طريقة HashCode للكائن أولاً للحصول على قيمة Hashcode المقابلة. في الواقع ، في التنفيذ المحدد لـ HashMap ، سيتم استخدام جدول لحفظ قيمة Hashcode للكائن الذي تم حفظه. إذا لم تكن هناك قيمة hashcode في الجدول ، فيمكن تخزينها مباشرة دون أي مقارنة. في حالة وجود قيمة Hashcode ، يتم استدعاء طريقة متساوية للمقارنة مع العنصر الجديد. إذا كان الشيء نفسه صحيحًا ، فلن يتم تخزين العناوين الأخرى. لذلك ، هناك مشكلة في حل الصراع هنا. وبهذه الطريقة ، يتم تقليل عدد المرات التي تسمى طريقة متساوية في الواقع إلى حد كبير. بعبارة ببساطة: تقوم طريقة HashCode في Java بتعيين المعلومات المتعلقة بالكائن (مثل عنوان تخزين الكائن ، وحقل الكائن ، وما إلى ذلك) إلى قيمة رقمية وفقًا لقواعد معينة ، وتسمى هذه القيمة قيمة التجزئة. الرمز التالي هو التنفيذ المحدد لطريقة PUT في java.util.hashmap:
public v put (k key ، v value) {if (key == null) return putfornullkey (value) ؛ int hash = hash (key.hashCode ()) ؛ 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) ؛ العودة لاغية. } يتم استخدام طريقة PUT لإضافة عنصر جديد إلى HashMap. من التنفيذ المحدد لطريقة PUT ، يمكننا أن نعلم أنه سيتم استدعاء طريقة HashCode أولاً للحصول على قيمة Hashcode للعنصر ، ثم تحقق مما إذا كانت قيمة Hashcode موجودة في الجدول. إذا كان موجودًا ، اتصل بالطريقة المتساوية لإعادة تحديد ما إذا كان العنصر موجودًا. في حالة وجوده ، قم بتحديث قيمة القيمة ، وإلا إضافة العنصر الجديد إلى HashMap. من هنا ، يمكننا أن نرى أن طريقة Hashcode موجودة لتقليل عدد المكالمات إلى طريقة متساوية وبالتالي تحسين كفاءة البرنامج.
يعتقد بعض الأصدقاء عن طريق الخطأ أنه افتراضيًا ، يقوم Hashdode بإرجاع عنوان التخزين للكائن. في الواقع ، هذا الرأي غير مكتمل. صحيح أن بعض JVMs تُرجع مباشرة عنوان التخزين للكائن عند تنفيذها ، ولكن هذا ليس هو الحال في معظم الوقت. لا يمكن القول إلا أن عنوان التخزين قد يكون مرتبطًا به. فيما يلي تنفيذ توليد قيم التجزئة في نقطة الساخنة JVM:
inline inline intptr_t get_next_hash (موضوع * self ، OOP OBJ) {intptr_t value = 0 ؛ إذا كان (HashCode == 0) {// يستخدم هذا النموذج مجموعة Global Park-Miller RNG ، // لذلك من الممكن أن يتسابق خيوطان وإنشاء نفس RNG. // على نظام MP ، سيكون لدينا الكثير من الوصول إلى RW إلى عالمي ، وبالتالي فإن آلية // تستحث الكثير من حركة التماسك. القيمة = OS :: Random () ؛ } آخر إذا (hashcode == 1) {// هذا الاختلاف له خاصية كونها مستقرة (idempotent) // بين عمليات STW. يمكن أن يكون هذا مفيدًا في بعض مخططات التزامن 1-0 //. intptr_t addrbits = intptr_t (obj) >> 3 ؛ value = addrbits ^ (addrbits >> 5) ^ gvars.stwrandom ؛ } آخر إذا (hashcode == 2) {value = 1 ؛ // لاختبار الحساسية} آخر if (hashcode == 3) {value = ++ gvars.hcsuregence ؛ } آخر إذا (hashcode == 4) {value = intptr_t (obj) ؛ } آخر {// مخطط XOR-Shift الخاص بـ Marsaglia مع حالة خاصة بالمعلومات // ربما يكون هذا هو أفضل تطبيق شامل-سنجعل // من المحتمل أن نجعل هذا هو الافتراضي في الإصدارات المستقبلية. غير موقعة t = self-> _ hashstatex ؛ t ^= (t << 11) ؛ Self-> _ hashstatex = self-> _ hashStatey ؛ Self-> _ hashStatey = Self-> _ HashStatez ؛ Self-> _ hashstatez = self-> _ hashStatew ؛ غير موقعة V = Self-> _ hashstatew ؛ v = (v ^ (v >> 19)) ^ (t ^ (t >> 8)) ؛ Self-> _ hashStatew = v ؛ القيمة = v ؛ } value & = markoopdesc :: hash_mask ؛ if (value == 0) value = 0xbad ؛ تأكيد (القيمة! = markoopdesc :: no_hash ، "instariant") ؛ tevent (hashcode: توليد) ؛ قيمة الإرجاع ؛}يقع هذا التنفيذ في نقطة Hotspot/SRC/Share/VM/Runtime/Synchronizer.cpp.
لذلك ، قد يقول بعض الأشخاص أنه هل يمكننا الحكم مباشرة على ما إذا كان كائنين متساوين بناءً على قيمة Hashcode؟ من المؤكد أنه غير ممكن ، لأن الكائنات المختلفة قد تولد نفس قيمة Hashcode. على الرغم من أنه لا يمكن الحكم على ما إذا كان كائنين متساوين بناءً على قيمة HashCode ، إلا أنه يمكنك الحكم مباشرة على أن الكائنين ليسا متساوين بناءً على قيمة Hashcode. إذا كانت قيم Hashcode للكائنات غير متساوية ، فيجب أن تكون كائنين مختلفين. إذا كنت ترغب في تحديد ما إذا كان كائنين متساوين حقًا ، فيجب عليك استخدام طريقة متساوين.
وهذا يعني ، بالنسبة لكائنين ، إذا كانت النتيجة التي تم الحصول عليها عن طريق استدعاء طريقة متساوٍ صحيحة ، يجب أن تكون قيم Hashcode للكائنات متساوية ؛
إذا كانت النتيجة التي تم الحصول عليها بواسطة طريقة متساوٍ خاطئة ، فقد لا تكون قيم Hashcode للكائنات مختلفة ؛
إذا كانت قيم Hashcode للكائنات غير متساوية ، فيجب أن تكون النتيجة التي تم الحصول عليها بواسطة طريقة متساوٍ خاطئة ؛
إذا كانت قيم hashcode للكائنات متساوية ، فإن النتيجة التي تم الحصول عليها بواسطة طريقة متساوية غير معروفة.
2. طريقة المساواة وطريقة هاشك
في بعض الحالات ، عند تصميم الفصل ، يحتاج المبرمجون إلى إعادة كتابة طريقة متساوية ، مثل فئة السلسلة ، ولكن تأكد من ملاحظة أنه أثناء إعادة كتابة طريقة متساوية ، يجب عليهم إعادة كتابة طريقة Hashcode. لماذا تقول ذلك؟
لنرى مثالًا أدناه:
package com.cxh.test1 ؛ import java.util.hashmap ؛ import java.util.hashset ؛ import java.util.set ؛ class people {private string name ؛ عصر INT الخاص ؛ الناس العامة (اسم السلسلة ، int age) {this.name = name ؛ this.age = العمر ؛ } public void setage (int age) {this.age = age ؛ } Override public boolean يساوي (كائن OBJ) {// todo method method tuto stub Return this.name.equals (((people) obj) .Name) && this.age == ((people) obj) .age ؛ }} الفئة العامة الرئيسية {public static void main (string [] args) {people p1 = new people ("jack" ، 12) ؛ system.out.println (p1.hashcode ()) ؛ hashmap <people ، integer> hashmap = new hashmap <people ، integer> () ؛ hashmap.put (p1 ، 1) ؛ System.out.println (hashmap.get (أشخاص جدد ("جاك" ، 12)) ؛}}هنا أعيد كتابة طريقة متساوية فقط ، مما يعني أنه إذا كان للكائنات شخصين نفس الاسم والعمر ، فإنهما يعتبران نفس الشخص.
القصد الأصلي لهذا الرمز هو إخراج نتيجة "1" ، ولكن في الواقع يخرج "NULL". لماذا؟ والسبب هو أنك تنسى إعادة كتابة طريقة Hashcode أثناء إعادة كتابة طريقة متساوية.
على الرغم من أن كائنين مع نفس الاسم والعمر يتم تحديدهما منطقيا على أن يكونا متساوين (على غرار فئة السلسلة) ، يجب أن يكون معروفًا أنه افتراضيًا ، تقوم طريقة HashCode بتخطيط عنوان تخزين الكائن. ثم ليس من المستغرب أن تكون نتيجة الإخراج للرمز أعلاه "فارغًا". السبب بسيط للغاية ، والكائن الذي أشار إليه بواسطة P1 و
System.out.println (hashmap.get (أشخاص جدد ("جاك" ، 12)) ؛ الأشخاص الجدد ("جاك" ، 12) في هذه الجملة يولد كائنين ، ويجب أن تكون عناوين التخزين الخاصة بهم مختلفة. فيما يلي التنفيذ المحدد لطريقة الحصول على Hashmap:
public v get (مفتاح الكائن) {if (key == null) return getFornullKey () ؛ int hash = hash (key.hashCode ()) ؛ لـ (الإدخال <k ، v> e = table [indexfor (hash ، table.length)] ؛ e! = null ؛ e = e.next) {object k ؛ if ( } إرجاع فارغ ؛ }لذلك ، عند استخدام HashMap للحصول على ، لأن قيم HashCDoe التي تم الحصول عليها تختلف (لاحظ أن الكود أعلاه قد يحصل على نفس قيمة Hashcode في بعض الحالات ، ولكن الاحتمال صغير نسبيًا ، لأنه لن يتم تنفيذ عناوين التخزين الخاصة بالكائنين ، إلا أنه من الممكن الحصول على نفس قيمة Hashcode) ، لذلك لن يتم تنفيذي عن العناوين في الطريقة التي يتم تنفيذي بها في العمل وستكون مناسبة مباشرةً.
لذلك ، إذا كنت تريد أن يخرج الكود أعلاه النتيجة "1" ، فهذا أمر بسيط للغاية. تحتاج فقط إلى إعادة كتابة طريقة Hashcode وجعل طريقة متساوٍ وطريقة HashCode متسقة دائمًا بشكل منطقي.
package com.cxh.test1 ؛ import java.util.hashmap ؛ import java.util.hashset ؛ import java.util.set ؛ فئة الناس {اسم السلسلة الخاصة ؛ عصر INT الخاص ؛ الناس العامة (اسم السلسلة ، int age) {this.name = name ؛ this.age = العمر ؛ } public void setage (int age) {this.age = age ؛ } Override public int hashcode () {// todo method method method tuto name.hashCode ()*37+age ؛ } Override public boolean يساوي (كائن OBJ) {// todo method method tuto stub Return this.name.equals (((people) obj) .Name) && this.age == ((people) obj) .age ؛ }} الفئة العامة الرئيسية {public static void main (string [] args) {people p1 = new people ("jack" ، 12) ؛ system.out.println (p1.hashcode ()) ؛ hashmap <people ، integer> hashmap = new hashmap <people ، integer> () ؛ hashmap.put (p1 ، 1) ؛ System.out.println (hashmap.get (New People ("Jack" ، 12))) ؛ }}وبهذه الطريقة ، ستكون نتيجة الإخراج "1".
المقطع التالي مقتطف من كتاب جافا الفعال:
أثناء تنفيذ البرنامج ، طالما لم يتم تعديل المعلومات المستخدمة في تشغيل طريقة المقارنة لطريقة متساوين ، يجب أن تُرجع طريقة HashCode باستمرار نفس عدد صحيح.
إذا كان الكائنان متساويان وفقًا لطريقة متساوين ، فعليك استدعاء طريقة HashCode للكائنين يجب أن تُرجع نفس نتيجة عدد صحيح.
إذا تمت مقارنة الكائنين عدم المساواة وفقًا لطريقة متساوين ، فإن طريقة HashCode لا تُرجع بالضرورة أعداد صحيحة مختلفة.
من السهل فهم المقالين الثاني والثالث ، ولكن يتم تجاهل المقالة الأولى غالبًا. هناك أيضًا مقطع مشابه للأول في الصفحة P495 في كتاب "Thought Java Programming Thought":
"العامل الأكثر أهمية عند تصميم HashCode () هو: كلما اتصلت بـ hashcode () على نفس الكائن ، يجب إنشاء نفس القيمة. إذا تمت إضافة كائن إلى hashmap مع pum () ، وإنشاء قيمة hashcode أخرى يتم إنشاءها في البيانات المتنوعة ، يجب أن يكون المستخدمون في حالة تغيير ، يجب أن يتم الحصول على هذه البيانات. ستؤدي طريقة HashCode () إلى إنشاء رمز تجزئة مختلف. "
هنا مثال:
حزمة com.cxh.test1 ؛ استيراد java.util.hashmap ؛ استيراد java.util.hashset ؛ استيراد java.util.set ؛ class people {private string name ؛ عصر INT الخاص ؛ الناس العامة (اسم السلسلة ، int age) {this.name = name ؛ this.age = العمر ؛ } public void setage (int age) {this.age = age ؛ } Override public int hashcode () {// todo method method method tuto name.hashCode ()*37+age ؛ } Override public boolean يساوي (كائن OBJ) {// todo method method tuto stub Return this.name.equals (((people) obj) .Name) && this.age == ((people) obj) .age ؛ }} الفئة العامة الرئيسية {public static void main (string [] args) {people p1 = new people ("jack" ، 12) ؛ system.out.println (p1.hashcode ()) ؛ hashmap <people ، integer> hashmap = new hashmap <people ، integer> () ؛ hashmap.put (p1 ، 1) ؛ p1.setage (13) ؛ system.out.println (hashmap.get (p1)) ؛ }}نتيجة إخراج الرمز هذا "لاغ" ، ويجب أن يكون الجميع واضحين بشأن الأسباب.
لذلك ، عند تصميم أساليب HashCode وطرق متساوية ، إذا كانت البيانات الموجودة في الكائن متقلبة ، فمن الأفضل عدم الاعتماد على هذا الحقل بطرق متساوية وطرق الترميز.
ما سبق هو كل محتوى هذه المقالة. آمل أن يكون ذلك مفيدًا لتعلم الجميع وآمل أن يدعم الجميع wulin.com أكثر.