集合Collection(List Set Queue)Map(HashMap)

集合

接口

集合主要是两大接口以及下面衍生的一些接口 接口实现类

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]
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值