العدد الأقصى للقيم في التعداد الجزء الأول

الجزء الأول ، النظري | الجزء الثاني ، عملي


استنادا إلى سقسقة من Evgeny Mandrikov الملقب godin:


في ذلك ، يتساءل عن الحد الأقصى لعدد القيم التي يمكن تحديدها في enum في Java. بعد سلسلة من التجارب واستخدام السحر الأسود ConstantDynamic ( JEP 309 ) ، يأتي مؤلف السؤال إلى الرقم 8191.

في سلسلة من مقالتين ، نبحث عن الحدود النظرية لعدد العناصر في التعداد ، ونحاول الاقتراب منها في الممارسة العملية ، ونكتشف على طول الطريق كيف يمكن أن يساعد JEP 309.

استطلاع


فصل مراجعة رأينا فيه التعداد مفككًا.

أولاً ، دعنا نرى ما يترجم التعداد التالي إلى:

 public enum FizzBuzz { Fizz, Buzz, FizzBuzz; } 

بعد التجميع والتفكيك:

japap -c -s -p -v FizzBuzz.class
 Classfile /dev/null/FizzBuzz.class Last modified 32 . 2019 .; size 903 bytes MD5 checksum add0af79de3e9a70a7bbf7d57dd0cfe7 Compiled from "FizzBuzz.java" public final class FizzBuzz extends java.lang.Enum<FizzBuzz> minor version: 0 major version: 58 flags: (0x4031) ACC_PUBLIC, ACC_FINAL, ACC_SUPER, ACC_ENUM this_class: #2 // FizzBuzz super_class: #13 // java/lang/Enum interfaces: 0, fields: 4, methods: 4, attributes: 2 Constant pool: #1 = Fieldref #2.#3 // FizzBuzz.$VALUES:[LFizzBuzz; #2 = Class #4 // FizzBuzz #3 = NameAndType #5:#6 // $VALUES:[LFizzBuzz; #4 = Utf8 FizzBuzz #5 = Utf8 $VALUES #6 = Utf8 [LFizzBuzz; #7 = Methodref #8.#9 // "[LFizzBuzz;".clone:()Ljava/lang/Object; #8 = Class #6 // "[LFizzBuzz;" #9 = NameAndType #10:#11 // clone:()Ljava/lang/Object; #10 = Utf8 clone #11 = Utf8 ()Ljava/lang/Object; #12 = Methodref #13.#14 // java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum; #13 = Class #15 // java/lang/Enum #14 = NameAndType #16:#17 // valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum; #15 = Utf8 java/lang/Enum #16 = Utf8 valueOf #17 = Utf8 (Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum; #18 = Methodref #13.#19 // java/lang/Enum."<init>":(Ljava/lang/String;I)V #19 = NameAndType #20:#21 // "<init>":(Ljava/lang/String;I)V #20 = Utf8 <init> #21 = Utf8 (Ljava/lang/String;I)V #22 = String #23 // Fizz #23 = Utf8 Fizz #24 = Methodref #2.#19 // FizzBuzz."<init>":(Ljava/lang/String;I)V #25 = Fieldref #2.#26 // FizzBuzz.Fizz:LFizzBuzz; #26 = NameAndType #23:#27 // Fizz:LFizzBuzz; #27 = Utf8 LFizzBuzz; #28 = String #29 // Buzz #29 = Utf8 Buzz #30 = Fieldref #2.#31 // FizzBuzz.Buzz:LFizzBuzz; #31 = NameAndType #29:#27 // Buzz:LFizzBuzz; #32 = String #4 // FizzBuzz #33 = Fieldref #2.#34 // FizzBuzz.FizzBuzz:LFizzBuzz; #34 = NameAndType #4:#27 // FizzBuzz:LFizzBuzz; #35 = Utf8 values #36 = Utf8 ()[LFizzBuzz; #37 = Utf8 Code #38 = Utf8 LineNumberTable #39 = Utf8 (Ljava/lang/String;)LFizzBuzz; #40 = Utf8 LocalVariableTable #41 = Utf8 name #42 = Utf8 Ljava/lang/String; #43 = Utf8 this #44 = Utf8 Signature #45 = Utf8 ()V #46 = Utf8 <clinit> #47 = Utf8 Ljava/lang/Enum<LFizzBuzz;>; #48 = Utf8 SourceFile #49 = Utf8 FizzBuzz.java { public static final FizzBuzz Fizz; descriptor: LFizzBuzz; flags: (0x4019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM public static final FizzBuzz Buzz; descriptor: LFizzBuzz; flags: (0x4019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM public static final FizzBuzz FizzBuzz; descriptor: LFizzBuzz; flags: (0x4019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM private static final FizzBuzz[] $VALUES; descriptor: [LFizzBuzz; flags: (0x101a) ACC_PRIVATE, ACC_STATIC, ACC_FINAL, ACC_SYNTHETIC public static FizzBuzz[] values(); descriptor: ()[LFizzBuzz; flags: (0x0009) ACC_PUBLIC, ACC_STATIC Code: stack=1, locals=0, args_size=0 0: getstatic #1 // Field $VALUES:[LFizzBuzz; 3: invokevirtual #7 // Method "[LFizzBuzz;".clone:()Ljava/lang/Object; 6: checkcast #8 // class "[LFizzBuzz;" 9: areturn LineNumberTable: line 1: 0 public static FizzBuzz valueOf(java.lang.String); descriptor: (Ljava/lang/String;)LFizzBuzz; flags: (0x0009) ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=1, args_size=1 0: ldc #2 // class FizzBuzz 2: aload_0 3: invokestatic #12 // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum; 6: checkcast #2 // class FizzBuzz 9: areturn LineNumberTable: line 1: 0 LocalVariableTable: Start Length Slot Name Signature 0 10 0 name Ljava/lang/String; private FizzBuzz(); descriptor: (Ljava/lang/String;I)V flags: (0x0002) ACC_PRIVATE Code: stack=3, locals=3, args_size=3 0: aload_0 1: aload_1 2: iload_2 3: invokespecial #18 // Method java/lang/Enum."<init>":(Ljava/lang/String;I)V 6: return LineNumberTable: line 1: 0 LocalVariableTable: Start Length Slot Name Signature 0 7 0 this LFizzBuzz; Signature: #45 // ()V static {}; descriptor: ()V flags: (0x0008) ACC_STATIC Code: stack=4, locals=0, args_size=0 0: new #2 // class FizzBuzz 3: dup 4: ldc #22 // String Fizz 6: iconst_0 7: invokespecial #24 // Method "<init>":(Ljava/lang/String;I)V 10: putstatic #25 // Field Fizz:LFizzBuzz; 13: new #2 // class FizzBuzz 16: dup 17: ldc #28 // String Buzz 19: iconst_1 20: invokespecial #24 // Method "<init>":(Ljava/lang/String;I)V 23: putstatic #30 // Field Buzz:LFizzBuzz; 26: new #2 // class FizzBuzz 29: dup 30: ldc #32 // String FizzBuzz 32: iconst_2 33: invokespecial #24 // Method "<init>":(Ljava/lang/String;I)V 36: putstatic #33 // Field FizzBuzz:LFizzBuzz; 39: iconst_3 40: anewarray #2 // class FizzBuzz 43: dup 44: iconst_0 45: getstatic #25 // Field Fizz:LFizzBuzz; 48: aastore 49: dup 50: iconst_1 51: getstatic #30 // Field Buzz:LFizzBuzz; 54: aastore 55: dup 56: iconst_2 57: getstatic #33 // Field FizzBuzz:LFizzBuzz; 60: aastore 61: putstatic #1 // Field $VALUES:[LFizzBuzz; 64: return LineNumberTable: line 3: 0 line 5: 13 line 7: 26 line 1: 39 } Signature: #47 // Ljava/lang/Enum<LFizzBuzz;>; SourceFile: "FizzBuzz.java" 


