Mengapa Kamus lebih disukai daripada Hashtable dalam C #?


1396

Di sebagian besar bahasa pemrograman, kamus lebih disukai daripada tagar. Apa alasan di balik itu?


21
> Ini belum tentu benar. Tabel hash adalah implementasi dari kamus. Yang tipikal pada saat itu, dan mungkin yang standar di .NET, tapi itu bukan satu-satunya definisi. Saya tidak yakin bahwa ini diperlukan oleh standar ECMA, tetapi dokumentasi MSDN dengan sangat jelas menyebutnya sebagai hashtable. Mereka bahkan menyediakan kelas SortedList untuk saat-saat ketika alternatif lebih masuk akal.
Promit

15
@Promit Saya selalu berpikir Dictionaryitu adalah implementasi dari Hashtable.
b1nary.atr0phy

2
Saya pikir alasannya adalah, bahwa dalam kamus Anda dapat menentukan jenis kunci dan nilai untuk diri Anda. Hashtable hanya dapat mengambil objek dan menyimpan pasangan berdasarkan hash (dari object.GetHashCode ()).
Radinator

2
@Dan Klaim Anda cukup keliru ... tabel hash hanya berisi satu contoh setiap kunci, dan pencarian tidak pernah menghasilkan banyak entri; jika Anda ingin mengaitkan beberapa nilai dengan setiap kunci, buat tabel hash nilai daftar nilai. Tidak ada struktur data seperti "Kamus" ... Kamus hanyalah nama yang digunakan beberapa perpustakaan untuk tabel hash mereka. misalnya, tabel hash non-generik C # disebut HashTable. Ketika mereka menambahkan obat generik ke bahasa tersebut, mereka menyebut versi generik Dictionary. Keduanya adalah tabel hash.
Jim Balter

3
@Dan Klaim Anda salah ... tabel hash ( en.wikipedia.org/wiki/Hash_table ) adalah implementasi khusus kamus, alias array asosiatif ( en.wikipedia.org/wiki/Associative_array ), dan, karena kamus, hanya berisi satu contoh dari setiap tombol, dan pencarian tidak pernah menghasilkan banyak entri; jika Anda ingin mengaitkan beberapa nilai dengan setiap kunci, buat tabel hash nilai daftar nilai. Dan. NET Kamus dan kelas Hashtable keduanya tabel hash.
Jim Balter

Jawaban:


1568

Untuk apa nilainya, Kamus adalah (secara konseptual) tabel hash.

Jika Anda bermaksud "mengapa kita menggunakan Dictionary<TKey, TValue>kelas dan bukan Hashtablekelas?", Maka itu adalah jawaban yang mudah: Dictionary<TKey, TValue>adalah tipe generik, Hashtablebukan. Itu berarti Anda mendapatkan keamanan jenis Dictionary<TKey, TValue>, karena Anda tidak dapat memasukkan objek acak apa pun ke dalamnya, dan Anda tidak harus membuang nilai yang Anda ambil.

Menariknya, Dictionary<TKey, TValue>implementasi dalam .NET Framework didasarkan pada Hashtable, seperti yang Anda tahu dari komentar ini dalam kode sumbernya:

Kamus generik disalin dari sumber Hashtable

Sumber


393
Dan juga koleksi generik jauh lebih cepat karena tidak ada tinju / unboxing
Chris S

6
Tidak yakin tentang Hashtable dengan pernyataan di atas, tetapi untuk ArrayList vs Daftar <t> itu benar
Chris S

36
Hashtable menggunakan Object untuk menyimpan sesuatu secara internal (Hanya cara non-generik untuk melakukannya) sehingga ia juga harus kotak / unbox.
Guvante

16
@BrianJ: "hash table" (dua kata) adalah istilah ilmu komputer untuk struktur seperti ini; Kamus adalah implementasi khusus. HashTable berhubungan secara kasar dengan Kamus <objek, objek> (meskipun dengan antarmuka yang sedikit berbeda), tetapi keduanya merupakan implementasi dari konsep tabel hash. Dan tentu saja, hanya untuk membingungkan masalah lebih lanjut, beberapa bahasa menyebut tabel hash mereka "kamus" (misalnya Python) - tetapi istilah CS yang tepat masih tabel hash.
Michael Madsen

