1.1.4 集合

Java集合框架深入解析

1、Map和ConcurrentHashMap的区别?

  • ConcurrentHashMap线程安全,而HashMap非线程安全
  • HashMap允许Key和Value为null,而ConcurrentHashMap不允许
  • HashMap不允许通过Iterator遍历的同时通过HashMap修改,而ConcurrentHashMap允许该行为,并且该更新对后续的遍历可见

详解(转载)https://blog.youkuaiyun.com/majiawenzzz/article/details/81286754

2、hashMap内部具体如何实现的?

常用的底层数据结构主要有数组和链表。数组存储区间连续,占用内存较多,寻址容易,插入和删除困难。链表存储区间离散,占用内存较少,寻址困难,插入和删除容易。

HashMap要实现的是哈希表的效果,尽量实现O(1)级别的增删改查。它的具体实现则是同时使用了数组和链表,可以认为最外层是一个数组,数组的每个元素是一个链表的表头。

详解(转载)https://blog.youkuaiyun.com/majiawenzzz/article/details/81286754

3、如果hashMap的key是一个自定义的类,怎么办?

如果hashMap的key是一个自定义的类,必须重写该类的hashcode()方法和equals()方法

 HashMap中的比较key是这样的,先求出key的hashcode(),比较其值是否相等,若相等再比较equals(),若相等则认为他们是相等 的。若equals()不相等则认为他们不相等。如果只重写hashcode()不重写equals()方法,当比较equals()时只是看他们是否为 同一对象(即进行内存地址的比较),所以必定要两个方法一起重写。HashMap用来判断key是否相等的方法,其实是调用了HashSet判断加入元素 是否相等。
参考https://blog.youkuaiyun.com/weixin_38780073/article/details/80728362

4、ArrayList和LinkedList的区别,如果一直在list的尾部添加元素,用哪个效率高?

LinkedList每次增加的时候,会new 一个Node对象来存新增加的元素,所以当数据量小的时候,这个时间并不明显,而ArrayList需要扩容,所以LinkedList的效率就会比较高,其中如果ArrayList出现不需要扩容的时候,那么ArrayList的效率应该是比LinkedList高的,当数据量很大的时候,new对象的时间大于扩容的时间,那么就会出现ArrayList'的效率比Linkedlist高了。
参考:https://blog.youkuaiyun.com/qq_34144916/article/details/81154528

5、HashMap底层,负载因子,为啥是2^n?

HashMap的底层存在着一个名字为table的Entry数组,在实例化HashMap的时候,会输入两个参数,一个是 int initCapacity(初始化数组大小,默认值是16),一个是float loadFactor(负载因子,默认值是0.75),首先会根据initCapacity计算出一个大于或者等于initCapacity且为2的幂的值capacity,例如initCapacity为15,那么capacity就会为16,还会算出一个临界值threshold,也就是capacity * loadFactor。

总的来说

负载因子表示一个散列表的空间的使用程度,有这样一个公式:initailCapacity*loadFactor=HashMap的容量。
所以负载因子越大则散列表的装填程度越高,也就是能容纳更多的元素,元素多了,链表大了,所以此时索引效率就会降低。
反之,负载因子越小则链表中的数据量就越稀疏,此时会对空间造成烂费,但是此时索引效率高。

HashMap的长度为什么要是2的n次方

HashMap为了存取高效,要尽量较少碰撞,就是要尽量把数据分配均匀,每个链表长度大致相同,这个实现就在把数据存到哪个链表中的算法;
这个算法实际就是取模,hash%length,计算机中直接求余效率不如位移运算,源码中做了优化hash&(length-1),
hash%length==hash&(length-1)的前提是length是2的n次方;
为什么这样能均匀分布减少碰撞呢?2的n次方实际就是1后面n个0,2的n次方-1  实际就是n个1;
例如长度为9时候,3&(9-1)=0  2&(9-1)=0 ,都在0上,碰撞了;
例如长度为8时候,3&(8-1)=3  2&(8-1)=2 ,不同位置上,不碰撞;

 

其实就是按位“与”的时候,每一位都能  &1  ,也就是和1111……1111111进行与运算

 

0000 0011     3

& 0000 1000      8

= 0000 0000      0

 

 

0000 0010     2

& 0000 1000    8

= 0000 0000      0

 

 -------------------------------------------------------------

 

0000 0011     3

& 0000 0111     7

= 0000 0011       3

 

 

0000 0010      2

& 0000 0111      7

= 0000 0010      2

