集合
接口
集合主要是两大接口以及下面衍生的一些接口 接口实现类
Collection
List: 存放的元素有序 可重复
ArrayList
底层也是一个Object[]数组
-
ArrayList和Array数组的区别?
ArrayList只能存引用类型的对象实例,但是数组引用类型和基本数据类型都可以存
ArrayList可以使用泛型,但数组不可以
ArrayList在创建时不必指定大小,之后可以方便地调用add remove get方法进行增删改查,之后可以动态地进行扩容缩容;但数组不一样,数组在创建时必须指定数组大小,并且数组修改和查询方便,但增加和删除很不方便;
-
ArrayList可以存null吗?可以
-
**ArrayList和Vector的区别?**都是List的实现类,但ArrayList线程不安全,Vector线程安全
LinkedList
底层是一个链表。并且是双向链表。
LinkedList基本上大家很少用,就连LinkedList的开发者都说I create it but i never use it.
ArrayList和LinkedList有什么区别?
1、底层存储:ArrayList存的是Object[]数组,是一块连续的存储空间,可以动态扩容;LinkedList存的是双向链表,不是一块连续的存储空间。
2、线程安全:两者都不是线程安全的
3、增删改查时间复杂度:
增:
ArrayList: add(E e)方法默认在数组尾部添加对象,如果没有达到容量限制的话时间复杂度O(1);如果达到了要扩容的,扩容是将元素复制到一个新的数组里去,时间复杂度O(n);
如果是调用public void add(int index , E element)这个方法在指定索引处添加元素,需要将index之后的元素后移,时间复杂度O(n);
LinkedList:
add(E e)默认在链表尾部增加对象,等同于addLast,尾部添加对象只要last.next=e;就好时间复杂度O(1)
addFirst也是时间复杂度O(1),指针来回指两下就行;
add(int index,E element)在指定位置添加元素的话,需要首先遍历链表到index,时间复杂度已经是O(n)了,添加元素就是指针来回指两下的事O(1);
删除:
ArrayList:
最常使用remove(int index)删除指定位置元素,要将index后元素前移以覆盖index位置元素,时间复杂度O(n)
LinkedList:
删除头尾都很容易O(1),删除指定位置要先遍历链表到index处O(n),真正删除只有O(1)
改查:
由于ArrayList实现了RandomAccess接口(标志着这个接口实现类是否可以随机访问结合任意位置元素),而LinkedList没实现RandomAccess接口,所以ArrayList改查的时间复杂度是O(1),而LinkedList时间复杂度是O(n)
Queue
存放的元素有序可重复,是遵循先进先出的队列
Queue只能队尾插入元素add(E e),队首删除元素remove(),只能获取队首元素element();
Deque是一个双端对列,队首队尾都可以插入删除获取元素
这两都是接口
Set:存放的元素无序不可重复 除了TreeSet有序
HashSet:
HashSet的底层是一个HashMap, add(E e)时其实是在调用HashMap的put(e,new Object()),将要加入的对象作为key加入HashMap里
HashSet无序唯一,因为HashMap的key是无序唯一的
HashSet怎么判断元素是否重复?
先用本地native方法算出hashCode,接着对hashCode进行扰动函数计算(无符号右移16位 异或 hashCode)算出hash值,先判断int类型的hash值相不相等,再判断key值是不是同一个内存地址,再用key值的equals方法判断相不相等
实际也就是HashMap的put方法里那句,先调用hashCode,再调用equals(xx)
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
LinkedHashSet:
LinkedHashSet是HashSet的子类,底层是一个LinkedHashMap,不过LinkedHashMap的Node比HashMap的Node多了一个前指针 指向前一个加入LinkedHashMap(LinkedHashSet)的Node,后指针同理。
所以LinkedHashSet可以说是先进先出的。按照加入的顺序取出。
LinkedHashSet是有序的唯一的。
TreeSet:
TreeSet的底层是一个红黑树(不是很深入了解),总之是一个有序的唯一的Set
如果TreeSet里存的是包装类型或者String类型,他们都实现了comparable接口并且重写compareTo方法,会按照自然顺序排序的,无需干预
如果TreeSet里存的是自定义的引用类型,那么要么像Integer他们一样实现comparable接口并且重写compareTo方法,要么在TreeSet的构造方法里注入匿名内部类的Comparator接口
举个🌰
package com.testng.demo.test;
import com.testng.demo.model.Role;
import com.testng.demo.model.User;
import java.util.*;
import java.util.stream.Collectors;
public class CollectionTest {
public static void main(String[] args) {
User user2 = new User("002", new Role());
User user1 = new User("001", new Role());
User user3 = new User("003", new Role());
user1.setAge(80);
user2.setAge(3);
user3.setAge(40);
//这里是按照年龄顺序排序了
TreeSet<User> treeSet = new TreeSet<>(new Comparator<User>() {
@Override
public int compare(User o1, User o2) {
return o1.getAge()-o2.getAge();
}
});
treeSet.add(user1);
treeSet.add(user2);
treeSet.add(user3);
Iterator<User> iterator = treeSet.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
}
}
输出:
User{name='null', age=3, id='002', password='null', role=com.testng.demo.model.Role@1d057a39}
User{name='null', age=40, id='003', password='null', role=com.testng.demo.model.Role@26be92ad}
User{name='null', age=80, id='001', password='null', role=com.testng.demo.model.Role@4c70fda8}
HashMap VS LinkedHashMap
LinkedHashMap的双向链表的前后指针保证了先进先出;
HashMap<Integer, String> map = new HashMap<>();
map.put(1,"1");
map.put(3,"3");
map.put(2,"2");
for(Map.Entry<Integer,String> entry:map.entrySet()){
System.out.println(entry.getKey()+" "+entry.getValue());
}
输出:
1 1
2 2
3 3
HashMap<Integer, String> map = new LinkedHashMap<>();
map.put(1,"1");
map.put(3,"3");
map.put(2,"2");
for(Map.Entry<Integer,String> entry:map.entrySet()){
System.out.println(entry.getKey()+" "+entry.getValue());
}
输出:
1 1
3 3
2 2
因此HashSet(底层是HashMap) VS LinkedHashSet(底层是LinkedHashMap)也同样如此:
HashSet<Integer> integers = new LinkedHashSet<>();
integers.add(1);
integers.add(3);
integers.add(2);
Iterator<Integer> iterator1 = integers.iterator();
while(iterator1.hasNext()){
System.out.println(iterator1.next());
}
输出:
1
3
2
HashSet<Integer> integers = new HashSet<>();
integers.add(1);
integers.add(3);
integers.add(2);
Iterator<Integer> iterator1 = integers.iterator();
while(iterator1.hasNext()){
System.out.println(iterator1.next());
}
输出:
1
2
3
所以这几个Set的应用场景不一样,如果是想要无序唯一的就用HashSet,如果是想要先进先出的唯一的就用LinkedHashSet,如果想要有序的唯一的就用TreeSet
HashSet和TreeSet用的还是挺多的
Map:存放key-value, key无序不可重复并且只能存放一个null,value随意
HashMap和HashTable(事实上现在很少很少用了)的区别:
两者都是存key-value的
线程安全:HashTable是线程安全的,所有的方法都加了syncronized关键字,HashMap线程不安全。
效率:相应的因为HashTable 加了synchronized所以HashTable的速率比HashMap慢一些。
底层存储:HashTable和HashMap底层都是数组+链表,但是HashMap有链表长度大于等于8且键值对个数大于64(小于64会优先数组扩容)的情况下将链表转换为红黑树的机制,HashTable没有;
HashTable的key不能存null,HashMap可以存一个null的key.
HashMap和HashSet的区别:
HashMap存键值对,HashSet存对象
HashSet的底层就是一个HashMap,并且HashSet的add方法实际上就是调用HashMap的put方法
HashMap和TreeMap的区别:
HashMap无序唯一,TreeMap有序(key有序)唯一,可以通过对TreeMap里存储的key类实现Comparable接口重写compareTo(E e)方法 或者在TreeMap的构造方法里注入一个匿名内部类Comparator重写compare(T o1,T o2)实现对自定义类的key排序。其他包装类型和String已经实现了Comparable接口不用我们再实现了。
HashMap为什么线程不安全?
JDK1.8以及之后的HashMap有可能会发生数据丢失的风险,比如有两个线程A.B都想往HashMap里put键值对,恰好两个key会发生哈希碰撞,就是都要放在同一个数组索引位置上,
假如时间点1两个线程都进行到putVal方法里的判断table[index]有无元素,假设这个时候table[i]没元素,那么线程AB都判断出可以新创建一个节点放在table[i]上。
假如时间点2线程B由于缺乏时间片资源之类的原因挂起了,线程A正常put进去了;
时间点3线程B获得资源继续put,由于之前已经进行过if判读,直接进行table[i]=new …
那么这时候线程A的数据直接被覆盖丢失了。
所以HashMap多线程的时候有数据丢失的风险。
建议使用ConcurrentHashMap.
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
集合的排序
主要是通过实现Comparable、Comparator接口/stream流实现
第一种Comparable(可以认为是对类内部进行排序)
具体语法:
在要排序的类上实现Comparable接口,并实现抽象方法int compareTo(T o)
之后再用Collections.sort(集合)就可以实现对集合排序。
一些tips:
//Comparable如果不传入泛型参数,就用Comparable,那么重写的将是compareTo(Object o)
//如果想要对该类顺序(从小到大排序),那么就返回this.xx-要比较的对象o.xx
//如果想要对该类逆序(从大到小排序),那么就返回要比较的对象o.xx-this.xx
@Override
public int compareTo(User o) {
举个🌰
package com.testng.demo.model;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
**//Comparable<T>如果不传入泛型参数,就用Comparable,那么重写的将是compareTo(Object o)**
@ApiModel
public class User implements Cloneable,Comparable<User> {
@ApiModelProperty(value = "姓名")
private String name;
@ApiModelProperty(value="年龄")
private int age;
@ApiModelProperty(value= "id")
private String id;
@ApiModelProperty(value = "密码")
private String password;
public User(String id, Role role) {
this.id = id;
this.role = role;
}
public User(){
}
private Role role;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Role getRole() {
return role;
}
public void setRole(Role role) {
this.role = role;
}
public User clone(){
try {
User clone = (User) super.clone();
clone.setRole(role.clone());
return clone;
} catch (CloneNotSupportedException e) {
throw new RuntimeException(e);
}
}
//如果想要对该类顺序(从小到大排序),那么就返回this.xx-要比较的对象o.xx
//如果想要对该类逆序(从大到小排序),那么就返回要比较的对象o.xx-this.xx
@Override
public int compareTo(User o) {
//因为id是String类型,所以用的String类重写过的compareTo
return this.id.compareTo(o.id);
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
", id='" + id + '\'' +
", password='" + password + '\'' +
", role=" + role +
'}';
}
}
package com.testng.demo.test;
import com.testng.demo.model.Role;
import com.testng.demo.model.User;
import java.util.ArrayList;
import java.util.Collections;
public class CollectionTest {
public static void main(String[] args) {
ArrayList<User> users = new ArrayList<>();
users.add(new User("002",new Role()));
users.add(new User("001",new Role()));
//如果不排序,ArrayList就会按照插入顺序输出数组元素
System.out.println(users);
//排序后,按照id输出数组元素
Collections.sort(users);
System.out.println(users);
}
}
输出:
[User{name='null', age=0, id='002', password='null', role=com.testng.demo.model.Role@c038203}, User{name='null', age=0, id='001', password='null', role=com.testng.demo.model.Role@cc285f4}]
[User{name='null', age=0, id='001', password='null', role=com.testng.demo.model.Role@cc285f4}, User{name='null', age=0, id='002', password='null', role=com.testng.demo.model.Role@c038203}]
Process finished with exit code 0
第二种Comparator:
可以在User类上实现Comparator接口,也可以采用匿名内部类(创建匿名接口实现类/继承抽象类的实例)的方式,需要覆盖Comparator接口的compareTo(T o1,T o2)方法
如果想要顺序(从小到大),o1-o2
如果想要逆序(从大到小),o2-o1
下面是匿名内部类方式实现的🌰
package com.testng.demo.model;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
//Comparable<T>如果不传入泛型参数,就用Comparable,那么重写的将是compareTo(Object o)
@ApiModel
public class User implements Cloneable
{
@ApiModelProperty(value = "姓名")
private String name;
@ApiModelProperty(value="年龄")
private int age;
@ApiModelProperty(value= "id")
private String id;
@ApiModelProperty(value = "密码")
private String password;
public User(String id, Role role) {
this.id = id;
this.role = role;
}
public User(){
}
private Role role;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Role getRole() {
return role;
}
public void setRole(Role role) {
this.role = role;
}
public User clone(){
try {
User clone = (User) super.clone();
clone.setRole(role.clone());
return clone;
} catch (CloneNotSupportedException e) {
throw new RuntimeException(e);
}
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
", id='" + id + '\'' +
", password='" + password + '\'' +
", role=" + role +
'}';
}
}
package com.testng.demo.test;
import com.testng.demo.model.Role;
import com.testng.demo.model.User;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
public class CollectionTest {
public static void main(String[] args) {
ArrayList<User> users = new ArrayList<>();
users.add(new User("002",new Role()));
users.add(new User("001",new Role()));
System.out.println(users);
// Collections.sort(users);
// System.out.println(users);
Collections.sort(users, new Comparator<User>() {
@Override
public int compare(User o1, User o2) {
//如果想要顺序的话,就用第一个参数-第二个参数
//如果想要逆序,就用第二个参数-第一个参数
return o1.getId().compareTo(o2.getId());
}
});
System.out.println(users);
}
}
第三种:流排序
流排序胜在语法简单,但是也衍生出了一个个新的List,占用的内存空间更多了
package com.testng.demo.test;
import com.testng.demo.model.Role;
import com.testng.demo.model.User;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
public class CollectionTest {
public static void main(String[] args) {
ArrayList<User> users = new ArrayList<>();
User user2 = new User("002", new Role());
User user1 = new User("001", new Role());
user1.setAge(80);
user2.setAge(3);
users.add(user2);
users.add(user1);
System.out.println(users);
//对ID顺序排序
List<User> users1=users.stream().sorted(Comparator.comparing(User::getId)).collect(Collectors.toList());
System.out.println(users1);
//对ID逆序排序
List<User> users2=users.stream().sorted(Comparator.comparing(User::getId).reversed()).collect(Collectors.toList());
System.out.println(users2);
//多属性排序
//先对ID逆序排序,再对age顺序排序
List<User> users3=users.stream().sorted(Comparator.comparing(User::getId).reversed().thenComparing(User::getAge)).collect(Collectors.toList());
System.out.println(users3);
}
}
输出:
[User{name='null', age=3, id='002', password='null', role=com.testng.demo.model.Role@c038203}, User{name='null', age=80, id='001', password='null', role=com.testng.demo.model.Role@cc285f4}]
[User{name='null', age=80, id='001', password='null', role=com.testng.demo.model.Role@cc285f4}, User{name='null', age=3, id='002', password='null', role=com.testng.demo.model.Role@c038203}]
[User{name='null', age=3, id='002', password='null', role=com.testng.demo.model.Role@c038203}, User{name='null', age=80, id='001', password='null', role=com.testng.demo.model.Role@cc285f4}]
[User{name='null', age=3, id='002', password='null', role=com.testng.demo.model.Role@c038203}, User{name='null', age=80, id='001', password='null', role=com.testng.demo.model.Role@cc285f4}]
Process finished with exit code 0
coding的时候集合的一些注意事项:
1、集合判空为了保证代码可读性,建议使用isEmpty(),而不是size()==0
2、使用Stream流将Collection集合转为Map的时候,value一定不能为空,否则会抛NPE
Map<String,Integer> collect = treeSet.stream().collect(**Collectors.toMap**(User::getId, User::getAge));
因为Collectors.toMap方法底层会有一个方法内部对value判空,如果为空直接throw NPE
3、如果想在循环时remove/add元素,一定要用iterator并且要调用iterator的remove/add方法,而不是集合自己的remove/add方法,否则iterator会莫名其妙发现自己少了元素。并且在调用iterator的remove/add方法之前一定要调用next方法;
Iterator<User> iterator = treeSet.iterator();
while(iterator.hasNext()){
//这种用集合的remove方式会报运行时异常:ConcurrentModificationException
// treeSet.remove(iterator.next());//运行时抛出异常
//使用iterator的remove方法一定要在remove之前调用next()方法让指针指向下一个元素,否则会报运行时异常:
//java.lang.IllegalStateException
iterator.next();//正确方式
iterator.remove();
}
4、如果想要对集合Collection去重,建议使用HashSet的构造方法new HashSet(Collection xx)
因为HashSet/LinkedHashSet底层是数组+链表/红黑树,而ArrayList底层是Object[],如果不用Set去重,自己写去重是要遍历ArrayList(已经是O(N))了,之后再调用list.contains()方法,contains方法底层是遍历数组O(N),那自己写去重就是O(N*N);
但你用Set去重,Set底层是HashMap,即数组+链表/红黑树,Set也是遍历ArrayList的n个元素(O(N)),之后再对每个元素用contains方法,但HashMap的Contains方法时间复杂度一定比数组的contains方法小非常多,首先是计算数组索引位置,如果链表长度为1的话,时间复杂度只有O(1);
所以从时间复杂度考虑,更推荐用Set对List去重。
举个🌰
ArrayList<Integer> list = new ArrayList<>();
list.add(3);
list.add(1);
list.add(1);
//[3, 1, 1]
System.out.println(list.toString());
//HashSet无序去重
HashSet<Integer> hashSet = new HashSet<>(list);
System.out.println(hashSet);//[1, 3]
//LinkedHashSet按照ArrayList元素加入**顺序有序去重**
LinkedHashSet linkedHashSet = new LinkedHashSet<>(list);
System.out.println(linkedHashSet);//[3, 1]