32
@BrianJ: Keduanya HashTable(kelas) dan Dictionary(kelas) adalah tabel hash (konsep), tetapi a HashTablebukan a Dictionary, juga bukan Dictionarya HashTable. Mereka digunakan dalam mode yang sangat mirip, dan Dictionary<Object,Object>dapat bertindak dengan cara yang HashTabletidak diketik sama seperti yang dilakukan, tetapi mereka tidak secara langsung berbagi kode apa pun (meskipun bagian kemungkinan akan diterapkan dengan cara yang sangat mirip).
Michael Madsen

625

Dictionary<<< >>> Hashtableperbedaan:

  • Generik <<< >>> Non-Generik
  • Membutuhkan sinkronisasi utas sendiri <<< >>> Menawarkan versi yang aman melalui Synchronized()metode
  • Item yang KeyValuePairdihitung : <<< >>> Item yang dihitung:DictionaryEntry
  • Lebih baru (> .NET 2.0 ) <<< >>> Lebih lama (sejak .NET 1.0 )
  • ada di System.Collections.Generic <<< >>> ada di System.Collections
  • Permintaan untuk pengecualian melempar kunci yang tidak ada <<< >>> Permintaan untuk mengembalikan kunci yang tidak ada null
  • berpotensi sedikit lebih cepat untuk tipe nilai <<< >>> sedikit lebih lambat (perlu tinju / unboxing) untuk tipe nilai

Dictionary/ Hashtablekesamaan:

  • Keduanya adalah hashtables internal == akses cepat ke data banyak item sesuai dengan kunci
  • Keduanya membutuhkan kunci yang tidak berubah dan unik
  • Kunci keduanya perlu GetHashCode()metode sendiri

Koleksi .NET yang serupa (kandidat untuk digunakan alih-alih Kamus dan Hashtable):

  • ConcurrentDictionary- Utas aman (dapat diakses dengan aman dari beberapa utas secara bersamaan)
  • HybridDictionary- Kinerja yang dioptimalkan (untuk beberapa item dan juga untuk banyak item)
  • OrderedDictionary- nilai dapat diakses melalui indeks int (berdasarkan urutan penambahan item)
  • SortedDictionary- item diurutkan secara otomatis
  • StringDictionary- sangat diketik dan dioptimalkan untuk string

11
@ Guillaume86, inilah mengapa Anda menggunakan TryGetValue sebagai gantinya msdn.microsoft.com/en-us/library/bb347013.aspx
Trident D'Gao

2
+1 untuk StringDictionary... btw StringDictionarytidak sama dengan Dictionary<string, string>ketika Anda menggunakan konstruktor default.
Cheng Chen

ParallelExtensionsExtras @ code.msdn.microsoft.com/windowsdesktop/… berisi ObservableConcurrentDictionary yang sangat baik untuk mengikat sekaligus konkurensi.
VoteCoffee

3
penjelasan yang luar biasa, sangat menyenangkan Anda juga mendaftar kesamaan untuk mengurangi pertanyaan yang mungkin muncul di pikiran seseorang
mkb


178

Karena Dictionaryadalah kelas generik ( Dictionary<TKey, TValue>), sehingga mengakses kontennya adalah tipe-aman (yaitu Anda tidak perlu menggunakan Object, seperti yang Anda lakukan dengan a Hashtable).

Membandingkan

var customers = new Dictionary<string, Customer>();
...
Customer customer = customers["Ali G"];

untuk

var customers = new Hashtable();
...
Customer customer = customers["Ali G"] as Customer;

Namun, Dictionarydiimplementasikan sebagai tabel hash secara internal, jadi secara teknis ia bekerja dengan cara yang sama.


88

FYI: Di .NET, Hashtableadalah utas yang aman untuk digunakan oleh banyak pembaca utas dan utas tunggal, sementara di Dictionaryanggota statis publik adalah utas aman, tetapi setiap anggota contoh tidak dijamin aman utas.

Kami harus mengubah semua Kamus kami kembali Hashtablekarena ini.


10
Menyenangkan. Kode sumber Kamus <T> terlihat jauh lebih bersih dan lebih cepat. Mungkin lebih baik menggunakan Kamus dan mengimplementasikan sinkronisasi Anda sendiri. Jika Kamus membaca benar-benar harus terkini, maka Anda hanya perlu menyinkronkan akses ke metode baca / tulis Kamus. Itu akan banyak mengunci, tetapi itu akan benar.
Triynko

