集合类型以及特点

集合分类

List可以有重复元素,按对象进入的顺序保存(有序),允许多个Null元素对象,取元素时可以用Iterator取出所有元素逐一遍历,或者用get(int index)获取指定下标的元素。

Set只存放一个对象不能存放重复元素(用对象的equals()方法来区分元素是否重复),而且无序,只允许一个Null元素对象,取元素时只能用Iterator取出所有元素再逐一遍历。

Map代表具有映射关系(key-value)的集合,其所有的key是一个Set集合,即key无序且不能重复。

List和Set继承自Collection接口,map不是。

一、Map 

1.1 HashMap:

        允许一条键为Null、多条值为Null、不支持线程同步。由数组+链表组成的,基于哈希表的Map实现。数组是HashMap的主体,存储一个个Entry<key,value>对象。链表则是主要为了解决哈希冲突而存在的。

  1. LinkedHashMap:输出顺序和输入顺序相同,底层由HashMap双向链表实现,解决了HashMap不能随时保持遍历顺序和插入顺序一致的问题。与HashMap相比,LinkedHashMap增加了两个属性用于保证迭代顺序,分别是 双向链表头结点header 和 标志位accessOrder (值为true时,表示按照访问顺序迭代;值为false时,表示按照插入顺序迭代)。
  2. ConcurrentHashMap:支持线程同步,原理是引入"分段锁"概念,把Map分成了N个Segment,具体可以理解为把一个大的Map拆分成N个小的HashTable,根据key.hashCode()决定把key放到哪个HashTable中。

HashMap的put的过程

  1. 首次扩容:先判断数组是否为空,若数组为空则进行第一次扩容(resize);

  2. 计算索引:通过hash算法,计算键值对在数组中的索引。具体来说是:根据Key的哈希值与数组长度-1进行与运算得出数组下标。

  3. 插入数据:

  • 如果数组下标位置元素为空,则直接将Entry<key,value>对象插入数据;
  • 如果数组下标位置元素非空,则会先判断当前位置上的Node的类型,看是红黑树Node,还是链表Node
    • 如果是红黑树Node,则将key和value封装为一个红黑树Node并添加到红黑树底部,在这个遍历过程中会判断红黑树中是否存在当前key,如果存在则覆盖value。
    • 如果是链表Node,则将key和value封装为一个链表Node并通过尾插法插入到链表的最后位置去,在遍历链表的过程中会判断是否存在当前key,如果存在则覆盖value。
    • 插入到链表后,会看当前链表的节点个数,如果超过了8,那么则会将该链表转成红黑树
  • 若链表长度达到8,则将链表转换成红黑树,并将数据插入树中;

        4.再次扩容:如果数组中元素个数(size)超过threshold,则再次进行扩容操作。

HashMap为何会产生循环链表?

        HashMap在多线程情况下不安全,当并发执行put操作时,可能会导致形成循环链表,从而引起死循环。                

       在多线程的情况下,当重新调整HashMap大小的时候,就会存在条件竞争,因为如果两个线程都发现HashMap需要重新调整大小了,它们会同时试着调整大小。在调整大小的过程中,存储在链表中的元素的次序会反过来,因为移动到新的bucket位置的时候,HashMap并不会将元素放在链表的尾部,而是放在头部,这是为了避免尾部遍历。如果条件竞争发生了,那么就会产生死循环了。

HashMap如何实现线程安全?

  1. 直接使用Hashtable类;

  2. 直接使用ConcurrentHashMap;

  3. 使用Collections将HashMap包装成线程安全的Map。

1.2TreeMap:

      TreeMap基于红黑树(Red-Black tree)实现。  默认键值按升序排序,可以自定义顺序遍历键。TreeMap的基本操作containsKey、get、put、remove方法,它的时间复杂度是log(N)。

1.3 HashTable:

        不允许键值为Null,支持线程同步,底层使用synchronized关键字来实现线程安全,对整张hash表进行锁定虽然安全但是造成了浪费。

        保证线程安全时尽量使用ConcurrentHashMap,它的性能好于Hashtable。因为ConcurrentHashMap在put时采用分段锁/CAS的加锁机制,而不是像Hashtable无论是put还是get都做同步处理。

1.4 HashMap和HashTable的区别:

Hashtable:

  • Hashtable是一个散列表,它存储的内容是键值对(key-value)映射。
  • Hashtable的函数都是同步的,这意味着它是线程安全的。它的key、value都不可以为null。
  • HashTable直接使用对象的hashCode。

HashMap:

  • 数组+链表组成的,基于哈希表的Map实现。
    • 数组是HashMap的主体,存储一个个Entry<key,value>对象。
    • 链表则是主要为了解决哈希冲突而存在的。
  • 不是线程安全的,HashMap可以接受为null的键(key)和值(value)。
  • HashMap重新计算hash值

1.5 Map的常用方法:

        1.map.getOrDefault(Object key, V defaultValue):此方法Map中会存储一一对应的key和value。
如果 在Map中存在key,则返回key所对应的的value。
如果 在Map中不存在key,则返回默认值。

String s="abcd";
        Map<Character,Integer> map = new HashMap<Character, Integer>();
        for (int i=0;i<s.length();i++) {
            char at = s.charAt(i);
            /**getOrDefault
             * 如果 在Map中存在key,则返回key所对应的的value。
             * 如果 在Map中不存在key,则返回默认值。
             */
            int num = map.getOrDefault(at,0);//获取此at字符在map中的次数
            map.put(at,num+1);//将at字符存入map时,num值+1可表示存入at字符的次数
        }

 二、List集合

       ArrayList 和Vector都是使用数组方式存储数据,而LinkedList使用双向链表实现存储,按序号索引数据需要进行前向或后向遍历,但是插入数据时只需要记录本项的前后项即可,所以插入速度较快。

2.1 ArrayList和LinkedList区别

        ArrayList和LinkedList都实现了List接口,ArrayList使用数组方式存储数据。它可以以O(1)时间复杂度对元素进行随机访问。而LinkedList是使用双向链表实现存储,每一个元素都和它的前一个和后一个元素链接在一起,在这种情况下,查找某个元素的时间复杂度是O(n)。
       ArrayList采用数组Array实现的,查找效率比LinkedList高。LinkedList采用双向链表实现的,插入和删除的效率比ArrayList要高,因为当元素被添加到集合任意位置的时候,不需要像数组那样重新计算大小或者是更新索引。
       LinkedList比ArrayList更占内存,因为LinkedList为每一个节点存储了两个引用,一个指向前一个元素,一个指向下一个元素。

2.2 Array数组、List列表和ArrayList的区别

需要对元素进行频繁的移动或删除,或者处理的是超大量的数据,可以使用LinkedList,而Array和ArrayList效率很低。

    2.2.1、Array数组:
        1.Array可以包含基本类型和对象类型
        2.Array数组在内存中是连续存储的,插入数据效率低,但是索引、赋值与修改速度快。
        3.声明数组的时候,必须指明数组的长度,数组的长度过长,会造成内存浪费,数组声明长度过短,可能造成数据溢出

string[] s=new string[3];//声明
s[0]="a"; s[1]="b"; s[2]="c";//赋值
s[1]="b1";//修改

    2.2.2、ArrayList列表:
        1.ArrayList只能包含对象类型。
        2.声明ArrayList对象时并不需要指定它的长度,ArrayList大小是按照其中存储的数据来动态扩充与收缩的。
        3.ArrayList继承了List接口,提供更多方法进行数据的添加,插入和移除  addAll(),removeAll(),iterator()
        4.类型不安全:ArrayList中插入不同类型的数据是允许的。因为ArrayList会把所有插入其中的数据都当作为object对象类型来处理。这样,在我们使用ArrayList中的数据来处理问题的时候,很可能会报类型不匹配的错误,也就是说ArrayList不是类型安全的
        5.拆箱装箱性能损耗:使用时会将其中的Object对象类型拆箱为对应的原类型来处理,性能低。

    2.2.3、List泛型:
        List是一个接口。只能通过允许指定的泛型类或方法操作的 特定类型,避免了前面讲的ArrayList类型安全问题与装箱拆箱的性能问题了。它的大部分用法都与ArrayList相似,因为List类也继承了IList接口。

最关键的区别在于:

        在声明List集合时,需要为其声明List集合内数据的对象类型。
        因为List是一个接口,所以不能被构造,但可以为List创建一个引用,而ArrayList就可以被构造。 

