2009-12-15 6 views
2

Рассмотрим следующий код:Почему Hashtable не возвращает true для «ContainsKey» для ключа байта типа [] в C#?

byte[] bytes = new byte[] { 1, 2, 5, 0, 6 }; 
byte[] another = new byte[] { 1, 2, 5, 0, 6 }; 

Hashtable ht = new Hashtable(); 
ht.Add(bytes, "hi"); 
Assert.IsTrue(ht.ContainsKey(another)); 

Почему это утверждение потерпеть неудачу? Если массив примитивного типа не должен использоваться с использованием ссылки на объект, не так ли? Так почему он должен возвращать ложные? Есть ли что-нибудь, что я могу сделать, чтобы эта хэш-таблица работала?

+0

System.Array - это класс поддержки, который рассматривается здесь в MSDN как ссылочный тип http://msdn.microsoft.com/en-us/library/system.array(VS.80).aspx –

+1

Предположим, что мы хэшировали эти два массива с одинаковым значением. Теперь предположим, что после кода выше вы добавили «bytes [0] = 100;». Теперь ht.Contains (bytes) возвращает true или false? Помните, что запросы выполняются * по хэш-значению *. Вот почему хеши выполняются по ссылке, а не по содержимому: * содержимое может меняться *. –

ответ

4

Вот пример реализации:

class Program { 
    static void Main(string[] args) { 
     byte[] bytes = new byte[] { 1, 2, 5, 0, 6 }; 
     byte[] another = new byte[] { 1, 2, 5, 0, 6 }; 

     Hashtable ht = new Hashtable(new ByteArrayComparer()); 
     ht.Add(bytes, "hi"); 
     System.Diagnostics.Debug.Assert(ht.ContainsKey(another)); 
    } 

    private class ByteArrayComparer : IEqualityComparer { 
     public int GetHashCode(object obj) { 
     byte[] arr = obj as byte[]; 
     int hash = 0; 
     foreach (byte b in arr) hash ^= b; 
     return hash; 
     } 
     public new bool Equals(object x, object y) { 
     byte[] arr1 = x as byte[]; 
     byte[] arr2 = y as byte[]; 
     if (arr1.Length != arr2.Length) return false; 
     for (int ix = 0; ix < arr1.Length; ++ix) 
      if (arr1[ix] != arr2[ix]) return false; 
     return true; 
     } 
    } 
    } 

Вы должны использовать более сильный хэш если вы помещаете тысячи массивов в хэш-таблицу. Например, проверьте this post.

+0

Это именно то, что я хотел видеть, но не будет преобразовывать base64 в байт-массивы более эффективны в методе «Equals (object x, object y)», а не в цикле через каждый элемент в массиве (особенно если размер массива составляет несколько десятков килобайт)? – Ash

+1

Нет, Convert.ToBase64String внутри цикл для каждого элемента массива для генерации строки. Затем для сравнения строк требуется еще один цикл. Это код, который вы не видите, но будет немного медленнее. –

+0

Итак, согласно моему единственному поисковому тесту, вышеупомянутая реализация выполняется в 0,4 мс, а при выполнении if (Convert.ToBase64String (arr1)! = Convert.ToBase64String (arr2)) возвращает false; 'выполняется в 0,3 мс. Не пытайтесь выбрать ник, но, как я уже говорил (и хотя это может показаться сумасшедшим), буквально каждая часть миллисекунды рассчитывает на это приложение. Я подсчитал, что за время жизни этого приложения сохранение 1 мс будет выполнять несколько десятков часов после выполнения приложения. Но большое спасибо за это, это была огромная помощь (если вы не думаете, что Base64 действительно будет плохой по какой-то причине). – Ash

7

Будучи массивом примитивного типа, не следует использовать с использованием ссылки на объект, не так ли?

Да, это необходимо. Массивы являются ссылочными типами.

Все работает так, как предполагается.

Если вы хотите различного поведения, вы можете реализовать компаратор для массивов, который сравнивает содержимое и передает его в хеш-таблицу.

+0

Итак, моя единственная «реальная» опция преобразует массив байтов в истинный примитив, такой как целое число или строка, и использовать это? Найти абсолютный наиболее эффективный способ поиска конкретного байта [] в этой таблице является чрезвычайно актуальной для моего проекта, над которым я работаю. Есть ли у вас какие-либо предложения? – Ash

+0

Есть ли тип массива, который I cna использует как тип значения? Возможно, что-то в 'System.Collections', о котором я не слышал? – Ash

+0

Вы пропустили ту часть, где он предложил вам просто реализовать компаратор, чтобы перейти к хеш-таблице? :-) – Ken

0

Он возвращает false, потому что хеши не совпадают. Если GetHashCode() не создает повторяемый хеш для одного и того же значения, он не будет работать в словаре.

byte[] bytes = new byte[] { 1, 2, 5, 0, 6 }; 
byte[] another = new byte[] { 1, 2, 5, 0, 6 }; 

string astring = "A string..."; 
string bstring = "A string..."; 

MessageBox.Show(bytes.GetHashCode() + " " + another.GetHashCode() + " | " + astring.GetHashCode() + " " + bstring.GetHashCode()); 
0

По умолчанию ссылочные типы сравниваются по их ссылкам, если только метод Equals для этого типа не был переопределен.

Поскольку вы хотите использовать ссылочный тип в качестве ключа в таблице, вы также должны переопределить метод GetHashCode, чтобы объекты, «равные», генерировали один и тот же хеш-код.

Хэш-таблица хранит объекты, вычисляя хэш методом GetHashCode, и любые последующие «хиты» рассчитываются с использованием этого. Вы можете сделать это, основывая значение, возвращаемое GetHasshCode для каждого из свойств объекта, в вашем случае каждый из байтов в массиве. Это пример того, где я использовал его, вы можете также сделать это в IEqualityComparer, которые вы могли бы использовать в своей Hashtable:

public override int GetHashCode() { 
     int hash = 17; 
    hash = hash * 23 + DrillDownLevel.GetHashCode(); 
    hash = hash * 23 + Year.GetHashCode(); 

    if (Month.HasValue) { 
    hash = hash * 23 + Month.Value.GetHashCode(); 
    } 

    if (Week.HasValue) { 
    hash = hash * 23 + .Week.Value.GetHashCode(); 
    } 

    if (Day.HasValue) { 
    hash = hash * 23 + obj.Day.Value.GetHashCode(); 
    } 

    return hash; 
}