转载: https://www.jianshu.com/p/9584e37fdd8e

https://www.cnblogs.com/zhimingxin/p/8609545.html

6、ConcurrentHashMap锁加在了哪些地方?

Java 7中的ConcurrentHashMap的底层数据结构仍然是数组和链表。与HashMap不同的是,ConcurrentHashMap最外层不是一个大的数组,而是一个Segment的数组。每个Segment包含一个与HashMap数据结构差不多的链表数组。

在读写某个Key时,先取该Key的哈希值。并将哈希值的高N位对Segment个数取模从而得到该Key应该属于哪个Segment,接着如同操作HashMap一样操作这个Segment。

Segment继承自ReentrantLock,所以我们可以很方便的对每一个Segment上锁。

对于读操作,获取Key所在的Segment时,需要保证可见性

对于写操作,并不要求同时获取所有Segment的锁,因为那样相当于锁住了整个Map。它会先获取该Key-Value对所在的Segment的锁,获取成功后就可以像操作一个普通的HashMap一样操作该Segment,并保证该Segment的安全性。
同时由于其它Segment的锁并未被获取,因此理论上可支持concurrencyLevel(等于Segment的个数)个线程安全的并发读写。

获取锁时,并不直接使用lock来获取,因为该方法获取锁失败时会挂起(参考可重入锁)。事实上,它使用了自旋锁,如果tryLock获取锁失败,说明锁被其它线程占用,此时通过循环再次以tryLock的方式申请锁。如果在循环过程中该Key所对应的链表头被修改,则重置retry次数。如果retry次数超过一定值,则使用lock方法申请锁。

这里使用自旋锁是因为自旋锁的效率比较高,但是它消耗CPU资源比较多,因此在自旋次数超过阈值时切换为互斥锁。

。具体实现上可以使用volatile关键字,也可使用锁。但使用锁开销太大,而使用volatile时每次写操作都会让所有CPU内缓存无效,也有一定开销。ConcurrentHashMap使用如下方法保证可见性,取得最新的Segment。

Java 8为进一步提高并发性,摒弃了分段锁的方案,而是直接使用一个大的数组。同时为了提高哈希碰撞下的寻址性能,Java 8在链表长度超过一定阈值(8)时将链表(寻址时间复杂度为O(N))转换为红黑树(寻址时间复杂度为O(long(N)))。

对于put操作,如果Key对应的数组元素为null,则通过CAS操作将其设置为当前值。如果Key对应的数组元素(也即链表表头或者树的根元素)不为null,则对该元素使用synchronized关键字申请锁,然后进行操作。如果该put操作使得当前链表长度超过一定阈值,则将该链表转换为树,从而提高寻址效率。

对于读操作,由于数组被volatile关键字修饰,因此不用担心数组的可见性问题。同时每个元素是一个Node实例(Java 7中每个元素是一个HashEntry),它的Key值和hash值都由final修饰,不可变更,无须关心它们被修改后的可见性问题。而其Value及对下一个元素的引用由volatile修饰,可见性也有保障。

对于Key对应的数组元素的可见性,由Unsafe的getObjectVolatile方法保证。

参考:https://blog.youkuaiyun.com/majiawenzzz/article/details/81286754

7、TreeMap底层,红黑树原理?

TreeMap 简介

TreeMap 是一个有序的key-value集合,它是通过红黑树实现的。
TreeMap 继承于AbstractMap,所以它是一个Map,即一个key-value集合。
TreeMap 实现了NavigableMap接口,意味着它支持一系列的导航方法。比如返回有序的key集合。
TreeMap 实现了Cloneable接口,意味着它能被克隆
TreeMap 实现了java.io.Serializable接口,意味着它支持序列化

TreeMap基于红黑树(Red-Black tree)实现。该映射根据其键的自然顺序进行排序,或者根据创建映射时提供的 Comparator 进行排序,具体取决于使用的构造方法。
TreeMap的基本操作 containsKey、get、put 和 remove 的时间复杂度是 log(n) 。
另外,TreeMap是非同步的。 它的iterator 方法返回的迭代器是fail-fastl的。

红黑树的原理

红黑树是一棵二叉搜索树,它在每个节点增加了一个存储位记录节点的颜色,可以是RED,也可以是BLACK;通过任意一条从根到叶子简单路径上颜色的约束,红黑树保证最长路径不超过最短路径的二倍,因而近似平衡。

