স্ট্রিংবিল্ডারের মতো দক্ষ স্ট্রিং.ফর্ম্যাট


160

ধরুন আমার সি # তে স্ট্রিংবিল্ডার রয়েছে যা এটি করে:

StringBuilder sb = new StringBuilder();
string cat = "cat";
sb.Append("the ").Append(cat).(" in the hat");
string s = sb.ToString();

এটি কি হিসাবে দক্ষ বা আরও দক্ষ হতে হবে:

string cat = "cat";
string s = String.Format("The {0} in the hat", cat);

যদি তাই হয় তবে কেন?

সম্পাদনা

কিছু আকর্ষণীয় উত্তরের পরে, আমি বুঝতে পেরেছিলাম যে আমি যা বলছিলাম তাতে আমার সম্ভবত আরও পরিষ্কার হওয়া উচিত ছিল। আমি এতটা জিজ্ঞাসা করছিলাম না যা কোন স্ট্রিং সংক্ষেপে দ্রুত ছিল, তবে যা একটি স্ট্রিংটিকে অন্যটিতে ইনজেকশনে দ্রুততর ।

উপরের দুটি ক্ষেত্রেই আমি পূর্বনির্ধারিত টেম্পলেট স্ট্রিংয়ের মাঝখানে এক বা একাধিক স্ট্রিং ইনজেকশন করতে চাই।

বিভ্রান্তির জন্য দুঃখিত


ভবিষ্যতের উন্নতির জন্য দয়া করে এগুলিকে ছেড়ে দিন।
মার্ক বাইক

4
একটি বিশেষ ক্ষেত্রে দৃশ্যে, দ্রুততমগুলির মধ্যে দুটিও নয়: যদি অংশটি প্রতিস্থাপন করা হয় তবে এটি নতুন অংশের আকারের সমান হয়, আপনি জায়গায় জায়গায় স্ট্রিংটি পরিবর্তন করতে পারেন। দুর্ভাগ্যক্রমে, এর জন্য প্রতিবিম্ব বা অনিরাপদ কোড দরকার এবং ইচ্ছাকৃতভাবে স্ট্রিংয়ের অপরিবর্তনীয়তা লঙ্ঘন করে। ভাল অনুশীলন নয়, তবে গতি যদি সমস্যা হয় ... :)
আবেল

উপরে বর্ণিত উদাহরণটিতে string s = "The "+cat+" in the hat";এটি লুপে ব্যবহৃত না হলে দ্রুততম হতে পারে, সেক্ষেত্রে দ্রুততমটি লুপের StringBuilder বাইরের একটি সূচনা সহ হবে ।
সূর্য প্রতাপ

উত্তর:


146

দ্রষ্টব্য: এই উত্তরটি তখনই লেখা হয়েছিল যখন .NET 2.0 বর্তমান সংস্করণ ছিল। এটি পরবর্তী সংস্করণগুলিতে আর প্রযোজ্য হতে পারে।

String.FormatStringBuilderঅভ্যন্তরীণভাবে ব্যবহার করে :

public static string Format(IFormatProvider provider, string format, params object[] args)
{
    if ((format == null) || (args == null))
    {
        throw new ArgumentNullException((format == null) ? "format" : "args");
    }

    StringBuilder builder = new StringBuilder(format.Length + (args.Length * 8));
    builder.AppendFormat(provider, format, args);
    return builder.ToString();
}

উপরের কোডটি এমস্কোরলিবের একটি স্নিপেট, সুতরাং প্রশ্নটি " StringBuilder.Append()চেয়ে দ্রুততর StringBuilder.AppendFormat()" হয়?

বেঞ্চমার্কিং ছাড়াই আমি সম্ভবত বলব যে উপরের কোড নমুনাটি আরও দ্রুত ব্যবহার করে চলবে .Append()। তবে এটি অনুমান, উপযুক্ত তুলনা পাওয়ার জন্য দু'জনকে বেঞ্চমার্কিং এবং / অথবা প্রোফাইলিংয়ের চেষ্টা করুন।

এই অধ্যায়, জেরি ডিকসন কিছু বেঞ্চমার্কিং করেছেন:

http://jdixon.dotnetdevelopersjournal.com/string_concatenation_stringbuilder_and_stringformat.htm

