我们为什么要用容器呢?
我们先来谈一谈同为存储数据的另一种方式,数组,它所有的局限性:
1.数组在创建的时候长度已经确定了,如果不够用要再建一个新的,更大的,把原来的数据存进去。
(在已知数据长度的情况下用数组存,效率并不会差)
2.集合在定义的时候可以设置泛型(要存储的数据类型,不能使基本类型),也可以不设置,默认为Object。而数组在定义时,必须定义类型和长度,不够灵活。
(如果要使用基本数据类型,只能用数组存储数据)
补充一下Collection相关的Collections:
Collections(类)是 容器Collection(接口)的一个工具类,里面封装了很多静态方法操作容器的实现类,可以实现对集合的排序,查找,输出成数组,保证多线程下的同步等功能。
Arrays(类)同样是对数组的一个工具类,可以实现对数组的排序查找和打印等功能。
常用的Java容器
容器分为两大类:Collection(单列集合)和Map(键值对的双列集合)
Collection下分两个容器,Set、List和Queue。
1.Set(集合)
Set这种数据结构源自于数学中集合的定义,数学中集合的定义是:由一个或多个确定的元素构成的整体叫做集合。其包含三个特点:1.确定性(集合中的元素必须是确定不变的);2.互异性(集合中的元素各不相同);3.无序性。当然,Java也实现了一种有序的Set,叫做TreeSet(通过红黑树(一种平衡查找树)实现有序,千万不要去看红黑树怎么实现的,太TM难了!!!)。
问题:Set如何实现的放入元素不重复?
通过hashCode方法(这个方法继承自Object类,可根据元素得特征进行重写,返回一个int值)的返回值进行比较,如果放入的元素返回的哈希值和Set集合中有一样的,再使用equals进行详细比对,确保数据无重复。
问题:那为什么不用equals方法直接比较,还另外使用Hashcode方法?
因为重写的equals方法对两个参数比较的很全面,工作运算大,在存储大量数据的集合中如果一个个进行比对很影响性能,所以使用hashCode(),它只是生成一个值,虽然用它比较并不准确,但是可以省去运行大量的equals方法的工作量,在hashCode不能准确判断时,再调用equals方法。
也就是说: 生成的hashCode存在重复现象,如果两个对象hashCode值一样,equals对比结果可能为false。
如果要把我们自己写的类,放入Set中,要重写Object中的equals和hashCode方法,因为hashCode默认返回的值是根据内存地址的处理结果。如果我们new了两个实例,放的是一样的数据,存入Set中,结果都存进去了,这肯定不是我们使用Set的初衷。同样的equals方法也需要重写。
Set的分类:
1.HashSet:实现Set接口,内部是由哈希表(实际上是new了一个HashMap实例)支持。
a.不允许有重复元素
b.不保证集合元素的顺序
c.允许有null,但只能有一个
2.TreeSet:内部由红黑树实现,这是一种平衡查找树,已经排好顺序,左小右大。
但如果要让存入的元素可以排序,一定要让元素具有可比性。需要让元素的类实现Comparable接口,实现其compareTo方法,进行比较。按照自定义的规则返回,把值大的情况返回正数,小的返回负数,相等返回0,可实现递增排序。反之可以变成递减的排序。
3.LinkedHashSet:顾名思义,是由链表构成的链式连接,保证了插入时的顺序。
2.List列表
List是一种数据项以线性排列的方式进行存储的数据结构。
问题:为什么是有序的,并且可重复呢?
ArrayList本质上是一个数组,存储内容时在内存中开辟一块连续的内存,然后将空间地址和索引对应。 而LinkedList是一个双向链表,存储数据时是相邻的元素,把之间的地址进行捆绑。由于元素存储时互不干扰,没有依赖关系,所以不像Set集合一样限制非重复元素。
List分类:
1.LinkedList:由双向链表实现,特点:增删快,查询慢。
2.ArrayList:底层是一个数组,特点:查询快,增删慢。
ArrayList和数组:
如果容量确定,需要存储基本数据类型时,数组更为高效。反之推荐ArrayList。
3.Vector:是线程安全的ArrayList,效率不如ArrayList。
4.Stack:是继承于Vector的一个后进先出的堆栈,Stack提供5个方法扩充Vector。push入栈和pop出栈,peek得到栈顶元素,empty检测栈是否为空,search检测一个元素在栈的位置。
3.Queue队列
和List的中的stack栈 是先进后出不同,Queue是一个先进先出的数据结构,所以叫做队列Queue。Deque是一个双端队列接口,继承自Queue接口,所以它可以从两端进行队列的操作,可以实现栈stack的功能。
基本的元素操作:(由于队列是先进先出,所以插入元素放在队尾,删除队首元素)
1.插入元素:add(e),offer(e);
2.删除元素:remove(),poll();
3.获取队列列首元素:element(),peek()
add(e),remove(),element()三个方法继承自Collection接口,当操作失败时会抛出异常。
offer(e),poll(),peek()则继承自Queue接口,对元素的操作出错起到很好的包容作用,操作失败返回false或者null。
1.阻塞队列:实现阻塞接口BlockingQueue,实现了两个阻塞方法take,put。这类队列阻塞情况分为两种:
a.队列没元素了,但是一个线程还想取数据,只能等到队列里面存进来数据,等待的过程就是阻塞(take方法可以实现);
b.队列元素满了,但是一个线程还想存数据,只能等到队列里面有空的空间,等待的过程还是阻塞(put方法可以实现)。
这一个过程就像是生产者消费者模型,生产者是往队列里添加元素的线程,消费者是从队列里拿元素的线程,而元素看作是商品。
阻塞队列实现类:
a.ArrayBlockingQueue:一个由数组支持的有界队列。
b.LinkedBlockingQueue:一个由链接结点支持的可选有界队列。
c.PriorityBlockingQueue:一个由优先级堆支持的无界优先级队列。
d.DelayQueue:一个由优先级堆支持、基于时间的调度队列。
e.一个利用BlockingQueue接口的简单聚集(rendezvous)机制。
2.非阻塞队列:
非阻塞队列实现类:
a.LinkedList:实现了Deque双端队列接口,既可以当做队列,还具有栈的功能。
b.PriorityQueue:其中的元素会按照自然排序(元素Comparable实现的逻辑进行排序)或者定制排序(集合实现的Comparator决定排序逻辑),而不是按照队列先进先出的定义进行排序(违反了队列的性质)。
c.ArrayDeque是Deque的一个的实现类,ArrayDeque是基于动态数组的集合,与ArrayList相似,不同的是ArrayList具有链表功能,支持随机访问get,而ArrayDeque具有队列和栈的功能。
4.Map映射
Map分类
2.HashMap
同样实现Map接口,但实现的是AbstractMap类。其内部并不是像HashTable一样是线程安全的,而且它的key允许为空,但只能有一个,value可以有多个是空的。所以通过get方法无法判断key是否存在,因为value为空和key不存在的情况返回值都是空。所以,判断一个key是否存在可以使用containsKey方法。
HashTable和HashMap的区别:
1.public class Hashtable extends Dictionary implements Map
public class HashMap extends AbstractMap implements Map
2.Table是线程安全的,Map不是(可以使用Collections提供的方法实现同步)。
3.Table所有的key和value都不允许为null,Map允许一个key为null,value可多个为null。
4.Table的hashCode使用的是数据对象的hashCode,而Map则是自己重新计算。
5.Table的容量的默认值时11,每次扩容后的容量为前一次的二倍加一。Map默认值是16,当前元素数和当前容量的比值达到负载因子时,Map自动扩容二倍。
3.WeakHashMap:是一种对HashMap的改进。对集合中的key进行弱引用,如果key不再被外界所引用,则会被垃圾回收器回收。
4.Properties:时HashTable的一个子类,用于放置String类型的key和set。它额外添加了两个方法,store()把Properties对象的内容以一种可读的形式存储到本地文件当中,而load()相反,使用来读取的。