集合collection与Map及其子类详解

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

提示:这里可以添加本文要记录的大概内容:
例如:随着人工智能的不断发展,机器学习这门技术也越来越重要,很多人都开启了学习机器学习,本文就介绍了机器学习的基础内容。


提示:以下是本篇文章正文内容,下面案例可供参考

一、collection集合是什么?

collection是一个大小可变的容器,Collection是单列集合,一个元素只能一个值
属于单列集合

  • 类继承关系


注意map并没有继承collection

一、子类

1.List集合

  • 添加的元素是有序,可重复,有索引
  • List是一个接口,不可以实例化,不能写成如下:
    List<Integer> list = new List<Integer>();//错误
    
1).ArrayList
1. 概念

· 底层结构基于数组,查询快,增删慢;线程不安全,效率高
· ArrayList是能够自动增长容量的数组,添加元素会自动增加size,Array需要预设size
· ArrayList适合于进行大量的随机访问的情况下使用(因为它查找快)
· Vector 和 ArrayList 的默认容量都为 10

2. 为什么ArrayList线程不安全

首先说一下什么是线程不安全:线程安全就是多线程访问时,采用了加锁机制, 当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。线程不安全就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据。从源码的角度来看,因为Vector的方法前加了synchronized 关键字,也就是同步锁的意思,sun公司希望Vector是线程安全的,而希望arraylist是高效的,
一个 ArrayList ,在添加一个元素的时候,它可能会有两步来完成:
1.在 Items[Size] 的位置存放此元素;
2.增大 Size 的值。
举例:
在单线程运行的情况下,如果 Size = 0,添加一个元素后,此元素在位置 0,而且 Size=1;
而如果是在多线程情况下,比如有两个线程,线程 A 先将元素存放在位置 0。但是此时线程A仅仅完成步骤1,CPU 调度线程A暂停,线程 B 得到运行的机会。线程B也向此 ArrayList 添加元素,因为此时 Size 仍然等于 0 ,所以线程B也将元素存放在位置0。然后线程A和线程B都继续运行完成剩余步骤,都增加 Size 的值。 此时ArrayListSize的值为2,但是元素实际上只有一个,这就是“线程不安全”了。

3. 为什么ArrayList效率高及其优缺点

· 效率高的原因就是因为线程不安全,缺点即使优点,避免了synchronized同步锁增加的线程开销
· ArrayList查询快,增删慢;
· Array(数组)是基于索引(index)的数据结构,它使用索引在数组中搜索和读取数据是很快的,因为数组的连续内存, 会有一部分或者全部数据一起进入到CPU缓存
· 但是要删除数据却是开销很大,因为这需要重排数组中的所有数据, (因为删除数据以后, 需要把后面所有的数据前移),增加同上

4. ArrayList扩容实现步骤

ArrayList底层的实现是Array, 数组扩容实现:
·新增数据空间判断
·新增数据的时候需要判断当前是否有空闲空间存储
·扩容需要申请新的连续空间
·把老的数组复制过去
·新加的内容
·回收老的数组空间
·扩容需要申请新的连续空间

5. ArrayList如何解决线程不安全?

一:使用synchronized关键字;
二:使用Collections.synchronizedList();使用方法如下:
假如你创建的代码如下:List<Map<String,Object>>data=new ArrayList<Map<String,Object>>();
那么为了解决这个线程安全问题你可以这么使用Collections.synchronizedList(),如:
List<Map<String,Object>> data=Collections.synchronizedList(newArrayList<Map<String,Object>>());
其他的都没变,使用的方法也几乎与ArrayList一样,大家可以参考下api文档;

6. 使用数组长度分配空间性能对比

