الطريقة الأكثر فعالية لزيادة قيمة الخريطة في Java

StackOverflow https://stackoverflow.com/questions/81346

  •  09-06-2019
  •  | 
  •  

سؤال

آمل ألا يعتبر هذا السؤال أساسيًا للغاية بالنسبة لهذا المنتدى، لكننا سنرى.أتساءل عن كيفية إعادة بناء بعض التعليمات البرمجية للحصول على أداء أفضل يتم تشغيله عدة مرات.

لنفترض أنني أقوم بإنشاء قائمة تكرار الكلمات، باستخدام خريطة (ربما HashMap)، حيث يكون كل مفتاح عبارة عن سلسلة تحتوي على الكلمة التي يتم حسابها وتكون القيمة عبارة عن عدد صحيح يتزايد في كل مرة يتم العثور على رمز مميز للكلمة.

في لغة Perl، ستكون زيادة هذه القيمة أمرًا سهلاً للغاية:

$map{$word}++;

ولكن في جافا، الأمر أكثر تعقيدًا.هنا الطريقة التي أفعلها حاليًا:

int count = map.containsKey(word) ? map.get(word) : 0;
map.put(word, count + 1);

والذي يعتمد بالطبع على ميزة autoboxing في إصدارات Java الأحدث.أتساءل عما إذا كان بإمكانك اقتراح طريقة أكثر فعالية لزيادة هذه القيمة.هل هناك أسباب أداء جيدة لتجنب إطار عمل المجموعات واستخدام شيء آخر بدلاً من ذلك؟

تحديث:لقد قمت باختبار العديد من الإجابات.انظر أدناه.

هل كانت مفيدة؟

المحلول

بعض نتائج الاختبار

لقد حصلت على الكثير من الإجابات الجيدة على هذا السؤال - شكرًا لكم - لذلك قررت إجراء بعض الاختبارات ومعرفة الطريقة الأسرع بالفعل.الطرق الخمس التي اختبرتها هي:

  • طريقة "ContainsKey" التي قدمتها السؤال
  • طريقة "TestForNull" التي اقترحها ألكسندر ديميتروف
  • طريقة "AtomicLong" التي اقترحها هانك جاي
  • طريقة "النفيس" التي اقترحها جرودولف
  • طريقة "MutableInt" التي يقترحها phax.myopenid.com

طريقة

وإليكم ما فعلته...

  1. أنشأنا خمس فئات متطابقة باستثناء الاختلافات الموضحة أدناه.كان على كل فصل تنفيذ عملية نموذجية للسيناريو الذي قدمته:فتح ملف بحجم 10 ميجابايت وقراءته، ثم إجراء حساب تكرار لجميع رموز الكلمات الموجودة في الملف.نظرًا لأن هذا يستغرق 3 ثوانٍ فقط في المتوسط، فقد طلبت منه إجراء عدد الترددات (وليس الإدخال/الإخراج) 10 مرات.
  2. توقيت حلقة من 10 التكرارات ولكن ليست عملية الإدخال/الإخراج وسجل إجمالي الوقت المستغرق (بالثواني على مدار الساعة) باستخدام طريقة إيان داروين في كتاب الطبخ جافا.
  3. أجرى جميع الاختبارات الخمسة على التوالي، ثم فعل ذلك ثلاث مرات أخرى.
  4. متوسط ​​النتائج الأربعة لكل طريقة.

نتائج

سأقدم النتائج أولاً والرمز أدناه لمن يهمه الأمر.

ال يحتوي علىمفتاح كانت الطريقة، كما هو متوقع، هي الأبطأ، لذا سأقدم سرعة كل طريقة مقارنة بسرعة تلك الطريقة.

  • يحتوي علىمفتاح: 30.654 ثانية (خط الأساس)
  • الذرية الطويلة: 29.780 ثانية (1.03 مرة أسرع)
  • تيستفورنول: 28.804 ثانية (1.06 مرة أسرع)
  • الدفين: 26.313 ثانية (1.16 مرة أسرع)
  • MutableInt: 25.747 ثانية (1.19 مرة أسرع)

