1、集合主要分两种
1.1 单一元素(Collection)
1.1.1 List
- 特点:有序可重复
- List接口中常见的实现类是ArrayList、LinkedList、Vector 三者的比较
- 线程的安全性 vector是线程安全的,每个方法都有synchronized关键字
- 扩容的机制 ArrayList扩容是扩大1.5倍,vector是扩容两倍
【1】Arraylist
用的最多的是ArrayList,底层结构是数组,LinkedList的底层结构是链表,且是双向链表
问题:java本身有数组,为啥还要ArrayList?
原本的数组有一个问题,就是使用的时候那必须进行初始化,而ArrayList则不要,在日常开发的时候往往不知道需要多大\
- ArrayList底层是一个Object数组,当真正使用数组的时候会给他初始化为10
- 扩容的时候,通过grow的方法,每一次扩容之前的1.5倍,注意,扩容不是直接在数组上进行扩容,而是基于数组的复制,使用Arrays.copy()进行数组的复制
【2】Vector
底层结构是数组,现在很少使用,是线程安全的 扩容直接扩大两倍
扩展 CopyOnWriteArrayList
如果不使用vector,线程安全的list还有什么?
在JUC包下,还有CopyOnWriteArrayList
- 首先介绍copy-on-write 简称cow机制,先复制再写
- 比如在linux进程中,所有的进程都是init进程fork出来的
- 除了进程号之外,fork出来的子进程,默认和父进程是一样的
- 当使用了cow机制,子进程被fork之后exec之前,两个进程具有相同的内存空间
- 这意味着子进程的代码段、数据段、堆栈都是指向父进程的物理空间
- 好处是,等到真正发生修改的时候,才去分配资源,可以减少分配资源带来的瞬间延迟
- 类似:单例模式的懒汉式,等真正用到的时候再分配
- 在文件系统中,也有cow机制,在修改数据额时候不会直接在原来的数据位置上进行操作,而是找个位置修改。保证数据的完整性。
- 比如:要修改数据块A的内容,先把A读出来,写到B快里面去
- 这个时候断电了,原来A的内容还在。
关于cowArrayList,是一个线程安全的list,底层是通过复制数组的方式来实现的
实现了RandomAccess接口,表示可以随机访问(数组具有随机访问的特性);同时实现了Cloneable接口,表示可克隆;同时也实现了Serializable接口,表示可被序列化
- 简单介绍add()方法
- 首先他会获取一个可重入锁,锁住
- 然后创建一个数组,进行复制,add真正的元素
- 最后的array指向新的数组
- 和cow文件的机制一样,不在自己的部分处理,复制一份处理,错误也没事
cowArrayList的缺点:
- 每次创建一个新的数组,很耗费内存
- 只能保持最终的一致性,不能保持最终的一致性
1.1.2 Queue
- 特点:先进先出
- 最大的特点:Queue<> q = new LinkedList<>();
1.1.3 set
- 特点:无序不重复
1.2 map集合
Map是Java里面的接口,常见的实现类有 HashMap、LinkedHashMap、TreeMap、ConcurrentHashMap
HashMap底层是数组+链表\红黑树
LinkedHashMap底层是数组+链表+双向链表\ (继承hashmap维护一个双链表)
Treemap底层是红黑树
ConcurrenthashMap底层是数组+链表\红黑树
1.2.1 hashMap
【1】关于初始值和大小这块
- HashMap有几个构造方法,但最主要的是初始值以及负载因子
- 大小为16,负载因子的大小为0.75,且Hashmap的大小只能是2次幂的。传进去的是10,实际上是16,传进去的7,实际是8。当输入的元素超过16*0.75=12的时候,就会扩容 -是否可以将负载因子增大?不建议,增大将会填充更多的元素,将会加大冲突的概率
- 因为大小一般为2的2次幂,所以扩容一般扩为2倍
【2】链表的方式
数组和链表:当JDK1.7的时候,使用的是头插法。当1.8的时候采用的是尾插法
如何扩容?
将hashMap扩展为原来的2倍,然后需要进行将之前的元素进行重新的hash计算,填入表中
【3】put和get的方法实现
- 在put的时候,首先对key做hash计算,计算出该key所在的index
- 如果没有碰撞,直接放到数组中,如果碰撞了,根据情况选择链表还是红黑树
【4】为什么重写equals方法还需要重写hashCode方法?
为了保证相同的对象具有相同的相同的值,不同的对象具有相同的值,因为根据观察,不同的对象可能具有相同的哈希吗
【5】什么情况下才会用到红黑树?
当数组的长度大于64时,且链表的长度大于8才会将链表转为红黑树。当红黑树大小为6时,会退化为链表 //链表的插入为O(1),查询为O(N),而对于红黑树的查询和修改都是O(Log(N))
【6】补充一个机制:fail-fast和fail-safe
- hashmap是fail-fast机制的,在使用迭代器遍历时,数据发生了修改,则会立马爆出异常
- 底层委会一个modcount变量。集合内容修改,会修改modcount的值,终止遍历
- JUC下的类都是fail-fast,不能并发的修改
- 而concurrenthashMap是fail-safe (类似于cow机制),遍历的时候复制出来一份进行遍历,保证了安全性,但是不能保证修改的实时性
【7】缺点:不能保证线程的安全性,采用hashtable,每个方法都加synchronized锁
1.2.2 ConcurrentHashMap (需要线程安全时候使用的)
底层是数组+链表/红黑树,支持高并发的访问和更新,是线程安全的
ConcurrentHashMap JDK1.7: 使用分段锁机制实现;ConcurrentHashMap JDK1.8: 则使用数组+链表+红黑树数据结构和CAS原子操作实现 并不是所有的链表长度超过8时会转为红黑树,需要判断数组的长度是否超过64 ConcurrentHashMap是fail-safe,通过在部分加锁和利用CAS算法来实现了同步,在get的时候没有加锁,node都用了volatile给修饰
2、面试完基础的学习
Collection接口对应的子接口分别是list、queue、set,还有一个接口是map,对应的List的实现类是ArrayList,和linkedlist,对应的Queue的实现类是也有linkedList
接口 实现类的对象 子类对象指向父类的引用
List<Integer> list1 = new ArrayList<Integer>();
List<Integer> list2 = new linkedList<Integer>();
Queue<String> q = new LinkedList<String>();
对于集合的遍历:
- ArrayList的遍历
三种的遍历方式
1. 普通的for循环随机进行访问
List<String> list = new ArrayList<>();
int size = list.size();
for(int i=0;i<size;i++){
//通过索引值去遍历
value = list.get(i);
}
2. 通过迭代器遍历,iterator()
List<String> list = new ArrayList<>();
//通过集合的iterator方法,返回一个迭代器
Iterator<String> iter = list.iterator();
//通过迭代器的方法,不需要知道集合的细节,去遍历
while(iter.hasnext()){
value = iter.next();
}
3、 增强的for循环遍历
List<String> list = new ArrayList<String>();
//从列表中进行取出
for(String s : list){
value = s;
}
- linkedList的遍历
- map集合的遍历
Collection和Collections的区别
- Collection是集合的总的接口
- Collections是对应集合的工具类 主要对应的操作:
`关于集合中排序的方法`
List<String> list = new ArrayList<>();
list.add("沉默王二");
list.add("沉默王三");
list.add("沉默王四");
list.add("沉默王五");
list.add("沉默王六");
System.out.println("原始顺序:" + list);
// 反转
Collections.reverse(list);
System.out.println("反转后:" + list);
// 洗牌
Collections.shuffle(list);
System.out.println("洗牌后:" + list);
// 自然升序
Collections.sort(list);
System.out.println("自然升序后:" + list);
// 交换
Collections.swap(list, 2,4);
System.out.println("交换后:" + list);
`关于集合中查找的方法`
//前提是集合已经排好序
Collections.binarySearch(List list,Object key);
//将集合进行排序
Collections.sort(list);
//返回最大的元素,,最小的类似,使用min的方式
Colection.max(集合);
//按照指定的排序方式进行返回最大的元素
Collection.max(集合,Comparator comp);
//返回指定集合对象出现的次数
Collection.frequency(集合,object key)
//使用指定的对象进行填充
fill(List list,Object obj)
`实现同步的控制`
SynchronizedList synchronizedList = Collections.synchronizedList(list);
怎样判断一个集合是否为空,和字符串是否为空
- 两个方法判断集合是否为空:isEmpty()方法判断是否为空,或者使用.size()方法判断集合中元素的个数
- 判断字符串为空 补充:字符串为空有两种 null和 "" 区别:String aaa = null; ---- String bbb = ""; ,""代表着空字符串,但是在堆内存中存在着对应的对象 --- null则是真正的为空,堆内存中没有对应的对象
判断字符串为空的方法 其中s是对应的对象
if(s==null||s.isEmpty())
比较字符串长度, 效率最高
if(s == null || s.length() <= 0)
比较直观,简便的方法
if(s==null||s=="")
效率很低
if(s==null||"".equals(s))
==对于引用类型来说比较的是值,equals比较的是地址,但是String类重写了Object类的equals方法,比较的是值
3、map集合的遍历
- 首先介绍Map集合中两种取值的方法,keySet()和entrySet(),与get相比,只能返回指定的键所映射的值,不能一次的全部进行取出,而keySet和entrySet都可以
- map集合中没有迭代器,只能将map集合转为set集合,在通过迭代器进行取出
问题: 如何将map集合中的值进行全部的取出?
3.1 通过Map.keySet的iterator遍历
通过将map集合转为set集合,然后进行遍历出来
Map<Integer,String> map = new HashMap<>();
map.put(001,"java");
map.put(002,"python");
Iterator<Integer> iterator = map.keyset().iterator();
while(iteartor.hashnext()){
Integer key = iterator.next();
String value = map.get(key);
sout( key, value);
}
//使用增强的for循环
for(Integer key: map.KeySet()){
map.get(key); //得到的是值,value
}
3.2 通过Map.entrySet使用iterator遍历
Map.entrySet对应的是一个集合,Map.Entry<Integer,String> entry
//对应的返回的则是map集合
Map<Integer,String> map = new HashMap<>();
map.put(001,"java");
map.put(002,"python");
Iterator<Map.Entry<Integer,String>> entries = map.entrySet().iterator();
while(entries.hasNext()){
Map.Entry<Integer,String> entry = entries.next();
}
//使用增强的for循环
for(Map.Entry<Integer,String> entry:map.entrySet()){
entry.getValue(); //对应的值则是一个map集合
}
3.3 使用lambda表达式forEach遍历
Map<Integer,String> map = new HashMap<>();
map.put(001,"java");
map.put(002,"python");
map.forEach((k,v) -> sout("key="+ k +"value" + v))
总结:推荐使用entrySet遍历Map类集合KV(增强的for循环的方式),而不是 keySet 方式进行遍历
keySet 其实是遍历了 2 次,第一次是转为 Iterator 对象,第二次是从 hashMap 中取出 key 所对应的 value值。而 entrySet 只是遍历了一次,就把 key 和 value 都放到了 Map.entry 中,效率更高。
getkey()方法,values()返回的是 V 值集合,是一个 list 集合对象;keySet()返回的是 K 值集合,是一个 Set 集合对象;entrySet()返回的是 K-V 值组合集合。
Map.entry<Integer,String> = map.entrySet();
增强的for循环
for(Map.entry<Integer,String> entry : map.EntrySet)
JDK8:遍历map集合 lambda表达式的操作
map.foreach((k,v)->sout(k,v))
4、Stream流
Stream流扩展了JDK中的Collection接口,增强了集合类对大量数据的处理能力,也提供了许多语法糖简化代码。处理数组list set用的
处理一下例题,学习Stream流
- 判断是否有未及格的学生
List<Student> studentList = studentRepository.findAll();
//将studentList转为流,方法anyMatch,在里面是lamanda表达式
return studentList.stream()
.anyMatch(
//终结操作,返回一个结果,anmatch相当于一个接口,将list的每一项传进来。anymatch有一个为true,则返回true
student->{
//可随意进行定义,这里返回一个布尔值,对应于接口的返回值,anymatch这里返回的是一个布尔值
return student.getScore()<60;
})
注意:只有终结操作才可以返回结果,语法格式,stream流后面跟的是一个lamada表达式
- 找出第一个未及格的学生
List<Student> studentlist = StudentRepository.findAll();
return studentlist.stream().filter(
student->student.getScore()<60).
//帅选出小于60的并转为一个集合
collect(Collectors.toList());
- 筛选出所有未及格的学生 按照阶段进行分组
List<Student> studentList = StudentRepository.findAll();
Map<String,List<Student>> collect = studentList
.stream()
//终结操作collect 转换为集合
.collect(Collectors.groupingBy(student->
Integer score = student.getScore();
if(score<20){
return "0-20";
}
));
//进行排序,使用foreach进行遍历
collect.forEach((v.k)->k.sort(
//按照什么样的顺序
Comparator.comparing(Student::getScore);
))
return collect;
首先转为stream流,按照需要进行分组,终结操作,选collect,通过Collectors.groupBy方法进行分组,student.getScore进行操作,学生的分数,再进行排序