注意: 长度尽量使用2的幂作为长度, 计算机分配空间大都使用次幂去分配, 减少碎片空间
代码:
```
package javatest;

import java.util.ArrayList;
import java.util.List;

/**
 * @ClassName Jtest
 * @Description TODO
 * @Author lingxiangxiang
 * @Date 4:54 PM
 * @Version 1.0
 **/
public class Jtest {

    public static int length = 1048576; //10的20次幂
    public static List<Integer> list1 = new ArrayList<>();
    public static List<Integer> list2 = new ArrayList<>(length);

    public static void addList(int sign) {
        long start = System.currentTimeMillis();
        for (int i = 0; i < length; i++) {
            if (sign == 0) {
                list1.add(sign);
            } else {
                list2.add(sign);
            }
        }
        long end = System.currentTimeMillis();
        System.out.println(sign + " exec time is: " + (end - start));
    }

    public static void main(String[] args) {
        addList(0);
        addList(1);
    }
}
```
执行结果:
```
0 exec time is: 25
1 exec time is: 17
```
说明:ArrayList在初始化的时候指定长度肯定是要比不指定长度的性能好很多, 这样不用重复的去申请空间, 复制数组, 销毁老的分配空间这些步骤
2).LinkedList
1、LinkList的概念

LinkList是一个底层结构基于的双链表
LinkedList适合在表中进行插入、删除使用性更比ArrayList更好
增删快,查询慢;线程不安全,效率高;
支持双链表,定位首尾速度也快;
链表不需要连续的空间, 大小不确定

2、LinkList的优缺点

查询慢的原因:
链表还需要在去内存中根据上下游标查找,CPU缓存比内存块太多
链表代销可变, 扩展性强, 只能顺着指针的方向查询, 速度较慢
增删快的原因:
链表增删只需要改变链表头内存地址即可,不用像ArrayList迁移数据
线程不安全的原因:同Arraylist上

3、LinkList和ArrayList

LinkList的线程不安全原因、解决线程不安全问题同ArrayList,可自行联想

3).Vector
1、概念

底层结构是数组,查询快,增删慢;
线程安全,效率低;
Vector 是 Java 早期提供的线程安全的有序集合,如果不需要线程安全,不建议使用此集合,毕竟同步是有线程开销的。
Vector 和 ArrayList 的默认容量都为 10

2、Vector和ArrayList

Vector和ArrayList一样都是数组List,很多性能都同ArrayList,可自行联想

4).Vector、ArrayList、LinkedList 使用场景有什么区别?

Vector 和 ArrayList 的内部结构是以数组形式存储的,因此非常适合随机访问,但非尾部的删除或新增性能较差,比如我们在中间插入一个元素,就需要把后续的所有元素都进行移动。

LinkedList 插入和删除元素效率比较高,但随机访问性能会比以上两个动态数组慢。

2.Set集合

添加的元素是无序(按照哈希算法排序),不重复,无索引的,增删改查的性能都很好
Set元素重复元素不能存入add方法返回false
Map的重复健将覆盖旧键,将旧值返回
线程不安全

1).HashSet
1、概念
  • 添加的元素是无序(按照哈希算法排序),不重复,唯一,无索引的;
  • JDK 1.8 之前 哈希表 = 链表 + 数组 + (哈希算法);
  • JDK 1.8 之后 哈希表 = 链表 + 数组 + 红黑树 +(哈希算法)当链表长度超过阈值8时会转为红黑树结构;
2、HashSet 是如何保证数据不可重复的

哈希表保证了无序和唯一性,元素唯一性依赖于两个方法:hashCode()和equals()
HashSet 的底层其实就是 HashMap,只不过 HashSet 实现了 Set 接口并且把数据作为 K 值,而 V 值一直使用一个相同的虚值来保存,我们可以看到源码:

public boolean add(E e) {
    return map.put(e, PRESENT)==null;// 调用 HashMap 的 put 方法,PRESENT 是一个至始至终都相同的虚值,一个为null的obkect对象
}

由于 HashMap 的 K 值本身就不允许重复,并且在 HashMap 中如果 K/V 相同时,会用新的 V 覆盖掉旧的 V,然后返回旧的 V,那么在 HashSet 中执行这一句话始终会返回一个 false,导致插入失败,这样就保证了数据的不可重复性。

2).TreeSet

底层数据结构是红黑树,按照大小默认升序排序,不重复,无索引;
排序是让存储在键位置的对象实现Comparable接口,重写compareTo方法,也就是让元素自身具备比较性,这种方式叫做元素的自然排序也叫做默认排序

3).LinkedHashSet

底层数据结构是链表和哈希表。(FIFO,有序,唯一)。通过链表保证有序性, 通过哈希表保证唯一性。

