Java容器:数据的集合

                我们为什么要用容器呢?

                我们先来谈一谈同为存储数据的另一种方式,数组,它所有的局限性

                    1.数组在创建的时候长度已经确定了,如果不够用要再建一个新的,更大的,把原来的数据存进去。

                    (在已知数据长度的情况下用数组存,效率并不会差)

                    2.集合在定义的时候可以设置泛型(要存储的数据类型,不能使基本类型),也可以不设置,默认为Object。而数组在定义时,必须定义类型和长度,不够灵活。

                      (如果要使用基本数据类型,只能用数组存储数据)


                补充一下Collection相关的Collections

                    Collections(类)是 容器Collection(接口)的一个工具类,里面封装了很多静态方法操作容器的实现类,可以实现对集合的排序,查找,输出成数组,保证多线程下的同步等功能。

                    Arrays(类)同样是对数组的一个工具类,可以实现对数组的排序查找和打印等功能。


                常用的Java容器

                容器分为两大类:Collection(单列集合)和Map(键值对的双列集合)

                Collection下分两个容器,SetList和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映射

                   映射也是来自数学的定义,我们可以把它想象成y=f(x)这样一个函数,x (key)根据f(x) (Map的映射规律)得到y (value),坐标轴上的x (key)和y (value)一一对应。
                         Map是一个键值对构成的双列集合,每一个键映射一个值,如果对应多个,会出现后面覆盖前面的情况。
                         load factor负载因子:是指当前元素数量和容量的比值的限制,如果达到了这个比值,Map集合的容量会自动增长。负载因子越趋于1,空间利用率越高,同时也会提高哈希冲突的概率,进行查找时的成本也会增大。负载因子默认值时0.75,是一个权衡数组空间和查询时间的妥协值。

                         (哈希冲突:是第一个对象的hashCode通过算法得到在Map的索引位置,和第二个对象的索引位置相同,这就是哈希冲突。
                          就必须讲一讲哈希码的生成算法--取模法:
                  计算过程,数组长度是5,这时有一个hashCode是6,如何选择6的存放位置呢,按照取模法,计算6%5=1,然后把6放到下标是1的位置。那么7就可以放到2的位置。如果是11的话,取模也是1,那么数组下标为1的位置就必须存储两个数了,这就叫做哈希冲突。冲突之后会根据Map内部的实现结构不同来解决冲突(HashMap在每个索引位置添加一个链表,把冲突项都添加到链表上),如果数据分布的比较广或者存储数据的数组长度(所以负载因子变大,哈希冲突概率会降低)大,哈希冲突就会少。  )   

    

                          Map分类

                          1.HashTable
                                实现Map接口,继承自Dictionary类,实现一个key-value映射的哈希表,所以其添加数据的过程和Set集合类似,都是先对hashCode进行比较,相同的话再使用equals方法判断。key和value任意一个都不可以是空的。

                          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()相反,使用来读取的。

                          







阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页