java集合总结

Java集合框架详解

Vector和ArrayList的区别?

1,Vector的方法都是同步的(Synchronized),是线程安全的(thread-safe),而ArrayList的方法不是,由于线程的同步必然要影响性能 ,因此,ArrayList的性能比Vector好。
2,当Vector或ArrayList中的元素超过它的初始大小时,Vector会将它的容量翻倍,而ArrayList只增加大约1.5的大小,这样ArrayList就 有利于节约内存空间。
3,Vector可以设置capacityIncrement(容量增长的参数),而ArrayList不可以。
4,List<Map<String,Object>> data=Collections.synchronizedList(new ArrayList<Map<String,Object>>());可以解决ArrayList的线程安全问题,或者使用ThreadLocal。

ArrayrList和LinkedList的区别?

1,ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。
2,对于随机访问get和set,ArrayList速度优于LinkedList,因为LinkedList要移动指针。
3,对于新增和删除操作add和remove,LinedList比较占优势,因为ArrayList要移动数据。

ArrayList的动态扩容?

1,在JKD1.6中,如果通过无参构造的话,初始数组容量为10.每次通过copeOf的方式扩容后容量为原来的大约1.5倍加1。
2,在JDK1.7中,如果通过无参构造的话,初始数组容量为0,当真正对数组进行添加时,才真正分配容量,每次按照大约1.5倍(位运算)的比率通过copeOf的方式扩容。
3,在JKD1.8中,arraylist这个类中,扩容调用的是grow()方法,通过grow()方法中调用的Arrays.copyof()方法进行对原数组的复制,在通过调用System.arraycopy()方法进行复制,达到扩容的目的。

HashMap和TreeMap的区别?

1、实现
TreeMap:实现了SortMap接口,基于红黑树
HashMap:基于哈希散列表实现,内部是一个数组,每个数组内是一个链表,链表可以扩展为红黑树
2、存储
TreeMap:默认按键的升序排序
HashMap:随机存
3、遍历
TreeMap:Iterator遍历是排序的
HashMap:Iterator遍历是随机的
4、性能损耗
TreeMap:插入、删除速度慢,需要维护树的平衡
HashMap:基本无损耗
5、键值对
TreeMap:键、值都不能为null
HashMap:键、值均可为null
6、安全
TreeMap:非并发安全Map
HashMap:非并发安全Map
7、效率
TreeMap:低
HashMap:高

HashMap和Hashtable有什么区别?

1、HashMap是非线程安全的,HashTable是线程安全的。
2、HashMap的键和值都允许有null值存在,而HashTable则不行。
3、因为线程安全的问题,HashMap效率比HashTable的要高。
4、Hashtable是同步的,而HashMap不是。因此,HashMap更适合于单线程环境,而Hashtable适合于多线程环境。

一般现在不建议用HashTable,因为:
1,是HashTable是遗留类,内部实现很多没优化和冗余。
2,HashTable内部是全部加了Syn锁,重量级锁,效率会很低。
3,即使在多线程环境下,现在也有同步的ConcurrentHashMap替代,没有必要因为是多线程而用HashTable。

HashMap和LinkedHashMap有什么区别?

LinkedHashMap是HashMap的一个子类,保存了记录的插入顺序,在用Iterator遍历LinkedHashMap时,先得到的记录肯定是先插入的,也可以在构造时用带参数,按照应用次数排序。在遍历的时候会比HashMap慢,不过有种情况例外,当HashMap容量很大,实际数据较少时,遍历起来可能会比LinkedHashMap慢,因为LinkedHashMap的遍历速度只和实际数据有关,和容量无关,而HashMap的遍历速度和他的容量有关,如果需要输出的顺序和输入的相同,那么用LinkedHashMap 可以实现,它还可以按读取顺序来排列。

HashMap的实现?