الاستنتاجات

يبدو أن أسلوب MutableInt وأسلوب Trove هما الأسرع بشكل ملحوظ، حيث أنهما فقط يعطيان زيادة في الأداء تزيد عن 10%.ومع ذلك، إذا كان الترابط يمثل مشكلة، فقد يكون AtomicLong أكثر جاذبية من الآخرين (لست متأكدًا حقًا).لقد قمت أيضًا بتشغيل TestForNull باستخدام final المتغيرات، ولكن الفرق كان لا يذكر.

لاحظ أنني لم أقم بتوصيف استخدام الذاكرة في السيناريوهات المختلفة.يسعدني أن أسمع من أي شخص لديه رؤى جيدة حول كيفية تأثير طريقتي MutableInt وTrove على استخدام الذاكرة.

شخصيًا، أجد طريقة MutableInt هي الأكثر جاذبية، لأنها لا تتطلب تحميل أي فئات تابعة لجهات خارجية.لذا، ما لم أكتشف مشاكل في ذلك، فهذه هي الطريقة التي سأتبعها على الأرجح.

الرمز

إليك الكود الحاسم من كل طريقة.

يحتوي علىمفتاح

import java.util.HashMap;
import java.util.Map;
...
Map<String, Integer> freq = new HashMap<String, Integer>();
...
int count = freq.containsKey(word) ? freq.get(word) : 0;
freq.put(word, count + 1);

TestForNull

import java.util.HashMap;
import java.util.Map;
...
Map<String, Integer> freq = new HashMap<String, Integer>();
...
Integer count = freq.get(word);
if (count == null) {
    freq.put(word, 1);
}
else {
    freq.put(word, count + 1);
}

AtomicLong

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicLong;
...
final ConcurrentMap<String, AtomicLong> map = 
    new ConcurrentHashMap<String, AtomicLong>();
...
map.putIfAbsent(word, new AtomicLong(0));
map.get(word).incrementAndGet();

الدفين

import gnu.trove.TObjectIntHashMap;
...
TObjectIntHashMap<String> freq = new TObjectIntHashMap<String>();
...
freq.adjustOrPutValue(word, 1, 1);

MutableInt

import java.util.HashMap;
import java.util.Map;
...
class MutableInt {
  int value = 1; // note that we start at 1 since we're counting
  public void increment () { ++value;      }
  public int  get ()       { return value; }
}
...
Map<String, MutableInt> freq = new HashMap<String, MutableInt>();
...
MutableInt count = freq.get(word);
if (count == null) {
    freq.put(word, new MutableInt());
}
else {
    count.increment();
}

نصائح أخرى

حسنًا، قد يكون سؤالًا قديمًا، ولكن هناك طريقة أقصر مع Java 8:

Map.merge(key, 1, Integer::sum)

ماذا يفعل :لو مفتاح لا وجود لها، وضعت 1 كقيمة وإلا المجموع 1 إلى القيمة المرتبطة بها مفتاح.معلومات اكثر هنا

القليل من الأبحاث في عام 2016: https://github.com/leventov/Java-word-count, كود المصدر المعياري

أفضل النتائج لكل طريقة (الأصغر هو الأفضل):

                 time, ms
kolobokeCompile  18.8
koloboke         19.8
trove            20.8
fastutil         22.7
mutableInt       24.3
atomicInteger    25.3
eclipse          26.9
hashMap          28.0
hppc             33.6
hppcRt           36.5

نتائج الزمان والمكان:

جوجل الجوافة هو صديقك...

.. على الأقل في بعض الحالات.لديهم هذا لطيف AtomicLongMap.لطيف بشكل خاص لأنك تتعامل معه طويل كقيمة في خريطتك.

على سبيل المثال

AtomicLongMap<String> map = AtomicLongMap.create();
[...]
map.getAndIncrement(word);

من الممكن أيضًا إضافة أكثر من 1 إلى القيمة:

