词典的实现(2)-借助顺序表(数组)实现词典
一,思路
1,词典实现类ArrayDictionary.java肯定要implements 词典的接口DictionaryInterface.java(参考:http://www.cnblogs.com/hapjin/p/4573826.html)。其次,为了序列化,还要implements Serializable接口
2,由于词典中的每个元素是一个键值对。因此,需要将查找键 和 值 封装在同一个对象中,因而定义了Entry.java来表示词典中的每个元素。其中,Entry类是作为ArrayDictionary的内部类来实现的。
3,由于每个Entry对象表示一个词典元素,因此ArrayDictionary.java需要一个Entry类型的数组来存放词典元素。这也是文章的标题--借助顺序表来实现词典的原因。
再深入讨论下,我们知道在JAVA类库中ArrayList.java就相当于已经帮程序员实现好了一个顺序表(数组),我们不需要再去关注数组的具体实现细节以及如何遍历数组,而是直接用ArrayList.java中的各种方法(add,remove……)来完成数组的基本操作,通过 iterator()方法获得遍历数组的迭代器。也可以借助ArrayList.java中方法来辅助实现DictionaryInterface接口中定义的方法。
但是,在本例中,采用的是自定义数组来实现词典的,因此就需要根据DictionaryInterface接口中的方法以及自已的需要来完成ArrayDictionary.java中的方法。
下面,具体理清下迭代器的实现思路:在ArrayDictionary.java中定义了三个私有内部类分为表示实现三个迭代器:
class EntryIterator<Entry>实现遍历Entry数组的迭代器
class KeyIterator<K>实现遍历查找键的迭代器
class ValueIterator<V>实现 值(Value) 的迭代器
其中,后两个迭代器会用到遍历Entry数组的迭代器,因为键(Key)和值(Value)是封装在Entry中的。
同时,还在ArrayDictionary.java定义了三个相应的获取遍历器的方法来获取上述三个遍历器。
ArrayDictionary.java代码如下:
1 package dictionary; 2 3 import java.io.Serializable; 4 import java.util.Iterator; 5 import java.util.NoSuchElementException; 6 7 public class ArrayDictionary<K,V> implements DictionaryInterface<K, V>, Serializable{ 8 9 private Entry<K,V>[] dictionary;//封装了 key-value对 的对象 10 private int currentSize;//数组元素个数 11 private final static int DEFAULT_SIZE = 25; 12 13 public ArrayDictionary(){ 14 this(DEFAULT_SIZE); 15 } 16 @SuppressWarnings("unchecked") 17 public ArrayDictionary(int initialCapacity){ 18 dictionary = new Entry[initialCapacity]; 19 currentSize = 0; 20 } 21 22 private class Entry<S,T>{ 23 S key; 24 T value; 25 26 private Entry(S searchKey, T dataValue){ 27 key = searchKey; 28 value = dataValue; 29 } 30 //键是不可改变的,因此没有setKey方法 31 public S getKey() { 32 return key; 33 } 34 public T getValue() { 35 return value; 36 } 37 public void setValue(T value) { 38 this.value = value; 39 } 40 }//end class Entry 41 42 /* 43 * EntryIterator类用来实现Entry数组的遍历器 44 */ 45 private class EntryIterator<Entry> implements Iterator<Entry>, Serializable{ 46 private int nextIndex;//标记当前正在遍历的元素 47 48 private EntryIterator(){ 49 nextIndex = 0; 50 } 51 @Override 52 public boolean hasNext() { 53 return nextIndex < currentSize; 54 } 55 @Override 56 public Entry next() { 57 if(hasNext()){ 58 Entry result = (Entry) dictionary[nextIndex];//将泛型的Entry转换为非泛型的Entry??? 59 nextIndex++; 60 return result; 61 } 62 else 63 throw new NoSuchElementException(); 64 } 65 @Override 66 public void remove() { 67 // TODO Auto-generated method stub 68 throw new UnsupportedOperationException(); 69 } 70 } 71 72 private class KeyIterator<K> implements Iterator<K>{ 73 74 private Iterator<Entry> entryIterator; 75 private KeyIterator(){ 76 entryIterator = new EntryIterator<ArrayDictionary.Entry>(); 77 } 78 @Override 79 public boolean hasNext() { 80 return entryIterator.hasNext();//有Entry元素就表示有Key(同时还会有Value) 81 } 82 83 @Override 84 public K next() { 85 Entry nextEntry = entryIterator.next(); 86 return (K) nextEntry.getKey();//??泛型与非泛型之间的转换??? 87 } 88 89 @Override 90 public void remove() { 91 throw new UnsupportedOperationException(); 92 } 93 94 } 95 96 private class ValueIterator<V> implements Iterator<V>,Serializable{ 97 98 private EntryIterator<Entry> entryIterator; 99 private ValueIterator(){ 100 entryIterator = new EntryIterator<ArrayDictionary.Entry>(); 101 } 102 @Override 103 public boolean hasNext() { 104 return entryIterator.hasNext(); 105 } 106 107 @Override 108 public V next() { 109 Entry<K, V> nextEntry = entryIterator.next(); 110 return nextEntry.getValue(); 111 } 112 113 @Override 114 public void remove() { 115 throw new UnsupportedOperationException(); 116 117 } 118 119 } 120 121 /* 122 * Task 根据查找键来 查找 该元素在Entry数组中的位置 123 * @param key 待查找的键 124 * @return 若查找成功,返回的index < currentSize .否则,index > currentSize 125 */ 126 private int locateIndex(K key){ 127 int index = 0; 128 // System.out.println(key.getClass()); 129 while((index < currentSize) && (!(key.equals(dictionary[index].getKey())))){ 130 // System.out.println("index = " + index); 131 index++; 132 } 133 return index; 134 } 135 136 @Override 137 public V add(K key, V value) { 138 V result = null; 139 int KeyIndex = locateIndex(key); 140 if(KeyIndex < currentSize){ 141 result = dictionary[KeyIndex].getValue(); 142 dictionary[KeyIndex].setValue(value); 143 } 144 else{ 145 if(isFull()) 146 doubleArray(); 147 dictionary[currentSize] = new Entry<K, V>(key, value); 148 currentSize ++; 149 System.out.println("currentSize = " + currentSize); 150 } 151 return result; 152 } 153 154 private void doubleArray(){ 155 156 } 157 158 @Override 159 public V remove(K key) { 160 V result = null; 161 int keyIndex = locateIndex(key); 162 if(keyIndex < currentSize){ 163 result = dictionary[keyIndex].getValue();//获得待删除key的value 164 //用最后一个元素代替待删元素,然后再删除最后一个元素 165 dictionary[keyIndex] = dictionary[currentSize - 1]; 166 currentSize--; 167 }//end if 168 return result; 169 } 170 171 @Override 172 public V getValue(K key) { 173 V result = null; 174 int keyIndex = locateIndex(key); 175 //System.out.println("searching key= " + keyIndex); 176 if(keyIndex < currentSize)//找到了 177 result = dictionary[keyIndex].getValue(); 178 return result; 179 } 180 181 @Override 182 public boolean contains(K key) { 183 return locateIndex(key) < currentSize; 184 } 185 186 @Override 187 public Iterator<K> getKeyIterator() { 188 // TODO Auto-generated method stub 189 return new KeyIterator(); 190 } 191 192 @Override 193 public Iterator<V> getValueIterator() { 194 // TODO Auto-generated method stub 195 return new ValueIterator(); 196 } 197 198 @Override 199 public boolean isEmpty() { 200 return currentSize == 0; 201 } 202 203 @Override 204 public boolean isFull() { 205 System.out.println("dictionary.length = " + dictionary.length); 206 return currentSize == DEFAULT_SIZE; 207 } 208 209 @Override 210 public int getSize() { 211 return currentSize; 212 } 213 214 @Override 215 public void clear() { 216 currentSize = 0; 217 } 218 219 }
词典数据结构实现完成之后,编写了一个TelephoneDirectory.java使用该词典来模拟电话本功能。以姓名为键,以电话号码为值来存储每条记录,这是ArrayDictionary实现的功能;从一个特定格式的文本文件中读取姓名及电话号码值,并将之存储到词典中,这是TelephoneDirectory的实现功能。
然后再编写客户端类Driver.java 来测试电话本的功能。测试流程如下:提供内容为 姓名 电话号码 的一个文本文件,在控制台上输入姓名,按回车查询该人名所对应的电话号码。
TelephoneDirectory.java代码如下:
1 package dictionary.common; 2 3 import java.util.Scanner; 4 import dictionary.ArrayDictionary; 5 import dictionary.DictionaryInterface; 6 import dictionary.common.Name; 7 8 public class TelephoneDirectory { 9 private DictionaryInterface<Name,String> phoneBook; 10 11 public TelephoneDirectory(){ 12 phoneBook = new ArrayDictionary<Name,String>(); 13 } 14 15 /* 16 * @Task 以行为单位,依次读取整个文件 17 * @param data 读取文本文件中指定格式的数据: 18 * 每行含有三个字符串:名,姓,电话号码。它们用空格分开 19 */ 20 public void readFile(Scanner data){ 21 while(data.hasNext()){ 22 String firstName = data.next(); 23 String lastName = data.next(); 24 String phoneNumber = data.next(); 25 26 Name fullName = new Name(firstName, lastName); 27 phoneBook.add(fullName, phoneNumber); 28 }//end while 29 data.close(); 30 }//end readFile() 31 32 public String getPhoneNumber(Name personName){ 33 return phoneBook.getValue(personName); 34 } 35 }
电话号码查询客户端Driver.java实现代码如下:
1 package dictionary.client; 2 3 import java.io.File; 4 import java.io.FileNotFoundException; 5 import java.io.IOException; 6 import java.util.Scanner; 7 import dictionary.common.Name; 8 import dictionary.common.TelephoneDirectory; 9 10 public class Driver { 11 private static final Name INPUT_ERROR = new Name("error", "error"); 12 private static final Name QUIT = new Name("quit", "quit"); 13 14 public static void main(String[] args) { 15 TelephoneDirectory directory = new TelephoneDirectory(); 16 String fileName = "data.txt";//file name can be read data into telephone book 17 Scanner data = null; 18 try{ 19 data = new Scanner(new File(fileName)); 20 }catch(FileNotFoundException e){ 21 System.out.println("File not found: " + e.getMessage()); 22 }catch(IOException e){ 23 System.out.println("I/O exception: " + e.getMessage()); 24 } 25 directory.readFile(data); 26 27 Name nextName = getName();//get name for search from user 28 while(! nextName.equals(QUIT)){ 29 if(nextName.equals(INPUT_ERROR)){ 30 System.out.println("Error in entering name.Try again. "); 31 break; 32 } 33 else{ 34 String phoneNumber = directory.getPhoneNumber(nextName); 35 if(phoneNumber == null) 36 System.out.println(nextName + " is not in the directory"); 37 else 38 System.out.println("The phone number for " + nextName + " is " + phoneNumber); 39 nextName = getName(); 40 } 41 }//end while 42 System.out.println("Bye!"); 43 }//end main 44 45 // return either the name read from user, INPUT_ERROR ,or QUIT 46 private static Name getName() { 47 Name result = null; 48 Scanner keyboard = new Scanner(System.in);// 扫描从键盘的输入 49 System.out.println("Enter first name and last name,or quit to end"); 50 String line = keyboard.nextLine();// 读取一行输入 51 if (line.trim().toLowerCase().equals("quit")) 52 result = QUIT;// 用户结束查询 53 else {// 构造 查询的 key对象-Name对象 54 String firstName = null; 55 String lastName = null; 56 Scanner scan = new Scanner(line);// 扫描输入的一行 57 58 if (scan.hasNext()) { 59 firstName = scan.next();// 提取出firstname 60 //System.out.println("keyinput--firstName:" + firstName); 61 if (scan.hasNext()) 62 lastName = scan.next();// 提取出lastname 63 else 64 result = INPUT_ERROR;// 用户没有输入lastname 65 } else 66 result = INPUT_ERROR; 67 if (result == null) 68 result = new Name(firstName, lastName); 69 } 70 return result; 71 } 72 }
姓名是由firstName与lastName组成,Name.java是用来封装人名的类,Name类的对象作为Map的查找键。当两个人的firstName 与 lastName相同时,它们应该表示同一个人,因此需要重写Name类的hashCode() 和 equals().
重写的规则参考:http://www.cnblogs.com/hapjin/p/4582795.html
1 package dictionary.common; 2 3 public class Name { 4 private String firstName; 5 private String lastName; 6 7 @Override 8 public int hashCode() { 9 final int prime = 31; 10 int result = 1; 11 result = prime * result 12 + ((firstName == null) ? 0 : firstName.hashCode()); 13 result = prime * result 14 + ((lastName == null) ? 0 : lastName.hashCode()); 15 return result; 16 } 17 @Override 18 public boolean equals(Object obj) { 19 if (this == obj) 20 return true; 21 if (obj == null) 22 return false; 23 if (getClass() != obj.getClass()) 24 return false; 25 Name other = (Name) obj; 26 if (firstName == null) { 27 if (other.firstName != null) 28 return false; 29 } else if (!firstName.equals(other.firstName)) 30 return false; 31 if (lastName == null) { 32 if (other.lastName != null) 33 return false; 34 } else if (!lastName.equals(other.lastName)) 35 return false; 36 return true; 37 } 38 public Name(){ 39 40 } 41 public Name(String firstName, String lastName){ 42 this.firstName = firstName; 43 this.lastName = lastName; 44 } 45 46 public String getFirstName() { 47 return firstName; 48 } 49 public void setFirstName(String firstName) { 50 this.firstName = firstName; 51 } 52 public String getLastName() { 53 return lastName; 54 } 55 public void setLastName(String lastName) { 56 this.lastName = lastName; 57 } 58 59 public String toString(){ 60 return firstName + " " + lastName; 61 } 62 }
运行结果,文本文件中提供的数据如下:
xiaoming lee 2009999999
xiaohong lee 1009999999
xiaohei lee 3009999999
查询结果如下: