Java集合框架概述
对多个对象的存储
数组弊端(故需要集合)
数组在内存存储方面的的特点
- 1、一旦初始化以后,其长度就确定了
- 2、数组一旦定义好,其元素的类型也就确定了。我们也只能操作指定类型的数据了,比如String[] arr; int[] arr1;
数组在存储数据方面的弊端
- 1、一旦初始化以后,其长度就不可修改
- 2、数组中提供的方法有限,对于添加、删除、插入数据等操作、非常不便,效率不高。
- 3、元素个数,没有现成属性或者方法使用
- 4、数组存储数据的特点:有序、可重复。对于无序、不可重复的需求,不能满足
Collection
单列数据
List:有序,可重复
Set:无序,不可重复
Collection接口的方法
-
add
-
contains
- 对比的时候是使用equals(),可根据需求重写
-
iterator
-
遍历元素
- 每次生成都是一个新的迭代器,类似“时钟”重新计时
-
List
1、有序可重复
-
ArrayList
-
作为List接口的主要实现类;
线程不安全,效率高;
底层使用Object[] elementData存储。 -
源码分析
-
jdk 7
- ArrayList list = new ArrayList();
底层创建了长度是10的Object[]数组elementData; - list.add(123);// elementData[0] = new Integer(123);
- …
- list.add(11);// 如果此处的添加导致底层elementData数组容量不够,则扩容。
- 默认情况下,扩容为原来容量的1.5倍,同时需要将原有数组中的数组复制到新的数组中。
- 总结:实际开发中使用带参的构造器 ArrayList list = new ArrayList(int capacity);
- ArrayList list = new ArrayList();
-
jdk 8
- ArrayList list = new ArrayList(); // 底层elementData[] 初始化为{},并没有创建长度为了10的数组
- list.add(123); // 第一次调用add()时,底层才创建长度10的数组,并将数据123添加到elementData中
- 后续添加与jdk 7 一样
-
jdk 7 的ArrayList的对象创建类似于单列的饿汉式,而jdk 8 中的ArrayLis的对象的创建类似于单列的懒汉式,延迟了数组的创建,节省了内存
-
-
-
LinkedList
-
对于频繁的插入。删除操作,使用此类效率比AarrayList高;底层使用双向链表存储
-
源码分析
- ListeedList list = new ListeedList(); // 内部声明了Node类型的first和last属性,默认值为null
- list.add(123); // 将123封装到Node中,创建了Node对象。
-
-
Vector
-
作为List接口的古老实现类;
线程安全,效率低;
底层使用Object[] elementData存储。 -
源码分析
- jdk 7 、 8 中通过Vector()构造器创建对象时,底层都创建了长度为10的数组。在扩容方面,默认扩容为原来的数组长度的2倍。
-
-
List接口方法
- 增、删、改、查、长度、遍历
- 遍历:Iterator、foreach、for
Set
-
概述
- Set接口是Collection的子接口,Set接口没有提供额外的方法,都是Collection的方法
- Set集合不允许包含相同的元素
- Set判断两个对象是否相同不是使用==运算符,而是根据equals()方法
-
属性
-
无序性
- 不等于随机性
- 无序性:存储的数据在底层的数组中并非按照数组索引的顺序添加,而是根据数据的哈希值确定
-
不可重复性
-
保证添加的元素按照equals()判断时,不能返回true,即,相同的元素只能添加一个
-
Set.add()
-
元素a先算hashCode(),通过某种算法,得到元素a应该添加到数组的位置,判断该位置上是否有元素
-
如果此位置上没有其他元素,则元素a添加成功。
-
如果此位置上有其他元素b(或以链表形式存在的多个元素),则比较元素a与元素b的hash值
关于此处的链表
jdk 7 和 jdk 8
七上八下- 如果hash值不相同,则元素a添加成功,添加到链表后。
- 如果hash值相同,进而需要调用元素a所在类的equals()方法:返回true,元素a添加失败;返回false,元素a添加成功,添加到链表后。
-
-
-
-
要求
-
1、向Set中添加的数据,其所在的类一定要重写hashCode()和equals()
-
2、重写的hashCode()和equals()尽可能保存一致性:相同的对象必须具有相等的散列码
- 2.1、在程序运行时,同一对象多次调用hashCode()方法应该返回相同的值
- 2.2、当两个对象的equals()方法比较返回true时,两个对象的hashCode()方法的返回值也应相等
- 2.3、对象中用作equals()方法比较的filed,都应该用来计算hashCode的值
-
-
-
HashSet
- Set的主要实现类;
线程安全;
可以存储null值。
- Set的主要实现类;
-
LinkedHashSet
-
HashSet的子类;
遍历其内部数据时,可以按照添加的顺序遍历 -
添加
- 添加数据的时候,每个数据还维护了两个引用,记录此数据前一个数据和后一个数据
-
-
TreeSet
-
可以按照添加对象的指定属性进行排序
-
两点
-
1、向TreeSet中添加的数据,要求是相同类的对象
-
2、add的两种排序方式
-
自然排序(实现Comparabel接口)
- 比较两个对象是否相同的标准为compareTo()返回0,不再是equals()
-
定制排序(Comparator接口)
- 比较两个对象是否相同的标准为compare(obj1,obj2)返回0,不再是equals()
- TreeSet treeSet = new TreeSet(comparator);
-
-
-
问题
-
1、集合Collection中存储的如果是自定义的类的对象,需要自定义类重写哪个方法?为什么?
-
重写equals()
-
List: equals()
-
Set: (HashSet、LinkedHashSet为例):equals()、hashCode()
-
TreeSet
- 自然排序:Comparable: CompareTo(obj)
- 定制排序:Comparator: compare(obj1,obj2)
-
-
-
2、ArrayList、LinkedList、Vector三者的相同点与不同点
-
相同点
- List的实现
-
不同点
-
-
3、List接口方法
- 增、删、改、查、长度、遍历
-
4、使用Iterator和foreach遍历List
-
5、 Set存储数据的特点?常见的实现类?彼此特点
- 从无序性和不可重复性出发去理解
Collections
是一个操作Set、List和Map等集合的工具类
其操作数组的工具类:Arrays
reverse(List)返转list
shuffle(list) 随机排序
Collections.sort(list)自然排序
Collections.sort(list,Comparator)定制排序
Collections.swap(list, int, int);交换元素位置
Collections.frequency(list, 元素) 某元素出现多少次
copy(List dest, List src)
-
正确写法(主要size的需求)
- List arrayList2 = Arrays.asList(new Object[arrayList1.size()]);
- Collections.copy(arrayList2, arrayList1);
Map
双列集合
Key-Value
Map结构
-
1、Map中的key:无序的,不可重复的,使用Set存储所有的key
- 这就要求key所在的类,重新equals()和hashCode()
-
2、Map中的value:无序的,可重复的,使用Collection 存储所有的value
- 这就要求value所在类,重写equals()
-
3、一个键值对:key-value构成了一个Entry对象,是Entry对象的两个属性
-
4、Map中的entry:无序的,不可重复的,使用Set存储所有的entry
Map 常用方法
-
添加、删除、修改
-
put(key,vlaue)
- 将指定key-value添加(或修改)到当前map对象中
-
putAll(map)
- 将m中的所有key-value对存放到当前map中
-
remove(key)
- 移除指定key的key-value对,并返回value
-
clear()
- 清空当前map中的所有数据
-
-
元素查询操作
-
Object get(key)
- 获取指定key对应的value
-
boolean containsKey(Object key)
- 是否包含指定的key
-
boolean containsValue(Object value)
- 是否包含指定的value
-
int size()
- 返回键值对的个数
-
boolean isEmpty()
- 判断当前map是否为空
-
boolean equals(obj)
- 判断当前map和参数对象obj是否相等
-
-
元视图操作
-
Set keySet()
- 返回所有key构成的Set
-
Collection values()
- 返回所有value构成的Collection集合
-
Set entrySet()
- 返回所有key-value对构成的Set集合
-
HashMap
-
主要实现类;
线程不安全,效率高;
存储null作为key和value -
子类
-
LinkedHashMap
-
保证在遍历map元素时,可以按照添加的顺序实现遍历。
- 原因:在原有的HashMap底层结构基础上,添加了一对指针,指向前一个和后一个元素。对于频繁的遍历操作,此类执行效率高于HashMap
-
底层实现(了解)
- 输出顺序按照put的顺序
- 重写了一些代码,使得可以记录前后元素
-
-
-
底层
-
jdk 7
- 数组+链表
-
jdk 8
- 数组+链表+红黑树
-
TreeMap
-
保证按照添加的key-value对进行排序,实现排序遍历。此时考虑key的自然排序或者定制排序
- 按照key进行排序,这就要求key为同一个类创建的对象
-
底层:红黑树
Hashtable
-
古老实现类;
线程安全,效率低;
不能存储null作为key和value -
子类
-
Properties
-
常用来处理配置文件
- key 和 value都是String类型
- 存取数据时,建议使用setProperty(key,value)和getProperty(key,value)方法
-
-
面试题
-
1、HashMap 的底层实现原理
-
jdk 7
-
HashMap map = new HashMap():
-
1、在实例化以后,底层创建了长度是16的一维数组Entry[] table。
-
2、~
非第一次执行put~ -
3、map.put(key1,value1):
-
3.1、首先,调用key1所在类的hashCode()计算key1哈希值,此哈希值经过某种算法计算以后,得到在Entry数组中的存放位置。
-
3.2、如果此位置上的数据为空,此时的key1-value1添加成功
-
3.2、如果此位置上的数据不为空,(意味着此位置上存在一个或多个数据(以链表形式存在)),比较key1和已经存在的一个或者多个数据的哈希值:
-
3.2.1、如果key1的哈希值与已经存在的数据的哈希值都不相同,此时key1-value1添加成功(七上八下)
-
3.2.2、如果key1的哈希值与已经存在的某一个数据(key2-value2)的哈希值相同,继续比较:调用key1所在类的equals(key2)
- 3.2.2.1、如果equals()返回false:key1-value1添加成功
- 3.2.2.2、如果equals()返回true:使用value1替换value2
-
-
-
-
jdk 8
-
与 jdk7 的不同点
-
1、new HashMap():底层没有创建一个长度为16的数组
-
2、jdk 8底层的数组是:Node[],而非Entry[]
-
单个Node组成结构
-
Hash
- Key
- Value
-
-
-
3、首次调用put()方法时,底层创建长度为16的数组
-
4、jdk 7 底层结构只有:数组+链表。jdk 8 中底层结构:数组+链表+红黑树
-
当数组的某一个索引位置上的元素以链表形式存在的数据个数 > 8且当前数组的长度 > 64 时,此时索引位置上的所有数据改为使用红黑树存储。
- 原因:为了快速索引,查找对比
-
-
-
-
jdk 8 源码分析
-
重要常量
-
DEFAULT_INITIAL_CAPACITY
- HashMap的默认容量:16
-
MAXIMUM_CAPACITY
- HashMap的最大支持容量:2^30
-
DEFAULT_LOAD_FACTOR
- HashMap的默认加载因子:0.75
-
TREEIFY_THRESHOLD
- Bucket中链表长度大于该默认值,转化为红黑树:8
-
UNTREEIFY_THRESHOLD
- Bucket中的红黑树存储的Node小于该默认值,转化为链表
-
MIN_TREEIFY_CAPACITY
- 桶中的Node被树化时最小的hash表容量。
- 当桶中Node的数量大到需要变红黑树时,若hash表容量小于MIN_TREEIFY_CAPACITY时,此时应该执行resize扩容操作这个MIN_TREEIFY_CAPACITY的值至少是TREEIFY_THRESHOLD的四倍 : 64
-
table
- 存储元素的数组,总是2^n
-
entrySet
- 存储具体元素的集
-
size
- HashMap中存储的键值对的数量
-
modeCount
- HashMap扩容和结构改变的次数
-
threshold
-
扩容的临界值 = 容量 * 填充因子
-
16 * 0.75 = 12
- 这数值,太小的话,很快就执行扩容操作,数组就有点浪费资源
- 这数值,太大的话,就是比较迟才扩容,但是很多时候链表很长了,数组还是没填充满,有位置剩。链表太长对索引查找不友好。
-
-
-
loadFactor
-
填充因子
- 其大小决定了HashMap的数据密度
-
-
-
-
扩容
- 扩容为原来容量的2倍,并将原有的数据复制过来
-
-
2、HashMap 和 Hashtable的异同
-
3、CurrentHashMap 与 Hashtable 的异同
使用:iterator.hasNext()和iterator.Next()
框架
= 注解+反射机制+设计模式
异常
throw
- 生成一个异常对象,并抛出。使用在方法内部
- 自动抛出异常对象
throws
- 处理异常的方式。使用在方法声明处的末尾
- try-catch-finally