আপডেট করা হয়েছে:

দুঃখের সাথে উপরের লিঙ্কটি মারা গেছে। তবে ওয়ে ব্যাক মেশিনে এখনও একটি অনুলিপি রয়েছে:

http://web.archive.org/web/20090417100252/http://jdixon.dotnetdevelopersjournal.com/string_concatenation_stringbuilder_and_stringformat.htm

দিনের শেষে এটি নির্ভর করে যে আপনার স্ট্রিং ফর্ম্যাটিংটি পুনরাবৃত্তি হিসাবে ডাকা হবে কিনা, অর্থাৎ আপনি 100 ম্যাগাজাইটের পাঠ্য 100 মিক্সের উপরে কিছু গুরুতর পাঠ্য প্রক্রিয়াকরণ করছেন, বা যখন ব্যবহারকারী বারবার বোতাম টিপবে তখন এটি বলা হচ্ছে কিনা whether আপনি যদি বিশাল ব্যাচের প্রসেসিংয়ের কাজ না করেন তবে আমি স্ট্রিং.ফোরামের সাথে লেগে থাকি, এটি কোড পঠনযোগ্যতাকে সহায়তা করে। যদি আপনি কোনও পারফেক্ট বাটনেলেক সন্দেহ করেন তবে আপনার কোডটিতে একজন প্রোফাইলার আটকে দিন এবং দেখুন এটি কোথায়।


8
জেরি ডিকসনের পৃষ্ঠায় মাপদণ্ডের সাথে একটি সমস্যা হ'ল তিনি কখনই .ToString()এই StringBuilderবস্তুকে ডাকেন না । অনেকগুলি পুনরাবৃত্তির সময়, সেই সময়টি একটি বড় পার্থক্য করে এবং এর অর্থ হল যে তিনি আপেলগুলির সাথে আপেলের তুলনা করছেন না। এই কারণেই তিনি এমন দুর্দান্ত পারফরম্যান্স দেখিয়েছেন StringBuilderএবং সম্ভবত তাঁর অবাক হওয়ার কারণ রয়েছে। আমি কেবল সেই ভুলটি সংশোধন করে বেঞ্চমার্কটি পুনরাবৃত্তি করেছি এবং প্রত্যাশিত ফলাফল পেয়েছি: String +অপারেটরটি দ্রুততমভাবে অনুসরণ করা হয়েছিল StringBuilder, তারপরে String.Formatপিছনে আনার সাথে ছিল ।
বেন কলিন্স 21