map.getAndAdd(word, 112L); 

@ هانك جاي

كمتابعة لتعليقي الخاص (عديم الفائدة إلى حد ما):يبدو أن Trove هو الطريق الصحيح.إذا أردت، لأي سبب من الأسباب، الالتزام بـ JDK القياسي، خريطة متزامنة و AtomicLong يمكن أن تجعل الكود أ صغير الحجم أجمل قليلا، على الرغم من YMMV.

    final ConcurrentMap<String, AtomicLong> map = new ConcurrentHashMap<String, AtomicLong>();
    map.putIfAbsent("foo", new AtomicLong(0));
    map.get("foo").incrementAndGet();

سوف نرحل 1 كقيمة في الخريطة ل foo.من الناحية الواقعية، فإن زيادة سهولة التعامل مع الخيوط هو كل ما يوصي به هذا النهج.

من الجيد دائمًا إلقاء نظرة على مكتبة مجموعات جوجل لهذا النوع من الشيء.في هذه الحالة أ مجموعة متعددة سوف تفعل الخدعة:

Multiset bag = Multisets.newHashMultiset();
String word = "foo";
bag.add(word);
bag.add(word);
System.out.println(bag.count(word)); // Prints 2

هناك طرق تشبه الخريطة للتكرار على المفاتيح/الإدخالات، وما إلى ذلك.داخليًا، يستخدم التنفيذ حاليًا ملف HashMap<E, AtomicInteger>, ، لذلك لن تتحمل تكاليف الملاكمة.

يجب أن تكون على علم بحقيقة أن محاولتك الأصلية

int count = map.containsKey(word) ? map.get(word) : 0;

تحتوي على عمليتين يحتمل أن تكونا مكلفتين على الخريطة، وهما containsKey و get.ينفذ الأول عملية من المحتمل أن تكون مشابهة إلى حد كبير للأخيرة، لذا فأنت تقوم بنفس العمل مرتين!

إذا نظرت إلى واجهة برمجة التطبيقات للخريطة، get تعود العمليات عادة null عندما لا تحتوي الخريطة على العنصر المطلوب.

لاحظ أن هذا سيجعل الحل مثل

map.put( key, map.get(key) + 1 );

خطيرة، لأنها قد تسفر NullPointerExceptionس.يجب عليك التحقق من أ null أولاً.

لاحظ أيضا, ، وهذا مهم جدًا HashMapس يستطيع يحتوي nulls حسب التعريف.لذلك لم يعد كل منهم null يقول "لا يوجد مثل هذا العنصر".في هذا الصدد، containsKey يسلك بشكل مختلف من get في الواقع أقول لك سواء هناك مثل هذا العنصر.ارجع إلى واجهة برمجة التطبيقات للحصول على التفاصيل.

ومع ذلك، بالنسبة لحالتك، قد لا ترغب في التمييز بين الملفات المخزنة null و"noSuchElement".إذا كنت لا تريد السماح nullقد تفضل أ Hashtable.قد يكون استخدام مكتبة المجمع كما تم اقتراحه بالفعل في الإجابات الأخرى حلاً أفضل للمعالجة اليدوية، اعتمادًا على مدى تعقيد التطبيق الخاص بك.

لإكمال الإجابة (ونسيت أن أضعها في البداية، بفضل وظيفة التحرير!)، فإن أفضل طريقة للقيام بذلك محليًا هي: get الى final متغير، تحقق null و put مرة أخرى مع أ 1.يجب أن يكون المتغير final لأنه غير قابل للتغيير على أي حال.قد لا يحتاج المترجم إلى هذا التلميح، لكنه أوضح بهذه الطريقة.

final HashMap map = generateRandomHashMap();
final Object key = fetchSomeKey();
final Integer i = map.get(key);
if (i != null) {
    map.put(i + 1);
} else {
    // do something
}

إذا كنت لا تريد الاعتماد على autoboxing، يجب أن تقول شيئا من هذا القبيل map.put(new Integer(1 + i.getValue())); بدلاً من.