具体性质如下:

  1. 每个节点颜色不是黑色,就是红色
  2. 根节点是黑色的
  3. 如果一个节点是红色,那么它的两个子节点就是黑色的(没有连续的红节点)
  4. 对于每个节点,从该节点到其后代叶节点的简单路径上,均包含相同数目的黑色节点

详解(转载):https://blog.youkuaiyun.com/tanrui519521/article/details/80980135

8、concurrenthashmap有啥优势,1.7,1.8区别?

concurrenthashmap是hashmap的多线程版本

jdk1.7中采用Segment + HashEntry的方式进行实现

1.8中放弃了Segment臃肿的设计,取而代之的是采用Node + CAS + Synchronized来保证并发安全进行实现。

(重复的题)

9、ArrayList是否会越界?

首先,ArrayList是基于数组实现的,是一个动态数组,其容量能自动增长,类似于C语言中的动态申请内存,动态增长内存。

对于ArrayList而言,它实现List接口、底层使用数组保存所有元素。其操作基本上是对数组的操作。

函数grow()解释了基于数组的ArrayList是如何扩容的。数组进行扩容时,会将老数组中的元素重新拷贝一份到新的数组中,每次数组容量的增长大约是其原容量的1.5倍。

接下来回到Add()函数,继续执行,elementData[size++] = e; 这行代码就是问题所在,当添加一个元素的时候,它可能会有两步来完成:1. 在 elementData[Size] 的位置存放此元素;2. 增大 Size 的值。

但是这里有一个问题,由于ArrayList不是线程安全的,当add到最后一个的时候,如果是多线程,很可能出现数组越界,而单线程则不会有这种情况。

参考:https://blog.youkuaiyun.com/u010010428/article/details/51258783

10、什么是TreeMap?

(重复)

11、ConcurrentHashMap的原理是什么?

(重复)

12、Java集合类框架的基本接口有哪些?

(一)总共有两大接口:Collection和Map,一个是元素集合,一个是键值对集合。

(二)其中List接口和Set接口继承了Collection接口,一个是有序元素集合,一个是无序元素集合

(三)ArrayList类和LinkList类实现了List接口

(3.1)ArrayList底层采用数组存储,因此适合查询,不适合增删

(3.2)LinkList底层采用双向链表,适合增删,不适合查询

(四)HashSet(哈希表、散列表)实现了Set接口

(五)TreeSet实现了SortedSet接口(图上没画出来)。无序,不可重复,但可按照元素大小自动排序,或者自定义排序方法

(六)HashMap和HashTable实现了Map,其中HashTable是线程安全的,但是HashMap性能更好

(七)TreeMap实现了SortedMap接口(图上没画出来)。无序,不可重复,但可按照元素大小自动排序或者自定义排序方法

 

转载:https://blog.youkuaiyun.com/qq_18433441/article/details/78221810

13、为什么集合类没有实现Cloneable和Serializable接口?
Cloneable.接口是用于浅克隆,而Serializable接口是用于深克隆,标识性接口,之所以用到克隆,有时需要把对象信息保存到本地磁盘,防止在传输时出现乱序,而那些容器没有这个必要,只是用来存储数据

克隆 序列化都是针对的某个实体对象。

克隆(cloning)或者序列化(serialization)的语义和含义是跟具体的实现相关的。因此应该由集合类的具体实现类来决定如何被克隆或者序列化

参考:https://www.nowcoder.com/questionTerminal/e139bf821ce646279df48577ea862919?orderByHotValue=1&page=1&onlyReference=false

参考:http://www.geekqanda.com/index.php/81359/%E4%B8%BA%E4%BB%80%E4%B9%88%E9%9B%86%E5%90%88%E7%B1%BB%E6%B2%A1%E6%9C%89%E5%AE%9E%E7%8E%B0cloneable%E5%92%8Cserializable%E6%8E%A5%E5%8F%A3%EF%BC%9F?show=81413

14、什么是迭代器?

可迭代是Java集合框架下的所有集合类的一种共性,也就是把集合中的所有元素遍历一遍。迭代的过程需要依赖一个迭代器对象,那么什么是迭代器呢?

   迭代器(Iterator)模式,又叫做游标模式,它的含义是,提供一种方法访问一个容器对象中各个元素,而又不需暴露该对象的内部细节。

从定义上看,迭代器是为容器而生,它本质上就是一种遍历的算法。因为容器的实现千差万别,很多时候不可能知道如何去遍历一个集合对象的元素。Java为我们提供了使用迭代的接口,Java的所有集合类丢失进行迭代的。