10
Atau, jika bacaan Anda tidak harus benar-benar terkini, Anda dapat memperlakukan kamus sebagai tidak berubah. Anda kemudian dapat mengambil referensi ke Kamus dan mendapatkan kinerja dengan tidak menyinkronkan bacaan sama sekali (karena itu tidak dapat diubah dan secara inheren aman-aman). Untuk memperbaruinya, Anda membuat salinan lengkap Kamus yang telah diperbarui di latar belakang, kemudian cukup menukar referensi dengan Interlocked.CompareExchange (dengan asumsi satu utas tulisan; beberapa utas penulisan akan memerlukan sinkronisasi pembaruan).
Triynko

38
.Net 4.0 menambahkan ConcurrentDictionarykelas yang menerapkan semua metode publik / terlindungi agar aman. Jika Anda tidak perlu mendukung platform lawas, ini akan memungkinkan Anda mengganti Hashtablekode multithreaded: msdn.microsoft.com/en-us/library/dd287191.aspx
Dan Is Fiddling By Firelight

anonim untuk penyelamatan. Jawaban keren
unkulunkulu

5
Saya ingat pernah membaca bahwa HashTable hanya aman untuk pembaca-penulis dalam skenario di mana informasi tidak pernah dihapus dari tabel. Jika pembaca meminta item yang ada di tabel sementara item yang berbeda dihapus, dan pembaca akan mencari lebih dari satu tempat untuk item tersebut, ada kemungkinan bahwa ketika pembaca sedang mencari penulis mungkin memindahkan item dari tempat yang belum diperiksa ke yang sudah, sehingga menghasilkan laporan palsu bahwa barang tersebut tidak ada.
supercat

68

Di .NET, perbedaan antara Dictionary<,>dan HashTableterutama bahwa yang pertama adalah tipe generik, sehingga Anda mendapatkan semua manfaat dari generik dalam hal pemeriksaan tipe statis (dan mengurangi tinju, tapi ini tidak sebesar yang orang cenderung berpikir dalam dalam hal kinerja - ada biaya memori yang pasti untuk tinju, meskipun).


34

Orang-orang mengatakan bahwa Kamus sama dengan tabel hash.

Ini belum tentu benar. Tabel hash adalah salah satu cara untuk mengimplementasikan kamus. Yang tipikal pada saat itu, dan itu mungkin yang standar di .NET di Dictionarykelas, tapi itu bukan satu-satunya definisi.

Anda juga dapat mengimplementasikan kamus menggunakan daftar tautan atau pohon pencarian dengan baik, itu tidak akan seefisien (untuk beberapa metrik efisien).


4
Dokumen MS mengatakan: "Mengambil nilai dengan menggunakan kuncinya sangat cepat, dekat dengan O (1), karena kelas Kamus <(Dari <(Dari <(TKey, TValue>)>)) diimplementasikan sebagai tabel hash." - jadi Anda harus dijamin hashtable saat berhadapan dengan Dictionary<K,V>. IDictionary<K,V>bisa jadi apa saja :)
snemarch

13
@ rix0rrr - Saya pikir Anda telah mundur, Kamus menggunakan HashTable bukan HashTable menggunakan Kamus.
Joseph Hamilton

8
@JosephHamilton - rix0rrr benar: "Tabel hash adalah implementasi dari kamus ." Dia berarti konsep "kamus", bukan kelas (perhatikan huruf kecil). Secara konseptual, tabel hash mengimplementasikan antarmuka kamus. Di .NET, Kamus menggunakan tabel hash untuk mengimplementasikan IDictionary. Ini berantakan;)
Robert Hensing

Saya bicarakan di .NET, karena itulah yang dia rujuk dalam jawabannya.
Joseph Hamilton

2
@JosephHamilton: implementasi (atau implementasi ) bahkan tidak berarti sama dengan penggunaan . Justru sebaliknya. Mungkin akan lebih jelas jika dia mengatakannya sedikit berbeda (tetapi dengan makna yang sama): "tabel hash adalah salah satu cara untuk mengimplementasikan kamus". Artinya, jika Anda ingin fungsionalitas kamus, salah satu cara untuk melakukan itu (untuk mengimplementasikan kamus), adalah dengan menggunakan hashtable.
ToolmakerSteve