5
6 বছর পরে, এটি আর বেশ কিছু নয়। নেট 4-এ স্ট্রিং.ফর্ম্যাট (স্ট্রিংবিল্ডার ইনস্ট্যান্স তৈরি করে এবং ক্যাশে করে যা এটি পুনরায় ব্যবহার করে, তাই এটি কিছু পরীক্ষার ক্ষেত্রে স্ট্রিংবিল্ডারের চেয়ে দ্রুত হতে পারে। আমি নীচে উত্তরে একটি সংশোধিত বেঞ্চমার্ক রেখেছি (যা এখনও বলে যে কনক্যাটটি সবচেয়ে দ্রুত এবং আমার পরীক্ষার ক্ষেত্রে ফর্ম্যাটটি স্ট্রিংবিল্ডারের চেয়ে 10% ধীর)।
ক্রিস এফ ক্যারল

45

থেকে MSDN ডকুমেন্টেশন :

স্ট্রিং বা স্ট্রিংবিল্ডার অবজেক্টের জন্য একটি কনটাকেনশন অপারেশনের কর্মক্ষমতা মেমরির বরাদ্দ কতবার ঘটে তার উপর নির্ভর করে। একটি স্ট্রিং কনটেনটেশন অপারেশন সর্বদা মেমরি বরাদ্দ করে, স্ট্রিংবিল্ডার অবজেক্ট বাফার যদি নতুন ডেটা সামঞ্জস্য করতে খুব কম হয় তবে স্ট্রিংবিল্ডার কনটেনটেশন অপারেশনটি কেবল মেমরি বরাদ্দ করে। ফলস্বরূপ, স্ট্রিং ক্লাস একটি সংক্ষিপ্ত ক্রিয়াকলাপের জন্য পছন্দসই হয় যদি স্ট্রিং অবজেক্টের একটি নির্দিষ্ট সংখ্যক সংশ্লেষ করা হয়। সেক্ষেত্রে পৃথক কনটেনটেশন অপারেশনগুলি এমনকি সংকলক দ্বারা একক ক্রিয়াকলাপের সাথে সংযুক্ত করা যেতে পারে। একটি স্ট্রিংবিল্ডার অবজেক্ট কনট্যাকটেশন অপারেশনের জন্য পছন্দনীয় যদি একটি স্বেচ্ছাসেবী সংখ্যক স্ট্রিং সংমিশ্রিত হয়; উদাহরণস্বরূপ, যদি কোনও লুপ ব্যবহারকারীর ইনপুটটির এলোমেলো সংখ্যক স্ট্রিংকে সম্মতি দেয়।


12

আমি কিছু দ্রুত পারফরম্যান্সের মানদণ্ড চালিয়েছি এবং ১০০ এরও বেশি গড়ে 100,000 ক্রিয়াকলাপের জন্য প্রথম পদ্ধতি (স্ট্রিং বিল্ডার) দ্বিতীয় (স্ট্রিং ফর্ম্যাট) এর প্রায় অর্ধেক সময় নেয়।

সুতরাং, যদি এটি খুব কম হয় তবে তাতে কিছু আসে যায় না। তবে এটি যদি সাধারণ কোনও অপারেশন হয় তবে আপনি প্রথম পদ্ধতিটি ব্যবহার করতে পারেন।


10

আমি স্ট্রিংয়ের প্রত্যাশা করব। ফর্ম্যাটটি ধীর হবে - এটি স্ট্রিংকে বিশ্লেষণ করতে হবে এবং তারপরে এটি যুক্ত করতে হবে।

কয়েক নোট:

  • ফর্ম্যাটটি পেশাদার অ্যাপ্লিকেশনগুলিতে ব্যবহারকারী-দৃশ্যমান স্ট্রিংগুলিতে যাওয়ার উপায়; এটি স্থানীয়করণ বাগগুলি এড়িয়ে চলে
  • যদি আপনি ফলাফল স্ট্রিংয়ের দৈর্ঘ্য আগেই জানেন তবে ক্ষমতাটির পূর্বনির্ধারিত করতে স্ট্রিংবিল্ডার (ইন্ট 32) কনস্ট্রাক্টরটি ব্যবহার করুন

8

আমি মনে করি বেশিরভাগ ক্ষেত্রে এই স্পষ্টতা এবং দক্ষতার মতো নয়, আপনার সবচেয়ে বড় উদ্বেগ হওয়া উচিত। আপনি যদি একসাথে প্রচুর পরিমাণে স্ট্রিং পিষে না ফেলে বা স্বল্প চালিত মোবাইল ডিভাইসের জন্য কিছু তৈরি না করেন, সম্ভবত এটি আপনার রান গতিতে খুব বেশি ঝোঁক ফেলবে না।

আমি খুঁজে পেয়েছি যে, আমি বেশ লিনিয়ার ফ্যাশনে স্ট্রিং তৈরি করছি এমন ক্ষেত্রে স্ট্রেট কনটেন্টেশন করা বা স্ট্রিংবিল্ডার ব্যবহার করা আপনার সেরা বিকল্প। আপনি যে ক্ষেত্রে বেশিরভাগ স্ট্রিং তৈরি করছেন তা গতিশীল এমন ক্ষেত্রে আমি এটি প্রস্তাব দিই। যেহেতু পাঠ্যের খুব সামান্যই অচল, তাই সর্বাধিক গুরুত্বপূর্ণ বিষয়টি এটি স্পষ্ট যে ভবিষ্যতে আপডেটের প্রয়োজন হলে প্রতিটি গতিশীল পাঠ্য স্থাপন করা হচ্ছে।

অন্যদিকে, আপনি যদি স্ট্যাটিক পাঠ্যের একটি বড় অংশের সাথে দুটি বা তিনটি ভেরিয়েবলের সাথে কথা বলছেন তবে এটি কিছুটা কম দক্ষ হলেও, আমি মনে করি স্ট্রিং থেকে আপনি যে স্পষ্টতা অর্জন করেছেন।ফর্ম্যাটটি এটিকে উপযুক্ত করে তোলে। 4 পৃষ্ঠার নথির কেন্দ্রে এক বিট গতিশীল পাঠ্য রাখার সময় আমি এই সপ্তাহের শুরুতে এটি ব্যবহার করেছি। আপনি যদি একসাথে তিনটি টুকরো টুকরো টুকরো করে আপডেট করার চেয়ে এক টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো করে আপডেট করা সহজ হবে।


হ্যাঁ! স্ট্রিং.ফর্ম্যাট ব্যবহার করুন যখন এটি করার অর্থ হয়, অর্থাত্ যখন আপনি স্ট্রিংগুলি বিন্যাস করছেন। আপনি যখন যান্ত্রিক সংক্ষেপণ সম্পাদন করছেন তখন স্ট্রিং কনটেনটেশন বা একটি স্ট্রিংবিল্ডার ব্যবহার করুন। পরবর্তী নিয়ামকের কাছে আপনার উদ্দেশ্যটি যোগাযোগ করে এমন পদ্ধতিটি বেছে নেওয়ার জন্য সর্বদা সচেষ্ট হন stri
রব

8

যদি কেবল স্ট্রিং.ফর্ম্যাটটি আপনার যা ভাবেন ঠিক তেমন করে না, এখানে নেট 45-এ 6 বছর পরে পরীক্ষার পুনরায় কাজ করা হবে।

কনক্যাট এখনও দ্রুততম তবে সত্যই এটি 30% এর চেয়ে কম পার্থক্য। স্ট্রিংবিল্ডার এবং ফর্ম্যাট সবে 5-10% দ্বারা পৃথক হয়। আমি 20% বিভিন্ন সময় কয়েকবার পরীক্ষা চালিয়েছি।

মিলিসেকেন্ড, এক মিলিয়ন পুনরাবৃত্তি:

  • সংঘটন: 367
  • প্রতিটি কি জন্য নতুন স্ট্রিংবিল্ডার: 452
  • ক্যাশেড স্ট্রিংবিল্ডার: 419
  • স্ট্রিং.ফর্ম্যাট: 475

আমি যে পাঠটি নিয়েছি তা হ'ল পারফরম্যান্সের পার্থক্যটি নগণ্য এবং সুতরাং আপনার পক্ষে সহজ সরল পঠনযোগ্য কোড লেখা বন্ধ করা উচিত নয়। আমার অর্থের জন্য যা প্রায়শই তবে সর্বদা হয় না a + b + c

const int iterations=1000000;
var keyprefix= this.GetType().FullName;
var maxkeylength=keyprefix + 1 + 1+ Math.Log10(iterations);
Console.WriteLine("KeyPrefix \"{0}\", Max Key Length {1}",keyprefix, maxkeylength);

var concatkeys= new string[iterations];
var stringbuilderkeys= new string[iterations];
var cachedsbkeys= new string[iterations];
var formatkeys= new string[iterations];

var stopwatch= new System.Diagnostics.Stopwatch();
Console.WriteLine("Concatenation:");
stopwatch.Start();

for(int i=0; i<iterations; i++){
    var key1= keyprefix+":" + i.ToString();
    concatkeys[i]=key1;
}

Console.WriteLine(stopwatch.ElapsedMilliseconds);

Console.WriteLine("New stringBuilder for each key:");
stopwatch.Restart();

for(int i=0; i<iterations; i++){
    var key2= new StringBuilder(keyprefix).Append(":").Append(i.ToString()).ToString();
    stringbuilderkeys[i]= key2;
}

Console.WriteLine(stopwatch.ElapsedMilliseconds);

Console.WriteLine("Cached StringBuilder:");
var cachedSB= new StringBuilder(maxkeylength);
stopwatch.Restart();

for(int i=0; i<iterations; i++){
    var key2b= cachedSB.Clear().Append(keyprefix).Append(":").Append(i.ToString()).ToString();
    cachedsbkeys[i]= key2b;
}

Console.WriteLine(stopwatch.ElapsedMilliseconds);

Console.WriteLine("string.Format");
stopwatch.Restart();

for(int i=0; i<iterations; i++){
    var key3= string.Format("{0}:{1}", keyprefix,i.ToString());
    formatkeys[i]= key3;
}

Console.WriteLine(stopwatch.ElapsedMilliseconds);

var referToTheComputedValuesSoCompilerCantOptimiseTheLoopsAway= concatkeys.Union(stringbuilderkeys).Union(cachedsbkeys).Union(formatkeys).LastOrDefault(x=>x[1]=='-');
Console.WriteLine(referToTheComputedValuesSoCompilerCantOptimiseTheLoopsAway);

2
"স্ট্রিং.ফর্ম্যাটটি আপনি যা ভাবেন ঠিক তেমন করে না" মানে আমি 4.5.৪ উত্স কোডে এটি ক্যাশেড স্ট্রিংবিল্ডার দৃষ্টান্তটি তৈরি এবং পুনরায় ব্যবহার করার চেষ্টা করে। তাই আমি সেই পদ্ধতির পরীক্ষায় অন্তর্ভুক্ত করেছি
ক্রিস এফ ক্যারল

6

স্ট্রিং.ফর্ম্যাট StringBuilderঅভ্যন্তরীণভাবে ব্যবহার করে ... সুতরাং যুক্তিযুক্তভাবে ধারণাটি নিয়ে যায় যে আরও ওভারহেডের কারণে এটি কিছুটা কম পারফর্মেন্ট হবে। তবে, একটি সাধারণ স্ট্রিং কনটেনটেশন হল একটি উল্লেখযোগ্য ডিগ্রি দ্বারা ... অন্য দুটির মধ্যে একটি স্ট্রিং ইনজেক্ট করার দ্রুততম পদ্ধতি। এই প্রমাণটি রিকো মারিয়ানি তার প্রথম পারফরম্যান্স কুইজে বহু বছর আগে দেখিয়েছিলেন। সরল সত্যটি হ'ল সংক্ষিপ্তকরণগুলি ... যখন স্ট্রিং পার্টসের সংখ্যাটি জানা থাকে (কোনও সীমা ছাড়াই..আপনি সহস্র অংশগুলি সংশ্লেষ করতে পারেন ... যতক্ষণ আপনি এর সর্বদা 1000 অংশগুলি জানেন) ... স্ট্রিংয়ের চেয়ে সর্বদা দ্রুত হয় StringBuilder। বিন্যাস। এগুলি একটি একক মেমরি বরাদ্দ দিয়ে একটি সিরিজ মেমরি অনুলিপি সহ সঞ্চালিত হতে পারে। প্রমাণ এখানে

এবং এখানে কিছু স্ট্রিং.কনক্যাট পদ্ধতির আসল কোড রয়েছে যা শেষ পর্যন্ত ফিল স্ট্রিংচেকডকে কল করে যা মেমরি অনুলিপি করতে পয়েন্টার ব্যবহার করে (রিফ্লেক্টরের মাধ্যমে আহরণ করা):

public static string Concat(params string[] values)
{
    int totalLength = 0;

    if (values == null)
    {
        throw new ArgumentNullException("values");
    }

    string[] strArray = new string[values.Length];

    for (int i = 0; i < values.Length; i++)
    {
        string str = values[i];
        strArray[i] = (str == null) ? Empty : str;
        totalLength += strArray[i].Length;

        if (totalLength < 0)
        {
            throw new OutOfMemoryException();
        }
    }

    return ConcatArray(strArray, totalLength);
}

public static string Concat(string str0, string str1, string str2, string str3)
{
    if (((str0 == null) && (str1 == null)) && ((str2 == null) && (str3 == null)))
    {
        return Empty;
    }

    if (str0 == null)
    {
        str0 = Empty;
    }

    if (str1 == null)
    {
        str1 = Empty;
    }

    if (str2 == null)
    {
        str2 = Empty;
    }

    if (str3 == null)
    {
        str3 = Empty;
    }

    int length = ((str0.Length + str1.Length) + str2.Length) + str3.Length;
    string dest = FastAllocateString(length);
    FillStringChecked(dest, 0, str0);
    FillStringChecked(dest, str0.Length, str1);
    FillStringChecked(dest, str0.Length + str1.Length, str2);
    FillStringChecked(dest, (str0.Length + str1.Length) + str2.Length, str3);
    return dest;
}

private static string ConcatArray(string[] values, int totalLength)
{
    string dest = FastAllocateString(totalLength);
    int destPos = 0;

    for (int i = 0; i < values.Length; i++)
    {
        FillStringChecked(dest, destPos, values[i]);
        destPos += values[i].Length;
    }

    return dest;
}

private static unsafe void FillStringChecked(string dest, int destPos, string src)
{
    int length = src.Length;

    if (length > (dest.Length - destPos))
    {
        throw new IndexOutOfRangeException();
    }

    fixed (char* chRef = &dest.m_firstChar)
    {
        fixed (char* chRef2 = &src.m_firstChar)
        {
            wstrcpy(chRef + destPos, chRef2, length);
        }
    }
}

তাহলে:

string what = "cat";
string inthehat = "The " + what + " in the hat!";

উপভোগ করুন!


নেট 4-এ স্ট্রিং.ফর্ম্যাট ক্যাচ করে এবং একটি স্ট্রিংবিল্ডার উদাহরণ পুনরায় ব্যবহার করে যাতে কিছু ব্যবহারের ক্ষেত্রে দ্রুত হতে পারে।
ক্রিস এফ ক্যারল

3

ওহ, দ্রুততমটি হবে:

string cat = "cat";
string s = "The " + cat + " in the hat";

না, স্ট্রিং কনটেনটেশন অত্যন্ত ধীর, কারণ .NET আপনার ক্ষেত্রে স্ট্রিং ভেরিয়েবলের অতিরিক্ত অনুলিপিগুলি কনক্যাট অপারেশনের মধ্যে তৈরি করে: এই ক্ষেত্রে দুটি অতিরিক্ত অনুলিপি এবং অ্যাসাইনমেন্টের জন্য চূড়ান্ত অনুলিপি। ফলাফল: StringBuilderপ্রথম স্থানে এই ধরণের কোডিংয়ের অনুকূলকরণের তুলনায় অত্যন্ত দুর্বল পারফরম্যান্স ।
আবেল

দ্রুত টাইপ করতে খুব দ্রুত;)
আপক্রিক