简单的说,迭代器就是一个接口Iterator,实现了该接口的类就叫做可迭代类,这些类多数时候指的就是java.util包下的集合类。

通过iterator()方法得到迭代器。iterator()方法是由Iterable接口规定的,ArrayList对该方法提供了具体的实现,在迭代器Iteartor接口中,有以下3个方法:

1.hasNext() 该方法英语判断集合对象是否还有下一个元素,如果已经是最后一个元素则返回false

2.next() 把迭代器的指向移到下一个位置,同时,该方法返回下一个元素的引用

3.remove()  从迭代器指向的Collection中移除迭代器返回的最后一个元素,该操作使用的比较少。

注意:从Java5.0开始,迭代器可以被foreach循环所替代,但是foreach循环的本质也是使用Iterator进行遍历的。

public class Test3 {  
  
    public static void main(String[] args) {  
        List<String>list=new ArrayList<>();  
        list.add("a");  
        list.add("b");  
        Iterator<String>it=list.iterator();//得到lits的迭代器  
        //调用迭代器的hasNext方法,判断是否有下一个元素  
        while (it.hasNext()) {  
            //将迭代器的下标移动一位,并得到当前位置的元素值  
            System.out.println(it.next());    
        }     
    }  
}  

 总结:

迭代器,提供一种访问一个集合对象各个元素的途径,同时又不需要暴露该对象的内部细节。java通过提供Iterator和Iterable俩个接口来实现集合类的可迭代性,迭代器主要的用法是:首先用hasNext()作为循环条件,再用next()方法得到每一个元素,最后在进行相关的操作。

参考:https://blog.youkuaiyun.com/weixin_42221030/article/details/80522815

15、Iterator和ListIterator的区别是什么?

Iterator是什么

Iterator代表迭代器,是Collection框架中的一个接口;用于遍历集合元素。它允许逐个迭代集合中的每个元素,从集合中获取元素或从集合中删除元素;但无法使用Iterator修改集合中的任何元素。

Iterator有一个iterator()方法,它会将迭代器返回到集合的开头。一旦得到一个集合开头的迭代器,然后遍历集合中的元素,就建立一个循环,每次循环迭代时调用hasNext()。

hasNext()如果返回true,则表示集合中存在下一个元素;如果返回false,则表示遍历所有元素。然后在循环内部,可以使用next()获取集合中的每个元素。next()方法返回集合的下一个元素。

缺点:

● 使用Iterator,就只能向前移动集合。

● 使用Iterator,就无法操纵或修改集合中的元素。

 

ListIterator是什么

ListIterator是Collection框架中的一个接口;是用于扩展Iterator接口的。使用ListIterator,可以向前和向后遍历集合的元素。还可以添加、删除或修改集合中的任何元素。简而言之,我们可以说它消除了Iterator的缺点。

1、遍历

使用Iterator,可以遍历所有集合,如Map,List,Set;但只能在向前方向上遍历集合中的元素。

使用ListIterator,只能遍历List实现的对象,但可以向前和向后遍历集合中的元素。

2、添加元素

Iterator无法向集合中添加元素;而,ListIteror可以向集合添加元素。

3、修改元素

Iterator无法修改集合中的元素;而,ListIterator可以使用set()修改集合中的元素。

4、索引

Iterator无法获取集合中元素的索引;而,使用ListIterator,可以获取集合中元素的索引。

参考:http://www.php.cn/java-article-416241.html

16、快速失败(fail-fast)和安全失败(fail-safe)的区别是什么?

一、快速失败
    在使用迭代器对集合对象进行遍历的时候,如果A线程对集合进行遍历,正好B线程对集合进行修改(增加、删除、修改)则A线程会抛出ConcurrentModificationException异常。

原理是:

    迭代器在遍历时直接访问集合中的内容,并且在遍历过程中使用一个 modCount 变量。集合在被遍历期间如果内容发生变化,就会改变modCount的值。每当迭代器使用hashNext()/next()遍历下一个元素之前,都会检测modCount变量是否为expectedmodCount值,是的话就返回遍历;否则抛出异常,终止遍历。

 注意:

    这里异常的抛出条件是检测到 modCount!=expectedmodCount 这个条件。如果集合发生变化时修改modCount值刚好又设置为了expectedmodCount值,则异常不会抛出。因此,不能依赖于这个异常是否抛出而进行并发操作的编程,这个异常只建议用于检测并发修改的bug。场景:java.util包下的集合类都是快速失败的,不能在多线程下发生并发修改(迭代过程中被修改)

