集合
-------| Collection 单例集合的根接口。
----------| List 如果是实现了List接口的集合类,具备的特点: 存储的元素有序,可重复。
----------| Set 如果是实现了Set接口的集合类,具备特点: 存储的元素无序,不可重复。
----------| Queue 队列
Collection接口中的方法:
增加
add(E e) 添加成功返回true,添加 失败返回false.
addAll(Collection c) 把一个集合 的元素添加到另外一个集合中去。
删除
clear() 清空集合中的元素
remove(Object o) 指定集合中的元素删除,删除成功返回true,删除失败返回false.
removeAll(Collection c) 删除c集合中与c2的交集元素。
retainAll(Collection c) 保留c集合与c2的交集元素,其他的元素一并删除。
查看
size() 查看元素个数
判断
isEmpty() 判断集合是否为空元素
contains(Object o) 判断集合中是否存在指定的元素,判断的是内存地址
containsAll(Collection<?> c)
迭代
toArray() 把集合中的元素全部储存到一个Object的数组中返回。
iterator() 返回一个迭代器
Contains() 底层是依赖于集合元素中equals方法的,可以重写该方法。
迭代器
迭代器的作用:就是用于抓取集合中的元素。
迭代器(Iterator)的方法:
hasNext() 问是否有元素可遍历。如果有元素可以遍历,返回true,否则返回false 。
(当前指针是否有指向元素,如果有返回true,没有返回false)
next() 获取元素…(获取当前指针指向的元素并返回当前元素,然后指针向下移动一个单位)
remove() 移除迭代器最后一次返回(其实就是在remove之前最后一次next的那个元素) 的元素。
List系的集合
ArrayList
ArrayList 底层是维护了一个Object数组实现 的, 特点: 查询速度快,增删慢。
什么时候使用ArrayList: 如果目前的数据是查询比较多,增删比较少的时候,那么就使用ArrayList存储这批数据。 比如 :高校的 图书馆
LinkedList
LinkedList底层是使用了双向链表数据结构实现。
什么时候使用LinkedList:更多的插入和删除元素,更少的读取数据。因为插入和删除元素不涉及重排数据,所以它要比ArrayList要快。
Vector
- Vector与ArrayList类似, 内部同样维护一个数组, Vector是线程安全的.
Vector与ArrayList的区别
- Vector是线程安全的, ArrayList不是线程安全的, 这是最主要的
- ArrayList不可以设置扩展的容量, 默认1.5倍; Vector可以设置, 默认2倍
- ArrayList无参构造函数中初始量为0; Vector的无参构造函数初始容量为10
Set系的集合
- 实现了Set接口的集合类,具备的特点: 无序,不可重复。
HashSet
- HashSet 底层是使用了哈希表来支持的,特点: 存取速度快.
HashSet的实现原理:
往HashSet添加元素的时候,HashSet会先调用元素的hashCode方法得到元素的哈希值,然后通过元素 的哈希值经过移位等运算,就可以算出该元素在哈希表中 的存储位置。
情况1: 如果算出元素存储的位置目前没有任何元素存储,那么该元素可以直接存储到该位置上。
情况2: 如果算出该元素的存储位置目前已经存在有其他的元素了,那么会调用该元素的equals方法与该位置的元素再比较一次
,如果equals(equals方法比较的是内存地址)返回的是true,那么该元素与这个位置上的元素就视为重复元素,不允许添加,如果equals方法返回的是false,那么该元素允许添加。
哈希表的其中一个特点:桶式结构,一个位置能放多个元素
TreeSet
如果元素具备自然顺序的特性,那么就按照元素自然顺序的特性进行排序存储。
- TreeSet的存储原理:底层是使用红黑树数(二叉树)据结构实现 存储规则:左小右大
Queue队列
Queue的常用实现类
ArrayBlockingQueue
底层使用数组实现的,默认不是一个公平队列(根据阻塞的先后顺序来访问队列),不过可以在初始化的时候设置为公平队列,是一个有界队列,可以设置队列大小,例如
ArrayBlockingQueue<Integer> Queue = new ArrayBlockingQueue<Integer>(3,true)
下面这个小例子就是阻塞队列的一个很好的应用,初始化一个长度为3的队列,在main方法里面启动一个子线程,子线程里面调用一个入队操作的方法,间隔时间是2秒一次,而又调用了一个arrayBlockingQueue()方法直接填满了队列,然后循环出队,时间间隔3秒。会发现一次打印1、2、3之后就开始打印后面入队的随机数,因为出队的间隔时间是3秒,而入队的间隔时间是2秒,而一开始塞满了队列,但是做入队操作也没有报错,这就是阻塞队列的特性,如果满了就一直阻塞住,整个过程就像排队一样,依次入队,依次出队。
static ArrayBlockingQueue<Integer> arrayBlockingQueue = new ArrayBlockingQueue<Integer>(3);
public static void main(String[] args) throws Exception {
System.out.println(arrayBlockingQueue.size());
new Thread(new Runnable() {
@Override
public void run() {
try {
addQueue();
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
arrayBlockingQueue();
}
public static void arrayBlockingQueue() throws Exception {
arrayBlockingQueue.put(1);
arrayBlockingQueue.put(2);
arrayBlockingQueue.put(3);
while (true) {
TimeUnit.SECONDS.sleep(3);
System.out.println(arrayBlockingQueue.size() + "-" + arrayBlockingQueue.take());
}
}
public static void addQueue() throws Exception {
while (true) {
TimeUnit.SECONDS.sleep(2);
arrayBlockingQueue.put(new Random().nextInt(100));
}
}
LinkedBlockingQueue
这个阻塞队列是基于链表实现的,也是一个有界队列,不过有默认大小,默认大小和最大长度都是Integer.MAX_VALUE。和ArrayBlockingQueue很相似,只不过底层控制并发阻塞的原理稍稍不同
PriorityBlockingQueue
这个队列也是基于数组实现,但是数据结构是一颗完全二叉树,存在数组中,值得注意的是这个队列是无界队列,虽然初始化的时候可以设置初始值,但是一旦队列的长度超过的时候就会自动扩容,最重要的是这个队列是一个优先级队列,所以并不是先进先出(FIFO)的顺序,而是按照元素的优先级来进行出队,所以初始化的时候可以设置一个比较器,确保存入的元素是可以比较的,这样就可以根据元素的优先级进行出队了。
public static void priorityBlockingQueue() throws Exception{
PriorityBlockingQueue<Integer> queue = new PriorityBlockingQueue<Integer>(5,new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2-o1;
}
});
queue.add(10);
queue.add(2);
queue.add(5);
while(true){
System.out.println(queue.take());
}
}
DelayQueue
这个队列也是一个优先级队列,但是它的优先级是以时间为准,放入队列中的元素一定要是Delayed类型,里面有两个方法,分别是compareTo()和getDelay()方法,第一个是设置比较规则,第二个则是判断时间是否已经到达,只有按照getDelay()方法中的代码判断时间已经到达了,这个时候获取队列尾部的元素才可以成功获取到,否则即使有元素也获取不到,也是一个无界队列,底层也是基于数据实现。
下面这个小例子模仿了订单超时的操作,超时时间设置的各有不同,可以发现,根据对比规则先把最早超时的那个订单出队了,依次出队,直到最晚超时的那个订单。
public static void delayQueue() throws Exception{
DelayQueue<TestDelayed> queue = new DelayQueue<TestDelayed>();
long time = System.currentTimeMillis()+10000;
queue.put(new TestDelayed(1,time + 500));
queue.put(new TestDelayed(2,time + 300));
queue.put(new TestDelayed(3,time + 100));
queue.put(new TestDelayed(4,time + 200));
queue.put(new TestDelayed(5,time + 400));
while (true){
TestDelayed delayed = (TestDelayed)queue.take();
System.out.println(delayed.orderNumber);
}
}
// 自定义Delayed类
class TestDelayed implements Delayed{
// 订单号
int orderNumber;
// 超时时间
long expire;
public TestDelayed(int orderNumber, long expire) {
this.orderNumber = orderNumber;
this.expire = expire;
}
// 超时时间减去当前时间,如果小于0就表示时间到了
@Override
public long getDelay(TimeUnit unit) {
return expire-System.currentTimeMillis();
}
// 设置对比规则
@Override
public int compareTo(Delayed o) {
long t1 = this.getDelay(TimeUnit.MILLISECONDS);
long t2 = o.getDelay(TimeUnit.MILLISECONDS);
return Long.compare(t1,t2);
}
}
SynchronousQueue
这个队列非常有意思,它自身并没有任何容量,所以每次入队操作之后就必须进行一次出队操作,否则就会阻塞,底层是使用CAS来保证并发问题,适合调度派发任务的场景,生产者源源不断的做入队操作,多个消费者则进行出队操作,它也支持公平队列的设置,在初始化的时候可以进行设置,好处就是在一些调度任务频繁但是又不像使用无界队列就可以使用SynchronousQueue,而且性能更加好。
public static void synchronousQueue() throws Exception{
SynchronousQueue<Integer> queue = new SynchronousQueue<Integer>();
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
try {
TimeUnit.SECONDS.sleep(1);
queue.put(new Random().nextInt(100));
} catch (Exception e) {
e.printStackTrace();
}
}
}
}).start();
while (true){
TimeUnit.SECONDS.sleep(1);
System.out.println(queue.take());
}
}
LinkedTransferQueue
这是一个很特殊的无界阻塞队列,底层基于链表实现,在JDK7的时候新增的,相比于上面的阻塞队列,它使用CAS和自旋这些无锁算法来保证并发安全问题,所以性能更好,默认就是一个公平队列,而且它更加灵活,可以控制是阻塞线程还是直接把元素传递给等待的消费的线程。
而它的新特性都是因为新实现的一个接口,TransferQueue接口,所以新特性都是在这个接口里面的方法来实现。
// 尝试直接把元素传递给等待的消费者,成功返回true,失败返回false,非阻塞
boolean tryTransfer(E e);
// 和上面的方法类似,但是会阻塞
void transfer(E e) throws InterruptedException;
// 和第一个方法一样,增加了超时等待时间
boolean tryTransfer(E e, long timeout, TimeUnit unit)
throws InterruptedException;
// 判断是否有等待的消费者,即调用了take()方法的线程
boolean hasWaitingConsumer();
// 获取等待的消费者的数量
int getWaitingConsumerCount();
已经有很专业权威的性能测试对比过LinkedTransferQueue和SynchronousQueue的性能了,分别是3倍(非公平模式)和14倍(公平模式),所以底层的原理和实现也是相当复杂。这里只做简单介绍。
public static void linkedTransferQueue() throws Exception{
LinkedTransferQueue<Integer> queue = new LinkedTransferQueue<Integer>();
new Thread(new Runnable() {
@Override
public void run() {
try {
while (true) {
System.out.println("thread");
TimeUnit.SECONDS.sleep(1);
// queue.transfer(3);
// System.out.println(queue.tryTransfer(3));
System.out.println(queue.hasWaitingConsumer());
System.out.println(queue.getWaitingConsumerCount());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
// 开启5个子线程获取元素
for(int i=0;i<5;i++){
new Thread(new Runnable() {
@Override
public void run() {
try {
queue.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
非阻塞队列
与阻塞队列对应的就是非阻塞队列,就是出队和入队如果执行失败也不会阻塞队列,会直接抛错或者返回空值。
ConcurrentLinkedQueue
底层基于链表的无界队列,使用CAS来保证并发安全,因为没有使用锁,所以size()方法可能不准确,因为可能同时做了入队和出队操作。
双端队列
顾名思义就是不一定是先进先出(FIFO),也可能是先进后出(FILO),出队操作在首尾都可以进行。
LinkedBlockingDeque
这个队列就是一个双端阻塞队列,底层是由双向链表实现的,可以指定队列大小,默认长度是Integer.MAX_VALUE。
Map系集合
双列集合:
-------------| Map 如果是实现了Map接口的集合类,具备的特点: 存储的数据都是以键值对的形式存在的,键不可重复,值可以重复。
----------------| HashMap
----------------| TreeMap
----------------| Hashtable
HashMap
----------------| HashMap 底层也是基于哈希表实现 的。
HashMap的存储原理:(详解00:09:45)
往HashMap添加元素的时候,首先会调用键的hashCode方法得到元素 的哈希码值,然后经过运算就可以算出该元素在哈希表中的存储位置。
情况1: 如果算出的位置目前没有任何元素存储,那么该元素可以直接添加到哈希表中。
情况2:如果算出 的位置目前已经存在其他的元素,那么还会调用该元素的equals方法与这个位置上的元素进行比较,如果equals方法返回 的是false,那么该元素允许被存储,如果equals方法返回的是true,那么该元素被视为重复元素,不允存储。
- HashMap可以插入null的key或value,插入的时候,检查是否已经存在相同的key,如果不存在,则直接插入,如果存在,则用新的value替换旧的value。
如果出现了相同键,那么后添加的数据的值会取代之前的值。
TreeMap
----------------| TreeMap TreeMap也是基于红黑树(二叉树)数据结构实现 的, 特点:会对元素的键进行排序存储。
TreeMap 要注意的事项:
- 往TreeMap添加元素的时候,如果元素的键具备自然顺序,那么就会按照键的自然顺序特性进行排序存储。
- 往TreeMap添加元素的时候,如果元素的键不具备自然顺序特性, 那么键所属的类必须要实现Comparable接口,把键的比较规则定义在CompareTo方法上。
- 往TreeMap添加元素的时候,如果元素的键不具备自然顺序特性,而且键所属的类也没有实现Comparable接口,那么就必须在创建TreeMap对象的时候传入比较器。
- HashMap,TreeMap 未进行同步考虑,是线程不安全的。
Hashtable
----------------| Hashtable(了解) 底层也是依赖了哈希表实现的,也就是实现方式与HashMap是一致,但是HashTable是线程安全的,操作效率低。
WeakHashMap
什么是WeakHashMap?
从名字可以得知主要和Map有关,不过还有一个Weak,我们就更能自然而然的想到这里面还牵扯到一种弱引用结构,因此想要彻底搞懂,我们还需要知道四种引用。如果你已经知道了,可以跳过。
1、四种引用
在jvm中,一个对象如果不再被使用就会被当做垃圾给回收掉,判断一个对象是否是垃圾,通常有两种方法:引用计数法和可达性分析法。不管是哪一种方法判断一个对象是否是垃圾的条件总是一个对象的引用是都没有了。
JDK.1.2 之后,Java 对引用的概念进行了扩充,将引用分为了:强引用、软引用、弱引用、虚引用4 种。而我们的WeakHashMap就是基于弱引用。
(1)强引用
如果一个对象具有强引用,它就不会被垃圾回收器回收。即使当前内存空间不足,JVM也不会回收它,而是抛出 OutOfMemoryError 错误,使程序异常终止。比如String str = "hello"这时候str就是一个强引用。
(2)软引用
内存足够的时候,软引用对象不会被回收,只有在内存不足时,系统则会回收软引用对象,如果回收了软引用对象之后仍然没有足够的内存,才会抛出内存溢出异常。
(3)弱引用
如果一个对象具有弱引用,在垃圾回收时候,一旦发现弱引用对象,无论当前内存空间是否充足,都会将弱引用回收。
(4)虚引用
如果一个对象具有虚引用,就相当于没有引用,在任何时候都有可能被回收。使用虚引用的目的就是为了得知对象被GC的时机,所以可以利用虚引用来进行销毁前的一些操作,比如说资源释放等。
我们的WeakHashMap是基于弱引用的,也就是说只要垃圾回收机制一开启,就直接开始了扫荡,看见了就清除。