في القائمة التقينا

  • حقل public static final واحد لكل قيمة محددة في التعداد
  • الحقل الاصطناعي الخاص $VALUES ، تفاصيل تنفيذ طريقة values()
  • تطبيق values() و valueOf() الأساليب
  • منشئ خاص
  • كتلة التهيئة الثابتة ، حيث يحدث في الواقع الشيء الأكثر إثارة للاهتمام. لننظر في الأمر بمزيد من التفصيل.

في شكل java code ، يبدو هذا الأخير كالتالي:

  static { Fizz = new FizzBuzz("Fizz", 0); Buzz = new FizzBuzz("Buzz", 1); FizzBuzz = new FizzBuzz("FizzBuzz", 2); $VALUES = new FizzBuzz[] { Fizz, Buzz, FizzBuzz }; } 

أولاً ، يتم إنشاء مثيلات عناصر التعداد. تتم كتابة الحالات التي تم إنشاؤها على الفور إلى الحقول public static final المقابلة.

ثم يتم إنشاء صفيف وتعبئته بروابط إلى مثيلات جميع عناصر التعداد. الروابط مأخوذة من حقول الفئة التي قمنا بتهيئتها في الفقرة أعلاه. يتم تخزين الصفيف المعبأ في الحقل private static final $VALUES .

