যৌগিক কীগুলি হল যখন আপনার মানচিত্র বা ক্যাশে সন্ধানের জন্য "কী" সংজ্ঞায়িত করার জন্য ডেটার সংমিশ্রণ প্রয়োজন। এর একটি উদাহরণ হতে পারে যেখানে আপনাকে গ্রাহকের নামের পাশাপাশি ব্যবহারকারীর ভূমিকার উপর ভিত্তি করে মান ক্যাশে করতে হবে। এই ধরনের ক্ষেত্রে, আপনার ক্যাশে এই দুটি (বা তার বেশি) মানদণ্ডের প্রতিটির উপর ভিত্তি করে অনন্য মান সঞ্চয় করতে সক্ষম হতে হবে।
কোডে কম্পোজিট কীগুলি পরিচালনা করা যেতে পারে এমন কয়েকটি ভিন্ন উপায় রয়েছে।
প্রথম উত্তর যা সবচেয়ে বেশি ঝাঁপিয়ে পড়ে তা হল কী হিসাবে ব্যবহার করার জন্য মানদণ্ডকে একটি স্ট্রিংয়ে একত্রিত করা। এটা সহজ এবং অনেক প্রচেষ্টা লাগে না:
private String getMapKey(Long userId, String userLocale) { return userId + "." userLocale; }
এই সমস্যা হ্যান্ডেল একটি চমত্কার মৌলিক উপায়. একটি স্ট্রিং কী ব্যবহার করা ডিবাগিং এবং তদন্তকে সহজ করে তুলতে পারে, কারণ ক্যাশে কী মানব-পাঠযোগ্য বিন্যাসে রয়েছে। কিন্তু এই পদ্ধতির সাথে সচেতন হওয়ার জন্য কয়েকটি সমস্যা রয়েছে:
মানচিত্রের সাথে প্রতিটি মিথস্ক্রিয়াতে একটি নতুন স্ট্রিং তৈরি করা প্রয়োজন। যদিও এই স্ট্রিং বরাদ্দটি সাধারণত ছোট হয়, যদি মানচিত্রটি ঘন ঘন অ্যাক্সেস করা হয় তবে এটি প্রচুর পরিমাণে বরাদ্দ করতে পারে যা সময় নেয় এবং আবর্জনা সংগ্রহ করতে হয়। স্ট্রিং বরাদ্দের আকারও বড় হতে পারে আপনার কী-এর উপাদানগুলি কত বড় বা আপনার কতগুলি আছে তার উপর নির্ভর করে।
আপনাকে নিশ্চিত করতে হবে যে আপনি যে কম্পোজিট কী তৈরি করেছেন তা অন্য কী মানের সাথে স্পুফ করা যাবে না:
public String getMapKey(Integer groupId, Integer accessType) { return groupId.toString() + accessType.toString(); }
উপরে, আপনার যদি groupId = 1 এবং accessType = 23 থাকে, তাহলে সেটা হবে groupId = 12 এবং accessType = 3-এর মতো একই ক্যাশে কী। স্ট্রিংগুলির মধ্যে একটি বিভাজক অক্ষর যোগ করে, আপনি এই ধরনের ওভারল্যাপ প্রতিরোধ করতে পারেন। কিন্তু একটি কী এর ঐচ্ছিক অংশ সম্পর্কে সতর্ক থাকুন:
public String getMapKey(String userProvidedString, String extensionName) { return userProvidedString + (extensionName == null ? "" : ("." + extensionName)); }
উপরের উদাহরণে, extensionName কী এর একটি ঐচ্ছিক অংশ। যদি extensionName ঐচ্ছিক হয়, userProvidedString একটি বিভাজক এবং বৈধ extensionName অন্তর্ভুক্ত করতে পারে এবং ক্যাশে ডেটাতে অ্যাক্সেস পেতে পারে যার অ্যাক্সেস থাকা উচিত ছিল না৷
স্ট্রিংগুলি ব্যবহার করার সময়, আপনি কীগুলির কোনও সংঘর্ষ এড়াতে কীভাবে আপনার ডেটা একত্রিত করছেন সে সম্পর্কে আপনি ভাবতে চাইবেন৷ বিশেষ করে কীটির জন্য ব্যবহারকারী-উত্পাদিত যেকোনো ইনপুটের চারপাশে।
আরেকটি বিকল্প হল কীগুলিকে একত্রিত না করা এবং পরিবর্তে, আপনার ডেটা স্ট্রাকচারগুলিকে নেস্ট করুন (মানচিত্রের মানচিত্রগুলির মানচিত্র):
Map<Integer, Map<String, String>> groupAndLocaleMap = new HashMap<>(); groupAndLocaleMap.computeIfAbsent(userId, k -> new HashMap()).put(userLocale, mapValue);
মানচিত্রের সাথে ইন্টারঅ্যাক্ট করার সময় কোনও নতুন মেমরি বরাদ্দ করার প্রয়োজন না হওয়ার সুবিধা রয়েছে কারণ কীগুলির জন্য পাস-ইন মানগুলি ইতিমধ্যেই বরাদ্দ করা হয়েছে। এবং যখন আপনাকে চূড়ান্ত মান পেতে একাধিক লুকআপ করতে হবে, তখন মানচিত্রগুলি ছোট হবে।
কিন্তু এই পদ্ধতির নেতিবাচক দিক হল এটি বাসা বাঁধার গভীরে গেলে এটি আরও জটিল হয়। এমনকি শুধুমাত্র দুটি স্তরের সাথে, মানচিত্র প্রারম্ভিকতা বিভ্রান্তিকর দেখতে পারে। আপনি যখন 3 বা তার বেশি ডেটার সাথে কাজ করা শুরু করেন, তখন এটি আপনার কোডটিকে খুব ভার্বোস করে তুলতে পারে। তার উপরে, নাল পয়েন্টার এড়াতে প্রতিটি স্তরের নাল চেকিং প্রয়োজন।
কিছু "কী অংশ" একটি মানচিত্র কী হিসাবে ভাল কাজ নাও করতে পারে। অ্যারে বা সংগ্রহে ডিফল্ট সমান পদ্ধতি নেই যা তাদের বিষয়বস্তুর তুলনা করে। সুতরাং, আপনাকে হয় সেগুলি বাস্তবায়ন করতে হবে বা অন্য বিকল্প ব্যবহার করতে হবে।
আপনার কীগুলির প্রতিটি স্তর কতটা অনন্য তার উপর নির্ভর করে নেস্টেড মানচিত্র ব্যবহার করা কম স্থান দক্ষ হয়ে উঠতে পারে।
শেষ বিকল্পটি হল মূল মানগুলিকে একটি স্ট্রিংয়ে একত্রিত করার পরিবর্তে, পরিবর্তে কীটির জন্য একটি কাস্টম অবজেক্ট তৈরি করুন:
private class MapKey { private final int userId; private final String userLocale; public MapKey(int userId, String userLocale) { this.userId = userId; this.userLocale = userLocale; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; MapKey mapKey = (MapKey) o; return userId == mapKey.userId && Objects.equals(userLocale, mapKey.userLocale); } @Override public int hashCode() { return Objects.hash(userId, userLocale); } }
যদিও প্রতিটি ইন্টারঅ্যাকশনের জন্য এখনও একটি নতুন বস্তুর জন্য একটি নতুন মেমরি বরাদ্দ প্রয়োজন। অবজেক্ট কী বরাদ্দ একটি যৌগিক স্ট্রিংয়ের জন্য প্রয়োজনীয় একটি থেকে উল্লেখযোগ্যভাবে ছোট। এর কারণ হল যে অংশগুলি কী তৈরি করে সেগুলিকে স্ট্রিং হিসাবে পুনরায় বরাদ্দ করার দরকার নেই। পরিবর্তে, শুধুমাত্র মোড়ানো অবজেক্ট কীটির জন্য নতুন মেমরির প্রয়োজন।
একটি যৌগিক কী অবজেক্ট কী সমতা এবং হ্যাশকোড বাস্তবায়নে কাস্টমাইজেশনের অনুমতি দিতে পারে। যেমন স্ট্রিং-এ ক্যাপিটালাইজেশন উপেক্ষা করা, বা কী-এর অংশ হিসেবে অ্যারে বা সংগ্রহ ব্যবহার করা।
যদিও এখানে নেতিবাচক দিক হল যে, আবার, এটি একটি যৌগিক স্ট্রিংয়ের চেয়ে অনেক বেশি কোডের প্রয়োজন। এবং এটি নিশ্চিত করতে হবে যে আপনার মানচিত্রের মূল শ্রেণীতে আপনার বৈধ সমান এবং হ্যাশকোড চুক্তি রয়েছে।
তাই আমি কোনটি নির্বাচন করা উচিত?
সাধারণভাবে বলতে গেলে, আমি একটি যৌগিক স্ট্রিং কী ব্যবহার করার পরামর্শ দেব। এটি সহজ এবং বোঝা সহজ, সর্বনিম্ন কোড প্রয়োজন এবং পরে ডিবাগ করা সবচেয়ে সহজ। যদিও এটি সম্ভবত সবচেয়ে ধীরগতির পারফরম্যান্স, সহজ, পঠনযোগ্য কোড লেখা সাধারণত অন্য দুটি বিকল্পের একটি ব্যবহার করে আপনি যে সুবিধাগুলি পাবেন তার চেয়ে বেশি গুরুত্বপূর্ণ। মনে রাখবেন:
"অকাল অপ্টিমাইজেশান হল সমস্ত মন্দের মূল" ডোনাল্ড নুথ
যদি আপনার কাছে প্রমাণ না থাকে বা বিশ্বাস করার কারণ না থাকে যে আপনার মানচিত্র/ক্যাশে লুকআপ একটি কর্মক্ষমতা বাধা হতে চলেছে, পাঠযোগ্যতার সাথে যান।
কিন্তু আপনি যদি এমন একটি পরিস্থিতিতে থাকেন যেখানে আপনার মানচিত্র বা ক্যাশে থ্রুপুট খুব বেশি, তাহলে অন্য দুটি বিকল্পের মধ্যে একটিতে স্যুইচ করা ভাল হতে পারে। আসুন দেখি কিভাবে 3টি একে অপরের সাথে পারফরম্যান্স অনুযায়ী তুলনা করে, সেইসাথে তাদের মেমরি বরাদ্দের আকারের সাথে।
উপরের 3টি পরিস্থিতি পরীক্ষা করার জন্য, আমি কোড লিখেছি যা একটি যৌগিক কী-এর জন্য সমস্ত 3টি পরিস্থিতির একই বাস্তবায়নকে প্রতিলিপি করবে। কী নিজেই একটি পূর্ণসংখ্যা মান, একটি স্ট্রিং মান এবং একটি দীর্ঘ মান নিয়ে গঠিত। তিনটি বাস্তবায়নই কী তৈরি করতে প্রতিটি রানে একই পরীক্ষার ডেটা ব্যবহার করেছে।
সমস্ত রান মানচিত্রে 1 মিলিয়ন রেকর্ডের সাথে কার্যকর করা হয়েছিল (জাভার হ্যাশম্যাপ ব্যবহার করা হয়েছিল)। কী আকারের বিভিন্ন সংমিশ্রণে 3 রান করা হয়েছিল:
100 ints, 100 স্ট্রিং, 100 longs — 1 মিলিয়ন অনন্য কী
1 int, 1 স্ট্রিং, 1,000,000 longs— 1 মিলিয়ন অনন্য কী
1,000,000 ints, 1 স্ট্রিং, 1 দীর্ঘ - 1 মিলিয়ন অনন্য কী
প্রথমে, আসুন দেখি প্রতিটি মানচিত্র স্তূপে কতটা স্থান নেয়। এটি গুরুত্বপূর্ণ কারণ এটি আপনার অ্যাপ্লিকেশন চালানোর জন্য কত মেমরির প্রয়োজন তা প্রভাবিত করে।
এখানে একটি আকর্ষণীয় সুস্পষ্ট নোট রয়েছে: শেষ দৃশ্যে (1,000,000 ints), নেস্টেড মানচিত্রের আকার অন্যদের তুলনায় উল্লেখযোগ্যভাবে বড়। এর কারণ হল, এই পরিস্থিতিতে, নেস্টেড মানচিত্রগুলি 1 মিলিয়ন এন্ট্রি সহ 1টি প্রথম-স্তরের মানচিত্র তৈরি করে৷ তারপর, দ্বিতীয় এবং তৃতীয় স্তরের জন্য, এটি শুধুমাত্র একটি এন্ট্রি সহ 1 মিলিয়ন মানচিত্র তৈরি করে।
এই সমস্ত নেস্টেড মানচিত্র অতিরিক্ত ওভারহেড সঞ্চয় করে এবং বেশিরভাগই খালি। এটি স্পষ্টতই একটি প্রান্তের ক্ষেত্রে, তবে আমি একটি বিন্দু তৈরি করতে এটি দেখাতে চেয়েছিলাম। নেস্ট ম্যাপ বাস্তবায়ন ব্যবহার করার সময়, স্বতন্ত্রতা (এবং সেই স্বতন্ত্রতার ক্রম) অনেক গুরুত্বপূর্ণ।
আপনি যদি অর্ডারটি 1, 1, 1 মিলিয়নে উল্টে দেন, আপনি আসলে সর্বনিম্ন স্টোরেজ প্রয়োজনীয়তা পাবেন।
অন্য দুটি পরিস্থিতিতে, নেস্টেড ম্যাপিং সবচেয়ে কার্যকরী, কাস্টম কী অবজেক্টটি সেকেন্ডে এবং স্ট্রিং কী সবশেষে আসে।
এর পরে, স্ক্র্যাচ থেকে এই প্রতিটি মানচিত্র তৈরি করতে যে সময় লাগে তা দেখা যাক:
আবার, আমরা মেমরি বরাদ্দকরণের জন্য 1 মিলিয়ন-1-1 পরিস্থিতিতে নেস্টেড মানচিত্রগুলি সবচেয়ে খারাপ কাজ করতে দেখি, কিন্তু তারপরও, এটি CPU সময়ের মধ্যে অন্যদের চেয়ে বেশি পারফর্ম করে। উপরে, আমরা দেখতে পারি কিভাবে স্ট্রিং কী সব ক্ষেত্রে সবচেয়ে খারাপ কার্য সম্পাদন করে যখন একটি কাস্টম কী অবজেক্ট ব্যবহার করা কিছুটা ধীর এবং নেস্টেড কীগুলির চেয়ে বেশি মেমরি বরাদ্দের প্রয়োজন হয়।
সবশেষে, চলুন দেখি সর্বোচ্চ থ্রুপুট দৃশ্যকল্প এবং এটি পড়া কতটা কার্যকর। আমরা 1 মিলিয়ন রিড অপারেশন চালিয়েছি (প্রতিটি কী তৈরির জন্য 1টি); আমরা কোনো অস্তিত্বহীন কী অন্তর্ভুক্ত করিনি।
এখানেই আমরা দেখতে পাই যে স্ট্রিং-ভিত্তিক কী লুকআপ কতটা ধীর। এটি এখন পর্যন্ত সবচেয়ে ধীর এবং এখন পর্যন্ত 3টি বিকল্পের মধ্যে সবচেয়ে বেশি মেমরি বরাদ্দ করে। কাস্টম কী অবজেক্ট নেস্টেড ম্যাপ বাস্তবায়নের "কাছে" সঞ্চালন করে কিন্তু এখনও একটি ছোট ব্যবধানে ধারাবাহিকভাবে ধীর।
যাইহোক, লুকআপ মেমরি বরাদ্দে, নেস্টেড মানচিত্রগুলি কতটা ভালভাবে জ্বলছে তা লক্ষ্য করুন। না, এটি গ্রাফে একটি ত্রুটি নয়; নেস্টেড মানচিত্রে একটি মান খুঁজতে খুঁজতে কোনও অতিরিক্ত মেমরি বরাদ্দের প্রয়োজন হয় না। এটা কিভাবে সম্ভব?
ঠিক আছে, একটি স্ট্রিং কীতে যৌগিক বস্তুগুলিকে একত্রিত করার সময়, আপনাকে প্রতিবার একটি নতুন স্ট্রিং অবজেক্টের জন্য মেমরি বরাদ্দ করতে হবে:
private String lookup(int key1, String key2, long key3) { return map.get(key1 + "." + key2 + "." + key3); }
একটি যৌগিক কী ব্যবহার করার সময়, আপনাকে এখনও একটি নতুন কী বস্তুর জন্য মেমরি বরাদ্দ করতে হবে। কিন্তু যেহেতু সেই বস্তুর সদস্যরা ইতিমধ্যেই তৈরি এবং রেফারেন্স করা হয়েছে, এটি এখনও একটি নতুন স্ট্রিং থেকে অনেক কম বরাদ্দ করে:
private String lookup(int key1, String key2, long key3) { return map.get(new MapKey(key1, key2, key3)); }
কিন্তু নেস্টেড মানচিত্র বাস্তবায়নের জন্য লুকআপে কোনো নতুন মেমরি বরাদ্দের প্রয়োজন নেই। আপনি প্রতিটি নেস্টেড মানচিত্রের কী হিসাবে প্রদত্ত অংশগুলি পুনরায় ব্যবহার করছেন:
private String lookup(int key1, String key2, long key3) { return map.get(key1).get(key2).get(key3); }
সুতরাং, উপরের উপর ভিত্তি করে, সবচেয়ে পারফরম্যান্স কোনটি?
এটি দেখা সহজ যে নেস্টেড মানচিত্রগুলি প্রায় সমস্ত পরিস্থিতিতেই উপরে উঠে আসে৷ আপনি যদি বেশিরভাগ ব্যবহারের ক্ষেত্রে কাঁচা পারফরম্যান্সের সন্ধান করেন তবে এটি সম্ভবত সেরা বিকল্প। যদিও, আপনার ব্যবহারের ক্ষেত্রে নিশ্চিত করতে আপনার নিজের পরীক্ষা করা উচিত।
মূল বস্তুটি একটি খুব ভাল সাধারণ-উদ্দেশ্য বিকল্পের জন্য তৈরি করে যখন নেস্টেড মানচিত্রগুলি আপনার বাস্তবায়নের জন্য ব্যবহার করা অবাস্তব বা অসম্ভব হয়ে যায়। এবং যৌগিক স্ট্রিং কী, যদিও বাস্তবায়ন করা সবচেয়ে সহজ, প্রায় সবসময়ই সবচেয়ে ধীরগতির হতে চলেছে।
কম্পোজিট কীগুলি প্রয়োগ করার সময় বিবেচনা করার শেষ পয়েন্টটি হল যে আপনি উপরেরটি একত্রিত করতে পারেন। উদাহরণস্বরূপ, আপনি প্রথম বা দুটি স্তরের জন্য নেস্টেড মানচিত্র ব্যবহার করতে পারেন এবং তারপর গভীর স্তরগুলি সরল করার জন্য একটি যৌগিক কী অবজেক্ট ব্যবহার করতে পারেন।
এটি এখনও স্টোরেজ এবং লুকআপ কর্মক্ষমতা অপ্টিমাইজ করার সময় দ্রুত লুকআপের জন্য আপনার ডেটাকে বিভাজিত রাখতে পারে। এবং সেইসাথে আপনার কোড পঠনযোগ্য রাখুন.
বেশিরভাগ সময়, এটি সহজ রাখুন। একটি মানচিত্র বা ক্যাশে স্টোরেজের জন্য আপনার যৌগিক কীগুলিকে একটি স্ট্রিং কীতে একত্রিত করুন যদি এটি সবচেয়ে সহজ বিকল্প হয় এবং কার্যকারিতা একটি প্রধান উদ্বেগের বিষয় নয়৷
পরিস্থিতিতে যেখানে কর্মক্ষমতা সমালোচনামূলক, আপনার নিজের পরীক্ষা করতে ভুলবেন না। কিন্তু নেস্টেড মানচিত্র ব্যবহার করা বেশিরভাগ ক্ষেত্রে সবচেয়ে কার্যকরী হবে। এটির সম্ভবত সবচেয়ে ছোট স্টোরেজ প্রয়োজনীয়তা থাকবে। এবং যৌগিক কীগুলি এখনও একটি কার্যকরী বিকল্প যখন নেস্টিং ম্যাপিংগুলি অব্যবহারিক হয়ে যায়।
এছাড়াও এখানে প্রকাশিত