二、安全失败(fail-safe)
 采用安全失败机制的集合容器,在遍历时不是直接在集合内容上访问的,而是先复制原有集合内容,在拷贝的集合上进行遍历。
 原理:

        由于迭代时是对原集合的拷贝进行遍历,所以在遍历过程中对原集合所作的修改并不能被迭代器检测到,所以不会触发Concurrent Modification Exception,例如CopyOnWriteArrayList。

缺点:

        基于拷贝内容的优点是避免了Concurrent Modification Exception,但同样地,迭代器并不能访问到修改后的内容,即:迭代器遍历的是开始遍历那一刻拿到的集合拷贝,在遍历期间原集合发生的修改迭代器是不知道的。

场景:

        java.util.concurrent包下的容器都是安全失败,可以在多线程下并发使用,并发修改。

快速失败和安全失败是对迭代器而言的。 快速失败:当在迭代一个集合的时候,如果有另外一个线程在修改这个集合,就会抛出ConcurrentModification异常,java.util下都是快速失败。 安全失败:在迭代时候会在集合二层做一个拷贝,所以在修改集合上层元素不会影响下层。在java.util.concurrent下都是安全失败
参考:https://blog.youkuaiyun.com/u010889616/article/details/79954413

17、HashMap和Hashtable有什么区别?

HashMap和Hashtable都实现了Map接口,但决定用哪一个之前先要弄清楚它们之间的分别。主要的区别有:线程安全性,同步(synchronization),以及速度。

  1. HashMap几乎可以等价于Hashtable,除了HashMap是非synchronized的,并可以接受null(HashMap可以接受为null的键值(key)和值(value),而Hashtable则不行)。
  2. HashMap是非synchronized,而Hashtable是synchronized,这意味着Hashtable是线程安全的,多个线程可以共享一个Hashtable;而如果没有正确的同步的话,多个线程是不能共享HashMap的。Java 5提供了ConcurrentHashMap,它是HashTable的替代,比HashTable的扩展性更好。
  3. 另一个区别是HashMap的迭代器(Iterator)是fail-fast迭代器,而Hashtable的enumerator迭代器不是fail-fast的。所以当有其它线程改变了HashMap的结构(增加或者移除元素),将会抛出ConcurrentModificationException,但迭代器本身的remove()方法移除元素则不会抛出ConcurrentModificationException异常。但这并不是一个一定发生的行为,要看JVM。这条同样也是Enumeration和Iterator的区别。
  4. 由于Hashtable是线程安全的也是synchronized,所以在单线程环境下它比HashMap要慢。如果你不需要同步,只需要单一线程,那么使用HashMap性能要好过Hashtable。
  5. HashMap不能保证随着时间的推移Map中的元素次序是不变的。

区别类似于hashmap与ConcurrentHashMap的区别,hashtable只是ConcurrentHashMap的替代

18、ArrayList和LinkedList有什么区别?

1.对ArrayList和LinkedList而言,在列表末尾增加一个元素所花的开销都是固定的。对ArrayList而言,主要是在内部数组中增加一项,指向所添加的元素,偶尔可能会导致对数组重新进行分配;而对LinkedList而言,这个开销是统一的,分配一个内部Entry对象。

2.在ArrayList的中间插入或删除一个元素意味着这个列表中剩余的元素都会被移动;而在LinkedList的中间插入或删除一个元素的开销是固定的。

3.LinkedList不支持高效的随机元素访问。

4.ArrayList的空间浪费主要体现在在list列表的结尾预留一定的容量空间,而LinkedList的空间花费则体现在它的每一个元素都需要消耗相当的空间

可以这样说:当操作是在一列数据的后面添加数据而不是在前面或中间,并且需要随机地访问其中的元素时,使用ArrayList会提供比较好的性能;当你的操作是在一列数据的前面或中间添加或删除数据,并且按照顺序访问其中的元素时,就应该使用LinkedList了。

https://www.cnblogs.com/Jacck/p/8034900.html