HashMap底层:链表数组,初始容量16.扩容阀值数组长度乘以0.75,到这个阀值,扩容两倍,如果当前map大小大于64,并且同一hash链表长度大于8,同一哈希值冲突部分自动转换为红黑树,如果小于64就不能转红黑树,直接扩容。根据key的哈希值计算出存放数组的索引位置。同一哈希值用链表形式向下存储,不同的key有可能哈希值一样。链表是单向链表。若链表元素个数小于等于6时,树结构还原成链表。
HashMap在多线程的环境下的put操作容易引起死循环,HahsMap里面的Entry链表会产生环形的数据结构,链表成了一个环,会一直在循环,是不安全的,但是HashTable效率太低了,所以多线程下一般使用ConCurrentHashMap。
注意:构造函数不会初始化数组,在put的时候进行初始化。

HashMap在多线程下形成死循环:
因为在HashMap扩容的过程中,会发生链表上的元素的位置发生改变,当HashMap在多线程的情况下,put元素,可能会发生扩容,当扩容的时候,如果一个链表上有A和B两个元素,线程1将A和B的位置改变还没执行完的时候,这时候线程2也将A和B的位置改变,当线程2还没执行完的时候线程1执行完了,将A和B的位置彻底改变了,这时候线程2就会出现B.next=A; A.next=B的情况,形成死循环。

为什么HashMap链表长度超过8会转成树结构?

1,纯链表的平均查找长度为(n+1)/2,红黑树平均查找长度则为log2n。长度为8的时候,红黑树平均查找长度为3,链表平均查找长度为8/2=4,这才有转换为树的必要。链表长度如果是小于等于6,6/2=3,速度已经很快,并且转化为树结构和生成树的时间不会很短,所以没必要转成红黑树。
2,选择6和8,中间有个差值7可以有效防止链表和树频繁转换。假设一下,如果设计成链表个数超过8则链表转换成树结构,链表个数小于8则树结构转换成链表,如果一个HashMap不停的插入、删除元素,链表个数在8左右徘徊,就会频繁的发生树转链表、链表转树,效率会很低。

为什么默认初始化桶数组大小为16,为什么加载因子的大小为0.75,这两个值的选取有什么特点?

如果桶初始化桶数组设置太大,就会浪费内存空间,16是一个折中的大小,既不会像1,2,3那样放几个元素就扩容,也不会像几千几万那样可以只会利用一点点空间从而造成大量的浪费。
加载因子设置为0.75而不是1,是因为设置过大,桶中键值对碰撞的几率就会越大,同一个桶位置可能会存放好几个value值,这样就会增加搜索的时间,性能下降,设置过小也不合适,如果是0.1,那么10个桶,threshold为1,你放两个键值对就要扩容,太浪费空间了。

HashSet和TreeSet的区别?

1、TreeSet是二叉树实现的(内部基于TreeMap,数据结构和TreeMap一致),Treeset中的数据是自动排好序的,不允许放入null值。
2、HashSet是哈希表实现的(内部基于HashMap,数据结构和HashMap一致),HashSet中的数据是无序的,可以放入null,但只能放入一 个null。
3、HashSet要求放入的对象必须实现HashCode()方法,放入的对象,是以hashcode码作为标识的,而具有相同内容的对象,hashcode一 样,所以放入的内容不能重复。但是同一个类的对象可以放入不同的实例。
4、TreeSet为基本操作(add、remove和contains)的时间复杂度是log(n)。另外,TreeSet是非同步的。它的iterator方法返回的迭代器是fail-fast的。

哈希?

哈希,又称为散列。是一个任意长度的输入,通过散列算法,得到一个固定长度的输出的值,也叫作压缩映射,容易产生哈希碰撞,即是可能出现同一个哈希值。比如:直接取余法就是一种hash算法。
解决哈希冲突,通常是:
1,开放寻址
2,再散列
3,链地址法(解决hashmap的hash冲突的时候采用这种方式)

注意:MD4,MD5,sha之类的所谓的加密算法,不是加密算法而是哈希算法,而且是不可逆的,网上那些可以逆的网站,是因为他们把常见的一些加密出来的密码统计起来了,然后做成了一张彩虹表。

位运算?

Java实际保存int型时,正数 第31位 =0 负数:第31位=1
int类型四个字节,一个字节8位,一共32位。
常用位运算有:
 位与 & (1&1=1 1&0=0 0&0=0)
 位或 | (1|1=1 1|0=1 0|0=0)
 位非 ~ ( ~1=0 ~0=1)
 位异或 ^ (1^1=0 1^0=1 0^0=0)
