前言:JAVA集合框架是JAVA编程中一个很重要的框架,在面试和开发中也会经常接触到,为了增强对集合框架的理解,特地在空余时间进行了总结。
一、集合框架的大体结构
二、详解主要类及接口
1.Collection接口
- 集合框架的顶级框架(不是Iterator)
- 是List和Set的父接口(List和Set一些公共的属性和方法放到了父类接口中)
- 但不是Map的父类
2.List接口
- 特点:有序,对象可以重复
- 遍历方式:
List<String> list = new ArrayList<String>();
list.add("a");
list.add("b");
list.add("a");
list.add("c");//1.下标(for循环)
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}//2.foreach jdk>=1.5才有的for循环增强版foreach
for (String s : list) {
System.out.println(s);
}//3.Iterator迭代器(hasNext/next/remove) it拿到迭代器对象,拿到对象要new,new 就需要调构造方法,构造方法也是方法
//.iterator()方法是从List父亲Collection继承来的,既然是从父类继承,那么父类的方法返回一个迭代器
//那么就意味一件事,在父类Collection的iterator()方法中,实例化了一个迭代器对象,所以说父类依赖于Iterator
Iterator<String> it = list.iterator();
Object v = null;
//把it想象成从-1开始,实际上没有-1位置
while(it.hasNext()) {//有没有下一个
v = it.next();//移动下一个
System.out.println(v);
/**如果遍历List集合时又想删除其中的元素,请使用迭代器
it.remove();//删除当前的元素v
*/
}//4.forEach jdk>=1.8
list.forEach(cc -> System.out.println(cc));//Lambda表达式(jdk>=1.8),表达式由三部分组成:参数列表,箭头(→),一个表达式或语句块。
/*其所有遍历的结果都是
a
b
a
c */新增内容:
ListIterator listIterator = list.listIterator();//迭代器,继承Iterator
while (listIterator.hasNext()) {//向前遍历
Object object = (Object) listIterator.next();
System.out.println(object);
}
System.out.println("xxxxxxxxxxxxx");
while (listIterator.hasPrevious()) {//向后遍历
Object object = (Object) listIterator.previous();
System.out.println(object);
}/*其遍历的结果是
a
b
c
d
e
xxxxxxxxxxxxx
e
d
c
b
a */如果直接使用向后遍历:
ListIterator listIterator = list.listIterator();
System.out.println("xxxxxxxxxxxxx");
while (listIterator.hasPrevious()) {
Object object = (Object) listIterator.previous();
System.out.println(object);
}/*其遍历的结果是
xxxxxxxxxxxxx */<ListIterator可向前向后遍历,list依赖于ListIterator,所以可向前向后遍历>
- ArrayList、Vector、LinkedList区别
| ArrayList | Vector | LinkedList |
结构 | 数组结构 | 数组结构 | 链表结构 |
线程是否同步 | 不同步 | 同步 | 不同步 |
线程是否安全 | 线程不安全 | 线程安全 | 线程不安全 |
⑴ArrayList 与Vector的区别
相同点:ArrayList 与Vector都实现了Collection接口,它们都是有序集合,并且数据是可重复的
不同点:
①同步性:Vector线程同步、安全,ArrayList 线程不同步、不安全;由于Vector使用了synchronized方法(加锁,线程安全),通常性能比ArrayList 差。
②数据增长:Vector默认增长为原来的两倍,ArrayList默认增长为原来的1.5倍(依据jdk)。Vector 和ArrayList可以设置初始容量,即初始空间大小,Vector可以设置负载因子,即增长的空间大小,而ArrayList则没有提供可以设置负载因子的方法。
⑵ArrayList和LinkedList的区别
相同点:ArrayList和LinkedList都是线程不同步和线程不安全
不同点:ArrayList使用数组方式存储数据,此数组可存储的元素数大于实际存储的数据以便增加和插入元素,它们都允许直接按下标查找元素,但是插入元素要涉及数组元素移动等内存操作,所以查询数据快而插入数据和删除数据慢;而LinkedList使用双向链表实现存储,按下标查找数据需要进行前向或后向遍历,但是插入数据和删除数据只需要记录本项的前后项即可,所以插入和删除速度快,而查询慢。
或者可以这样说,ArrayList查询快,写数据慢;LinkedList查询慢,写数据快。ArrayList查询快是因为底层是由数组实现,通过下标定位数据快。写数据慢是因为复制数组耗时。LinkedList底层是双向链表,查询数据依次遍历慢。写数据只需修改指针引用。
(但是如果详细去探索在不同场景下ArrayList和LinkedList的读写速度的话,那么LinkedList写入的速度不一定比ArrayList写入的速度,详细情况请看【】)
应用场景: 平时项目中的查询所有操作使用的都是ArrayList;LinkedList ——贪食蛇游戏
package p2;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
/**
*测试ArrayList和LinkedList的读写速度
* 在集合中装5万条数据
* @author Min.Z
*
*/
public class Demo1 {
static final int N=50000;
static long timeList(List list){//增加消耗的时长
long start=System.currentTimeMillis();
Object o = new Object();
for(int i=0;i<N;i++) {
list.add(0, o);
}
return System.currentTimeMillis()-start;
}
static long readList(List list){//读取的时长
long start=System.currentTimeMillis();
for(int i=0,j=list.size();i<j;i++){
list.get(i);
}
return System.currentTimeMillis()-start;
}
static List addList(List list){
Object o = new Object();
for(int i=0;i<N;i++) {
list.add(0, o);
}
return list;
}
public static void main(String[] args) {
System.out.println("ArrayList添加"+N+"条耗时:"+timeList(new ArrayList()));
System.out.println("LinkedList添加"+N+"条耗时:"+timeList(new LinkedList()));
List list1=addList(new ArrayList<>());
List list2=addList(new LinkedList<>());
System.out.println("ArrayList查找"+N+"条耗时:"+readList(list1));
System.out.println("LinkedList查找"+N+"条耗时:"+readList(list2));
}
}
3.Set接口
- 特点:无序,对象不可重复
- 遍历方式:
Set<Integer> set = new HashSet<Integer>();
set.add(1);
set.add(21);
set.add(5);
set.add(3);
set.add(6);
set.add(3);
//1.foreach
for (Integer n : set) {
System.out.println(n);
}
//2.Iterator
Iterator<Integer> it = set.iterator();
while(it.hasNext()) {
System.out.println(it.next());
}
//3.forEach
set.forEach(cc -> System.out.println(cc));
/**
* 其结果都是1 3 21 5 6
* Set无序并不代表没有规律。无论遍历多少遍或者用其它的方法遍历,打印的结果都是一样的。
* 其原因是:因为里面的算法是一样的,都是哈希算法。对于这个我会有一篇博客专门讲解。
*/
- HashSet、TreeSet、LinkedHashSet区别
①.需要速度快的集合,使用HashSet
②.需要集合有排序功能,使用TreeSet
③.需要按照插入的顺序存储集合(插入数据的顺序和输出顺序是一样的),使用LinkedHashSet
4.Map接口
- 特点:无序,以键值对的形式添加元素,键不能重复,值可以重复,键相同,值会覆盖
- 遍历方式:
Map<String, String> map = new HashMap<String,String>();
map.put("zs", "张三");
map.put("ls", "李四");
map.put("ww", "王五");
map.put("zs", "王哈");
map.put("hh", "哈哈");//1.map.keySet()获得Map所有的键,返回一个Set集合并保存,再根据键去查找值
Set<String> keySet = map.keySet();//map集合的key
//①foreach
for (String key : keySet) {
System.out.println("键:"+key+" 值:"+map.get(key));
}
//②Iterator
Iterator<String> it = keySet.iterator();
String k = null;
while(it.hasNext()) {
k=it.next();
System.out.println("键:"+k+" 值:"+map.get(k));
}//2.通过entrySet()方法将map集合中的映射关系取出(这个关系就是Map.Entry类型),再遍历此Set
Set<Map.Entry<String, String>> entrySet = map.entrySet();//因为键不重复,所以取出的整体(Map.Entry)绝对不重复
//①foreach
for (Entry<String, String> entry : entrySet) {
System.out.println("键:"+entry.getKey()+" 值:"+entry.getValue());}
//②Iterator
Iterator<String> it2 = keySet.iterator();
String k2 = null;
while(it2.hasNext()) {
k2=it2.next();
System.out.println("键:"+k2+" 值:"+map.get(k2));
}
/**
* 虽然使用keySet及entrySet来进行遍历能取得相同的结果,但两者的遍历速度是有差别的。
* keySet():迭代后只能通过keySet()取key
* entrySet():迭代后可以entry.getKey(),entry.getValue()取key和value。返回的是Entry接口 (整体,映射关系)
* 说明:keySet()的速度比entrySet()慢了很多,也就是keySet方式遍历Map的性能不如entrySet性能好
* 所以当我们数据是海量的时候,使用entrySet遍历可优化程序性能
*/
/**
* 思考:在Map中keySet()遍历中,我们获得的键都是Set集合,所以决定了Map集合的键是无序且不能重复的
* 且Map的打印顺序都是根据键找值,就说明,即Map打印的顺序也与哈希算法有关。
*/
- HashMap、TreeMap、LinkedHashMap区别
①.在Map中插入、删除和定位元素,HashMap是最好的选择
②.需要集合有排序功能,使用TreeMap更好
③.需要按照插入的顺序存储集合,使用LinkedHashMap
- HashMap的实现原理
通过put和get存储和获取对象。
存储对象时,我们将K/V传给put方法时,它调用hashcode计算hash从而得到bucket位置,进一步存储,HashMap会根据当前bucket的占用情况自动调整容量。
获取对象时,我们将K传递给get,他调用hashcode计算hash从而得到bucket位置,并进一步调用equals()方法确认键值对。