19、ArrayList,Vector,LinkedList的存储性能和特性是什么?

 ArrayList 和Vector他们底层的实现都是一样的,都是使用数组方式存储数据,此数组元素数大于实际存储的数据以便增加和插入元素,它们都允许直接按序号索引元素,但是插入元素要涉及数组元素移动等内存操作,所以索引数据快而插入数据慢。

      Vector中的方法由于添加了synchronized修饰,因此Vector是线程安全的容器,但性能上较ArrayList差,因此已经是Java中的遗留容器。

    LinkedList使用双向链表实现存储(将内存中零散的内存单元通过附加的引用关联起来,形成一个可以按序号索引的线性结构,这种链式存储方式与数组的连续存储方式相比,内存的利用率更高),按序号索引数据需要进行前向或后向遍历,但是插入数据时只需要记录本项的前后项即可,所以插入速度较快。

 Vector属于遗留容器(Java早期的版本中提供的容器,除此之外,Hashtable、Dictionary、BitSet、Stack、Properties都是遗留容器),已经不推荐使用,但是由于ArrayList和LinkedListed都是非线程安全的,如果遇到多个线程操作同一个容器的场景,则可以通过工具类Collections中的synchronizedList方法将其转换成线程安全的容器后再使用(这是对装潢模式的应用,将已有对象传入另一个类的构造器中创建新的对象来增强实现)。

参考:https://www.cnblogs.com/jiangyi-uestc/p/5682699.html

20、Collection 和 Collections的区别。

1、java.util.Collection 是一个集合接口(集合类的一个顶级接口)。它提供了对集合对象进行基本操作的通用接口方法。Collection接口在Java 类库中有很多具体的实现。Collection接口的意义是为各种具体的集合提供了最大化的统一操作方式,其直接继承接口有List与Set。

 Collection   
├List   
│├LinkedList   
│├ArrayList   
│└Vector   
│ └Stack   
└Set

2、Collections则是集合类的一个工具类/帮助类,其中提供了一系列静态方法,用于对集合中元素进行排序、搜索以及线程安全等各种操作。

1) 排序(Sort)
使用sort方法可以根据元素的自然顺序 对指定列表按升序进行排序。列表中的所有元素都必须实现 Comparable 接口。此列表内的所有元素都必须是使用指定比较器可相互比较的

2) 混排(Shuffling)
混排算法所做的正好与 sort 相反: 它打乱在一个 List 中可能有的任何排列的踪迹。也就是说,基于随机源的输入重排该 List, 这样的排列具有相同的可能性(假设随机源是公正的)。这个算法在实现一个碰运气的游戏中是非常有用的。例如,它可被用来混排代表一副牌的 Card 对象的一个 List 。另外,在生成测试案例时,它也是十分有用的。

Collections.Shuffling(list)

3) 反转(Reverse)
使用Reverse方法可以根据元素的自然顺序 对指定列表按降序进行排序。
Collections.reverse(list)

4) 替换所以的元素(Fill)
使用指定元素替换指定列表中的所有元素。

Collections.fill(li,"aaa");

5) 拷贝(Copy)
用两个参数,一个目标 List 和一个源 List, 将源的元素拷贝到目标,并覆盖它的内容。目标 List 至少与源一样长。如果它更长,则在目标 List 中的剩余元素不受影响。
Collections.copy(list,li): 前面一个参数是目标列表 ,后一个是源列表。

6) 返回Collections中最小元素(min)
根据指定比较器产生的顺序,返回给定 collection 的最小元素。collection 中的所有元素都必须是通过指定比较器可相互比较的。
Collections.min(list)

7) 返回Collections中最小元素(max)
根据指定比较器产生的顺序,返回给定 collection 的最大元素。collection 中的所有元素都必须是通过指定比较器可相互比较的。
Collections.max(list)

8) lastIndexOfSubList
返回指定源列表中最后一次出现指定目标列表的起始位置
int count = Collections.lastIndexOfSubList(list,li);

9) IndexOfSubList
返回指定源列表中第一次出现指定目标列表的起始位置
int count = Collections.indexOfSubList(list,li);

10) Rotate
根据指定的距离循环移动指定列表中的元素
Collections.rotate(list,-1);
如果是负数,则正向移动,正数则方向移动

参考:https://www.cnblogs.com/cathyqq/p/5279859.html

21、你所知道的集合类都有哪些?主要方法?

线性表,链表,哈希表是常用的数据结构,在进行Java开发时,JDK已经为我们提供了一系列相应的类来实现基本的数据结构。这些类均在java.util包中。本文试图通过简单的描述,向读者阐述各个类的作用以及如何正确使用这些类。  

Collection 
├List 
│├LinkedList 
│├ArrayList 
│└Vector 
│ └Stack 
└Set 
Map 
├Hashtable 
├HashMap 
└WeakHashMap 