2
@ আবেল: উত্তরের বিবরণটির অভাব হতে পারে, তবে এই উদাহরণটিতে এই পদ্ধতির দ্রুততম বিকল্প। সংকলক এটি একটি একক স্ট্রিং.কনক্যাট () কলে রূপান্তরিত করবে, সুতরাং স্ট্রিংবিল্ডারের সাথে প্রতিস্থাপন করা কোডটি আসলে ধীর করে দেবে।
ড্যান সি

1
@ বৈভব সঠিক: এক্ষেত্রে কনকনেটেশন সবচেয়ে দ্রুত। অবশ্যই, এই পার্থক্যটি ন্যূনতম হবে যদি না অনেক সময় পুনরাবৃত্তি হয়, বা সম্ভবত অনেক বেশি বড় স্ট্রিংয়ের উপরে পরিচালিত হয়।
বেন কলিন্স 21

0

এটা সত্যিই নির্ভর করে। কয়েকটি যুক্তিযুক্ত ছোট স্ট্রিংয়ের জন্য স্ট্রিংগুলি যুক্ত করার জন্য এটি দ্রুততর।

String s = "String A" + "String B";

তবে বৃহত্তর স্ট্রিংয়ের জন্য (খুব খুব বড় স্ট্রিং) স্ট্রিংবুডার ব্যবহার করার পরে এটি আরও দক্ষ।


0

উপরের দুটি ক্ষেত্রেই আমি পূর্বনির্ধারিত টেম্পলেট স্ট্রিংয়ের মাঝখানে এক বা একাধিক স্ট্রিং ইনজেকশন করতে চাই।

কোন ক্ষেত্রে, আমি স্ট্রিংয়ের পরামর্শ দেব For ফর্ম্যাটটি দ্রুততম কারণ এটি সঠিক উদ্দেশ্যে তৈরি করা হয়েছে।



-1

আমি প্রস্তাব দেব না, যেহেতু স্ট্রিং.ফর্ম্যাটটি কনটেনটেশনের জন্য ডিজাইন করা হয়নি, এটি বিভিন্ন ইনপুটগুলির আউটপুট যেমন একটি তারিখের বিন্যাসের জন্য নকশা করা হয়েছিল।

String s = String.Format("Today is {0:dd-MMM-yyyy}.", DateTime.Today);
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.