集合框架

集合框架

https://blog.csdn.net/qq_36748278/article/details/76703792


1、Collection

Collection是 Set List Queue和 Deque的接口
Queue: 先进先出队列
Deque: 双向链表

**注:**Collection和Map之间没有关系,Collection是放一个一个对象的,Map 是放键值对的
**注:**Deque 继承 Queue,间接的继承了 Collection

image-20210327151931556

1.1、List(有序可重复)

1.1.1、集合的遍历
  • for遍历
  • 迭代器遍历:heros.iterator();hasNext()判断是否有下一个。
  • 迭代器的for写法:for (Iterator iterator = heros.iterator(); iterator.hasNext()😉
  • 增强for循环
1.1.2、ArrayList(常用)
  • add:可直接把对象加在最后面,也可以指定位置加对象
  • contains:判断一对象是否在容器中,判断标准是是否为同一个对象,而不是内容是否相同
  • get(5):获取指定位置的对象,如果输入的下标越界,一样会报错
  • indexOf(new Hero(“hello 1”)):获取对象所处的位置,判断的是对象,不是内容。
  • remove(2),remove(speciaHero):根据下标删除ArrayList元素或者根据对象删除
  • set(5,new Hero(“hero 5”)):用于替换指定位置的元素,把下标为5的元素替换为hero 5
  • size():用于获取ArrayList的大小
  • toArray:可以把一个ArrayList对象转换为数组,如果要转换为一个Hero数组,那么需要传递一个Hero数组类型的对象给toArray(),这样toArray方法才知道,你希望转换为哪种类型的数组,否则只能转换为Object数组
  • addAll():把另一个容器所有对象都加进来
  • clear():清空一个ArrayList
1.1.3、LinkedList(链表)(常用)

序列分先进先出FIFO,先进后出FILO,FIFO在Java中又叫Queue 队列,FILO在Java中又叫Stack 栈。与ArrayList一样,LinkedList也实现了List接口,诸如add,remove,contains等等方法。

1.1.4、双向链表-Deque

除了实现List接口外,还实现了双向链表结构Deque,可以很方便的在头尾插入删除数据。

  • getFirst():查看最前面的数据
  • getLast():查看最后面的数据
  • removeFirst():移除最前面的数据
  • removeLast():移除最后面的数据
  • addFirst():在最前面插入数据
  • 队列-Queue,除了实现List,Deque之外,还实现了Queue接口(队列),
1.1.5、Queue是先进先出队列FIFO
  • offer():在最后添加元素
  • poll():取出第一个元素
  • peek():查看第一个元素,不会删除。
  • 栈-Stack
  • push():把元素添加在最后位置
  • pull():将最后一个元素取出

1.2、Set(无序不可重复)

  • HashSet:底层数据结构是哈希表。
    保证元素唯一性:hashCode()和equals()方法。
  • LinkedHashSet:底层数据结构是链表和哈希表
    保证元素唯一性:哈希表
    保证元素有序(存入和取出顺序一致,是特殊的Set):链表
  • TreeSet:底层数据结构是红黑树。
    保证元素排序:自然排序和比较器排序
    自然排序(元素具有比较性):让元素所属的类实现Comparable接口
    比较器排序(集合具有比较性):让集合接收一个Comparator的实现类对象
    保证元素唯一性:根据比较的返回值是否是0来决定
1.2.1、HashSet(常用)
  • 元素不能重复
  • 没有顺序:严格的说,是没有按照元素的插入顺序排列;HashSet的具体顺序,既不是按照插入顺序,也不是按照hashcode的顺序。
  • 遍历:Set不提供get()来获取指定位置的元素,所以遍历需要用到迭代器,或者增强型for循环
1.2.1.1、HashSet的实现原理:
  1. HashSet是基于HashMap实现的,默认构造函数是构建一个初始容量为16,负载因子为0.75 的HashMap。封装了一个HashMap 对象来存储所有的集合元素,所有放入 HashSet 中的集合元素实际上由 HashMap 的 key 来保存,而HashMap 的 value 则存储了一个 PRESENT,它是一个静态的 Object 对象。
  2. 当我们试图把某个类的对象当成 HashMap的 key,或试图将这个类的对象放入 HashSet 中保存时,重写该类的equals(Object obj)方法和 hashCode() 方法很重要,而且这两个方法的返回值必须保持一致:当该类的两个的 hashCode() 返回值相同时,它们通过 equals() 方法比较也应该返回 true。通常来说,所有参与计算 hashCode() 返回值的关键属性,都应该用于作为 equals() 比较的标准。
  3. HashSet的其他操作都是基于HashMap的。
1.2.2、TreeSet

TreeSet的底层实现是TreeMap,TreeSet会自动排序,里面的元素都是有序的,根据存储元素实现的Comparable接口,重写compareTo方法来进行排序。当TreeSet的泛型对象不是java的基本类型的包装类时,对象需要重写Comparable#compareTo()方法

HashSet与TreeSet的区别
  • 1、HashSet与TreeSet接口的一点不同,HashSet 保存的数据是无序的,TreeSet保存的数据是有序的,所以如果要想保存的数据有序应该使用TreeSet子类。

  • 2、利用TreeSet保存自定义类对象的时候,自定义所在的类一定要实现Comparable接口,如果没有实现这个接口那么就无法区分大小关系,而且在TreeSet中如果要进行排序,那么就要将所有的字段都进行比较,就是说在TreeSet中是依靠comparato()方法返回的是不是0来判断是不是重复元素的。

  • 3、如果是HashSet子类,那么其判断重复数据的方式不是依靠的comparable接口而是Object类之中的两个方法:(1)取得对象的哈希码 hashCode();(2)对象比较:equals(); 这俩个方法均不需要自己编写,在eclipse里面可以使用右键source 选择自动生成。就像生成Getter 和Setter 方法一样。

最后:

  • TreeSet 依靠的是Comparable 来区分重复数据;

  • HashSet 依靠的是hashCode()、equals()来区分重复数据

  • Set 里面不允许保存重复数据。

1.2.3、Iterator:迭代器

概念:

  • Iterator接口提供了很多对集合元素进行迭代的方法。每一个集合类都包括了可以返回迭代器实例的迭代方法。迭代器可以在迭代过程中删除底层集合的元素,但是不可以直接调用集合的remove(Object obj)删除,可以通过迭代器的remove()方法删除
  • Iterator提供了同意遍历操作集合元素的统一接口,Collection接口实现了Iterable接口,每个集合都通过实现Iterable接口中的iterator()方法返回Iterator接口的实例,然后对集合的元素进行迭代操作

2、Map

2.1、HashMap(重点,天天用)

  • HashMap的键值对:存储数据的方式是键值对。put(“adc”,”abc)
  • 键不能重复,值可以重复:以相同的key 把不同的value插入到 Map中会导致旧元素被覆盖,只留下最后插入的元素。
2.1.1、HashMap原理与字典

在展开HashMap原理的讲解之前,首先回忆一下大家初中和高中使用的汉英字典。比如要找一个单词对应的中文意思,假设单词是Lengendary,首先在目录找到Lengendary在第 555页。然后,翻到第555页,这页不只一个单词,但是量已经很少了,逐一比较,很快就定位目标单词Lengendary。555相当于就是Lengendary对应的hashcode

2.1.2、HashMap的底层结构
  • HashMap的主干是一个Entry数组。Entry是HashMap的基本组成单元,每一个Entry包含一个key-value键值对。整体结构图:
    在这里插入图片描述
  • HashMap由数组+链表组成的。
  • 数组是HashMap的主体,链表则是主要为了解决哈希冲突而存在的,如果定位到的数组位置不含链表(当前entry的next指向null),那么对于查找,添加等操作很快,仅需一次寻址即可;如果定位到的数组包含链表,对于添加操作,其时间复杂度为O(n),首先遍历链表,存在即覆盖,否则新增;对于查找操作来讲,仍需遍历链表,然后通过key对象的equals方法逐一比对查找。所以,性能考虑,HashMap中的链表出现越少,性能才会越好。
2.1.3、List查找的低效率

假设在List中存放着无重复名称,没有顺序的2000000个Hero,要把名字叫做“hero 1000000”的对象找出来,List的做法是对每一个进行挨个遍历,直到找到名字叫做“hero 1000000”的英雄。最差的情况下,需要遍历和比较2000000次,才能找到对应的英雄。
测试逻辑:
\1. 初始化2000000个对象到ArrayList中
\2. 打乱容器中的数据顺序
\3. 进行10次查询,统计每一次消耗的时间

不同计算机的配置情况下,所花的时间是有区别的。 在本机上,花掉的时间大概是600毫秒左右

2.1.4、HashMap的性能表现

使用HashMap 做同样的查找
\1. 初始化2000000个对象到HashMap中。
\2. 进行10次查询
\3. 统计每一次的查询消耗的时间

可以观察到,几乎不花时间,花费的时间在1毫秒以内

2.1.5、分析HashMap性能卓越的原因

–hashcode概念-----
所有的对象,都有一个对应的hashcode(散列值)
比如字符串“gareen”对应的是1001 (实际上不是,这里是方便理解,假设的值)
比如字符串“temoo”对应的是1004
比如字符串“db”对应的是1008
比如字符串“annie”对应的也****是1008

-----保存数据-----
准备一个数组,其长度是2000,并且设定特殊的hashcode算法,使得所有字符串对应的hashcode,都会落在0-1999之间
要存放名字是"gareen"的英雄,就把该英雄和名称组成一个键值对,存放在数组的1001这个位置上
要存放名字是"temoo"的英雄,就把该英雄存放在数组的1004这个位置上
要存放名字是"db"的英雄,就把该英雄存放在数组的1008这个位置上
要存放名字是"annie"的英雄,然而 "annie"的hashcode 1008对应的位置已经有db英雄了,那么就在这里创建一个链表,接在db英雄后面存放annie

-----查找数据-----
比如要查找gareen,首先计算"gareen"的hashcode是1001,根据1001这个下标,到数组中进行定位,(根据数组下标进行定位,是非常快速的) 发现1001这个位置就只有一个英雄,那么该英雄就是gareen.
比如要查找annie,首先计算"annie"的hashcode是1008,根据1008这个下标,到数组中进行定位,发现1008这个位置有两个英雄,那么就对两个英雄的名字进行逐一比较(equals),因为此时需要比较的量就已经少很多了,很快也就可以找出目标英雄
这就是使用hashmap进行查询,非常快原理。

这是一种用空间换时间的思维方式

分析HashMap性能卓越的原因

2.2、TreeMap

TreeMap:基于红黑树实现。TreeMap没有调优选项,因为该树总处于平衡状态。
TreeMap<K,V>的Key值是要求实现java.lang.Comparable,所以迭代的时候TreeMap默认是按照Key值升序排序的;TreeMap的实现是基于红黑树结构。适用于按自然顺序或自定义顺序遍历键(key)。

  • TreeMap 是非线程安全的,继承自SortedMap接口。

  • TreeMap中是默认按照升序进行排序的,可以通过自定义比较器实现降序。

  • TreeMap():构建一个空的映像树

  • TreeMap(Map m): 构建一个映像树,并且添加映像m中所有元素

  • TreeMap(Comparator c): 构建一个映像树,并且使用特定的比较器对关键字进行排序

  • TreeMap(SortedMap s): 构建一个映像树,添加映像树s中所有映射,并且使用与有序映像s相同的比较器排序

2.3、LinkedHashMap

LinkedHashMap:很像HashMap,但是用Iterator进行遍历的时候,它会按插入顺序或最先使用的顺序(least-recently-used(LRU)order)进行访问。除了用Iterator外,其他情况下,只是比HashMap稍慢一点。
LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder)

2.4、IdentityHashMap

在 HashMap 中, 它用 equals 方法来判断两个键是否相等, 而在 IdentityHashMap 中, 它用 == 来判断两个键是否相等. 除此之外, 它和 HashMap 没有任何区别.

2.5、SortedMap

SortedMap(只有TreeMap这一个实现)的键肯定是有序的,因此这个接口里面就有一些附加功能的方法了。

3、Collections

  • reverse():使List中的数据发生翻转
  • shuffle():混淆List中数据的顺序
  • sort():对List中的数据进行排序
  • swap():交换两个数据的位置
  • rotate():把List中的数据,向右滚动指定单位的长度
  • synchronizedList():把非线程安全的List转换为线程安全的List

3.1、Collection与Conllections的区别

  • Collection是集合类的上级接口,继承与他的接口主要有Set 和List.
  • Collections是针对集合类的一个帮助类,他提供一系列静态方法实现对各种集合的搜索、排序、线程安全化等操作。

4、二叉树

4.1、二叉树概念

  • 二叉树由各种节点组成
  • 二叉树特点:
    每个节点都可以有左子节点,右子节点
    每一个节点都有一个

4.2、二叉树排序–插入数据

  • 插入基本逻辑是,小、相同的放左边大的放右边
  • 假设通过二叉树对如下10个随机数进行排序
    67,7,30,73,10,0,78,81,10,74
  • \1. 67 放在根节点
    \2. 7 比 67小,放在67的左节点
    \3. 30 比67 小,找到67的左节点7,30比7大,就放在7的右节点
    \4. 73 比67大, 放在67的右节点

4.3、二叉树排序–遍历

  • 二叉树的遍历分左序,中序,右序
    左序即: 中间的数遍历后放在左边
    中序即: 中间的数遍历后放在中间
    右序即: 中间的数遍历后放在右边

5、泛型Generic

不指定泛型的容器,可以存放任何类型的元素。指定了泛型的容器,只能存放指定类型的元素以及其子类

        //对于不使用泛型的容器,可以往里面放英雄,也可以往里面放物品
        List heros = new ArrayList();    
        heros.add(new Hero("盖伦"));    
        //本来用于存放英雄的容器,现在也可以存放物品了
        heros.add(new Item("冰杖"));   
        //引入泛型Generic
        //声明容器的时候,就指定了这种容器,只能放Hero,放其他的就会出错
        List<Hero> genericheros = new ArrayList<Hero>();
        genericheros.add(new Hero("盖伦"));
        //如果不是Hero类型,根本就放不进去
        //genericheros.add(new Item("冰杖"));
        //除此之外,还能存放Hero的子类
        genericheros.add(new APHero());

泛型的简写

为了不使编译器出现警告,需要前后都使用泛型,像这样:List genericheros = new ArrayList();

不过JDK7提供了一个可以略微减少代码量的泛型简写方式,如果低于7就是报错:List genericheros2 = new ArrayList<>();

6、集合之间的关系与区别

6.1、ArrayList与HashSet

  • ArrayList: 有顺序
    HashSet: 无顺序,HashSet的具体顺序,既不是按照插入顺序,也不是按照hashcode的顺序。
  • List中的数据可以重复
    Set中的数据不能够重复
    重复判断标准是:
    首先看hashcode是否相同
    如果hashcode不同,则认为是不同数据
    如果hashcode相同,再比较equals,如果equals相同,则是相同数据,否则是不同数据
    更多关系hashcode,请参考hashcode原理

6.2、ArrayList与LinkedList

  • ArrayList 插入,删除数据慢
    LinkedList, 插入,删除数据快
    ArrayList是顺序结构,所以定位很快,指哪找哪。 就像电影院位置一样,有了电影票,一下就找到位置了。
  • LinkedList 是链表结构,就像手里的一串佛珠,要找出第99个佛珠,必须得一个一个的数过去,所以定位慢

6.3、HashMap与HashTable

  • HashMap和Hashtable都实现了Map接口,都是键值对保存数据的方式
  • 区别1:
    HashMap可以存放 null;可以用null作为key,作为value
    Hashtable不能存放null;不可以用null作为key,作为value
  • 区别2:
    HashMap不是线程安全的类
    Hashtable是线程安全的类

7、HashCode原理


7.1、List查找的低效率

假设在List中存放着无重复名称,没有顺序的2000000个Hero,要把名字叫做“hero 1000000”的对象找出来,List的做法是对每一个进行挨个遍历,直到找到名字叫做“hero 1000000”的英雄。最差的情况下,需要遍历和比较2000000次,才能找到对应的英雄。
测试逻辑:
\1. 初始化2000000个对象到ArrayList中
\2. 打乱容器中的数据顺序
\3. 进行10次查询,统计每一次消耗的时间

不同计算机的配置情况下,所花的时间是有区别的。 在本机上,花掉的时间大概是600毫秒左右

7.2、HashMap的性能表现

使用HashMap 做同样的查找
\1. 初始化2000000个对象到HashMap中。
\2. 进行10次查询
\3. 统计每一次的查询消耗的时间

可以观察到,几乎不花时间,花费的时间在1毫秒以内

7.3、HashMap原理与字典

在展开HashMap原理的讲解之前,首先回忆一下大家初中和高中使用的汉英字典。比如要找一个单词对应的中文意思,假设单词是Lengendary,首先在目录找到Lengendary在第 555页。然后,翻到第555页,这页不只一个单词,但是量已经很少了,逐一比较,很快就定位目标单词Lengendary。555相当于就是Lengendary对应的hashcode

7.4、分析HashMap性能卓越的原因

–hashcode概念-----
所有的对象,都有一个对应的hashcode(散列值)
比如字符串“gareen”对应的是1001 (实际上不是,这里是方便理解,假设的值)
比如字符串“temoo”对应的是1004
比如字符串“db”对应的是1008
比如字符串“annie”对应的也****是1008

-----保存数据-----
准备一个数组,其长度是2000,并且设定特殊的hashcode算法,使得所有字符串对应的hashcode,都会落在0-1999之间
要存放名字是"gareen"的英雄,就把该英雄和名称组成一个键值对,存放在数组的1001这个位置上
要存放名字是"temoo"的英雄,就把该英雄存放在数组的1004这个位置上
要存放名字是"db"的英雄,就把该英雄存放在数组的1008这个位置上
要存放名字是"annie"的英雄,然而 "annie"的hashcode 1008对应的位置已经有db英雄了,那么就在这里创建一个链表,接在db英雄后面存放annie

-----查找数据-----
比如要查找gareen,首先计算"gareen"的hashcode是1001,根据1001这个下标,到数组中进行定位,(根据数组下标进行定位,是非常快速的) 发现1001这个位置就只有一个英雄,那么该英雄就是gareen.
比如要查找annie,首先计算"annie"的hashcode是1008,根据1008这个下标,到数组中进行定位,发现1008这个位置有两个英雄,那么就对两个英雄的名字进行逐一比较(equals),因为此时需要比较的量就已经少很多了,很快也就可以找出目标英雄
这就是使用hashmap进行查询,非常快原理。

这是一种用空间换时间的思维方式

分析HashMap性能卓越的原因

8、HashSet判断是否重复

HashSet的数据是不能重复的,相同数据不能保存在一起,到底如何判断是否是重复的呢?
根据HashSet和HashMap的关系,我们了解到因为HashSet没有自身的实现,而是里面封装了一个HashMap,所以本质上就是判断HashMap的key是否重复。

再通过上一步的学习,key是否重复,是由两个步骤判断的:
hashcode是否一样
如果hashcode不一样,就是在不同的坑里,一定是不重复的
如果hashcode一样,就是在同一个坑里,还需要进行equals比较
如果equals一样,则是重复数据
如果equals不一样,则是不同数据。

9、Comparator比较器

假设Hero有三个属性 name,hp,damage;一个集合中放存放10个Hero,通过Collections.sort对这10个进行排序;那么到底是hp小的放前面?还是damage小的放前面?Collections.sort也无法确定;所以要指定到底按照哪种属性进行排序;这里就需要提供一个Comparator给定如何进行两个对象之间的大小比较

//引入Comparator,指定比较的算法
        Comparator<Hero> c = new Comparator<Hero>() {
            @Override
            public int compare(Hero h1, Hero h2) {
                //按照hp进行排序
                if(h1.hp>=h2.hp)
                    return 1;  //正数表示h1比h2要大
                else
                    return -1;
            }
        };
        Collections.sort(heros,c);

10、聚合操作

JDK8之后,引入了对集合的聚合操作,可以非常容易的遍历,筛选,比较集合中的元素。

public class TestAggregate {
  
    public static void main(String[] args) {
        Random r = new Random();
        List<Hero> heros = new ArrayList<Hero>();
        for (int i = 0; i < 10; i++) {
            heros.add(new Hero("hero " + i, r.nextInt(1000), r.nextInt(100)));
        }
 
        System.out.println("初始化集合后的数据 (最后一个数据重复):");
        System.out.println(heros);
         
        //传统方式
        Collections.sort(heros,new Comparator<Hero>() {
            @Override
            public int compare(Hero o1, Hero o2) {
                return (int) (o2.hp-o1.hp);
            }
        });
         
        Hero hero = heros.get(2);
        System.out.println("通过传统方式找出来的hp第三高的英雄名称是:" + hero.name);
         
        //聚合方式
        String name =heros
            .stream()
            .sorted((h1,h2)->h1.hp>h2.hp?-1:1)
            .skip(2)
            .map(h->h.getName())
            .findFirst()
            .get();
 
        System.out.println("通过聚合操作找出来的hp第三高的英雄名称是:" + name);
         
    }
}

聚合操作

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值