4).Set/HashSet集合为什么可以去重?
  • HashSet是继承hashMap的,所以根据hashCode算法(哈希算法),他会将对象集合分成若干个存储区域,每个对象可以计算出一个哈希码,可以将哈希码分组,每组分别对应某个存储区域,根据一个对象的哈希码就可以确定该对象应该存储在哪个区域。(这也是快速查找元素的方法)
  • Set集合去重主要是调用 add 方法时,使用了 hashCode 方法和 equals 方法:如果在 Set集合 中找不到与该元素 hashCode 值相同的元素,则说明该元素是新元素,会被添加到 Set 集合中;如果两个元素的 hashCode 值相同,并且使用 equals 方法比较后,返回值为 true,那么就说明两个元素相同,新元素不会被添加到 Set 集合中;如果 hashCode 值相同,但是 equals 方法比较后,返回值为 false ,则两个元素不相同,新元素会被添加到 Set 集合中。
  • 应用场景:
    使用 Set集合存储自定义类型元素去重 ,需要重写 HashCode 方法和 equals 方法
    使用 Set集合存储User ,并且不能有重复数据
public class User {
    private int id;
    private String name;
    
    public User(int id, String name) {
        this.id = id;
        this.name = name;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }

    public static void main(String[] args) {
        Set<User> hs = new HashSet<>();
        //初始化 u1
        User u1 = new User(1,"zs");
        //初始化 u2
   		User u2 = new User(1,"zs");
        //将 u1和 u2都加入到 HashSet集合中
        hs.add(u1);
        hs.add(u2);
        //遍历输出HashSet集合
        for (User h : hs) {
            System.out.println(h);
        }
    }
}
控制台打印:
User{id=1, name='zs'}
User{id=1, name='zs'}

根据Set集合去重的原理我们需要重写HashCode 方法和 equals 方法
重写后:

public class User {
    private int id;
    private String name;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public User(int id, String name) {
        this.id = id;
        this.name = name;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }
 
    //重写 equals 方法
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        User user = (User) o;
        return id == user.id &&
                Objects.equals(name, user.name);
    }
	
    //重写 hashCode 方法
    @Override
    public int hashCode() {
        return Objects.hash(id, name);
    }

    public static void main(String[] args) {
        HashSet<User> hs = new HashSet<>();
        User u1 = new User(1,"zs");
        User u2 = new User(1,"zs");
        hs.add(u1);
        hs.add(u2);
        for (User h : hs) {
            System.out.println(h);
        }
    }
}

控制台打印:
User{id=1, name='zs'}

3.List 和 Set 有什么区别?

区别分为以下几个方面:
List 允许有多个 null 值,Set 只允许有一个 null 值;(LIst是可重复录入数据的,set继承hashMap有k值的唯一性);
List 可以保证每个元素的存储顺序,Set 无法保证元素的存储顺序(因为它会按照哈希算法排序)。

二、Collection 和 Collections 有什么区别?

Collection 和 Collections 的区别如下:

Collection 是集合类的上级接口,继承它的主要有 List 和 Set;
Collections 是针对集合类的一个工具类,它提供了一些列的静态方法实现,如 Collections.sort() 排序、Collections.reverse() 逆序等

#排序
sort(List<T> list);

#查找
int binarySearch(List<T> list ,T key);//二分查找
T max(Collection<?> coll);

#反转
reverse(List<?> list);

#随机置换
shuffle(List<?> list);

二、Map是什么

Map是映射接口,用于存放键值对<key,value>,通过key来查找value,key不能为空,唯一且不重复
Map属于双列集合
线程不安全
Set元素重复元素不能存入add方法返回false
Map的重复健将覆盖旧键,将旧值返回
在这里插入图片描述

一、AbstractMap

该类实现了Map中的大部分API

1、HashMap

底层是哈希表数据结构,线程是不同步的,可以存入null键,null值(说明HashMap是非线程安全的)。
如何保证唯一性:
要保证键的唯一性,需要覆盖hashCode方法,和equals方法

1.LinkedHashMap

LinkedHashMap相比于HashMap内部多维护了一个双向列表,目的就是保证输入顺序和输出顺序一致,带来的劣势也很明显,性能的消耗大大提升

