java零基础入门-高级特性篇(四) HashSet 和 Collections
本章继续讲集合,先来看看Set集合。Set集合的特点,1:无序,2:无重复。上一章讲了HashMap,最后提到HashSet的底层实现其实就是HashMap。那么为什么用HashMap就可以实现无序和不重复,下面看看具体如何使用HashMap实现HashSet。
hashset和hashmap的区别
hashset底层用hashmap实现,那为什么不直接用hashmap就完了,非要整个hashset出来?
如果有这个问题,可以回头看看前面讲的集合框架的设计。设计hashset是用来保存那种不需要使用下标操作元素,并且不能重复的集合。set集合的元素和List集合的元素一样,都是一个对象。而hashmap的元素是key-value键值对,因为数据存储类型不同,所以需要将set和map区分开来。看一下图,帮助回忆一下。
hashmap如何实现hashset
Set集合最重要的一个方法就是add(E e),如何往一个Set集合添加元素,了解了添加元素的原理,查找元素理解起来就简单许多。hashset的add方法,用的就是是hashmap的put方法。下面是源代码:
map.put(e,PRESENT) == null
这里需要重点理解的是,一个对象如何存入一个key-value。从上面这句代码中,可以发现,在往set集合添加元素的时候,这个元素被用来当做map的key,而value是一个常量。
为什么直接将对象作为key呢?因为hashmap使用哈希算法对key进行计算,计算后的结果就是底层数组的位置,所以当使用hashset的时候,需要对放进set的对象进行哈希计算,至于value,hashset不关心。
hashmap的key的特性就是不会重复,后添加相同的数据会将前一个数据覆盖掉。正好满足了set集合不重复的特性,所以直接用hashmap即可以满足hashset集合的要求。
其实这里容易绕晕的是几个底层实现的结构,这里用一个图来说明一下。HashSet利用HashMap的Key的特性来实现,而HashMap是利用数组和链表的特性来实现,这样应该明白这几个结构之间的关系了吧。
这次hashset真的讲完了。
Collections工具
使用集合存放数据的时候,会有很多情况要对集合进行操作。特别是List集合,因为List集合的有序性,会需要按照特定的顺序操作集合,而java也专门提供了Collections工具来对集合进行操作。下面来看看几个例子
List list = new ArrayList();
list.add("one");//此处省略 ,一共添加五个元素one,two,three,fore,five
Collection.reverse(list);//反转集合元素的顺序
Collection.shuffle(list);//随机顺序,多次随机结果可能不一样
Collection.sort(list);//升序排序,先数字后字母,数字0-9,字母A-Z a-z的顺序,逐位比较
Collection.swap(list,0,3);//交换第一个和第四个元素位置
看一个swap的源码实现
Object tmp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
嗯,完了,是不是有一种这玩意我都会写,干嘛要用这个的感觉?其实确实可以自己写,但是一般提供工具给别人用,肯定要提供全套,再就是合理的使用工具会减少不必要的代码,提升代码的可读性。
Collection 和 Collections
这两个长得很像,但是作用差别很大,初学者切勿将两者概念混淆。Collection是集合体系中的上层接口,而Collections是操作集合的工具。何谓工具?还记不记得我们讲的静态方法?不记得的快去复习类和对象的文章。
Collections作为一个工具类,里面提供的方法都是静态方法,所以在上面的例子中,都是直接使用类Collections来调用方法,Collections提供了大量的静态方法来操作集合,有没有加深对静态成员这个概念的理解?
用Collections工具类创建线程安全的集合
上次讲vector的时候,说了他是线程安全的集合,而List是线程不安全的。但是可以通过一些方法让List变成线程安全的,所以vector目前已经没有使用的必要了。那么如何让List变成线程安全的集合呢?答案就是使用Collections工具可以将List变为线程安全。
Collections有一系列的synchronized方法来使集合变为线程安全的,一系列是指不仅仅可以将List变为线程安全的,Set,Map也有方法变成线程安全的。
List list = Collections.synchronizedList(new ArrayList());
Set set = Collections.synchronizedSet(new HashSet());
Map map = Collections.synchronizedMap(new HashMap());
使用以上三个工具方法就可以将普通集合变为线程安全的集合。这些方法有什么魔法么,为什么外面包一层就线程安全了?下面来简单介绍一下多线程以及使用多线程会遇到的问题。
多线程是什么?
多线程就是并行处理问题。比如去银行取钱,如果只有一个取款机队伍就会排很长,但是如果有多个取款机同时办理业务,速度就会快很多。这就是多线程的思路,多个线程(取款机)处理一个问题(取钱)。
为什么多线程有安全问题?
假设现在有2个人要取款,如果2个人同时操作一个取款机会发生什么?第一个人密码输了3位数,第二个人跑来按3位数,第一个人删了准备重新输,又被第二个人按了3下,这样下去两个人都别想取钱。多个线程抢同一个资源就会产生线程安全问题,实际开发中遇到的线程安全问题会比这种情况还要复杂。
怎么解决?
新款取款机,带门带锁的!一旦一个人进去了,先锁门,然后就可以放心大胆的取钱了,这时候没有人会来跟你抢着取钱了。代码里面也是可以上锁的,所以线程安全的集合都是带有锁机制的。
高效并发容器
这里是补充知识,了解即可。其实上面的这几个方法确实可以将普通集合转为线程安全的集合,但是实现很粗糙,导致效率不是很高。所以就有了专门为并发情况设计的更加高效的并发容器,比如CopyOnWriteArrayList,CopyOnWriteArraySet,ConcurrentHashMap。
上锁也是有很多种上锁的方法,继续取钱的例子。
最极端粗暴的上锁方式就是,银行每次只准进一个人,这样绝对不会有任何问题,绝对安全,银行所有保安都盯着你,你还能玩出花来?但是这样做效率极端底下。比如老版本的vector和hashtable就是用类似这种粗暴的方法来上锁。
再来看稍微先进点的上锁方式。就是银行可以进很多人,但是办理不同业务的分开来,取钱的一个队,存钱的一个队,办理财换密码再来一个队,每个队同时只能一个人办,办的人进小房间,上锁。这种上锁的粒度比上面那种要小,效率要快很多。
最后就是最先进的锁了。也就是类似并发容器这种,升级版的取款机不仅可以取钱还可以存钱,还能办其他业务~也就是说不管办什么业务,找个人最少的队排着就行了,所有机器可以同时办相同或者不同的业务。这种锁的粒度最小,只对操作对象的数据进行上锁,效率最高。