集合
一、集合框架
1.集合框架设计的目标
2.集合框架的类型
3.集合框架包含的内容
4.集合框架的优点
5.集合框架中泛型有什么优点
二、集合接口
序号 | 接口 | 描述 |
---|---|---|
1 | Collection | Collection是最基本的集合接口。一个Collection代表一组Object,Java不提供继承Collection的类,只提供继承于的子接口(List,Set).Collection接口存储一组不唯一,无序的对象。 |
2 | List | List是一个有序的Collection,此接口能有序的控制每个元素的位置,能通过索引来访问LIst中的元素。第一个索引为0,可以存储相同的元素。List接口存储一组不唯一,有序的对象。 |
3 | Set | Set具有与Collection完全一样的接口,只是行为上不同,Set不保存重复的元素。Set接口存储一组唯一,无序的对象 |
4 | SortedSet | 继承与Set,保持有序的集合。 |
5 | Map | Map接口存储一组键值对象,提供key到value的映射。 |
6 | Map.Entry | 描述Map中的元素(键值对),Map的内部接口 |
7 | SortedMap | 继承于Map,保持key值在升序排列 |
8 | Enumeration | 被迭代器取代。枚举集合中的元素。 |
SortedSet ss = new TreeSet();
ss.add("aa");
ss.add("bb");
ss.add("aa");
ss.add("ee");
ss.add("dd");
System.out.println("SortedSet-->,"+ss); // SortedSet-->,[aa, bb, dd, ee]
System.out.println("first-->,"+ss.first());// first-->,aa
System.out.println("end-->,"+ss.last()); // end-->,ee
System.out.println("spliterator-->"+ss.spliterator()); //Java8新增,生成Spliterator接口
public static void testMapEntry() {
Map<String, Object> map = new HashMap<>();
map.put("hah", "11");
map.put("hehe", "22");
map.put("xixi", "33");
for (String key : map.keySet()) {
System.out.println("key:" + key + ",value:" + map.get(key));
}
Iterator<Map.Entry<String, Object>> it = map.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<String, Object> entry = it.next();
System.out.println("key:" + entry.getKey() + ",value:" + entry.getValue());
}
// 大容量时
for(Map.Entry<String,Object> entry:map.entrySet()){
System.out.println("key:" + entry.getKey() + ",value:" + entry.getValue());
}
}
// Enumeration用法
public static void testEnumeration() {
Enumeration<String> days ;
Vector<String> list = new Vector<>();
list.add("monday");
list.add("tuesday");
list.add("wednesday");
days = list.elements();
while (days.hasMoreElements()){
System.out.println(days.nextElement());
}
}
三、集合实现类
Java提供了一套实现Collection接口的标准集合类。一些具体的类可以直接拿来使用。另一些抽象类,提供了接口的部分实现
序号 | 类 | 描述 |
---|---|---|
1 | AbstractCollection | 实现了大部分的集合接口 |
2 | AbstractList | 继承与AbstractCollection,并且实现了大部分List接口 |
3 | AbstractSequentialList | 继承于AbstractList,提供了对数据元素的链式访问,而不是随机访问。 |
4 | LinkedList | 该类实现了List接口,允许有null元素,主要用于创建链表结构,该类没有同步方法,如果多个线程要访问同一个List,则必须使用自己的同步方法。List list = Collection.synchronizedList(new LinkedList(...)) |
5 | ArrayList | 实现了List接口,实现了可变大小的数组。随机访问和遍历的时候,提供了更好的性能。该类是非同步的,多线程的时候不能使用。ArrayList增长当前长度的50%,插入,删除效率低下。 |
6 | AbstractSet | 继承AbstractCollection,实现了Set接口 |
7 | HashSet | 实现了Set接口,不允许重复元素,无序,最多允许一个为null的元素 |
8 | LinkedHashSet | |
9 | TreeSet | 实现了Set接口,可以显示排序等功能。 |
10 | AbstractMap | 实现了大部分的Map接口 |
11 | HashMap | HashMap是一个散列表,它存储的内容是键值对(key-value)映射。该类实现了Map接口,根据键的hashcode值来存储数据,具有良好的访问速度,最多允许一个key为null的数据,不支持线程同步 |
12 | TreeMap | 继承了AbstractMap,并且使用一棵树 |
13 | WeakHashMap | 继承于AbstarctMap,使用弱密钥的哈希表 |
14 | LinkedHashMap | 继承于HashMap,使用元素的自然顺序进行排序 |
15 | IdentityHashMap |
java.util包中定义的类
序号 | 类 | 备注 |
---|---|---|
1 | Vector | 与ArrayList相似,线程同步。可以在多线程的情况下使用。该类允许设置默认的增长长度,默认的扩容方式是原来的2倍。 |
2 | Stack | Vector的子类,实现了一个标准的后进先出的栈 |
3 | Dictionary | 抽象类,存储key/value键值对,作用和Map相似 |
4 | Hashtable | 是Dictionary的子类 |
5 | Properties | 继承要Hashtable,持久的属性集,键值都是字符串 |
6 | BitSet | 一个Bitset类创建一种特殊类型的数组来保存位值。BitSet中数组大小会随需要增加。 |
四、迭代器(Iterator)
Iteratior接口,提供了很多对集合元素进行迭代的方法。每一个集合都包含了可以返回迭代器实例的迭代方法。迭代器可以在迭代的过程中删除底层集合迭代的元素,不可以直接调用集合的 **#remove()**方法删除,可以通过迭代器的**#remove()**方法删除
1.Iteator和ListIteratio区别
- Iterator可以来遍历List和Set集合,而ListIterator只可以对List进行遍历
- Iterator对集合只能是前向遍历,ListIterator既可以向前也可以向后
- ListIterator继承了Iterator接口,并包含其他功能增加元素#add(),替换元素#set(E e),获取前一个元素#previousIndex(),后一个元素的索引#nextIndex()等。
public static void testListIterator(){
List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
list.add("d");
list.add("e");
ListIterator iterator = list.listIterator();
while (iterator.hasNext()){
if(iterator.nextIndex()==3){
iterator.set("3");
}
System.out.print(iterator.next()+"--");
}
iterator.add("f");
iterator.add("g");
System.out.println(list);
}
// print a--b--c--d--e--[a, b, 3, d, e, f, g]
2.快速失败(fail-fast)和安全失败(fail-safe)区别
差别在于ConcurrentModification异常:
- 快速失败:当你在迭代一个集合的时候,如果有另一个线程在访问并且修改这个集合,就会抛出ConcurrentModification异常,在java.util包中都是快速失败
- 安全失败:当你在迭代的时候回去底层集合做一个拷贝,所以在修改的时候不会受影响,不会抛出ConcurrentModification异常,在java.util.concurrent包中都是安全失败的
五、区别
1.List和Set区别
- List储存有序可重复的元素,Set存储无序不可重复元素
- Set检索效率低下,删除和插入效率高,插入和删除不会改变元素位置(实现类有HashSet,TreeSet)
- List和数组类似,可以动态增长,根据实际存储数据的长度自动增长list的长度。检索元素效率高,插入删除效率低,因为会引起其他元素位置的改变
2.List和Map区别
- List是对象集合,允许对象重复
- Map是键值对集合,key不允许重复
3.Array和ArrayList区别
- Array可以容纳基本数据类型和对象,而ArrayList只能容纳对象
- Array是指定大小的,而ArrayList大小是固定的,而且可自动扩容。
- Array没有提供ArrayList那么多功能,如add,removeAll,Iterator等方法
Array在以下情况下,会更适用
- 1.多维数组使用array
[][]
[][]比list好用 - 2.如果列表大小已经指定,大部分情况下存储或者遍历他们
- 3.对于遍历基本数据结构类型,尽管Collections使用自动装箱来减轻编码任务,在指定大小的基本类型的列表上工作也会变得很慢。
4.ArrayList和LinkedList
-
优点 缺点 适用场景 ArrayList ArrayList是实现了基于动态数组的数据结构,因为地址连续,一旦存储好了,查询效率会比较高(在内存里是连续放的) 因为地址连续,ArrayList要移动数据,所以删除和插入操作小路比较低 当需对数据进行随机访问时,选用ArrayList,查询效率高 LinkedList LinkedList是基于链表的数据结构,地址是任意的,所以在开辟新的空间的时候不需要等一个连续的地址,对于新增和删除操作,LinkedList优势较大,LinkedList是用于头尾操作和插入指定位置的操作。 LinkedList需要移动指针,查询操作效率较低 需要对数据进行多次增加,删除,编辑的时候,采用LInkedList
5.ArrayList和Vector
- 1.Vector是线程安全的,线程安全就是说多线程访问同一代码,不会产生不确定结果,而ArrayList不是。Vector类源码中可以看到,多个方法都是通过synchronized进行修饰,这样就导致效率上无法和ArrayList相比较。
- 2.两个都是采用线性连续空间存储,但是当空间不足时,两个类的增加方式是不通的。
//ArrayList.java 1.5倍扩容
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
//Vector.java
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
capacityIncrement : oldCapacity);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);
}
- 3.Vector可以设置增长因子,ArrayList 不可以。// Vector v = new Vector(3,2);
public Vector(int initialCapacity, int capacityIncrement) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
this.elementData = new Object[initialCapacity];
this.capacityIncrement = capacityIncrement; // 增长因子
}
使用场景:
6.HasMap和Hashtable
- 1.Hashtable是继承Dictionary,HashMap继承的是java2.0的Map接口
- 2.HashMap去掉了Hashtable中的contains方法,而添加了containsKey和containsValue方法。
- 3.HashMap允许空键值,而Hashtable不允许
- 【重点】4.Hashtable是同步的,而HashMap是非同步的,效率上比Hashtable要高。因此HashMap适用于单线程环境,而Hashtable更适用于多线程环境。
- 5.HashMap的Iterator迭代器是fail-fase快速失败的迭代器,而Hashtable的enumerator迭代器不是fail-fast的。
- 【重点】6.Hashtable中的默认数组大小为11,扩容方法是old*2+1,HashMap的默认大小为16,扩容每次为2的指数大小。
一般不建议使用Hashtable
- 1.Hashtable是遗留类,内部很多没有优化和冗余。
- 2.即使在多线程的情况下,现在有同步的ConcurrentHashMap代替,没有必要因为多线程而使用Hashtable。
7.HashSet和HashMap区别
- 1.Set是线性结构值不能重复,HashSet是Set的hash实现,HashSet中值不能重复是用HashMap中的key来实现的。
- 2.Map是键值对映射,可以是空键值对。HashMap是Map的hash实现,key的唯一性是通过key的hashcode的唯一来确定的,value的值则是链表结构。
8.HashSet和TreeSet区别
- 1.HashSet是用一个hash表的实现的,因此它的元素时无序的。添加,删除和HashSet包含的方法的持续时间的复杂度为O(1)。
- 2.TreeSet是用一个树状结构来实现的,因此它是有序的。添加、删除和TreeSet包含的方法的持续时间的复杂度是o(logn)。
如何旋转HashSet和TreeSet
9. HashMap和ConcurrentHashMap的区别
ConcurrentHashMap是线程安全的HashMap的实现。
- 1.ConcurrentHashMap对整个桶数组进行了分割分段(Segment),然后每一个分段上都用lock锁进行保护,相对于Hashtable的sync关键字的粒度更加精细了一点,并性能更好。而HashMap没有锁机制,不是线程安全的。JDK1.8之后ConcurrentHashMap启用了一种全新的方式实现,利用CAS算法。
- 2.HashMap的键值对允许有null,而ConcurrentHashMap都不允许。
六、问题
1.ArrayList插入1w条数据,如何提高效率?
private static void testArrayListContains() {
Long t1 = System.currentTimeMillis();
List<Integer> list1 = new ArrayList<Integer>();
for (int i = 0; i < 100000; i++) {
list1.add(i);
}
Long t2 = System.currentTimeMillis();
System.out.println("第一种耗时" + (t2 - t1) + "ms");
Long t3 = System.currentTimeMillis();
List<Integer> list2 = new ArrayList<Integer>(100000);
for (int i = 0; i < 100000; i++) {
list2.add(i);
}
Long t4 = System.currentTimeMillis();
System.out.println("第2种耗时" + (t4 - t3) + "ms");
}
// 第一种耗时16ms
// 第2种耗时9ms
2.ConcurrentHashMap为何读不用加锁
JDK1.7
- 1).HashEntry中的key、hash、next均为final类型,只能表头插入、删除节点
- 2).HashEntry类的value域被声明为volatile型
- 3).不允许null作为键和值,当读线程读到一个HashEntry的value域的值为null的时候,便知道产生了冲突-发生了重排序现象(put设置新value对象的字节码指令,重排序),需要加锁后重新读入这个新的value值
JDK1.8
- 1.Node的val和next均为volatile型
- 2.tabAt和casTabAt对应的unsafe操作实现了volatile语义
3.list中可以存放重复字符串,如何删除某个字符串
- 1.调用iterator相关方法删除,
- 2.倒删,防止正序删除导致的数组重排,index跳过数组等问题