Collection和Map
一、集合和数组的区别:1、数组是固定长度的;集合可变长度的。2、数组可以存储基本数据类型,也可以存储引用数据类型;集合只能存储引用数据类型。3、数组存储的元素必须是同一个数据类型;集合存储的对象可以是不同数据类型。二、集合和Map的继承关系:Java 容器分为 Collection 和 Map 两大类,Collection集合的子接口有Set、List、Queue三种子接口。我们比较常用的是Se
目录
一、集合和数组的区别
1、数组是固定长度的;集合可变长度的。
2、数组可以存储基本数据类型,也可以存储引用数据类型;集合只能存储引用数据类型。
3、数组存储的元素必须是同一个数据类型;集合存储的对象可以是不同数据类型。
二、集合和Map的继承关系
Java 容器分为 Collection 和 Map 两大类,Collection集合的子接口有Set、List、Queue三种子接口。我们比较常用的是Set、List,注意:Map接口不是collection的子接口。
Collection集合主要有List和Set两大接口
1、List:一个有序(元素存入集合的顺序和取出的顺序一致)容器,元素可以重复,可以插入多个null元素,元素都有索引。
常用的实现类有 ArrayList、LinkedList和 Vector。
2、Set:一个无序(存入和取出顺序有可能不一致)容器,不可以存储重复元素,只允许存入一个null元素,必须保证元素唯一性。
Set 接口常用实现类是 HashSet、LinkedHashSet 以及 TreeSet。
Map是一个键值对集合,存储键、值和之间的映射。 Key无序,唯一;value 不要求有序,允许重复。Map没有继承于Collection接口,从Map集合中检索元素时,只要给出键对象,就会返回对应的值对象。
Map 的常用实现类:HashMap、TreeMap、HashTable、LinkedHashMap、ConcurrentHashMap。
三、集合框架底层数据结构
Collection:
1、List
1。Arraylist: Object数组
2.Vector: Object数组
3.LinkedList: 双向循环链表
2、 Set
1.HashSet(无序,唯一):基于 HashMap 实现的,底层采用 HashMap 来保存元素
2.LinkedHashSet: LinkedHashSet 继承与 HashSet,并且其内部是通过LinkedHashMap 来实现的。有点类似于我们之前说的LinkedHashMap 其内部是基于 Hashmap 实现一样,不过还是有一点点区别的。
4.TreeSet(有序,唯一): 红黑树(自平衡的排序二叉树。)
Map:
1、HashMap: JDK1.8之前HashMap由数组+链表组成的,数组是HashMap的主
体,链表则是主要为了解决哈希冲突而存在的(“拉链法”解决冲突).JDK1.8以后
在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转
化为红黑树,以减少搜索时间
2、LinkedHashMap:LinkedHashMap 继承自 HashMap,所以它的底层仍然是
基于拉链式散列结构即由数组和链表或红黑树组成。另外,LinkedHashMap 在上面
结构的基础上,增加了一条双向链表,使得上面的结构可以保持键值对的插入顺序。
同时通过对链表进行相应的操作,实现了访问顺序相关逻辑。
3、HashTable: 数组+链表组成的,数组是 HashMap 的主体,链表则是主要为
了解决哈希冲突而存在的
4、TreeMap: 红黑树(自平衡的排序二叉树)
四、集合对比
1.ArrayList、LinkedList、Vector
1.ArrayList 和Vector他们底层的实现都是一样的,都是使用数组方式存储数据,此数组元素数大于实际存储的数据以便增加和插入元素,它们都允许直接按序号索引元素,但是插入元素和删除要涉及数组元素移动等内存操作,所以查询数据快而插入数据慢。
Vector中的方法由于添加了synchronized修饰,因此Vector是线程安全的容器,但性能上较ArrayList差,因此已经是Java中的遗留容器
2.Linkedlist,双向链表,优点,增加删除,用时间很短,但是因为没有索引,对索引的操作,比较麻烦,只能循环遍历,但是每次循环的时候,都会先判断一下,这个索引位于链表的前部分还是后部分,每次都会遍历链表的一半 ,而不是全部遍历。
双向链表,都有一个previous和next, 链表最开始的部分都有一个fiest和last指向第一个元素,和最后一个元素。增加和删除的时候,只需要更改一个previous和next,就可以实现增加和删除,所以说,LinkedList对于数据的删除和增加相当的方便
3.ArrayList和LinkedListed都是非线程安全的,如果遇到多个线程操作同一个容器的场景,则可以通过工具类Collections中的synchronized List方法将其转换成线程安全的容器后再使用(这是对装潢模式的应用,将已有对象传入另一个类的构造器中创建新的对象来增强实现),或者CopyOrWriteArrayList
2.ArrayList、Set
Set集合是无序不可以重复的的、List集合是有序可以重复的。
ArrayList是数组存储的方式,HashSet存储会先进行HashCode值得比较(hashcode和equals方法),若相同就不会再存储。
补充一下:Hashset就是采用哈希算法存取对象的集合,对象用完之后没有回收就是内存泄漏。一个对象一旦hashCode生成之后,再对属性值修改后其Hashcode值就会发生改变,再通过hashSet删除就删除不掉了
3.Hashtable、HashMap、LinkedHashMap、TreeMap、Properties
Hashtable:线程安全,速度慢。底层是哈希表数据结构。是同步的。不允许null作为键,null作为值。
Properties:用于配置文件的定义和操作,使用频率非常高,同时键和值都是字符串。是集合中可以和IO技术相结合的对象。
HashMap:线程不安全,速度慢。底层也是哈希表数据结构。是不同步的。允许null作为键,null作为值。替代了Hashtable.
LinkedHashMap: 可以保证HashMap集合有序。存入的顺序和取出的顺序一致。
TreeMap:可以用来对Map集合中的键进行排序.
五、扩容
JDK1.7–》相当于设计模式中的饿汉式 第一次创建无参构造器时就创建一个初始容量为10的数组
JDK1.8–》相当于涉及模式中的懒汉式 ArrayList可以通过构造⽅法在初始化的时候指定底层数组的⼤⼩。 通过⽆参构造⽅法的⽅式ArrayList()初始化,则赋值底层数Object[] elementData为⼀个默认空数组
Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA ={},所以数组容量为0,只有真正对数据进⾏添加add时,才分配默认DEFAULT_CAPACITY = 10的初始容量。
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
假设我们有一个长度为10的数组,此时需要新增一个,发现满了,ArrayList会进行扩容
重新定义一个10*1.5的数组
然后把原数组的数据,原封不动的复制到新数组中,然后把ArrauList的地址指向新数组
//详细扩容
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);
}
1.7的时候是初始化就创建一个容量为10的数组,1.8后是初始化先创建一个空数组,第一次add时才扩容为10
ArrayList(int initialCapacity)会不会初始化数组⼤⼩?
会初始化大小,但是list的大爱小没有变,也就是size不变
⽽且将构造函数与initialCapacity结合使⽤,然后使⽤set()会抛出异常,尽管该数组已创建,但是⼤⼩设置不正确。
使⽤sureCapacity()也不起作⽤,因为它基于elementData数组⽽不是⼤⼩。
还有其他副作⽤,这是因为带有sureCapacity()的静态DEFAULT_CAPACITY。
进⾏此⼯作的唯⼀⽅法是在使⽤构造函数后,根据需要使⽤add()多次。
六、fail-fast和fail-safe
1.fail-fast
在用迭代器遍历一个集合对象时,如果遍历过程中对集合对象的内容进行了修改(增加、删除、修改),则会抛出Concurrent Modification Exception。
原理:迭代器在遍历时直接访问集合中的内容,并且在遍历过程中使用一个 modCount 变量。集合在被遍历期间如果内容发生变化,就会改变modCount的值。每当迭代器使用hashNext()/next()遍历下一个元素之前,都会检测modCount变量是否为expectedmodCount值,是的话就返回遍历;否则抛出异常,终止遍历。
注意:这里异常的抛出条件是检测到 modCount!=expectedmodCount 这个条件。如果集合发生变化时修改modCount值刚好又设置为了expectedmodCount值,则异常不会抛出。因此,不能依赖于这个异常是否抛出而进行并发操作的编程,这个异常只建议用于检测并发修改的bug。
场景:java.util包下的集合类都是快速失败的,不能在多线程下发生并发修改(迭代过程中被修改)。
解决办法:
在遍历过程中,所有涉及到改变modCount值得地方全部加上synchronized。
使用CopyOnWriteArrayList来替换ArrayList
2.fail-safe
采用安全失败机制的集合容器,在遍历时不是直接在集合内容上访问的,而是先复制原有集合内容,在拷贝的集合上进行遍历。
原理:由于迭代时是对原集合的拷贝进行遍历,所以在遍历过程中对原集合所作的修改并不能被迭代器检测到,所以不会触发Concurrent Modification Exception。
缺点:基于拷贝内容的优点是避免了Concurrent Modification Exception,但同样地,迭代器并不能访问到修改后的内容,即:迭代器遍历的是开始遍历那一刻拿到的集合拷贝,在遍历期间原集合发生的修改迭代器是不知道的。
场景:java.util.concurrent包下的容器都是安全失败,可以在多线程下并发使用,并发修改。
3.fail-fast(快速失败)和fail-safe(安全失败)比较
Iterator的安全失败是基于对底层集合做拷贝,因此,它不受源集合上修改的影响。java.util包下面的所有的集合类都是快速失败的,
而java.util.concurrent包下面的所有的类都是安全失败的。快速失败的迭代器会抛出ConcurrentModificationException异常,而安全失败的迭代器永远不会抛出这样的异常。
七、怎么确保一个集合不能被修改
可以使用 Collections. unmodifiableCollection(Collection c) 方法来创建一个
只读集合,这样改变集合的任何操作都会抛出 Java. lang.
UnsupportedOperationException 异常。
List<String> list = new ArrayList<>();
list. add("x");
Collection<String> clist = Collections. unmodifiableCollection(list);
clist. add("y"); // 运行时此行报错
System. out. println(list. size());
更多推荐
所有评论(0)