Map<String, Integer> map = new HashMap<>();
String key = "a random key";
int count = map.getOrDefault(key, 0);
map.put(key, count + 1);

وهذه هي الطريقة التي يمكنك من خلالها زيادة القيمة باستخدام رمز بسيط.

فائدة:

  • عدم إنشاء فئة أخرى لـ int القابل للتغيير
  • رمز قصير
  • سهل الفهم
  • لا يوجد استثناء مؤشر فارغ

هناك طريقة أخرى وهي استخدام طريقة الدمج، ولكن هذا كثير جدًا بالنسبة لزيادة القيمة فقط.

map.merge(key, 1, (a,b) -> a+b);

اقتراح:يجب أن تهتم بسهولة قراءة التعليمات البرمجية أكثر من تحقيق مكاسب قليلة في الأداء في معظم الأوقات.

هناك طريقة أخرى تتمثل في إنشاء عدد صحيح قابل للتغيير:

class MutableInt {
  int value = 0;
  public void inc () { ++value; }
  public int get () { return value; }
}
...
Map<String,MutableInt> map = new HashMap<String,MutableInt> ();
MutableInt value = map.get (key);
if (value == null) {
  value = new MutableInt ();
  map.put (key, value);
} else {
  value.inc ();
}

بالطبع هذا يعني إنشاء كائن إضافي ولكن الحمل بالمقارنة مع إنشاء عدد صحيح (حتى مع Integer.valueOf) لا ينبغي أن يكون كثيرًا.

يمكنك الاستفادة من computeIfAbsent طريقة في Map الواجهة المقدمة في جافا 8.

final Map<String,AtomicLong> map = new ConcurrentHashMap<>();
map.computeIfAbsent("A", k->new AtomicLong(0)).incrementAndGet();
map.computeIfAbsent("B", k->new AtomicLong(0)).incrementAndGet();
map.computeIfAbsent("A", k->new AtomicLong(0)).incrementAndGet(); //[A=2, B=1]

طريقة computeIfAbsent يتحقق مما إذا كان المفتاح المحدد مرتبطًا بالفعل بقيمة أم لا؟إذا لم تكن هناك قيمة مرتبطة، فإنه يحاول حساب قيمتها باستخدام وظيفة التعيين المحددة.على أية حال، تقوم بإرجاع القيمة الحالية (الموجودة أو المحسوبة) المرتبطة بالمفتاح المحدد، أو فارغة إذا كانت القيمة المحسوبة فارغة.

في ملاحظة جانبية، إذا كان لديك موقف تقوم فيه سلاسل رسائل متعددة بتحديث مجموع مشترك، فيمكنك إلقاء نظرة عليه LongAdder class.في ظل المنافسة العالية، تكون الإنتاجية المتوقعة لهذه الفئة أعلى بكثير من AtomicLong, ، على حساب استهلاك مساحة أكبر.

قد يكون تدوير الذاكرة مشكلة هنا، نظرًا لأن كل مربع لـ int أكبر من أو يساوي 128 يؤدي إلى تخصيص كائن (راجع Integer.valueOf(int)).على الرغم من أن جامع البيانات المهملة يتعامل بكفاءة عالية مع الكائنات قصيرة العمر، إلا أن الأداء سيتأثر إلى حد ما.

إذا كنت تعلم أن عدد الزيادات التي تم إجراؤها سوف يفوق عدد المفاتيح إلى حد كبير (= الكلمات في هذه الحالة)، فكر في استخدام حامل int بدلاً من ذلك.قدم Phax بالفعل رمزًا لهذا الغرض.ها هو مرة أخرى، مع تغييرين (جعلت فئة المالك قيمة ثابتة وتم تعيين القيمة الأولية على 1):

static class MutableInt {
  int value = 1;
  void inc() { ++value; }
  int get() { return value; }
}
...
Map<String,MutableInt> map = new HashMap<String,MutableInt>();
MutableInt value = map.get(key);
if (value == null) {
  value = new MutableInt();
  map.put(key, value);
} else {
  value.inc();
}

