集合框架
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
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的实现原理:
- HashSet是基于HashMap实现的,默认构造函数是构建一个初始容量为16,负载因子为0.75 的HashMap。封装了一个HashMap 对象来存储所有的集合元素,所有放入 HashSet 中的集合元素实际上由 HashMap 的 key 来保存,而HashMap 的 value 则存储了一个 PRESENT,它是一个静态的 Object 对象。
- 当我们试图把某个类的对象当成 HashMap的 key,或试图将这个类的对象放入 HashSet 中保存时,重写该类的equals(Object obj)方法和 hashCode() 方法很重要,而且这两个方法的返回值必须保持一致:当该类的两个的 hashCode() 返回值相同时,它们通过 equals() 方法比较也应该返回 true。通常来说,所有参与计算 hashCode() 返回值的关键属性,都应该用于作为 equals() 比较的标准。
- 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进行查询,非常快原理。
这是一种用空间换时间的思维方式
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进行查询,非常快原理。
这是一种用空间换时间的思维方式
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);
}
}