集合
1、集合的分类?
集合在大的方向上可分为collection和map两大类:
1.collection是集合类的根接口,Java中没有提供这个接口的直接实现类,而是让其被继承产生两个实现类list和set,queue也是collection的一个子接口与list平级。
2.List下有ArrayList,LinekdList,Vector,CopyOnWriteArrayList
3.Map集合下有HashMap,TreeMap,HashTable,LinkedHashMap,ConcurrentHashMap
4.set集合下有HashSet,TreeSet,LinkedHashSet,CopyOnWriteArraySet
2、对集合的理解:
1、java中的集合框架是java.until包中的集合接口和实现类组成的,主要包括collection接口及其实现类和map接口及其实现类,还有iterator迭代器,collections工具等。
2、collection接口包括list和set。list是有序列表集合,元素可以重复,索引从0开始,他的实现类有ArrayList,LinkedList,Vector,其中ArrayList和Vector底层实现是动态数组,LinkedList底层是双向链表实现存储;set是无序的散列表集合,元素不允许重复,set的实现类有HashSet和TreeSet,其中HashSet底层是HashMap实现存储,TreeSet底层是TreeMap实现存储。
3、Map是键值对集合接口,采用key-value方式进行存储,其中key是无序不可重复的,value是可重复的,Map的实现类有HashMap,TreeMap,LinkedHashMap,HashTable.
4、iterator迭代器接口用于遍历集合中的元素,适用于LIst和set集合
5、collections是操作集合的工具类。
3、如何选用集合:
1、如果需要根据键值对来获取集合中的元素值时可以选择Map接口的实现类,需要排序时选择TreeMap,不需要时选择HashMap,需要保持有序的时候选择LinkedHashMap;
2、只需要存储元素值得时候可以选择collection接口得实现类,需要保持元素唯一性时选择Set,不需要选择List
4、List的基本用法:
1、排序:使用collections.sort()方法对list集合内进行排序,排序的规则有两种:
(1)实现comparable接口重写compareTo(T t)方法,设置默认排序方法
(2)sort()方法传入第二个参数,该参数为comparator接口得实现类对象,用于自定义排序规则。
2、遍历方式:
(1)实现类RandomAccess接口的List(ArrayList,vector),优先选择使用for循环遍历,其次使用foreach循环遍历;
(2)为实现RandomAccess接口得List(LinkedList),优先选择使用iterator迭代器循环遍历(foreach循环底层实现也是iterator迭代器),大得size得集合不使用for循环。
5、ArrayList的理解:
1、首先ArrayList是List集合的一个实现类,其底层实现是一个数组,数组的查询是直接通过索引,查询速度快,时间复杂度为O(1),增删的话,根据位置不同时间复杂度也有所不同,其时间复杂度是O(n);
2、扩容机制:如果是无参构造的方法创建的ArrayList对象时,实际上就是初始化一个空数组,当添加第一个元素的时候,数组的容量扩容值10,当数组容量再次不足的时候触发其扩容机制,就是当集合中的元素个数大于集合的容量的时候,也就是说执行add()方法导致先有的集合容量过小而装不下的时候,就会出发扩容机制,调用grow()方法,每次扩容后的容量是原有容量的1.5倍;
3、线程问题:ArrayList是线程不安全的。在add()的时候,首先会检查数组的容量,如果够用就会执行elementData[size++]=e方法,这个语句执行分为两步,第一步把e放入elementData缓冲区,第二步size进行加1操作,这两个操作都不是原子性操作,在并发的时候容易导致线程不安全。
6、LinkedList的理解:
1、LinkedList也是List集合的一个实现类,其底层是一个双向的链表,内部有一个next指针指向下一个节点,prev指针指向上一个节点,因为是链表所以查询速度比较慢,当我们查询一个元素的时候需要从第一个元素开始直到查询结束,时间复杂度为O(n)。但LinkedList的增删操作速度比较快,比如说要增加一个节点,只需要将后一个节点的prev指针指向这个节点,此节点的next指针指向后一个节点,前一个节点的next指针指向此节点,此节点的prev指向前一个节点,时间复杂度为O(1),空间复杂度一般比ArrayList大,因为每个节点还需要存储next,prev两个指针。
2、LinkedList也是线程不安全的,其添加元素的操作通过LinkLast方法在尾部进行添加,添加完之后把size加1,就单单size++就不是原子性的操作,其执行分为三步:a:把size的值加载到内存中,b:把内存的值存储到变量中,c:进行加1操作
7、Vector的理解:
Vector也是List的一个实现类,其底层也是一个动态数组,其初始容量为10;Vector就是一个加了synchronized的ArrayList,线程是安全的,但就是因为加了synchronized,所以他的效率没有arraylist效率高,一般不推荐使用
8、不使用Vector解决Arralist的线程安全的其他解决方案:
1、Java还有一个包java.until.concurrent(JUC)包下有一个类CopyOnWriteArrayList也是线程安全的,他也是List的一个实现类
2、他在add()方法的时候使用LOCK锁的方式来解决并发问题,其中在进行添加一个数据的时候,用来copyOf()方法,将数据复制一份,然后再set进去。
3、CopyOnWriteArrayList底层也是一个数组,但他的数组是使用volatile修饰的,主要是为了保证数据的可见性,get操作的时候没有进行加锁,因为volatile保证了数据的可见性当数据被修改的时候,读操作能立刻知道。
9、CopyOnWriteArrayList认识:
1、CopyOnWriteArrayList是java.until.concurrent并发包提供的线程安全的List集合容器,兼顾安全与性能,用于在线程安全的场景下取代Vector和ArrayList,CopyOnWriteArrayList在add(),set(),remove()操作的时候,通过reentrantLock加锁,然后复制出一个新的数据的方式来实现线程安全,CopyOnWriteArrayList适合读多写少的场景。(Vector是在方法中都加了synchronized,所以其并发性能比CopyOnWriteArrayList低)
2、CopyOnWriteArrayList经常增删改集合中的数据,执行add(),set(),remove()方法,每次都需要复制一个新的数组比较消耗内存,CopyOnWriteArrayList只能保持数据的最终一致性不能保持数据的实时一致性。(缺点:内存占用,不能保持数据的实时一致性)
3、CopyOnWriteArrayList的实现原理是:所有操作的底层都是通过创建数组的新副本来实现的;CopyOnWriteArrayList需要被修改的时候,并不修改原有的内容,而是对原有的数据进行一次复制,将复制的内容写入新副本,写完之后再将修改完的副本替换原有的数据,这样就可以保证写操作不会影响读操作。
10、List、map、set的区别:
1、List存储的顺序是按照存入的顺序,Set的存储时无序的,Map时键值对存储
2、List是可以存储相同的元素,set不可以存储相同的元素,Map中的key是不可以重复的但value是可以重复的
11、HashSet的理解:
1、HashSet是set集合的实现类,其底层是现实HashMap的key,初始容量为16,负载因子为0.75,扩容机制变为原来的2倍
2、HashSet存储的顺序并不是按照存入的顺序,而是按照hash值来存储的,所以取元素也是根据hash值,元素的哈希值是根据hashcode方法获取的,(hashSet自检重复)hashSet会计算元素的Hashcode值来判断元素所在的位置,如果这个位置没有其他元素则代表元素不重复,如果有元素,则判断该位置得链内其他元素的hashcode值,如果没有相同的hashcod值就代表不重复,如果有,则调用equals()方法进行判断,如果为true则为同一个元素,为false则不是同一个元素,该元素就在该链的位置上顺延。
3、HashSet线程不安全,可以通过CopyOnWriteArraySet来解决其线程安全问题。
12、LinkedHashSet的理解:
LinkedHashSet是在HashSet的基础上维护了一个双向链表,使得LinkedHashSet存取有序。
13、TreeSet的理解:
TreeSet是使用二叉树的原理对新add()的对象按照指定的顺序排序,每增加一个对象都会进行排序,将对象插入二叉树指定的位置
14、HashSet、LinkedHashSet、TreeSet的区别:
1、HashSet是set接口的实现类,底层通过hashMap的key来存放数据,是无序的,线程不安全,他的值可以为NULL,不允许存放重复数据
2、LinkedHashSet是HashSet的子类,他是有序的,线程不安全,可以按照添加的顺序遍历出来
3、treeSet底层使用红黑树,其中存放的数据必须为可排序的(实现comparable接口或comparator接口)TreeSet会根据定义的排序规则自动排序,线程不安全。
15、Queue是什么:
queue是一个队列,具有先进先出(FIFO)的特点,也是collection的一个子类
16、HashMap的理解:
1、jdk1.7底层实现是通过数组+链表到了Jdk1.8之后底层实现是数组+链表+红黑树组成。所有的数据都通过一个Node节点进行封装,Node节点封装了hash值,key,value和next指针。hash值是通过key计算出的hashcode值进行对数组的容量减一求余得到的(官方的计算方式是&运算符进行的)。不同的key计算出的hash值可能一致,从而导致冲突,而解决冲突的方式就是拉链法(链表和红黑树:也就是说当链表的长度小于8时解决冲突的就是链表,当链表的长度大于8且数组的从长度大于64的时候就会转换为红黑树存储)。因为这种存储方式所以HashMap存储时无序的。
2、因为他的懒加载机制,在put值得时候会判断是否为空,如果为空则才进行初始化,并不是在new得时候就进行初始化。
3、HashMap是Map得一个实现类,其默认的初始容量大小为16,扩容机制是根据扩容因子来扩容得,当容量的使用量达到总容量的0.75得时候就会出发扩容机制。当HashMap中的元素个数超过数组X加载因子的时候就会对数组进行扩容;当添加元素得时候,如果链表得长度大于阈值(8)并且数组长度大于64们会产生数组扩容。方法为resize()方法。
4、jdk1.7的时候,在并发扩容的时候会造成环形链,导致死循环和数据丢失;jdk1.8解决了环形链问题,但在并发执行put的时候,会发合适呢个数据覆盖。
5、hashMap的长度为2的幂次方的时候,不同的key计算得到的下标相同的机率很低,不易产生冲突。
17、LinkedHashMap的理解:
LinkedHashMap解决了HashMap不能保证存取顺序的问题,其内部维护了一个单链表,有头尾节点,同时LinkedHashMap节点Entry对象内部不仅继承了HashMap的Node属性,还有before和after用于标识前置节点和后置节点,用于实现按插入顺序或访问顺序排序。
18、TreeMap的理解:
TreeMap实现sortedMap接口,能够把它保存的记录根据键排序,默认情况下是升序排序。TreeMap内部排序实现:TreeMap是按照key的自然顺序或者comparator定义的顺序排序,内部通过红黑树来实现,所以key所属的类实现comparable接口,或者自定义一个实现comparator接口的比较器,传给TreeMap进行key的比较,从而实现按照key排序。
19、HashMap,LinkedHashMap,TreeMap的使用场景:
1、HashMap:一般情况下,以key-value的方式插入,删除和查询元素
2、LinkedHashMap:需要遍历顺序和添加顺序保持一致的情况下使用
3、TreeMap:需要按照添加顺序或按照key排序的情况下使用
20、ConcurrentHashMap和HashTable的区别:
1、ConcurrentHashMap(认识)是一个线程安全且性能比较多高的Map集合,是为了解决HashMap在并发环境下线程不安全而设计的实现,避免对全局加锁,转而局部分段加锁操作,并且利用Synchronized,volatile,CAS,final 等Lock-free技术来减少锁竞争对性能的影响,极大的提高并发环境下的操作速度。
2、区别:底层实现上,ConcurrentHashMap在jdk1.7是数组+链表,在jdk1.8是数组+链表+红黑树二叉树实现的,而hashtable底层是数组+链表实现的;在线程安全上:ConcurrentHashMap在jdk1.7使用分段锁对整个数组进行分割分段,每个锁只锁容器中的一部分,多线程访问容器中不同段的数据,就不会存在锁竞争,提高了并发的访问率,在JDK1.8摈弃了segment的概念,直接用node数组+链表+红黑树的数据结构实现,并发使用synchronized和CAS来实现;HashTable使用synchronized来保证线程安全,效率十分低下,当一个线程访问同步方法的时候其他的线程可能会进入阻塞状态。
21、如何保证HashMap的线程安全:
1、可以通过hashtable,该类的出现主要就是为了解决HashMap的线程安全问题,直接用了Synchronized锁,所以在效率上不是很高,一般不建议使用
2、ConcurentHashMap是java.until.cincurrent并发包下的。他就是对HashMap进行了一个扩展,也就是解决了他的线程安全问题。CincurentHashMap用了大量的CAS来进行优化。
22、什么是CAS:
CAS(compare and swap)比较和替换是设计并发算法用到的一种技术。简单的来说,比较和替换是使用一个期望值和一个变量的当前值进行比较,如果当前变量的值与我们期望的值相等,就用一个新值替换当前变量的值。
23、collection和collections的区别:
collection是一个集合接口,它提供了对集合进行各种操作的通用接口方法。collection接口在Java类库中有很多具体的操作。collection接口的意义是为各种具体的集合提供最大化的统一操作方式,其直接继承方式有List和set.
collections是操作集合接口的一个工具类,其中提供了一系列的静态方法,用于对集合中的元素进行排序,搜索以及线程安全等各种操作。
24、ArrayList和LinkedList的区别:
1、ArrayList底层是动态数组支持随机访问,增删改操作效率比较低,LinkedList底层是双向链表,不支持随机访问,增删改操作效率高,使用下标访问一个元素,ArrayList的时间复杂度是O(1),LinkedList时间复杂度是O(n);
2、ArrayList会预留一定的空间,而LinkedList的每一个元素都要消耗比ArrayList多的空间因为要存放prev,data,naxt
25、ArrayList和Vector的区别:
1、ArraList线程不安全,Vector使用synchronized实现此线程同步,是线程安全的;
2、Vector每次扩容或增加1倍,ArrayList扩容增加0.5倍
26、HashMap和HashTable的区别:
1、HashMap允许空键值,HashTable不允许;
2、HashMap线程不安全但是效率高,hashTable线程安全但是效率低;
3、HashMap底层使用数组+链表+红黑树,hashTable底层使用数组+链表;
4、创建时如果不指定初始容量HashTable的默认初始容量为11,之后每次扩容是原来的2n+1;HashMap的默认初始容量为16,之后每次扩容是原来的2n;
26、HashMap和HashSet的区别:
1、HashMap实现Map接口,HashSet实现set接口;
2、HashMap底层使用数组+链表+红黑树实现的,HashSet底层实现是hashMap;
3、HashMap使用key-value存储,hashSet仅存储对象;
4、HashSet存储对象时,将对象作为Key,将Object对象作为Value;
26、Java集合实践经验:
1、正确选择要使用的集合类型对于性能来说非常重要
2、预估集合存储元素的数目,指定初始容量来避免重新计算hash值或者扩容等
3、为了类型安全,可读性和健壮性等原因可使用泛型,使用泛型还可避免运行时的ClassCastException
4、使用jdk提供的不变类作为Map的键可以避免为我们自己的类实现hashcode()和equals()方法
5、在底层使用集合实际上为空的情况下,返回长度是0的集合或数组而不是null