إذا كنت بحاجة إلى أداء فائق، فابحث عن تطبيق Map المصمم مباشرةً لأنواع القيمة البدائية.ذكر جرودولف غنو تروڤ.

بالمناسبة، مصطلح البحث الجيد لهذا الموضوع هو "الرسم البياني".

بدلاً من استدعاء يحتوي على Key() فمن الأسرع فقط استدعاء Map.get والتحقق مما إذا كانت القيمة التي تم إرجاعها فارغة أم لا.

    Integer count = map.get(word);
    if(count == null){
        count = 0;
    }
    map.put(word, count + 1);

هل أنت متأكد من أن هذا هو عنق الزجاجة؟هل قمت بأي تحليل للأداء؟

حاول استخدام ملف تعريف NetBeans (المجاني والمدمج في NB 6.1) للاطلاع على نقاط الاتصال.

أخيرًا، غالبًا ما تكون ترقية JVM (على سبيل المثال من 1.5 إلى> 1.6) أداة رخيصة لتعزيز الأداء.حتى الترقية في رقم الإصدار يمكن أن توفر تعزيزات جيدة للأداء.إذا كنت تعمل على نظام التشغيل Windows وهذا تطبيق من فئة الخادم، فاستخدم -server في سطر الأوامر لاستخدام Server Hotspot JVM.يتم اكتشاف ذلك تلقائيًا على أجهزة Linux وSolaris.

هناك طريقتان:

  1. استخدم خوارزمية الحقيبة مثل المجموعات الموجودة في مجموعات Google.

  2. قم بإنشاء حاوية قابلة للتغيير والتي يمكنك استخدامها في الخريطة:


    class My{
        String word;
        int count;
    }

واستخدم put("word", new My("Word") );ثم يمكنك التحقق من وجوده وزيادته عند الإضافة.

تجنب طرح الحل الخاص بك باستخدام القوائم، لأنه إذا قمت بالبحث والفرز في حلقة داخلية، فسوف يكون أدائك سيئًا.يعد حل HashMap الأول سريعًا جدًا في الواقع، ولكن من المحتمل أن يكون الحل المناسب الموجود في مجموعات Google أفضل.

يبدو حساب الكلمات باستخدام مجموعات Google كما يلي:



    HashMultiset s = new HashMultiset();
    s.add("word");
    s.add("word");
    System.out.println(""+s.count("word") );


يعد استخدام HashMultiset أمرًا أنيقًا للغاية، لأن خوارزمية الحقيبة هي ما تحتاجه عند عد الكلمات.

أعتقد أن الحل الذي ستقدمه سيكون هو الطريقة القياسية، ولكن - كما لاحظت بنفسك - ربما لا يكون هو أسرع طريقة ممكنة.

يمكنك أن تنظر إلى غنو تروڤ.هذه مكتبة تحتوي على جميع أنواع المجموعات البدائية السريعة.سيستخدم مثالك أ TObjectIntHashMap الذي يحتوي على طريقة AdjustOrPutValue التي تفعل ما تريده بالضبط.

أحد الاختلافات في أسلوب MutableInt والذي قد يكون أسرع، إذا كان قليلًا من الاختراق، هو استخدام مصفوفة int ذات عنصر واحد:

Map<String,int[]> map = new HashMap<String,int[]>();
...
int[] value = map.get(key);
if (value == null) 
  map.put(key, new int[]{1} );
else
  ++value[0];

سيكون من المثير للاهتمام أن تتمكن من إعادة تشغيل اختبارات الأداء باستخدام هذا الاختلاف.قد يكون الأسرع.


يحرر:كان النمط أعلاه جيدًا بالنسبة لي، ولكن في النهاية قمت بالتغيير لاستخدام مجموعات Trove لتقليل حجم الذاكرة في بعض الخرائط الكبيرة جدًا التي كنت أقوم بإنشائها - وكمكافأة، كان الأمر أسرع أيضًا.