بعد ذلك ، القائمة جاهزة للعمل.

عنق الزجاجة


فصل ممل حيث نبحث عن قيود على عدد عناصر التعداد.

يمكنك بدء البحث باستخدام الفصل JLS §8.9.3 "تعداد الأعضاء":

JLS 8.9.3 عدد الأعضاء
أعضاء التعداد من النوع E هم كل مما يلي:
...
* لكل إحصاء ثابت c معلن في نص إعلان E ، E لديه
حقل نهائي ثابت ثابت عام من النوع E له نفس الشيء
اسم ج. يحتوي الحقل على مُهيئ متغير يقوم بإنشاء مثيل E ويمرر أي
وسيطات c إلى المُنشئ الذي تم اختياره لـ E. يحتوي الحقل على نفس التعليقات التوضيحية
كما ج (إن وجدت).

يتم الإعلان عن هذه الحقول ضمنيًا بنفس الترتيب الخاص بها
التعداد الثوابت ، قبل أي حقول ثابتة أعلن صراحة في جسم
إعلان E.
...
* الأساليب المعلنة ضمنيًا التالية:
 /** * Returns an array containing the constants of this enum * type, in the order they're declared. This method may be * used to iterate over the constants as follows: * * for(E c : E.values()) * System.out.println(c); * * @return an array containing the constants of this enum * type, in the order they're declared */ public static E[] values(); /** * Returns the enum constant of this type with the specified * name. * The string must match exactly an identifier used to declare * an enum constant in this type. (Extraneous whitespace * characters are not permitted.) * * @return the enum constant with the specified name * @throws IllegalArgumentException if this enum type has no * constant with the specified name */ public static E valueOf(String name); 



لذلك ، كل فئة من فئات التعداد لها طريقة values() تُرجع صفيفًا مع جميع العناصر المعلنة في هذا التعداد. ويترتب على ذلك أن التعداد الكروي في الفراغ لا يمكن أن يحتوي على أكثر من عناصر Integer.MAX_VALUE + 1 .

المضي قدما. يتم تمثيل التعدادات في Java java.lang.Enum لفئة java.lang.Enum ، وبالتالي فهي تخضع لجميع القيود الملازمة للفئات في JVM.