2、TreeMap

1.TreeMap概念和特点

TreeMap继承与AbstractMap,同时实现了NavigableMap接口,因此,TreeMap中的内容是有序键值对
TreeMap是一个红黑树

2.TreeMap的如何实现键值对的排序

方式一:元素自身具备比较性
  和TreeSet一样原理,需要让存储在键位置的对象实现Comparable接口,重写compareTo方法,也就是让元素自身具备比较性,这种方式叫做  元素的自然排序也叫做默认排序。
方式二:容器具备比较性
  当元素自身不具备比较性,或者自身具备的比较性不是所需要的。那么此时可以让容器自身具备。需要定义一个类实现接口Comparator,重  写compare方法,并将该接口的子类实例对象作为参数传递给TreeMap集合的构造方法

二、SortedMap

SortedMap是一个接口,它继承与Map接口。SortedMap中的内容与Map中的区别在于,它是有序的键值对,里面排序的方法是通过比较器(Comparator)实现的
SortedMap的排序方式有两种:自然排序和指定比较器排序
SortedMap定义的API:

//继承与Map的API不再赘写
package java.util;
public interface SortedMap<K,V> extends Map<K,V> {
    Comparator<? super K> comparator(); //返回比较器对象
    SortedMap<K,V> subMap(K fromKey, K toKey); //返回指定key范围内的Map
    SortedMap<K,V> headMap(K toKey); //返回小于指定key的部分集合
    SortedMap<K,V> tailMap(K fromKey); //返回大于等于指定key的部分集合
    K firstKey(); //返回第一个元素的key
    K lastKey();  //返回最后一个元素的key
}

1、NavigableMap

NavigableMap是一个接口,它继承与SortedMap接口,所以它肯定也是有序的,另外,NavigableMap还有一些导航的方法:如获取“大于或等于某个对象的键值对”等等
NavigableMap除了继承了SortedMap的特性外,还提供了如下功能:

  1. 提供了操作键值对的方法:lowerEntry、floorEntry、cellingEntry和higherEntry方法分别返回小于、小于等于、大于等于和大于给定键的键所关联的Map.Entry对象。
  2. 提供了操作键的方法:lowerKey、floorKey、cellingKey和higherKey方法分别返回小于、小于等于、大于等于和大于给定键的键。
  3. 获取键值对的子集。

NavigableMap继承与SortedMap,它的API:


package java.util;
public interface NavigableMap<K,V> extends SortedMap<K,V> {
    Map.Entry<K,V> lowerEntry(K key); 
    K lowerKey(K key);
    Map.Entry<K,V> floorEntry(K key);
    K floorKey(K key);
    Map.Entry<K,V> ceilingEntry(K key);
    K ceilingKey(K key);
    Map.Entry<K,V> higherEntry(K key);
    K higherKey(K key);
    Map.Entry<K,V> lastEntry();
    Map.Entry<K,V> pollLastEntry();
    NavigableMap<K,V> descendingMap();
    NavigableSet<K> navigableKeySet();
    NavigableSet<K> descendingKeySet();
    NavigableMap<K,V> subMap(K fromKey, boolean fromInclusive,
                             K toKey,   boolean toInclusive);
    NavigableMap<K,V> headMap(K toKey, boolean inclusive);
    NavigableMap<K,V> tailMap(K fromKey, boolean inclusive);
    SortedMap<K,V> subMap(K fromKey, K toKey);
    SortedMap<K,V> headMap(K toKey);
    SortedMap<K,V> tailMap(K fromKey);

三、Hashtabe

Hashtabe是线程安全的,它不允许空值

三、set和map如何变得线程安全

使用Guava构建ConcurrentHashSet
https://blog.csdn.net/l153097889/article/details/77891250

总结

感谢:
https://blog.csdn.net/qq_28081081/article/details/80413669
https://www.cnblogs.com/williamjie/p/9099020.html
https://www.cnblogs.com/lingshang/p/10897912.html
https://blog.csdn.net/qq_44309181/article/details/103917065

map:
https://www.cnblogs.com/douyu2580860/p/8358768.html

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值