<<:有符号左移
>>:有符号的右移
>>>:无符号右移
例如:8 << 2 = 32 8>>2 = 2
取模的操作 a % (Math.pow(2,n)) 等价于 a&( Math.pow(2,n)-1) -----》 a%(x) 等价于 a&(x-1)

Java集合框架的基础接口有哪些?

Collection:
为集合层级的根接口,一个集合代表一组对象。这些对象即为它的元素,Java平台不提供这个接口不论什么直接的实现。
Set:
是一个不能包括反复元素的集合,这个接口对数学集合抽象进行建模。被用来代表集合,就如一副牌。
List:
是一个有序集合。能够包括反复元素,你能够通过它的索引来訪问不论什么元素。List更像长度动态变换的数组。
Map:
是一个将key映射到value的对象.一个Map不能包括反复的key:每一个key最多仅仅能映射一个value。

Iterator迭代器?

Iterator接口提供遍历不论什么Collection的接口,我们能够从一个Collection中使用迭代器方法来获取迭代器实例。迭代器代替了Java集合框架中的Enumeration。迭代器同意调用者在迭代过程中移除元素。
1,原理:
用到了Iterator设计模式,又叫做游标(Cursor)模式。迭代器模式:提供一种方法访问一个容器(container)对象中各个元素,而又不需暴露该对象的内部细节。
  从定义可见,迭代器模式是为容器而生。很明显,对容器对象的访问必然涉及到遍历算法。你可以一股脑的将遍历方法塞到容器对 象中去;或者根本不去提供什么遍历算法,让使用容器的人自己去实现。这两种情况好像都能够解决问题。
   然而在前一种情况,容器承受了过多的功能,它不仅要负责自己“容器”内的元素维护(添加、删除等等),而且还要提供遍历自身 的接口;而且由于遍历状态保存的问题,不能对同一个容器对象同时进行多个遍历。第二种方式倒是省事,却又将容器的内部细节 暴露无遗。所以,这时候在内部定义一个迭代器去控制元素的遍历。
注意:对于自身不是线程安全的容器,modCount也只是用来保证快速失败,因此不要在多线程的环境下使用iterator进行并行遍历操作。如果需要并行遍历可以使用Spliterator进行并行遍历。
2,Enumeration和Iterator接口的区别:
Enumeration的速度是Iterator的两倍,也使用更少的内存,Enumeration是非常基础的,也满足了基础的须要。但是,与Enumeration相比:
1,Iterator更加安全,由于当一个集合正在被遍历的时候。它会阻止其他线程去改动集合(就是快速失败)。
2,迭代器可以从集合中移除元素,而Enumeration不能做到。
3,为何没有像Iterator.add()这种方法:
因为Iterator的协议不能确保迭代的次序,所以ListIterator没有提供一个add操作,它要确保迭代的顺序。
4,为何迭代器在不需要移动游标的情况下,直接获取下一个元素:
它可以在当前Iterator的顶层实现,但是它用得很少,如果将它加到接口中,每个继承都要去实现它,这没有意义。
5,Iterater和ListIterator之间有什么区别:
(1)我们可以使用Iterator来遍历Set和List集合,而ListIterator只能遍历List。
(2)Iterator只可以向前遍历,而LIstIterator可以双向遍历。
(3)ListIterator从Iterator接口继承,然后添加了一些额外的功能,比如添加一个元素、替换一个元素、获取前面或后面元素的 索引位置。
6,为何Iterator接口没有详细的实现?
Iterator接口定义了遍历集合的方法。但它的实现则是集合实现类的责任。每一个能够返回用于遍历的Iterator的集合类都有它自己的Iterator实现内部类。
这就同意集合类去选择迭代器是fail-fast还是fail-safe的。比方,ArrayList迭代器是fail-fast的。而CopyOnWriteArrayList迭代器是fail-safe的。
7,快速失败(fail-fast)和安全失败(fail-safe)的区别:
一:快速失败(fail—fast)
在用迭代器遍历一个集合对象时,如果遍历过程中对集合对象的内容进行了修改(增加、删除、修改),则会抛出Concurrent Modification Exception。
原理:迭代器在遍历时直接访问集合中的内容,并且在遍历过程中使用一个 modCount 变量。集合在被遍历期间如果内容发生变化,就会改变modCount的值。每当迭代器使用hashNext()/next()遍历下一个元素之前,都会检测modCount变量是否为expectedmodCount值,是的话就返回遍历;否则抛出异常,终止遍历。
注意:这里异常的抛出条件是检测到 modCount!=expectedmodCount 这个条件。如果集合发生变化时修改modCount值刚好又设置为了expectedmodCount值,则异常不会抛出。因此,不能依赖于这个异常是否抛出而进行并发操作的编程,这个异常只建议用于检测并发修改的bug。
场景:java.util包下的集合类都是快速失败的,不能在多线程下发生并发修改(迭代过程中被修改)。
二:安全失败(fail—safe)
采用安全失败机制的集合容器,在遍历时不是直接在集合内容上访问的,而是先复制原有集合内容,在拷贝的集合上进行遍历。
原理:由于迭代时是对原集合的拷贝进行遍历,所以在遍历过程中对原集合所作的修改并不能被迭代器检测到,所以不会触发Concurrent Modification Exception
缺点:基于拷贝内容的优点是避免了Concurrent Modification Exception,但同样地,迭代器并不能访问到修改后的内容,即:迭代器遍历的是开始遍历那一刻拿到的集合拷贝,在遍历期间原集合发生的修改迭代器是不知道的。
场景:java.util.concurrent包下的容器都是安全失败,可以在多线程下并发使用,并发修改。