دعونا نلقي نظرة على الوصف رفيع المستوى لهيكل ملف الفصل الوارد في JVMS §4.1 "هيكل ClassFile":

 ClassFile { u4 magic; u2 minor_version; u2 major_version; u2 constant_pool_count; cp_info constant_pool[constant_pool_count-1]; u2 access_flags; u2 this_class; u2 super_class; u2 interfaces_count; u2 interfaces[interfaces_count]; u2 fields_count; field_info fields[fields_count]; u2 methods_count; method_info methods[methods_count]; u2 attributes_count; attribute_info attributes[attributes_count]; } 

كما نعلم بالفعل من JLS §8.9.3 ، يتم إنشاء حقل يحمل نفس الاسم لكل عنصر تعداد في الفئة الناتجة. يُعرّف عدد الحقول في الفصل fields_count غير الموقّع fields_count من 16 fields_count ، والذي يحدنا إلى 65_535 حقلًا في ملف فئة واحد أو 65_534 عنصر تعداد. يتم حجز حقل واحد لصفيف $VALUES ، حيث يؤدي استنساخه إلى إرجاع طريقة values() . لم يتم ذكر ذلك بشكل صريح في المواصفات ، ولكن من غير المحتمل التوصل إلى حل أكثر أناقة.

يتم تخزين أسماء الحقول والأساليب والفئات والقيم الثابتة وغير ذلك الكثير في التجمع الثابت.
إذا كنت لا تعرف أي شيء عن البنية الداخلية للمجمع الثابت ، فإنني أوصي بقراءة المقالة القديمة من lany . على الرغم من حقيقة أنه منذ كتابتها في مجموعة الثوابت ظهرت الكثير من الأشياء الجديدة والمثيرة للاهتمام ، تظل المبادئ الأساسية على حالها.
يقتصر حجم تجمع ثوابت الفئة أيضًا على عدد العناصر 65_535. مجموعة الثوابت للفئة المشكلة بشكل صحيح ليست فارغة أبدًا. كحد أدنى ، سيكون هناك اسم لهذه الفئة.

على سبيل المثال ، تحتوي مجموعة ثوابت فئة فئة التعداد الفارغ التي تم تجميعها بواسطة javac من OpenJDK 14-ea + 29 بدون معلومات تصحيح الأخطاء على 29 إدخالًا.

ويترتب على ذلك أن عدد العناصر 65_534 في تعداد واحد غير ممكن أيضًا. في أفضل الأحوال ، يمكننا الاعتماد على 65_505 أو رقم قريب من ذلك.

الوتر الأخير في هذه المقدمة المطولة:

يمكن <clinit> القيمة إلى الحقل static final فقط في كتلة التهيئة الثابتة ، والتي يتم تمثيلها على مستوى ملف الفئة بطريقة تسمى <clinit> . لا يمكن أن تشغل bytecode لأي طريقة أكثر من 65_535 بايت. عدد مألوف ، أليس كذلك؟

putstatic إحدى تعليمات الكتابة الثابتة الثابتة 3 بايتات ، مما يعطينا تقدير تقريبي قدره 65_535 / 3 = 21_845 . في الواقع ، هذا التقدير مبالغ فيه. تأخذ التعليمة القيمة للكتابة إلى الحقل من أعلى المكدس ، وهو أحد الإرشادات السابقة الموضوعة هناك. ويأخذ هذا الإرشادات أيضًا البايتات الثمينة. ولكن حتى إذا لم تأخذ ذلك في الاعتبار ، فإن الرقم الناتج لا يزال أقل بكثير من 65_505.

باختصار:

  • يحد تنسيق ملف الفئة الحد الأقصى لعدد عناصر التعداد إلى 65_505 تقريبًا
  • آلية تهيئة الحقل النهائي الثابتة تحدنا أكثر. من الناحية النظرية - ما يصل إلى 21_845 عنصرًا كحد أقصى ، في الممارسة العملية يكون هذا الرقم أقل

في المقالة الأخيرة من السلسلة ، سنركز على التحسين غير الصحي وإنشاء ملفات فئة.

Source: https://habr.com/ru/post/ar483392/


All Articles