集合
1.List,Set,Map 三者的区别
- List:(对付顺序的好帮⼿): 存储的元素是有序的、可重复的
- Set:(注重独⼀⽆⼆的性质): 存储的元素是⽆序的、不可重复的
- Map:(⽤ Key 来搜索的专家): 使⽤键值对(kye-value)存储,Key 是⽆序的、不可重复的,value 是⽆序的、可重复的,每个键最多映射到 ⼀个值。
2.如何选用集合
根据集合的特点:
需要根据键值获取到元素值时就选⽤ Map 接⼝下的集合
需要排序时选择 TreeMap
不需要排序时就选择 HashMap
需要保证线程安全就选⽤ ConcurrentHashMap ,hashtable
只需要放元素值时
需要保证元素唯⼀时选择实现Set 接⼝的集合⽐如 TreeSet 或 HashSet ,
不需要就选择实现 List 接⼝的⽐如 ArrayList 或LinkedList
3.为什么用集合不用数组
使⽤数组存储对象具有⼀定的弊端
数组的缺点是⼀旦声明之后,⻓度就不可变了
声明数组时的数据类型也决定了该数组存储的数据的类型
数组存储的数据是有序的、可重复的,特点单⼀
集合提⾼了数据存储的灵活性
Java 集合不仅可以⽤来存储不同类型不同数量的对象,还可以保存具有映射关系的数据
4.线程安全的集合代替不安全集合
java.util.concurrent 包中提供了很多并发容器
Arraylist , LinkedList , Hashmap , HashSet , TreeSet , TreeMap , PriorityQueue 都 不是线程安全的
HashMap - ConcurrentHashMap \ HashTble不用
ArrayList - CopyOnWriteArrayList \ Vector不用
LinkedList - ConcurrentLinkedQueue
List
1.Arraylist 和 Vector 的区别
- ArrayList 是 List 的主要实现类,底层使⽤ Object[ ]存储,适⽤于频繁的查找⼯作,线程不安全 ;
- Vector 是 List 的古⽼实现类,底层使⽤ Object[ ]存储,线程安全的。
2.Arraylist 与 LinkedList 区别?
- 是否保证线程安全: ArrayList 和 LinkedList 都是不同步的,也就是不保证线程安全
- 底层数据结构:ArrayList - Object 数组,LinkedList双向链表
- 插⼊和删除是否受元素位置的影响:
ArrayList 采⽤数组存储,所以插⼊和删除元素的时间复杂度受元素位置的影响。
LinkedList 删除元素时间复杂度不受元素位置的影响,近似O(1)
在指定位置 ii 插⼊和删除元素的话时间复杂度近似为 o(n)) 因为需要先移动到指定位置再插⼊。
- 是否⽀持快速随机访问:LinkedList 不⽀持⾼效的随机元素访问,⽽ ArrayList ⽀持。快速随 机访问就是通过元素的序号快速获取元素对象(对应于 get(int index) ⽅法)。
- 内存空间占⽤: ArrayList 的空间浪费主要体现在在 list 列表的结尾会预留⼀定的容量空间,⽽ LinkedList 的空间花费则体现在它的每⼀个元素都需要消耗⽐ ArrayList 更多的空间
3.在对List做for循环的时候,是否可以删除当前元素
1、for循环遍历list,会漏掉重复的值,要加上i–
for(int i=0;i<list.size();i++){
if(list.get(i).equals("del"))
list.remove(i);
// i--;
}
删除某个元素后,list的大小发生了变化,而你的索引也在变化,所以会导致你在遍历的时候漏掉某些元素。比如当你删除第1个元素后,继续根据索引访问第2个元素时,因为删除的关系后面的元素都往前移动了一位,所以实际访问的是第3个元素。因此,这种方式可以用在删除特定的一个元素时使用,但不适合循环删除多个元素时使用。
2.增强for循环,删除完毕马上使用break跳出
for(String x:list){
if(x.equals("del")){
list.remove(x);
//break;
}
}
这种方式的问题在于,删除元素后继续循环会报错误信息ConcurrentModificationException,因为元素在使用的时候发生了并发的修改,导致异常抛出。但是删除完毕马上使用break跳出,则不会触发报错。
3.iterator遍历
Iterator<String> it = list.iterator();
while(it.hasNext()){
String x = it.next();
if(x.equals("del")){
it.remove();
}
}
这种方式可以正常的循环及删除。但要注意的是,使用iterator的remove方法,如果用list的remove方法同样会报上面提到的ConcurrentModificationException错误。
总结:
(1)循环删除list中特定一个元素的,可以使用三种方式中的任意一种,但在使用中要注意上面分析的各个问题。
(2)循环删除list中多个元素的,应该使用迭代器iterator方式。
Set
1.Comparable和Comparator区别
- comparable 接⼝实际上是出⾃ java.lang 包 它有⼀个 compareTo(Object obj) ⽅法⽤来排序
- comparator 接⼝实际上是出⾃ java.util 包它有⼀个 compare(Object obj1, Object obj2) ⽅法⽤ 来排序
public class ComparatorDemo {
public static void main(String[] args){
int[] value = {-5,-2,-1,0,1,2,5};
ArrayList<Integer> arrayList = new ArrayList<>();
for (int i = 0; i < value.length; i++) {
arrayList.add(value[i]);
}
System.out.println("原来数据"+arrayList);
// void reverse(List list):反转
Collections.reverse(arrayList);
System.out.println("反转:"+arrayList);
// void sort(List list),按⾃然排序的升序排序
Collections.sort(arrayList);
System.out.println("自然排序:"+arrayList);
// 定制排序的⽤法
Collections.sort(arrayList, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2.compareTo(o1);
}
});
System.out.println("定制排序Comparator的compara方法:"+arrayList);
}
}
原来数据[-5, -2, -1, 0, 1, 2, 5]
反转:[5, 2, 1, 0, -1, -2, -5]
自然排序:[-5, -2, -1, 0, 1, 2, 5]
定制排序Comparator的compara方法:[5, 2, 1, 0, -1, -2, -5]
2.比较HashSet、LinkedHashSet 和 TreeSet 三者的异同
HashSet 是 Set 接⼝的主要实现类 ,HashSet 的底层是 HashMap,线程不安全的,可以存储 null 值
LinkedHashSet 是 HashSet 的⼦类,能够按照添加的顺序遍历
TreeSet 底层使⽤红⿊树,能够按照添加元素的顺序进⾏遍历,排序的⽅式有⾃然排序和定制排序
Map
1.HashMap 和 Hashtable 的区别
1.线程是否安全: HashMap 是⾮线程安全的,HashTable 是线程安全的,因为 HashTable 内部的⽅法
基本都经过 synchronized 修饰。
2.效率: 因为线程安全的问题,HashMap 要⽐ HashTable 效率⾼⼀点。另外,HashTable 基本被淘
汰,不要在代码中使⽤它;concurrentHashMap
3. 对 Null key 和 Null value 的⽀持: HashMap 可以存储 null 的 key 和 value,但 null 作为键只能有
⼀个,null 作为值可以有多个;HashTable 不允许有 null 键和 null 值,否则会抛出NullPointerException
4.初始容量⼤⼩和每次扩充容量⼤⼩的不同:
- HashMap 默认的初始化⼤⼩为 16。之后每次扩充,容量变为原来的 2 倍
- HasTable 默认的初始⼤⼩为 11,之后每次扩充,容量变为原来的 2n+1
5.底层数据结构:
-HashMap 数组+链表或者红黑树
-HashTable 数组+链表
2.HashMap和HashSet区别
HashSet 底层就是基于 HashMap 实现的。
HashSet 的 源码⾮常⾮常少,因为除了 clone() 、 writeObject() 、 readObject() 是 HashSet ⾃⼰不得不实现 之外,其他⽅法都是直接调⽤ HashMap 中的⽅法。
HashMap | HashSet |
---|---|
实现了 Map 接⼝ | 实现 Set 接⼝ |
存储键值对 | 仅存储对象 |
put()添加元素 | add()添加元素 |
HashMap 使⽤键 (Key)计算 Hashcode | HashSet 使⽤成员对象来计算 hashcode 值 |
对于两个对象来说 hashcode 可能相同,所以 equals()⽅法⽤来判断对象的相等性
3.hashCode()与 equals():
- 如果两个对象相等,则 hashcode ⼀定也是相同的
- 两个对象相等,对两个 equals ⽅法返回 true
- 两个对象有相同的 hashcode 值,它们也不⼀定是相等的
- 综上,equals ⽅法被覆盖过,则 hashCode ⽅法也必须被覆盖
- hashCode()的默认⾏为是对堆上的对象产⽣独特值。如果没有重写 hashCode(),则该 class 的两个 对象⽆论如何都不会相等(即使这两个对象指向相同的数据)。
4.HashMap 的底层实现
1.8之后:HashMap 底层是 数组和链表或者红黑树
HashMap 通过 key 的hashCode 经过扰动函数处理过后得到 hash 值,然后通过 (n - 1) & hash 判断当前元素存放的位置(这⾥的 n 指的是数组的⻓度),如果当前位置存在元素的话,就判断该元素与要存⼊的元素的 hash
值以及 key 是否相同,如果相同的话,直接覆盖,不相同就通过拉链法解决冲突。
所谓扰动函数指的就是 HashMap 的 hash ⽅法。使⽤ hash ⽅法也就是扰动函数是为了防⽌⼀些实现
⽐较差的 hashCode() ⽅法 换句话说使⽤扰动函数之后可以减少碰撞。
相⽐于 JDK1.8 的 hash ⽅法 ,JDK 1.7 的 hash ⽅法的性能会稍差⼀点点,因为毕竟扰动了 4 次。
所谓 “拉链法” 就是:将链表和数组相结合。也就是说创建⼀个链表数组,数组中每⼀格就是⼀个链 表。若遇到哈希冲突,则将冲突的值加到链表中即可。
1.8 解决哈希冲突时有了较⼤的变化,当链表⻓度⼤于阈值(默认为 8)时,将链表转化为红⿊树,以减少搜索时间。
TreeMap、TreeSet 以及 JDK1.8 之后的 HashMap 底层都⽤到了红⿊树。红⿊树就是为了解 决⼆叉查找树的缺陷,因为⼆叉查找树在某些情况下会退化成⼀个线性结构。
HashMap遍历
- 使用迭代器(Iterator)EntrySet 的方式进行遍历;
- 使用迭代器(Iterator)KeySet 的方式进行遍历;
- 使用 For Each EntrySet 的方式进行遍历;
- 使用 For Each KeySet 的方式进行遍历;
- 使用 Lambda 表达式的方式进行遍历;
- 使用 Streams API 单线程的方式进行遍历;
- 使用 Streams API 多线程的方式进行遍历。
4.ForEach KeySet
public class HashMapTest {
public static void main(String[] args) {
// 创建并赋值 HashMap
Map<Integer, String> map = new HashMap();
map.put(1, "Java");
map.put(2, "JDK");
map.put(3, "Spring Framework");
map.put(4, "MyBatis framework");
map.put(5, "Java中文社群");
// 遍历
for (Integer key : map.keySet()) {
System.out.print(key);
System.out.print(map.get(key));
}
}
}
5.Lambda
public class HashMapTest {
public static void main(String[] args) {
// 创建并赋值 HashMap
Map<Integer, String> map = new HashMap();
map.put(1, "Java");
map.put(2, "JDK");
map.put(3, "Spring Framework");
map.put(4, "MyBatis framework");
map.put(5, "Java中文社群");
// 遍历
map.forEach((key, value) -> {
System.out.print(key);
System.out.print(value);
});
}
}
5.ConcurrentHashMap 和 Hashtable 的区别
底层数据结构: 1.8之后都是数组和链表或者红黑树
实现线程安全的⽅式:
- 1.7 ConcurrentHashMap(分段锁) 对整个桶数组进⾏了分割分段(Segment)
每⼀把锁只锁容器其中⼀部分数据,多线程访问容器⾥不同数据段的数据,就不会存在锁竞争,提⾼并发访问率。
- 1.8之后 Node 数组+链表+红⿊树的数据结构来实现,并发控制使⽤ synchronized 和 CAS 来操作。
Hashtable(同⼀把锁) :只有一把全表锁,使⽤ synchronized 来保证线程安全,效率⾮常低下。
synchronized 只锁定当前链表或红⿊⼆叉树的⾸节点,这样只要 hash 不冲突,就不会产⽣并发,效率 ⼜提升 N 倍。
6.Collections ⼯具类
排序
void reverse(List list)//反转
void shuffle(List list)//随机排序
void sort(List list)//按⾃然排序的升序排序
void sort(List list, Comparator c)//定制排序,由Comparator控制排序逻辑
void swap(List list, int i , int j)//交换两个索引位置的元素
void rotate(List list, int distance)//旋转。当distance为正数时,将list后distance个元素整体移到前⾯。当distance为负数时,将 list的前distance个元素整体移到后⾯
查找,替换操作
int binarySearch(List list, Object key)//对List进⾏⼆分查找,返回索引,注意List
必须是有序的
int max(Collection coll)//根据元素的⾃然顺序,返回最⼤的元素。 类⽐int
min(Collection coll)
int max(Collection coll, Comparator c)//根据定制排序,返回最⼤元素,排序规则由
Comparatator类控制。类⽐int min(Collection coll, Comparator c)
void fill(List list, Object obj)//⽤指定的元素代替指定list中的所有元素。
int frequency(Collection c, Object o)//统计元素出现次数
int indexOfSubList(List list, List target)//统计target在list中第⼀次出现的索
引,找不到则返回-1,类⽐int lastIndexOfSubList(List source, list target).
boolean replaceAll(List list, Object oldVal, Object newVal), ⽤新元素替换旧元素
同步控制:最好不要⽤下⾯这些⽅法,效率⾮常低
Collections 提供了多个 synchronizedXxx() ⽅法·,该⽅法可以将指定集合包装成线程同步的集合, 从⽽解决多线程并发访问集合时的线程安全问题。
synchronizedCollection(Collection<T> c) //返回指定 collection ⽀持的同步(线程安全的)collection。
synchronizedList(List<T> list)//返回指定列表⽀持的同步(线程安全的)List。
synchronizedMap(Map<K,V> m) //返回由指定映射⽀持的同步(线程安全的)Map。
synchronizedSet(Set<T> s) //返回指定 set ⽀持的同步(线程安全的)set。
Arrays.asList()避坑指南
Arrays.asList() 在平时开发中还是⽐较常⻅的,我们可以使⽤它将⼀个数组转换为⼀个 List 集合。
String[] myArray = { "Apple", "Banana", "Orange" };
List<String> myList = Arrays.asList(myArray);
//上⾯两个语句等价于下⾯⼀条语句
List<String> myList = Arrays.asList("Apple","Banana", "Orange");