21

Collections& Genericsberguna untuk menangani kelompok objek. Di .NET, semua objek koleksi berada di bawah antarmuka IEnumerable, yang pada gilirannya memiliki ArrayList(Index-Value))& HashTable(Key-Value). Setelah .NET framework 2.0, ArrayList& HashTablediganti dengan List& Dictionary. Sekarang, Arraylist& HashTabletidak lagi digunakan dalam proyek saat ini.

Datang ke perbedaan antara HashTable& Dictionary, Dictionaryadalah generik di mana Hastabletidak generik. Kita dapat menambahkan semua jenis objek HashTable, tetapi saat mengambil kita perlu melemparkannya ke jenis yang diperlukan. Jadi, ini bukan tipe yang aman. Tetapi untuk dictionary, saat mendeklarasikan sendiri, kita dapat menentukan jenis kunci dan nilai, sehingga tidak perlu digunakan saat mengambil.

Mari kita lihat sebuah contoh:

HashTable

class HashTableProgram
{
    static void Main(string[] args)
    {
        Hashtable ht = new Hashtable();
        ht.Add(1, "One");
        ht.Add(2, "Two");
        ht.Add(3, "Three");
        foreach (DictionaryEntry de in ht)
        {
            int Key = (int)de.Key; //Casting
            string value = de.Value.ToString(); //Casting
            Console.WriteLine(Key + " " + value);
        }

    }
}

Kamus,

class DictionaryProgram
{
    static void Main(string[] args)
    {
        Dictionary<int, string> dt = new Dictionary<int, string>();
        dt.Add(1, "One");
        dt.Add(2, "Two");
        dt.Add(3, "Three");
        foreach (KeyValuePair<int, String> kv in dt)
        {
            Console.WriteLine(kv.Key + " " + kv.Value);
        }
    }
}

2
alih-alih secara eksplisit menetapkan datatype untuk KeyValuePair, kita bisa menggunakan var. Jadi, ini akan mengurangi mengetik - foreach (var kv di dt) ... hanya saran.
Ron

16

Kamus:

  • Mengembalikan / melempar Pengecualian jika kami mencoba menemukan kunci yang tidak ada.

  • Ini lebih cepat daripada Hashtable karena tidak ada tinju dan unboxing.

  • Hanya anggota statis publik yang aman utas.

  • Kamus adalah tipe generik yang berarti kita dapat menggunakannya dengan tipe data apa pun (Saat membuat, harus menentukan tipe data untuk kunci dan nilai).

    Contoh: Dictionary<string, string> <NameOfDictionaryVar> = new Dictionary<string, string>();

  • Dictionay adalah implementasi Hashtable yang aman untuk tipe, Keysdan Valuessangat diketik.

Hashtable:

  • Ia mengembalikan nol jika kami mencoba menemukan kunci yang tidak ada.

  • Ini lebih lambat daripada kamus karena membutuhkan tinju dan unboxing.

  • Semua anggota di Hashtable aman,

  • Hashtable bukan tipe generik,

  • Hashtable adalah struktur data yang diketik secara longgar, kita dapat menambahkan kunci dan nilai dari jenis apa pun.


"Ini mengembalikan / melempar Pengecualian jika kita mencoba menemukan kunci yang tidak ada." Tidak jika Anda menggunakanDictionary.TryGetValue
Jim Balter

16

The Pemeriksaan luas dari Struktur Data Menggunakan C # artikel di MSDN menyatakan bahwa ada juga perbedaan dalam strategi resolusi tabrakan :

Kelas Hashtable menggunakan teknik yang disebut sebagai rehashing .

Rehashing bekerja sebagai berikut: ada satu set fungsi hash yang berbeda, H 1 ... H n , dan ketika memasukkan atau mengambil item dari tabel hash, awalnya fungsi hash H 1 digunakan. Jika ini mengarah ke tabrakan, H 2 dicoba sebagai gantinya, dan seterusnya hingga H n jika diperlukan.

Kamus menggunakan teknik yang disebut chaining .

Dengan pengulangan, jika terjadi tabrakan, hash dihitung ulang, dan slot baru yang sesuai dengan hash dicoba. Dengan rantai, bagaimanapun, struktur data sekunder digunakan untuk menahan tabrakan . Secara khusus, setiap slot dalam Kamus memiliki array elemen yang memetakan ke ember itu. Jika terjadi tabrakan, elemen yang bertabrakan akan ditambahkan ke daftar bucket.