Collection接口 
  Collection是最基本的集合接口,一个Collection代表一组Object,即Collection的元素(Elements)。一些Collection允许相同的元素而另一些不行。一些能排序而另一些不行。Java SDK不提供直接继承自Collection的类,Java SDK提供的类都是继承自Collection的“子接口”如List和Set。 
  所有实现Collection接口的类都必须提供两个标准的构造函数:无参数的构造函数用于创建一个空的Collection,有一个Collection参数的构造函数用于创建一个新的Collection,这个新的Collection与传入的Collection有相同的元素。后一个构造函数允许用户复制一个Collection。 
  如何遍历Collection中的每一个元素?不论Collection的实际类型如何,它都支持一个iterator()的方法,该方法返回一个迭代子,使用该迭代子即可逐一访问Collection中每一个元素。典型的用法如下: 
    Iterator it = collection.iterator(); // 获得一个迭代子 
    while(it.hasNext()) { 
      Object obj = it.next(); // 得到下一个元素 
    } 
  由Collection接口派生的两个接口是List和Set。 

List接口 
  List是有序的Collection,使用此接口能够精确的控制每个元素插入的位置。用户能够使用索引(元素在List中的位置,类似于数组下标)来访问List中的元素,这类似于Java的数组。 
和下面要提到的Set不同,List允许有相同的元素。 
  除了具有Collection接口必备的iterator()方法外,List还提供一个listIterator()方法,返回一个ListIterator接口,和标准的Iterator接口相比,ListIterator多了一些add()之类的方法,允许添加,删除,设定元素,还能向前或向后遍历。 
  实现List接口的常用类有LinkedList,ArrayList,Vector和Stack。 

LinkedList类 
  LinkedList实现了List接口,允许null元素。此外LinkedList提供额外的get,remove,insert方法在LinkedList的首部或尾部。这些操作使LinkedList可被用作堆栈(stack),队列(queue)或双向队列(deque)。 
  注意LinkedList没有同步方法。如果多个线程同时访问一个List,则必须自己实现访问同步。一种解决方法是在创建List时构造一个同步的List: 
    List list = Collections.synchronizedList(new LinkedList(...)); 

ArrayList类 
  ArrayList实现了可变大小的数组。它允许所有元素,包括null。ArrayList没有同步。 
size,isEmpty,get,set方法运行时间为常数。但是add方法开销为分摊的常数,添加n个元素需要O(n)的时间。其他的方法运行时间为线性。 
  每个ArrayList实例都有一个容量(Capacity),即用于存储元素的数组的大小。这个容量可随着不断添加新元素而自动增加,但是增长算法并没有定义。当需要插入大量元素时,在插入前可以调用ensureCapacity方法来增加ArrayList的容量以提高插入效率。 
  和LinkedList一样,ArrayList也是非同步的(unsynchronized)。 

Vector类 
  Vector非常类似ArrayList,但是Vector是同步的。由Vector创建的Iterator,虽然和ArrayList创建的Iterator是同一接口,但是,因为Vector是同步的,当一个Iterator被创建而且正在被使用,另一个线程改变了Vector的状态(例如,添加或删除了一些元素),这时调用Iterator的方法时将抛出ConcurrentModificationException,因此必须捕获该异常。 

Stack 类 
  Stack继承自Vector,实现一个后进先出的堆栈。Stack提供5个额外的方法使得Vector得以被当作堆栈使用。基本的push和pop方法,还有peek方法得到栈顶的元素,empty方法测试堆栈是否为空,search方法检测一个元素在堆栈中的位置。Stack刚创建后是空栈。 

Set接口 
  Set是一种不包含重复的元素的Collection,即任意的两个元素e1和e2都有e1.equals(e2)=false,Set最多有一个null元素。 
  很明显,Set的构造函数有一个约束条件,传入的Collection参数不能包含重复的元素。 
  请注意:必须小心操作可变对象(Mutable Object)。如果一个Set中的可变元素改变了自身状态导致Object.equals(Object)=true将导致一些问题。 

Map接口 
  请注意,Map没有继承Collection接口,Map提供key到value的映射。一个Map中不能包含相同的key,每个key只能映射一个value。Map接口提供3种集合的视图,Map的内容可以被当作一组key集合,一组value集合,或者一组key-value映射。 

Hashtable类 
  Hashtable继承Map接口,实现一个key-value映射的哈希表。任何非空(non-null)的对象都可作为key或者value。 
  添加数据使用put(key, value),取出数据使用get(key),这两个基本操作的时间开销为常数。 