List<int> list = new List<int>();    //正确
List list;                           //正确   list=null; 
List list=new List();                //是错误的用法
List list = new ArrayList();         //常用,正确,这句创建了一个ArrayList的对象后把上溯到了List。此时它是一个List对象了,有些ArrayList有但是List没有的属性和方法,它就不能再用了。
ArrayList list=new ArrayList();      //不常用,正确,创建一对象则保留了ArrayList的所有属性。

    2.2.4、Array, ArrayList之间的转换

        arraylist.toArray()、Arrays.asList(arr)

2.3 List 列表的常用声明,面向接口编程的好处

List list = new ArrayList() 常用,而不用 ArrayList alist = new ArrayList()呢?

       因为List有多个实现类,如 LinkedList或者Vector等等,使用List list = new ArrayList(),当需要换成其它的实现类时只要改变这一行就行了:List list = new LinkedList(); 其它使用了list地方的代码根本不需要改动。
       如果你用 ArrayList alist = new ArrayList(), 也许哪一天你需要换成其它的实现类呢?这下你有的改了,特别是如果你使用了 ArrayList特有的方法和属性。
        所以如果没有特别需求的话,最好使用List list = new LinkedList(); ,便于程序代码的重构. 这就是面向接口编程的好处。

三、Set集合

3.1HashSet

        散列集HashSet是一个用于实现Set接口的具体类,HashSet是基于HashMap实现的,默认构造函数是构建一个初始容量为16,负载因子为0.75 的HashMap。只存储一个对象,而Map存储两个对象Key和Value(仅仅key对象有序)。        

        当向HashSet中加入一个元素时,它需要判断集合中是否已经包含了这个元素,从而避免重复存储。HashSet首先会调用对象的hashCode()方法获取其哈希码,并通过哈希码确定该对象在集合中存放的位置。假设这个位置之前已经存了一个对象,则HashSet会调用equals()对两个对象进行比较。若相等则说明对象重复,此时不会保存新加的对象。若不等说明对象不重复,但是它们存储的位置发生了碰撞,此时HashSet会采用链式结构在同一位置保存多个对象,即将新加对象链接到原来对象的之后。之后,再有新添加对象也映射到这个位置时,就需要与这个位置中所有的对象进行equals()比较,若均不相等则将其链到最后一个对象之后。

3.2LinkedHashSet

        LinkedHashSet是用一个链表实现来扩展HashSet类,它支持对规则集内的元素排序。HashSet中的元素是没有被排序的,而LinkedHashSet中的元素可以按照它们插入规则集的顺序提取。

3.3TreeSet

        TreeSet是基于TreeMap实现的,所以底层也是红黑树。TreeSet扩展自AbstractSet,并实现了NavigableSet,AbstractSet扩展自AbstractCollection,树形集是一个有序的Set,其底层是一颗树,这样就能从Set里面提取一个有序序列了。在实例化TreeSet时,我们可以给TreeSet指定一个比较器Comparator来指定树形集中的元素顺序。树形集中提供了很多便捷的方法。

3.4TreeSet和HashSet的区别

        HashSet、TreeSet中的元素都是不能重复的,并且它们都是线程不安全的,区别是:

  1. HashSet中的元素可以是null,但TreeSet中的元素不能是null;

  2. HashSet不能保证元素的排列顺序,而TreeSet支持自然排序、定制排序两种排序的方式;

  3. HashSet底层是采用哈希表实现的,而TreeSet底层是采用红黑树实现的。

四、迭代器 Iterator

        Iterator提供了统一遍历操作集合元素的统一接口, Collection接口实现Iterable接口,
每个集合都通过实现Iterable接口中iterator()方法返回Iterator接口的实例, 然后对集合的元素进行迭代操作.
        有一点需要注意的是:在迭代元素的时候不能通过集合的方法删除元素, 否则会抛出ConcurrentModificationException 异常. 但是可以通过Iterator接口中的remove()方法进行删除.

4.1Iterator和ListIterator的区别是:

        Iterator可用来遍历Set和List集合,但是ListIterator只能用来遍历List。
        Iterator对集合只能是前向遍历,ListIterator既可以前向也可以后向。
        ListIterator实现了Iterator接口,并包含其他的功能,比如:增加元素,替换元素,获取前一个和后一个元素的索引,等等。

原文链接:https://blog.csdn.net/gejiangbo222/article/details/81540616

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值