إحدى الميزات الرائعة حقًا هي أن TObjectIntHashMap فئة لديها واحد adjustOrPutValue استدعاء ذلك، اعتمادًا على ما إذا كانت هناك قيمة بالفعل عند هذا المفتاح، إما أنه سيتم وضع قيمة أولية أو زيادة القيمة الحالية.هذا مثالي لزيادة:

TObjectIntHashMap<String> map = new TObjectIntHashMap<String>();
...
map.adjustOrPutValue(key, 1, 1);

مجموعات جوجل HashMultiset:
- أنيق جدًا في الاستخدام
- ولكن تستهلك وحدة المعالجة المركزية والذاكرة

الأفضل أن يكون لديك طريقة مثل: Entry<K,V> getOrPut(K); (أنيق، ومنخفض التكلفة)

مثل هذه الطريقة ستحسب التجزئة والفهرس مرة واحدة فقط ، وبعد ذلك يمكننا أن نفعل ما نريد مع الإدخال (إما استبدال أو تحديث القيمة).

أكثر أناقة:
- خذ أ HashSet<Entry>
- تمديده بحيث get(K) ضع إدخالاً جديدًا إذا لزم الأمر
- يمكن أن يكون الدخول كائنًا خاصًا بك.
--> (new MyHashSet()).get(k).increment();

"وضع" بحاجة إلى "الحصول على" (لضمان عدم وجود مفتاح مكرر).
لذلك قم مباشرة بـ "وضع"،
وإذا كانت هناك قيمة سابقة فقم بإضافة:

Map map = new HashMap ();

MutableInt newValue = new MutableInt (1); // default = inc
MutableInt oldValue = map.put (key, newValue);
if (oldValue != null) {
  newValue.add(oldValue); // old + inc
}

إذا بدأ العد عند 0، أضف 1:(أو أي قيم أخرى...)

Map map = new HashMap ();

MutableInt newValue = new MutableInt (0); // default
MutableInt oldValue = map.put (key, newValue);
if (oldValue != null) {
  newValue.setValue(oldValue + 1); // old + inc
}

يلاحظ : هذا الرمز ليس مؤشر ترابط آمن.استخدمها للبناء ثم استخدم الخريطة، وليس لتحديثها بشكل متزامن.

تحسين : في الحلقة، احتفظ بالقيمة القديمة لتصبح القيمة الجديدة للحلقة التالية.

Map map = new HashMap ();
final int defaut = 0;
final int inc = 1;

MutableInt oldValue = new MutableInt (default);
while(true) {
  MutableInt newValue = oldValue;

  oldValue = map.put (key, newValue); // insert or...
  if (oldValue != null) {
    newValue.setValue(oldValue + inc); // ...update

    oldValue.setValue(default); // reuse
  } else
    oldValue = new MutableInt (default); // renew
  }
}

الأغلفة البدائية المختلفة، على سبيل المثال، Integer غير قابلة للتغيير، لذا لا توجد طريقة أكثر إيجازًا للقيام بما تطلبه إلا إذا يمكنك أن تفعل ذلك مع شيء من هذا القبيل AtomicLong.يمكنني تجربة ذلك خلال دقيقة وتحديثه.بالمناسبة، جدول التجزئة يكون جزء من إطار المجموعات.

سأستخدم Apache Collections Lazy Map (لتهيئة القيم إلى 0) واستخدم MutableIntegers من Apache Lang كقيم في تلك الخريطة.

أكبر تكلفة هي الاضطرار إلى البحث في الخريطة مرتين في طريقتك.في بلدي عليك أن تفعل ذلك مرة واحدة فقط.ما عليك سوى الحصول على القيمة (سيتم تهيئتها في حالة غيابها) وزيادتها.

ال جافا الوظيفية المكتبة TreeMap بنية البيانات لديها update الطريقة في أحدث رأس الجذع:

public TreeMap<K, V> update(final K k, final F<V, V> f)

