一、Java集合框架概述
1、什么是集合
- 集合框架:用于存储数据的容器。
- 数组、集合等存储数据的结构,叫Java容器。
- 此时的存储,是指内存层面的存储,不涉及持久化的存储。
- 任何集合框架都包含三大块的内容:对外的接口、接口的实现、对集合运算的算法。
2、集合的特点
-
数组的特点/缺点:
- 长度固定。一旦初始化,长度不能修改。
- 类型确定。类型严格(算是一个好处),当然要想可以放多种类型的数据,也可以声明为Object类型。
- 方法有限。添加、删除元素效率低;获取元素个数不方便。
- 元素有序可重。对于无序、不重复的元素,不能满足。
-
集合的特点/优点
- 容量自增长。
3、集合的体系
- Collection接口和Map接口是所有集合框架的父接口。
- Collection接口继承树
说明:
- List接口:存储有序、可重复的数据。“动态数组”
- Set接口:存储无序、不可重复的数据。“数学中的集合”
- Map接口继承树
说明:
- Map接口:双列集合,用于存储成对的数据(key-value)。“数学中的函数”
二、Collection接口中的方法(15个)
1、说明
- 因为Collection接口是List、Set、Queue接口的父接口,所以定义在Collection接口的中方法可以用于操作子接口的实现类的对象。
2、15个方法
方法 | 描述 |
---|---|
add(Object obj) | 添加元素 |
addAll(Collection c) | 添加另一个集合中所以元素 |
size() | 元素个数 |
clear() | 清空集合 |
isEmpty() | 是否为空 |
contains(Object obj) | 包含某个元素 |
containsAll(Collection c) | 包含某个集合中所有元素 |
remove(Object obj) | 删除元素 |
removeAll(Collection c) | 差集 |
retainAll(Collection c) | 交集 |
equals(Object obj) | 判断集合是否相等 |
toArray() | 转换为数组 |
toArray(T[] a) | |
hashCode() | 求集合的哈希值 |
iterator() | 返回迭代器对象 |
注意事项:
-
想Collection接口的实现类对象中添加数据obj时,要求obj所在类重写equals()。
-
数组--->集合:Arrays.asList()
-
List arr1 = Arrays.asList(new int[]{123, 456}); System.out.println(arr1.size());//1
-
List arr2 = Arrays.asList(new Integer[]{123, 456}); System.out.println(arr2.size());//2
-
asList中是整型时,用int,默认会把数组当场一个对象。
-
3、Iterator迭代器接口
- Iterator是什么?
- Iterator对象成为迭代器,是设计模式的一种。主要用于遍历集合中的元素。
- 迭代器模式:提供一种方法访问一个容器(container)对象中各个元 素,而又不需暴露该对象的内部细节。迭代器模式,就是为容器而生。
- Iterator接口中方法
- hasNext()
- next()
- remove()遍历过程中移除某元素
- Iterator可以删除集合的元素,但是是遍历过程中通过迭代器对象的 remove方 法,不是集合对象的remove方法。
- 如果还未调用next()或在上一次调用 next 方法之后已经调用了 remove 方法, 再调用remove都会报IllegalStateException
- 怎么用Iterator遍历集合
//用集合中的iterator方法得到迭代器对象
Iterator iterator = coll.iterator();
//遍历集合
while(iterator.hasNext()){
System.out.println(iterator.next());
}
//当然我们也可以用foreach来遍历,其实foreach本质也是调用了迭代器。
-
Iterator原理
- 得到的迭代器对象,默认指针指向第一个元素的前面
- hasNext()来判断下一位是否有元素。(在调用it.next()方法之前必须要调用it.hasNext()进行检测。若不调用,且 下一条记录无效,直接调用it.next()会抛出NoSuchElementException异常)
- next():两个作用:①指针下移②取出指针指向的元素
-
注意
- 集合对象每次调用iterator()方法都得到一个全新的迭代器对象,默认游标都在集合 的第一个元素之前。因此下面这种写法不正确
while (coll.iterator().hasNext()){
System.out.println(coll.iterator().next());
}
三、List接口
1、List接口概述
- List接口是Collection的子接口
- List被称为“动态数组”,是因为数组的局限性而代替数组的一种容器。
- 存储的元素有序、可重复。每个元素都有对应的顺序索引。
- 实现类有三个:ArrayList、LinkedList、Vector
2、ArrayList、LinkedList、Vector的异同(面试题)
- 同
- 三个类都是List接口的实现类,因此存储数据都是有序可重复的。
- 异
实现类 | 地位 | since | 线程安全否 | 底层 | 应用场景 |
---|---|---|---|---|---|
ArrayList | 主要实现类 | 1.2 | 线程不安全、效率高 | Object[] | 遍历、查找 |
LinkedList | 1.2 | 双向链表 | 频繁插入、删除 | ||
Vector | 古老实现类 | 1.0 | 线程安全、效率低 | Object[] |
3、源码分析(加分项)
-
ArrayList源码分析-----JDK7
- 创建:调用空参构造器时,底层创建一个长度为10的Object[]数组elementData
- 扩容:默认扩容为原来的1.5倍(使用移位操作),并将原来数组中的数据复制到新的数组中
- 结论:开发中使用带参构造器指定容量:
ArrayList list = new ArrayList(int capacity)
-
ArrayList源码分析-----JDK8
- 创建:调用空参构造器时,底层创建一个Object[]数组elementData,初始化为空{}
- 扩容:同JDK7
- 对比:jdk7中的ArrayList的对象的创建类似于单例的饿汉式,而jdk8中的ArrayList的对象的创建类似于单例的懒汉式,延迟了数组的创建,节省内存。
-
LinkedList源码分析
- 创建:调用空参构造器时,内部声明了Node类型的first和last属性,默认值为null
- 添加:将数据封装在Node中,创建Node对象
-
Vector源码分析
- 创建:调用空参构造器时,底层创建一个长度为10的Object[]数组elementData
- 扩容:默认扩容为原来的2倍,并将原来数组中的数据复制到新的数组中
4、List常用方法
-
List除了从Collection集合继承的方法外,还有一些独有的、根据索引来操作集合元素的方法。
-
总结的简化版(常用的、便于记忆的总结)
常用方法的作用 | 方法名 |
---|---|
增 | add(Object obj) |
删 | remove(Object obj)、remove(int index) |
改 | set(int index, Object obj) |
查 | get(int index) |
插 | add(int index, Object obj) |
长度 | size() |
遍历 | ①、Iterator迭代器 ②、增强for循环 ③、普通for循环 |
- 区分List中的remove方法
- list.remove(2):删除索引为2的,因为有remove(int index)的方法,直接匹配上不用自动装箱就能匹配,name何必自动装箱呢?
- list.remove(new Integer(2)):删除数据2,匹配的是remove(Object obj)的方法
四、Set接口
1、Set接口概述
- Set接口是Collection的子接口。
- Set中没有额外的方法
- Set中的元素不可重复,判断是否相等用的是equals()方法
- 实现类有HashSet、LinkedHashSet、TreeSet
2、HashSet、LinkedHashSet、TreeSet的异同
- HashSet:是Set接口的主要实现类;线程不安全;可以存储null值;
- LinkedHashSet:是HashSet的子类;可以按照添加顺序遍历,遍历效率高;
- TreeSet:可以按照添加对象的指定属性排序
- 要求1:添加的对象类型相同
- 要求2:对象的类要实现Comparable接口来进行自然排序,或者,实现Comparator接口来进行定制排序
- 自然排序中,比较两个对象是否相同的标准为(是否可添加“相同对象”):compareTo()返回0.不再是equals().
- 定制排序中,比较两个对象是否相同的标准为:compare()返回0.不再是equals().
3、理解无序、可重复
- 无序
- 指的是存储在底层数组的数据,并不是按照数组索引的顺序添加的,而是根据数据的哈希值决定的。
- 无序性,不等于随机性
- 不可重复
- 相同的元素只能添加一个。元素按照equals()判断,不能返回true。
4、添加元素的过程(以HashSet为例)
-
添加过程并不会单独考察,但是对于理解HashMap有很大帮助
-
我们向HashSet中添加元素a,首先调用元素a所在类的hashCode()方法,计算元素a的哈希值,此哈希值接着通过某种算法计算出在HashSet底层数组中的存放位置(即为:索引位置),判断数组此位置上是否已经有元素:
- 如果此位置上没有其他元素,则元素a添加成功。 --->情况1
- 如果此位置上有其他元素b(或以链表形式存在的多个元素),则比较元素a与元素b的hash值:
- 如果hash值不相同,则元素a添加成功。--->情况2
- 如果hash值相同,进而需要调用元素a所在类的equals()方法:
- equals()返回true,元素a添加失败
- equals()返回false,则元素a添加成功。--->情况3
-
对于添加成功的情况2和情况3而言:元素a 与已经存在指定索引位置上数据以链表的方式存储
- jdk 7 :元素a放到数组中,指向原来的元素。
- jdk 8 :原来的元素在数组中,指向元素a
- 总结:七上八下
-
HashSet底层:数组+链表的结构。
5、重写hashCode()和equals()方法
-
要求:向Set(主要指:HashSet、LinkedHashSet)中添加的数据,其所在的类一定要重写hashCode()和equals()
-
重写要求:重写的hashCode()和equals()尽可能保持一致性:相等的对象必须具有相等的散列码
-
重写两个方法的小技巧:对象中用作 equals() 方法比较的 Field,都应该用来计算 hashCode 值。
6、面试题
-
在List内去除重复数字值,要求尽量简单
- 可以用Set实现
-
//程序的输出结果 HashSet set = new HashSet(); Person p1 = new Person(1001,"AA"); Person p2 = new Person(1002,"BB"); set.add(p1); set.add(p2); p1.name = "CC"; set.remove(p1); System.out.println(set);//[Person{id=1002, name='BB'}, Person{id=1001, name='CC'}] /* 存放p1、p2时,根据p1和p2的属性算出了hashCode来确定存放位置 p1的name改为CC后,再删除p1--->根据此时p1的属性算hashCode来找set中和p1相同的元素,此时算出来的hashCode大概率不是p1的位置,相当于没有找到,所以删除并没有成功。 */ set.add(new Person(1001,"CC")); System.out.println(set); //[Person{id=1002, name='BB'}, Person{id=1001, name='CC'}, Person{id=1001, name='CC'}] /* 存放新建的p,是根据1001和CC来算hashCode,此时数组中这个位置空的,所以存放成功 */ set.add(new Person(1001,"AA")); System.out.println(set);//[Person{id=1002, name='BB'}, Person{id=1001, name='CC'}, Person{id=1001, name='CC'}, Person{id=1001, name='AA'}] /* 存放新建的p,先hashCode后和p1是一样的,再equals发现不一样,加到链表上,存放成功 */
五、Map接口
1、Map接口概述
- Map接口继承树
- Map接口:
- 存储key-value的键值对,类似于数学中的函数
- Map实现类对比
Map实现类 | 底层实现 | 线程 | 地位 | 特点 |
---|---|---|---|---|
HashMap | JDK7及以前:数组+链表 JDK8:数组+链表+红黑树 |
线程不安全 | 主要实现类 | 可以存储null的k-v |
LinkedHashMap | 同上 | 同上 | 同上 | 可按照添加顺序遍历 |
TreeMap | --- | --- | --- | 可排序遍历 |
Hashtable | --- | 线程安全 | 古老实现类 | 不可存储null的k-v |
Properties | --- | --- | --- | 常用来处理配置文件 |
2、面试题
-
HashMap的底层实现原理★★★★★(见下面第四点)
-
HashMap和Hashtable的异同
-
CurrentHashMap和Hashtable的异同(暂时不学)
3、Map结构的理解
-
Map中的key:无序的、不可重复的,使用Set存储所有的key ---> key所在的类要重写equals()和hashCode() (以HashMap为例);当然如果是LinkedHashMap还要实现排序接口
-
Map中的value:无序的、可重复的,使用Collection存储所有的value --->value所在的类要重写equals()
-
一个键值对:key-value构成了一个Entry对象。
-
Map中的entry:无序的、不可重复的,使用Set存储所有的entry
四、 HashMap的底层实现原理★★★★★(高频面试题)
1、JDK7
-
HashMap map = new HashMap()
:底层创建一个长度为16的数组Entry[] table。 -
map.put(key1, value1)
- 调用key1所在类的hashCode()方法计算key1的哈希值,用哈希值在计算得到在Entry[]数组中的存放位置
- 如果此位置为空,添加成功------>情况1
- 如果此位置不为空,比较key1和已经存在的数据的哈希值
- 如果key1的哈希值和已经存在的数据的哈希值不同,添加成功------>情况2
- 如果key1的哈希值和已经存在的某一个数据(key2,value2)的哈希值相同,继续调用key1所在类的equals()方法,传入参数key2进行比较
- 如果equals()返回值为false,添加成功------>情况3
- 如果equals()返回值为true,用value1替换value2
- 调用key1所在类的hashCode()方法计算key1的哈希值,用哈希值在计算得到在Entry[]数组中的存放位置
-
扩容:(当超出临界值且要添加数据的位置不为空时)默认扩容为原来的2倍,并复制数据到新数组
2、JDK8与JDK7的不同之处
HashMap map = new HashMap()
时,底层没有直接创建数组,而是在首次put()时创建- 数组是Node[]类型,而非Entry[]类型
- JDK7底层是:数组+链表;而JDK8底层是:数组+链表+红黑树
- 形成链表时,七上八下(jdk7:新的元素指向旧的元素。jdk8:旧的元素指向新的元素)
- 当数组的某一个索引位置上的元素以链表形式存在的数据个数 > 8 且当前数组的长度 > 64时,此时此索引位置上的所数据改为使用红黑树存储。
3、LinkedHashMap底层实现原理
static class Entry<K,V> extends HashMap.Node<K,V> {
Entry<K,V> before, after;//能够记录添加的元素的先后顺序
Entry(int hash, K key, V value, Node<K,V> next) {
super(hash, key, value, next);
}
}
六、Map接口常用方法
1、添加、删除、修改操作:
-
Object put(Object key,Object value)
:将指定key-value添加到(或修改)当前map对象中void putAll(Map m)
:将m中的所有key-value对存放到当前map中 -
Object remove(Object key)
:移除指定key的key-value对,并返回value
void clear()
:清空当前map中的所有数据
2、 元素查询的操作:
-
Object get(Object key)
:获取指定key对应的value -
boolean containsKey(Object key)
:是否包含指定的keyboolean containsValue(Object value)
:是否包含指定的value -
int size()
:返回map中key-value对的个数 -
boolean isEmpty()
:判断当前map是否为空 -
boolean equals(Object obj)
:判断当前map和参数对象obj是否相等
3、元视图操作的方法:
Set keySet()
:返回所有key构成的Set集合Collection values()
:返回所有value构成的Collection集合Set entrySet()
:返回所有key-value对构成的Set集合
4、总结常用方法:
- 添加:
put(Object key,Object value)
- 删除:
remove(Object key)
- 修改:
put(Object key,Object value)
- 查询:
get(Object key)
- 长度:
size()
- 遍历:
keySet() / values() / entrySet()
七、Properties
1、配置文件
-
默认存放路径为当前工程下
-
创建配置文件的方式
- 右键工程名--->new--->File:这时要写上
.properties
,如jdbc.properties
- 右键工程名--->new--->Resource Bundle:这时秩序写名称就会自动补全后缀
- 右键工程名--->new--->File:这时要写上
-
存取数据时,建议使用
setProperty(String key,String value)
方法和getProperty(String key)
方法
八、Collections工具类
1、Collections概述
- Collections是一个操作Set、List、Map等集合的工具类
- Collections 中提供了一系列静态的方法对集合元素进行排序、查询和修改等操作, 还提供了对集合对象设置不可变、对集合对象实现同步控制等方法
2、Collections常用方法
排序操作
- 排序操作都是针对List来讲的,因为Map本身无序,何谈排序
- 返回值都是void,说明对List本身做了修改
方法 | 解释 |
---|---|
reverse(List) | 反转List中的元素 |
shuffle(List) | 随机排序 |
sort(List) | 自然排序 |
Sort(List, Comparator) | 定制排序 |
swap(List, int i, int j) | 交换 |
查询、替换
方法 | 解释 |
---|---|
Object max/min(Collection) | 返回自然排序的最大、小值 |
Object max/min(Collection, Comparator) | 返回定制排序的最大、小值 |
int frequency(Collection, Object) | 返回某集合中某元素的出现次数 |
void copy(List dest, List src) | 复制 |
boolean replaceAll(List list, Object oldVal, Object newVal) | 替换 |
- void copy(List dest, List src)中,新List容量不能小于旧List,因此要用
List dest = Arrays.asList(new Object[list.size()]);
来创建