Hashtable通过initial capacity和load factor两个参数调整性能。通常缺省的load factor 0.75较好地实现了时间和空间的均衡。增大load factor可以节省空间但相应的查找时间将增大,这会影响像get和put这样的操作。 
使用Hashtable的简单示例如下,将1,2,3放到Hashtable中,他们的key分别是”one”,”two”,”three”: 
    Hashtable numbers = new Hashtable(); 
    numbers.put(“one”, new Integer(1)); 
    numbers.put(“two”, new Integer(2)); 
    numbers.put(“three”, new Integer(3)); 
  要取出一个数,比如2,用相应的key: 
    Integer n = (Integer)numbers.get(“two”); 
    System.out.println(“two = ” + n); 
  由于作为key的对象将通过计算其散列函数来确定与之对应的value的位置,因此任何作为key的对象都必须实现hashCode和equals方法。hashCode和equals方法继承自根类Object,如果你用自定义的类当作key的话,要相当小心,按照散列函数的定义,如果两个对象相同,即obj1.equals(obj2)=true,则它们的hashCode必须相同,但如果两个对象不同,则它们的hashCode不一定不同,如果两个不同对象的hashCode相同,这种现象称为冲突,冲突会导致操作哈希表的时间开销增大,所以尽量定义好的hashCode()方法,能加快哈希表的操作。 
  如果相同的对象有不同的hashCode,对哈希表的操作会出现意想不到的结果(期待的get方法返回null),要避免这种问题,只需要牢记一条:要同时复写equals方法和hashCode方法,而不要只写其中一个。 
  Hashtable是同步的。 

HashMap类 
  HashMap和Hashtable类似,不同之处在于HashMap是非同步的,并且允许null,即null value和null key。,但是将HashMap视为Collection时(values()方法可返回Collection),其迭代子操作时间开销和HashMap的容量成比例。因此,如果迭代操作的性能相当重要的话,不要将HashMap的初始化容量设得过高,或者load factor过低。 

WeakHashMap类 
  WeakHashMap是一种改进的HashMap,它对key实行“弱引用”,如果一个key不再被外部所引用,那么该key可以被GC回收。 

总结 
  如果涉及到堆栈,队列等操作,应该考虑用List,对于需要快速插入,删除元素,应该使用LinkedList,如果需要快速随机访问元素,应该使用ArrayList。 
  如果程序在单线程环境中,或者访问仅仅在一个线程中进行,考虑非同步的类,其效率较高,如果多个线程可能同时操作一个类,应该使用同步的类。 
  要特别注意对哈希表的操作,作为key的对象要正确复写equals和hashCode方法。 
  尽量返回接口而非实际的类型,如返回List而非ArrayList,这样如果以后需要将ArrayList换成LinkedList时,客户端代码不用改变。这就是针对抽象编程。

转载:https://www.cnblogs.com/guweiwei/p/6638022.html

22、List、Set、Map是否继承自Collection接口?

List,Set是的Map不是

23、阐述ArrayList、Vector、LinkedList的存储性能和特性

(重复)

24、List、Map、Set三个接口存取元素时,各有什么特点?

List与Set都是单列元素的集合,它们有一个功共同的父接口Collection。

Set里面不允许有重复的元素,

存元素:add方法有一个boolean的返回值,当集合中没有某个元素,此时add方法可成功加入该元素时,则返回true;当集合含有与某个元素equals相等的元素时,此时add方法无法加入该元素,返回结果为false。

取元素:没法说取第几个,只能以Iterator接口取得所有的元素,再逐一遍历各个元素。

 

List表示有先后顺序的集合,

存元素:多次调用add(Object)方法时,每次加入的对象按先来后到的顺序排序,也可以插队,即调用add(int index,Object)方法,就可以指定当前对象在集合中的存放位置。

取元素:方法1:Iterator接口取得所有,逐一遍历各个元素

        方法2:调用get(index i)来明确说明取第几个。

 

Map是双列的集合,存放用put方法:put(obj key,obj value),每次存储时,要存储一对key/value,不能存储重复的key,这个重复的规则也是按equals比较相等。

取元素:用get(Object key)方法根据key获得相应的value。

        也可以获得所有的key的集合,还可以获得所有的value的集合,

        还可以获得key和value组合成的Map.Entry对象的集合。

总结:List以特定次序来持有元素,可有重复元素。Set 无法拥有重复元素,内部排序。Map 保存key-value值,value可多值。

参考:https://www.cnblogs.com/guweiwei/p/6635530.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值