مثال على الاستخدام:

import static fj.data.TreeMap.empty;
import static fj.function.Integers.add;
import static fj.pre.Ord.stringOrd;
import fj.data.TreeMap;

public class TreeMap_Update
  {public static void main(String[] a)
    {TreeMap<String, Integer> map = empty(stringOrd);
     map = map.set("foo", 1);
     map = map.update("foo", add.f(1));
     System.out.println(map.get("foo").some());}}

يطبع هذا البرنامج "2".

@فيلمانتاس بارانوسكاس:فيما يتعلق بهذه الإجابة، أود أن أعلق إذا كان لدي نقاط مندوب، ولكن ليس لدي.أردت أن أشير إلى أن فئة العداد المحددة هناك ليست آمنة لمؤشر الترابط لأنها لا تكفي فقط لمزامنة inc() دون مزامنة القيمة().المواضيع الأخرى التي تستدعي القيمة () ليست مضمونة لرؤية القيمة ما لم يحدث ذلك قبل إنشاء علاقة مع التحديث.

لا أعرف مدى كفاءته ولكن الكود أدناه يعمل أيضًا. تحتاج إلى تحديد ملف BiFunction في البداية.بالإضافة إلى ذلك، يمكنك تحقيق أكثر من مجرد زيادة باستخدام هذه الطريقة.

public static Map<String, Integer> strInt = new HashMap<String, Integer>();

public static void main(String[] args) {
    BiFunction<Integer, Integer, Integer> bi = (x,y) -> {
        if(x == null)
            return y;
        return x+y;
    };
    strInt.put("abc", 0);


    strInt.merge("abc", 1, bi);
    strInt.merge("abc", 1, bi);
    strInt.merge("abc", 1, bi);
    strInt.merge("abcd", 1, bi);

    System.out.println(strInt.get("abc"));
    System.out.println(strInt.get("abcd"));
}

الإخراج هو

3
1

إذا كنت تستخدم مجموعات الكسوف, ، يمكنك استخدام أ HashBag.سيكون هذا هو النهج الأكثر كفاءة من حيث استخدام الذاكرة وسيكون أداؤه جيدًا أيضًا من حيث سرعة التنفيذ.

HashBag مدعوم من أ MutableObjectIntMap الذي يخزن ints البدائية بدلا من Counter أشياء.وهذا يقلل من الحمل الزائد للذاكرة ويحسن سرعة التنفيذ.

HashBag يوفر واجهة برمجة التطبيقات (API) التي تحتاجها نظرًا لأنها ملف Collection والذي يسمح لك أيضًا بالاستعلام عن عدد مرات ظهور العنصر.

هنا مثال من مجموعات الكسوف كاتا.

MutableBag<String> bag =
  HashBag.newBagWith("one", "two", "two", "three", "three", "three");

Assert.assertEquals(3, bag.occurrencesOf("three"));

bag.add("one");
Assert.assertEquals(2, bag.occurrencesOf("one"));

bag.addOccurrences("one", 4);
Assert.assertEquals(6, bag.occurrencesOf("one"));

ملحوظة: أنا ملتزم بمجموعات Eclipse.

الأمر بسيط جدًا، فقط استخدم الوظيفة المضمنة فيه Map.java كما يتبع

map.put(key, map.getOrDefault(key, 0) + 1);

نظرًا لأن الكثير من الأشخاص يبحثون في موضوعات Java عن إجابات Groovy، فإليك كيفية القيام بذلك في Groovy:

dev map = new HashMap<String, Integer>()
map.put("key1", 3)

map.merge("key1", 1) {a, b -> a + b}
map.merge("key2", 1) {a, b -> a + b}

أتمنى أن أفهم سؤالك بشكل صحيح، فأنا قادم إلى Java من Python حتى أتمكن من التعاطف مع معاناتك.

اذا كنت تمتلك

map.put(key, 1)

كنت ستفعل

map.put(key, map.get(key) + 1)

أتمنى أن يساعدك هذا!

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top