EnumSet是什么?

java.util.EnumSet是使用枚举类型的集合实现。当集合创建时,枚举集合中的全部元素必须来自单个指定的枚举类型,能够是显示的或隐示的。EnumSet是不同步的,不同意值为null的元素。它也提供了一些实用的方法,比方copyOf(Collection c)、of(E first,E…rest)和complementOf(EnumSet s)。

哪些集合类是线程安全的?

Vector、HashTable、Properties和Stack是同步类,所以它们是线程安全的,能够在多线程环境下使用。

Comparable和Comparator接口的区别?

Comparable:
Comparable可以认为是一个内比较器,实现了Comparable接口的类有一个特点,就是这些类是可以和自己比较的,至于具体和另一个实现了Comparable接口的类如何比较,则依赖compareTo方法的实现,compareTo方法也被称为自然比较方法。如果开发者add进入一个Collection的对象想要Collections的sort方法帮你自动进行排序的话,那么这个对象必须实现Comparable接口。compareTo方法的返回值是int,有三种情况:
1、比较者大于被比较者(也就是compareTo方法里面的对象),那么返回正整数
2、比较者等于被比较者,那么返回0
3、比较者小于被比较者,那么返回负整数
Comparator:
Comparator可以认为是是一个外比较器,有两种情况可以使用实现Comparator接口的方式:
1、一个对象不支持自己和自己比较(没有实现Comparable接口),但是又想对两个对象进行比较
2、一个对象实现了Comparable接口,但是开发者认为compareTo方法中的比较方式并不是自己想要的那种比较方式
Comparator接口里面有一个compare方法,方法有两个参数To1和To2,是泛型的表示方式,分别表示待比较的两个对象,方法返回值和Comparable接口一样是int,有三种情况:
1、o1大于o2,返回正整数
2、o1等于o2,返回0
3、o1小于o3,返回负整数
两种比较器Comparable和Comparator,后者相比前者有如下优点:
1、如果实现类没有实现Comparable接口,又想对两个类进行比较(或者实现类实现了Comparable接口,但是对compareTo方法内的比较算法不满意),那么可以实现Comparator接口,自定义一个比较器,写比较算法。
2、实现Comparable接口的方式比实现Comparator接口的耦合性 要强一些,如果要修改比较算法,要修改Comparable接口的实现类,而实现Comparator的类是在外部进行比较的,不需要对实现类有任何修 改。从这个角度说,其实有些不太好,尤其在我们将实现类的.class文件打成一个.jar文件提供给开发者使用的时候。实际上实现Comparator接口的方式后面会写到就是一种典型的策略模式。
当然,这不是鼓励用Comparator,意思是开发者还是要在具体场景下选择最合适的那种比较器而已。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值