菜鸟学习笔记:Java容器2--Map、Set、迭代器
- Map容器
-
- HashMap的使用
- Hash表讲解
- Map实现
- Set容器
-
- HashSet的使用
- 实现
- Iterator迭代器
Map容器
HashMap的使用
与List容器不同,Map容器中存放的并不是一个有序数列,它是以键值对的方式对数据进行存储,在容器中用一个不重复的键来存储一个固定的值。
public static void main(String[] args) {
// 定义一个Map的容器对象
Map map1 = new HashMap();
map1.put("jack", 20);
map1.put("rose", 18);
map1.put("lucy", 17);
map1.put("java", 25);
System.out.println(map1.get("jack"));//输出20
System.out.println(map1.get("java"));//输出25
}
查看Map接口
可以看到许多针对这个接口的操作不再像List那样围绕这索引index来,大多数都是围绕着K键和V值来进行,Map的实现类主要有HashMap和TreeMap。下面我们通过代码来自己实现一个Map。
Hash表讲解
在实现Map之前有必要先对Hash表做一个简单的了解。
散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。
概念还是很复杂,我们举一个简单点的例子来帮助理解:
如果给我们一个存有少量数值的数组,给你一个值让你进行查询,那么我们肯定会想到从头开始遍历比较,如图,当我们要查询数组中是否存在1这个元素时需要访问数组9次,很明显效率非常低。
那么有没有更简单的方法那?看到3存放的位置,我们可以想到,存储数据时是不是可以将对应值存储到对应位置那,有了这个规则当我们再次访问时每次查询只需要一次访问就可以做到,这样就得到了下面的结构。
但这样存储有形成了一个问题,如果我们要存入19,数组不够长怎么办?想想用什么方法可以把任何数据放缩到16之内那,那自然就是取模操作了,存数据之前先将数据与数组长度进行取模运算,这个取模的公式说白了就是所谓的散列函数(也叫哈希函数)。这样19%17(数组长度)=2,所以19该存放到2的位置如图:
这当然又产生了一个新的问题,数据冲突了怎么办?这时想想看如果我们在数组中不直接存储而去存储一个引用那不是可以无限扩展了吗?恰好我们之前学习的链表非常适合这种操作,所以我们在建立数组时不让其存储数据,而是存储一个引用变量,这个引用作为一个头节点指向一个链表,在链表中完成对数据的存储,这样最终的存储效果如下图:
这就是hash表以及处理冲突的方法(当然处理冲突的方法不止这一种,要感兴趣的同学可以去深入学习下数据结构这门课程),也是HashMap的底层原理,有了这个基础,我们就可以用我们的代码去实现HashMap。
Map实现
首先定义一个类来对键key与值进行存取。
class MyEntry {
Object key;
Object value;
public MyEntry(Object key, Object value) {
super();
this.key = key;
this.value = value;
}
}
对应HashMap的实现现在完全可以更具数组+链表的形式完成,链表采用LinkedList结构,每个键值对采用我们自己定义的MyEntry对象,并在程序中保证键值不重复,依据以上条件书写我们自己Map的实现代码,由于篇幅有限我们只实现put和get方法,其他方法大家可根据Map的特点以及之前List的思路自己去完成:
public class MyMap {
LinkedList[] arr = new LinkedList[9]; //链表类型数组
int size;
//写入元素
public void put(Object key,Object value){
MyEntry e = new MyEntry(key,value);
//运用hashcode作为key的唯一标识进行取模(也就是hash函数的计算)
int hash = key.hashCode();
//取绝对值
hash = hash<0?-hash:hash;
//hash函数
int a = hash%arr.length;
//链表存储,如果不发生冲突直接新建链表进行存储
if(arr[a]==null){
LinkedList list = new LinkedList();
arr[a] = list;
list.add(e);
}else{
//发生冲突add入链表中
LinkedList list = arr[a];
for(int i=0;i<list.size();i++){
//发生重复进行替换
MyEntry e2 = (MyEntry) list.get(i);
if(e2.key.equals(key)){
e2.value = value; //键值重复直接覆盖!
return;
}
}
arr[a].add(e);
}
}
//获取元素
public Object get(Object key){
int a = key.hashCode()%arr.length;
if(arr[a]!=null){
LinkedList list = arr[a];
for(int i=0;i<list.size();i++){
MyEntry e = (MyEntry) list.get(i);
if(e.key.equals(key)){
return e.value;
}
}
}
return null;
}
这样就实现了我们自己的Map结构。
Set容器
Set容器是Collection接口的子接口,它与数学中的“集合”的概念相对应,容器中的元素是无序并不可重复的。Java中提供的Set容器类有HashSet和TreeSet等。
HashSet的使用
运用举例:
public static void main(String[] args) {
// 定义一个Map的容器对象
Sap set = new HashSet();
set.add("jack");
set.add("rose");
set.add(new String("rose"));
System.out.println(set.size);//输出2,重复元素会被覆盖。
System.out.println(set.contains("rose"));//输出true,"rose"存在
}
有了Map和List的基础,理解Set并不难,这里我们也对Set做一下简单的实现。
实现
自己实现Set的关键就在于Set的不可重复性,根据之前的讲解很快就能想到Map的键,所以在底层可以采用Map作为Set数据的存放容器,以我们要存入Set的数据为K,以一个常量为value,这样就实现了一个简单的HashSet,代码如下:
public class MyHashSet {
HashMap map;
//定义存放在value处的常量
private static final Object PRESENT = new Object();
public MyHashSet(){
map = new HashMap();
}
public int size(){
return map.size();
}
public void add(Object o){
map.put(o, PRESENT); //set的不可重复就是利用了map里面键对象的不可重复!
}
}
其他方法的实现与List和Map大致相似,大家有兴趣可以自行实现。
Iterator迭代器
容器遍历在工作中非常常用,对于List这样的有序可重复容器,遍历方法和数组一样很简单,但对于无序的Set和Map无序容器就需要考虑另外的方法,Collection接口为我们提供了一个iterator方法,它会返回一个iterator对象,这个对象可以帮助我们完成对容器的遍历。
Iterator接口定义了如下的方法:
boolean hasNext(); //判断是否有元素没有被遍历
Object next(); //返回游标当前位置的元素,并将游标移动到下一个位置
void remove(); //在执行完next之后删除游标左面的元素,一次next操作后该操作只能执行一次
public static void main(String[] args) {
// 定义一个Map的容器对象
Sap set = new HashSet();
set.add("jack");
set.add("rose");
set.add("tom");
set.add("john");
for(Iterator iter; Iter.hasNext();){
//或者Iterator iter = set.iterator();
//while(iter.hasNext()){
String str = (String) iter.next();
System.out.println(str);
}
}
Iterator同样可以运用于Map中,使用前需要先将Map转化为set:
public static void main(String[] args) {
Map<Integer, String> map=new HashMap<Integer, String>();
map.put(1, "s1");
map.put(10, "s3");
map.put(3, "s2");
//利用map中的内部接口转为set集合
Set entry = map.entrySet();
//set集合有Iterator接口方法
Iterator itera = entry.iterator();
//利用Iterator接口迭代输出,找出每一个Map.entry接口对象
while(itera.hasNext()){
Entry me = itera.next();
//输出key和value
System.out.println(me.getKey()+"="+me.getValue());
}
}
对于Iterator类这里就不作实现了,附上AbstractList的内部类Itr的实现以便大家阅读理解。
private class Itr implements Iterator<E> {
//游标,用来标记当前元素
int cursor = 0;
//上一个元素的下标,如果不执行remove方法始终比cursor 小1。执行remove方法后设置为-1
int lastRet = -1;
//允许修改的次数,违规操作会抛异常
int expectedModCount = modCount;
//检查是否还有下一个元素,用于判断遍历是否完成
public boolean hasNext() {
return cursor != size();
}
//cursor加1,并且返回当前下标为cursor的元素
public E next() {
checkForComodification();
try {
int i = cursor;
E next = get(i);
lastRet = i;
cursor = i + 1;
return next;
} catch (IndexOutOfBoundsException e) {
checkForComodification();
throw new NoSuchElementException();
}
}
//移除下标为cursor的元素
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
AbstractList.this.remove(lastRet);
if (lastRet < cursor)
cursor--;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException e) {
throw new ConcurrentModificationException();
}
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
上一篇:菜鸟学习笔记:Java提升篇1(容器上篇——List)
下一篇:菜鸟学习笔记:Java提升篇3(容器3——泛型、排序)