16

Sejak .NET Framework 3.5 ada juga HashSet<T>yang menyediakan semua kelebihan Dictionary<TKey, TValue>jika Anda hanya perlu kunci dan tidak ada nilai.

Jadi, jika Anda menggunakan Dictionary<MyType, object>dan selalu menetapkan nilai nulluntuk mensimulasikan tabel hash jenis aman Anda mungkin harus mempertimbangkan beralih ke HashSet<T>.


14

Ini Hashtableadalah struktur data yang diketik secara longgar, sehingga Anda dapat menambahkan kunci dan nilai dari jenis apa pun ke Hashtable. The Dictionarykelas adalah jenis-aman Hashtableimplementasi, dan tombol dan nilai-nilai yang sangat diketik. Saat membuat Dictionarycontoh, Anda harus menentukan tipe data untuk kunci dan nilai.


11

Perhatikan bahwa MSDN mengatakan: "Kamus <(Dari <(TKey, TValue>)>) kelas diimplementasikan sebagai tabel hash ", bukan "Kamus <(Dari <(TKey, TValue>)>) kelas diimplementasikan sebagai HashTable "

Kamus TIDAK diimplementasikan sebagai HashTable, tetapi diimplementasikan mengikuti konsep tabel hash. Implementasi tidak terkait dengan kelas HashTable karena penggunaan Generics, meskipun secara internal Microsoft bisa menggunakan kode yang sama dan mengganti simbol tipe Object dengan TKey dan TValue.

Di .NET 1.0 Generik tidak ada; ini adalah tempat HashTable dan ArrayList awalnya dimulai.


Bisakah Anda memperbaiki kutipan MSDN itu? Ada yang hilang atau salah; itu tidak gramatikal dan agak tidak bisa dipahami.
Peter Mortensen

10

HashTable:

Kunci / nilai akan dikonversi menjadi tipe objek (tinju) saat menyimpan ke heap.

Kunci / nilai perlu dikonversi ke jenis yang diinginkan saat membaca dari tumpukan.

Operasi ini sangat mahal. Kita harus menghindari tinju / unboxing sebanyak mungkin.

Kamus: Varian umum dari HashTable.

Tidak ada tinju / unboxing. Tidak perlu konversi.


8

Objek Hashtable terdiri dari ember yang berisi elemen koleksi. Bucket adalah subkelompok elemen virtual dalam Hashtable, yang membuat pencarian dan pengambilan lebih mudah dan lebih cepat daripada di sebagian besar koleksi .

Kelas Kamus memiliki fungsi yang sama dengan kelas Hashtable. Kamus jenis tertentu (selain Obyek) memiliki kinerja yang lebih baik daripada Hashtable untuk jenis nilai karena unsur-unsur Hashtable adalah tipe Objek dan, oleh karena itu, tinju dan unboxing biasanya terjadi jika menyimpan atau mengambil jenis nilai.

Untuk bacaan lebih lanjut: Jenis Koleksi Kamus dan Hashtable


7

Perbedaan penting lainnya adalah bahwa Hashtable aman untuk digunakan. Hashtable memiliki keamanan utas pembaca multi / penulis tunggal (MR / SW) yang berarti Hashtable memungkinkan SATU penulis bersama dengan banyak pembaca tanpa mengunci.

Dalam kasus Kamus tidak ada keamanan utas; jika Anda memerlukan keamanan utas, Anda harus menerapkan sinkronisasi Anda sendiri.

Untuk menguraikan lebih lanjut:

Hashtable memberikan keamanan benang melalui Synchronizedproperti, yang mengembalikan pembungkus benang-aman di sekitar koleksi. Pembungkus bekerja dengan mengunci seluruh koleksi pada setiap operasi tambah atau hapus. Oleh karena itu, setiap utas yang mencoba mengakses koleksi harus menunggu gilirannya mengambil satu kunci. Ini tidak dapat diskalakan dan dapat menyebabkan penurunan kinerja yang signifikan untuk koleksi besar. Selain itu, desainnya tidak sepenuhnya terlindungi dari kondisi balapan.

Kelas koleksi .NET Framework 2.0 seperti List<T>, Dictionary<TKey, TValue>, dll. Tidak menyediakan sinkronisasi utas apa pun; kode pengguna harus memberikan semua sinkronisasi ketika item ditambahkan atau dihapus pada banyak utas secara bersamaan

Jika Anda perlu mengetikkan keamanan juga keamanan utas, gunakan kelas koleksi bersamaan di .NET Framework. Bacaan lebih lanjut di sini .

Perbedaan tambahan adalah bahwa ketika kita menambahkan beberapa entri dalam Kamus, urutan penambahan entri dipertahankan. Ketika kami mengambil item dari Kamus kami akan mendapatkan catatan dalam urutan yang sama seperti yang kami masukkan. Sedangkan Hashtable tidak mempertahankan urutan penyisipan.


Dari apa yang saya mengerti, Hashsetjaminan keamanan MR / SW thread dalam skenario penggunaan yang tidak melibatkan penghapusan . Saya pikir itu mungkin dimaksudkan untuk sepenuhnya aman MR / SW, tetapi menangani penghapusan dengan aman sangat meningkatkan biaya keselamatan MR / SW. Sementara desain Dictionarybisa menawarkan keamanan MR / SW dengan biaya minimal dalam skenario tanpa-hapus, saya pikir MS ingin menghindari memperlakukan skenario tanpa-hapus sebagai "istimewa".
supercat

5

Satu lagi perbedaan yang bisa saya pahami adalah:

Kami tidak dapat menggunakan Kamus <KT, VT> (generik) dengan layanan web. Alasannya adalah tidak ada standar layanan web yang mendukung standar generik.


Kita dapat menggunakan daftar umum (Daftar <string>) dalam layanan web berbasis sabun. Namun, kami tidak dapat menggunakan kamus (atau hashtable) di layanan web. Saya pikir alasan untuk ini adalah. Net xmlserializer tidak dapat menangani objek kamus.
Siddharth

5

Dictionary<> adalah tipe generik dan tipe aman.

Anda bisa menyisipkan tipe nilai apa pun di HashTable dan ini kadang-kadang bisa menimbulkan pengecualian. Tetapi Dictionary<int>hanya akan menerima nilai integer dan juga Dictionary<string>hanya akan menerima string.

Jadi, lebih baik menggunakan Dictionary<>daripada HashTable.


0

Di sebagian besar bahasa pemrograman, kamus lebih disukai daripada tagar

Saya tidak berpikir ini benar, sebagian besar bahasa memiliki satu atau yang lain, tergantung pada terminologi yang mereka sukai .

Dalam C #, bagaimanapun, alasan yang jelas (bagi saya) adalah bahwa C # HashTables dan anggota lain dari System.Collections namespace sebagian besar sudah usang. Mereka hadir di c # V1.1. Mereka telah digantikan dari C # 2.0 oleh kelas Generic di System.Collections. namespace General.


Salah satu keuntungan dari hashtable di kamus adalah bahwa jika kunci tidak ada dalam kamus, itu akan menimbulkan kesalahan. Jika kunci tidak ada dalam hashtable, itu hanya mengembalikan null.
Bill Norman

Dalam C # saya masih akan menghindari menggunakan System.Collections.Hashtable karena mereka tidak memiliki keunggulan generik. Anda dapat menggunakan Kamus's TryGetValue atau HasKey jika Anda tidak tahu apakah kuncinya akan ada.
kristianp

Aduh, bukan HasKey, itu harus ContainsKey.
kristianp

-3

Menurut apa yang saya lihat dengan menggunakan .NET Reflector :

[Serializable, ComVisible(true)]
public abstract class DictionaryBase : IDictionary, ICollection, IEnumerable
{
    // Fields
    private Hashtable hashtable;

    // Methods
    protected DictionaryBase();
    public void Clear();
.
.
.
}
Take note of these lines
// Fields
private Hashtable hashtable;

Jadi kita dapat yakin bahwa DictionaryBase menggunakan HashTable secara internal.


16
System.Collections.Generic.Dictionary <TKey, TValue> tidak berasal dari DictionaryBase.
snemarch

"Jadi kita bisa yakin bahwa DictionaryBase menggunakan HashTable secara internal." - Itu bagus, tapi tidak ada hubungannya dengan pertanyaan.
Jim Balter
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.