java学习笔记——基础17(List接口、Set接口以及其实现的集合类,哈希表(Hash table),Queue接口(队列),Deque 接口(双端队列))

1、List接口
2、ArrayList集合、LinkedList集合
3、Set接口
4、哈希表(Hash table)
5、HashSet集合、LinkedHashSet集合、TreeSet集合
6、判断集合唯一性原理
7、Queue接口(队列)
8、PrioritQueue 优先级队列
9、Deque 接口(双端队列)、ArrayDeque 实现类

01List接口的特点

1
2
3
4
5
6
7
8
9
10
A:List接口的特点:
a:"它是一个元素【存取有序】的集合。"
例如,存元素的顺序是112233。那么集合中,元素的存储就是按照112233的顺序完成的)。
b:"它是一个【带有索引】的集合",通过索引就可以精确的操作集合中的元素(与数组的索引是一个道理)。

c:"集合中【可以有重复】的元素",通过元素的equals方法,来比较是否为重复的元素。

d:List接口的常用子类有:
 ArrayList集合
 LinkedList集合

02List接口的特有方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
A:List接口的特有方法(带索引的方法)
a:增加元素方法
 add(Object e):"向集合末尾处,添加指定的元素"
 add(int index, Object e) "向集合指定索引处,添加指定的元素,原有元素依次后移"

/*
* add(int index, E)
* 将元素插入到列表的指定索引上
* 带有索引的操作,防止越界问题
* java.lang.IndexOutOfBoundsException
* ArrayIndexOutOfBoundsException
* StringIndexOutOfBoundsException
*/
public static void function(){
List<String> list = new ArrayList<String>();
list.add("abc1");
list.add("abc2");
list.add("abc3");
list.add("abc4");
System.out.println(list);

list.add(1, "itcast");
System.out.println(list);
}
——————————————————————————————————————————————————————————————————————————————————————————
b:删除元素删除
 remove(Object e):"将指定元素对象,从集合中删除,返回值为被删除的元素"
 remove(int index):"将指定索引处的元素,从集合中删除,返回值为被删除的元素"
/*
* E remove(int index)
* 移除指定索引上的元素
* 返回被删除之前的元素
*/
public static void function_1(){
List<Double> list = new ArrayList<Double>();
list.add(1.1);
list.add(1.2);
list.add(1.3);
list.add(1.4);

Double d = list.remove(0);
System.out.println(d);
System.out.println(list);
}

——————————————————————————————————————————————————————————————————————————————————————————
c:替换元素方法
 set(int index, Object e):"将指定索引处的元素,替换成指定的元素,返回值为替换前的元素"
注意:
"指定的索引【必须】是List集合的有效索引"、例如集合长度是4,就不能指定替换索引为4处的元素。
也就是说,set(int index, Object element)方法"【不会改变】List集合的【长度】"

" /*
* E set(int index, E)
* 修改指定索引上的元素
* 返回【被修改之前】的元素
*/"
public static void function_2(){
List<Integer> list = new ArrayList<Integer>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);

Integer i = list.set(0, 5);
System.out.println(i);
System.out.println(list);
}
d:查询元素方法
 get(int index):获取指定索引处的元素,并返回该元素
——————————————————————————————————————————————————————————————————————————————————————————
e:指定元素的索引 indexOf
int indexOf(Object o)
"返回此列表中第一次出现的【指定元素的索引】;如果此列表不包含该元素,则返回 -1。"
更确切地讲,返回满足 (o==null ? get(i)==null : o.equals(get(i))) 的"最低索引 i"
如果没有这样的索引,则返回 -1

03迭代器的并发修改异常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
A:迭代器的并发修改异常

"/*
* 迭代器的并发修改异常 java.util.ConcurrentModificationException (并发修改异常)
* 就是在遍历的过程中,使用了集合方法【修改】了【集合的长度】,不允许的
*/"
public class ListDemo1 {
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("abc1");
list.add("abc2");
list.add("abc3");
list.add("abc4");

//对集合使用迭代器进行获取,获取时候判断集合中是否存在 "abc3"对象
//如果有,添加一个元素 "ABC3"
Iterator<String> it = list.iterator();
while(it.hasNext()){
String s = it.next();
//对获取出的元素s,进行判断,是不是有"abc3"
if(s.equals("abc3")){
"添加一个元素 "ABC3",造成集合长度变化,抛出异常"
list.add("ABC3");//error

"修改指定索引的元素为"ABC3",【没有】造成集合长度变化,正常运行"
list.set(list.indexOf("abc3"),"ABC3");//正常运行
}
System.out.println(s);
}
}
}

运行上述代码发生了错误 java.util.ConcurrentModificationException这是什么原因呢?
在迭代过程中,使用了集合的方法对元素进行操作。
"导致迭代器并不知道集合中的变化,容易引发数据的不确定性"

"并发修改异常解决办法"
"在迭代时,【不要使用】集合的方法操作元素"
或者"通过ListIterator迭代器操作元素是可以"的,ListIterator的出现,解决了使用Iterator迭代过程中可能会发生的错误情况。

04数据的存储结构

1
2
3
4
5
6
7
8
9
10
A:数据的存储结构
a:栈结构:"后进先出/先进后出"(手枪弹夹) FILO (first in last out)
b:队列结构:"先进先出/后进后出"(银行排队) FIFO(first in first out)
c:数组结构:
"【查询快】:通过索引快速找到元素"
"【增删慢】:每次增删都需要开辟新的数组,将老数组中的元素拷贝到新数组中"
" 开辟新数组耗费资源"
d:链表结构
" 【查询慢】:每次都需要从链头或者链尾找起"
"【增删快】:只需要修改元素记录的下个元素的地址值即可不需要移动大量元素"

方法调用的内存图

05ArrayList集合的自身特点

1
2
3
4
5
6
7
8
9
10
11
A:ArrayList集合的自身特点
底层采用的是数组结构
ArrayList al=new ArrayList();//创建了一个长度为0的Object类型数组
al.add("abc");"//底层会创建一个长度为10的Object数组 "Object[] obj=new Object[10]
//obj[0]="abc"
"//如果添加的元素的超过10个,底层会开辟一个1.5*10的长度的新数组"
"//把原数组中的元素【拷贝】(Arrays.copyOf)到新数组,再把最后一个元素添加到新数组中"
原数组:
a b c d e f g h k l
添加m:
a b c d e f g h k l m null null null null

06LinkedList集合的自身特点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
A:LinkedList集合的自身特点
LinkedList 类是 "List 接口 "的 实现类,LinkedList 还实现了 "Deque 接口",
可以被当成"双端队列"来使用 , 因此既可以被当成"栈"来使用,也可以 当成"队列"使用 。

"底层采用链表结构,每次查询都要从【链头】或【链尾】找起,【查询】相对数组【较慢】"
"但是【删除元素】直接【修改元素记录的地址值】即可,不需要大量移动元素,【增删】相对数组【较快】"

LinkedList的"索引"决定是从"链头"开始找还是从"链尾"开始找
"如果该元素【小于】元素长度一半,从【链头】开始找起;如果【大于】元素长度的一半,则从【链尾】找起"
实现所有可选的列表操作,并且允许所有元素(包括 null)。
除了实现 List 接口外,
LinkedList 类还为在"列表的开头及结尾" get、remove 和 insert 元素提供了统一的命名方法。
这些操作允许将"链接列表"用作"堆栈、队列"或双端队列。

LinkedList集合数据存储的结构是链表结构。
"方便元素添加、删除的集合"
实际开发中对一个集合元素的添加与删除经常涉及到"首尾操作"
而LinkedList提供了大量"首尾操作"的方法

07LinkedList特有方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
具体查看 :"25 Deque 接口(双端队列)与 ArrayDeque 实现类、LinkedList 实现类"

public class LinkedListDemo {
public static void main(String[] args) {
LinkedList<Integer> lds = new LinkedList<>();
"//将元素加入队列的尾部"
lds.offer(23);

"//将一个元素加入【栈】的顶部"
lds.push(111);

"//将元素加入【队列】的尾部"
lds.offer(407);

"//将元素添加到【队列】的头部(相当于【栈】的顶部〉"
lds.addFirst(56);

"//输出:[56, 111, 23, 407]"
System.out.println(lds);

"//以 List 的方式(按【索引访问】的方式〉来遍历集合元素"
for(int ee:lds){
System.out.println(ee);
}

//访问并不删除顶的元素:56
System.out.println(lds.peekFirst()) ;
//访问并不删除队列的最后一个元素:407
System.out.println(lds.peekLast()) ;

"//将【栈】顶的元素弹出 ,输出 56"
System.out.println(lds.pop());

//输出:[111, 23, 407]
System.out.println(lds);

//访问并删除【队列】的最后一个元素: 407
System.out.println(lds.pollLast());

//输出:[111, 23]
System.out.println(lds);

}
}


————————————————————————————————————————————————————————————————————————————————————

*A:LinkedList特有方法:获取,添加,删除
*
* LinkedList 链表集合的特有功能
* "自身特点: 链表底层实现,查询慢,增删快"
*
"* 子类的特有功能,不能多态调用,只能向下强制转换"
*
public class LinkedListDemo {
public static void main(String[] args) {
function_3();
}

————————————————————————————————————————————————————————————————————————————————————
/*
* E removeFirst() 移除并返回链表的开头
* E removeLast() 移除并返回链表的结尾
*/
public static void function_3(){
LinkedList<String> link = new LinkedList<String>();
link.add("1");
link.add("2");
link.add("3");
link.add("4");

String first = link.removeFirst();
String last = link.removeLast();
System.out.println(first);
System.out.println(last);

System.out.println(link);
}

————————————————————————————————————————————————————————————————————————————————————
/*
* E getFirst() 获取链表的开头
* E getLast() 获取链表的结尾
*/
public static void function_2(){
LinkedList<String> link = new LinkedList<String>();
link.add("1");
link.add("2");
link.add("3");
link.add("4");

if(!link.isEmpty()){
String first = link.getFirst();
String last = link.getLast();
System.out.println(first);
System.out.println(last);
}
}

public static void function_1(){
LinkedList<String> link = new LinkedList<String>();
link.addLast("a");
link.addLast("b");
link.addLast("c");
link.addLast("d");

link.addFirst("1");
link.addFirst("2");
link.addFirst("3");
System.out.println(link);
}

————————————————————————————————————————————————————————————————————————————————————
/*
* addFirst(E) 添加到链表的开头
* addLast(E) 添加到链表的结尾
*/
public static void function(){
LinkedList<String> link = new LinkedList<String>();

link.addLast("heima");

link.add("abc");
link.add("bcd");

link.addFirst("itcast");
System.out.println(link);


}
}

08 各List实现类的性能分析,集合Vector类的特点,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
Java 提供的 List 就是一个线性表接口,
而ArrayList 、 LinkedList 又是线性表的两种典型实现 :
"ArrayList是基于数组的线性表""LinkedList是基于链的线性表"
"Queue 代表了队列"" Deque 代表了双端队列(既可作为【队列】使用,也可作为【栈】使用)"
"LinkedList 集合"不仅提供了 "List 的功能",还提供了"双端队列""栈"的功能。


接下来对各种实现类的性能进行分析。

"总体来说""ArrayList "的性能"比" "LinkedList" 的性能要"好"
"数组""随机访问""性能""最好"
所有的内部以数组作为底层实现的集合在随机访问时性能都比较好 ;
而内部以"链表"作为底层实现的集合在"执行插入、删除操作"时有"较好"的性能 。

》如果需要"遍历 List 集合元素"
对于 ArrayList、 Vector 集合 , 应该使用"随机访问方法 (get) ""遍历"集合元素,这样性能更好 ;
对于" LinkedList 集合",则应该采用"迭代器 (Iterator) ""遍历"集合元素 。

》如果需要经常"执行插入、删除操作"来改变包含大量数据的 List 集合的大小:
可考虑"使用LinkedList 集合"
使用 "ArrayList 、 Vector 集合"可能需要经常"重新分配内部数组的大小",效果可能"较差"

》如果有"多个线程"需要"同时访问" List 集合中的元素,
开发者可考虑使用 "Collections "将集合"包装""线程安全"的集合。

————————————————————————————————————————————————————————————————————————————————————

*B:Vector类的特点
Vector集合数据存储的结构是数组结构,为JDK中"最早"提供的"集合",它是"线程同步"
Vector中提供了一个独特的取出方式,就是枚举Enumeration,它其实就是早期的迭代器。
此接口Enumeration的功能与 Iterator 接口的功能是类似的。
" Vector集合已被 ArrayList【替代】。枚举Enumeration 已被 迭代器Iterator【替代】"

方法调用的内存图

09Set接口的特点

1
2
3
4
5
6
7
8
9
10
Set接口类似于个"罐子""程序可以依次把多个对象“丢进”Set集合"
集合通常"不能"记住"元索的添加顺序"
"Set集合与Collection基本相同""【没有】提供【任何】【额外的方法】"
实际上Set就是Collection,只是行为略有不同("Set不允许包含重复元素")。

A:Set接口的特点
a:它是个"【不包含】重复元素"的集合。
b:Set集合取出元素的方式可以采用:"【迭代器】、【增强for】""不能通过索引进行取值"
HashSet"没有提供get()方法",同HashMap一样,"Set内部是无序的",只能通过迭代的方式获得
c:Set集合有多个子类,这里我们介绍其中的"HashSet、LinkedHashSet"这两个集合。

10Set集合存储和迭代(以HashSet为例)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
A:Set集合存储和迭代
"HashSet类(散列集) 实现 Set 接口,由哈希表Hash table(实际上是一个 HashMap 实例)支持"
"【不保证】 set 的迭代顺序""特别是它【不保证】该顺序恒久不变"
此类允许"使用 null 元素"
"/*
* Set接口,特点不重复元素,没索引
*
* Set接口的实现类,HashSet
* 特点: 无序集合,存储和取出的顺序不同,没有索引,不存储重复元素
* 代码的编写上,和ArrayList完全一致
*/"
public class HashSetDemo {
public static void main(String[] args) {
Set<String> set = new HashSet<String>();
set.add("cn");
set.add("heima");
set.add("java");
set.add("java");
set.add("itcast");

Iterator<String> it = set.iterator();
while(it.hasNext()){
System.out.println(it.next());
}
System.out.println("==============");

for(String s : set){
System.out.println(s);
}
}
}

11哈希表的数据结构

1
2
3
4
5
6
7
8
9
10
11
A:哈希表的数据结构:(参见图解)

"加载因子:表中填入的记录数 / 哈希表的长度"
例如:
加载因子是0.75 代表:
数组中的16个位置,其中存入16*0.75=12个元素

如果在存入第13个(>12)元素,导致存储链子过长,会降低哈希表的性能,那么此时会"扩充哈希表(再哈希 Rehash)",
底层会"开辟一个长度为原长度2倍的数组",把老元素拷贝到新数组中,再把新元素添加数组中

当存入"元素数量" > "哈希表长度*加载因子",就要"扩容",因此"加载因子决定扩容时机"

方法调用的内存图

12字符串对象的哈希值(HashCode)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
A:字符串对象的哈希值
"/*
* 对象的哈希值,普通的十进制整数
* 父类Object,方法 public int hashCode() 计算结果int整数
*/"
举例:
StringBuilder sb = new StringBuilder("abc");
StringBuilder tb = new StringBuilder("abc");
int s = sb.hashCode();
int t = tb.hashCode();
System.out.println("s: " + s);"//StringBuilder没有重写HashCode方法,返回对象的内存地址值"
System.out.println("t: " + t);//StringBuilder没有重写HashCode方法,返回对象的内存地址值
System.out.println(sb.equals(tb));"//比较的是对象的内存地址值"

System.out.println("————————————————————————————————————————1");
String ss= new String("abc");"//String重写HashCode方法,根据内容的值进行计算"
String st = "abc";
System.out.println("ss.hashCode(): " + ss.hashCode());
System.out.println("st.hashCode(): " + st.hashCode());
System.out.println(ss.equals(st));"//比较的是对象的内容是否完全相同"

——————————————————————————————————————————————————————————————————————————————————————————
public class HashDemo {
public static void main(String[] args) {
Person p = new Person();
int i = p.hashCode();
System.out.println(i);

String s1 = new String("abc");
String s2 = new String("abc");
System.out.println(s1.hashCode());//96354
System.out.println(s2.hashCode());//96354

"两个【不同】字符串的hashCode值完全可能【相同】"
System.out.println("重地".hashCode());//1179395
System.out.println("通话".hashCode());//1179395

}
}

//String类重写hashCode()方法
//字符串都会存储在底层的value数组中{'a','b','c'}
public int hashCode() {
int h = hash;//hash初值为0
if (h == 0 && value.length > 0) {
char val[] = value;

for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}

13哈希表的存储过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
A:哈希表的存储原理
当向哈希表中"存放元素"时,需要根据元素的"特有数据结合相应的算法"
这个算法其实就是"Object类中的【hashCode方法】"
由于任何对象都是Object类的子类,所以"任何对象都拥有这个方法"
即就是"在给哈希表中【存放对象】时,会【调用】对象的【hashCode方法】"

这里需要注意:
算出对象在表中的存放位置,如果"两个对象hashCode方法算出【结果一样】",这样现象称为"哈希冲突"
这时会调用对象的"equals方法",比较这"两个对象【是不是】同一个对象"
(1)如果"equals方法返回的是true",那么就"不会"把第二个对象存放在哈希表中,
(2)如果"返回的是false",就会"把这个值存放在哈希表中"

总结:"保证HashSet集合元素的唯一",其实就是根据"对象的 hashCode和 equals 方法"来决定的。
!!!如果我们往集合中存放"自定义的对象",那么保证其唯一,
"必须""重写hashCode和equals方法"建立属于当前对象的比较方式。

——————————————————————————————————————————————————————————————————————————————————————————
B:哈希表的存储过程
public static void main(String[] args) {
HashSet<String> set = new HashSet<String>();
set.add(new String("abc"));
set.add(new String("abc"));
set.add(new String("bbc"));
set.add(new String("bbc"));
System.out.println(set);
}

存取原理:
每存入一个新的元素都要走以下三步:

1.首先调用"本类的hashCode()方法"算出哈希值

2."在容器中找是否与【新元素】【哈希值相同】的【老元素】",
"如果没有直接存入"
如果有转到第三步

3."新元素会与该索引位置下的老元素利用equals方法"一一对比
一旦"新元素.equals(老元素)"返回true,停止对比,"说明重复","不再存入"
如果与该索引位置下的老元素都通过equals方法对比返回false,说明"没有重复","存入"

——————————————————————————————————————————————————————————————————————————————————————————
举例:
HashSet<String> hset = new HashSet<>();
hset.add("abc");"//hashCode: 96354"
hset.add("abc");
hset.add("ad%");"//hashCode: 96354"
hset.add("yut");
System.out.println(hset);

上述代码:
1行: "abc"的hashCode 为 96354

2行: "abc"的hashCode 为 96354,调用新元素.equals(老元素),
"abc".equals("abc")结果为true,说明"元素重复,不添加到HashSet中";

3行: "ad%"的hashCode 也为 96354,调用新元素.equals(老元素),
"ad%".equals("abc")结果为false"没有元素重复,添加到HashSet中";

——————————————————————————————————————————————————————————————————————
方法调用的内存图
——————————————————————————————————————————————————————————————————————
方法调用的内存图
——————————————————————————————————————————————————————————————————————

14HashSet存储自定义的对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
A:HashSet存储自定义的对象
"/*
* HashSet集合的自身特点:
* 底层数据结构,哈希表
* 存储,取出都比较快
* 线程不安全,运行速度快
*/"
public class HashSetDemo1 {
public static void main(String[] args) {

//将Person对象中的姓名,年龄,相同数据,看作同一个对象
"//判断对象是否重复,依赖对象自己的方法 hashCode(),equals()"
HashSet<Person> setPerson = new HashSet<Person>();
setPerson.add(new Person("a",11));
setPerson.add(new Person("b",10));"hashCode方法返回对象的地址值,地址值不同,会存入HashSet"
setPerson.add(new Person("b",10));"hashCode方法返回对象的地址值,地址值不同,会存入HashSet"
setPerson.add(new Person("c",25));
setPerson.add(new Person("d",19));
setPerson.add(new Person("e",17));
"//每个对象的【地址值都不同】,调用【Obejct类】的hashCode方法返回【不同】【哈希值】,【直接存入】"
System.out.println(setPerson);
}
}

public class Person {
private String name;
private int age;

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 Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
public Person(){}

public String toString(){
return name+".."+age;
}

}

15自定义对象重写hashCode和equals方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
 A:自定义对象重写hashCode和equals方法
"给HashSet中存放【自定义类型元素】时,
需要【重写】对象中的hashCode和equals方法,建立【自己的比较方式】,
才能【保证】HashSet集合中的【对象唯一】"

也就是说,
HashSet集合"判断两个元素相等的标准"是:
两个对象通过"equals()方法"比较"相等",返回true"并且"两个对象的"hashCode()方法"返回值"也相等"
"则 HashSet集合中的 两个元素【相同】,【不予添加】"
"/*
* HashSet集合的自身特点:
* 底层数据结构,哈希表
* 存储,取出都比较快
* 线程不安全,运行速度快
*/"
public class HashSetDemo1 {
public static void main(String[] args) {

//将Person对象中的姓名,年龄,相同数据,看作同一个对象
"//判断对象是否重复,依赖【对象自己】的方法 hashCode,equals"
HashSet<Person> setPerson = new HashSet<Person>();
setPerson.add(new Person("a",11));
setPerson.add(new Person("b",10));
setPerson.add(new Person("b",10));
setPerson.add(new Person("c",25));
setPerson.add(new Person("d",19));
setPerson.add(new Person("e",17));
System.out.println(setPerson);
}
}

public class Person {
private String name;
private int age;

"/*
* 没有做重写父类(Obejct类)的hashCode和equals方法,每次运行结果都是不同整数,
因为每个对象的【地址值都不同】,调用【Obejct类】的hashCode方法返回【不同】【哈希值】
* 如果子类重写父类hashCode和equals方法,将会得到自定义的哈希值
* 存储到HashSet集合的依据:用hashCode和equals方法进行判断
*
* 尽可能让不同的属性值产生不同的哈希值(优化hashCode的产生方法),这样就不用再调用equals方法去比较属性
*
*/"
public int hashCode(){
return name.hashCode()+age*55;
}
//方法equals重写父类,保证和父类相同
//public boolean equals(Object obj){}
public boolean equals(Object obj){
if(this == obj)
return true;
if(obj == null)
return false;
if(obj instanceof Person){
Person p = (Person)obj;
return name.equals(p.name) && age==p.age;
}
return false;
}

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 Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
public Person(){}

public String toString(){
return name+".."+age;
}



}

16LinkedHashSet集合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
A:LinkedHashSet集合
" /*
* LinkedHashSet 基于【链表】的【哈希表】实现
* 继承自HashSet:"有序"的hashSet
*
* LinkedHashSet 自身特性,"具有顺序",存储和取出的顺序相同
* 线程不安全的集合,"运行速度块"
*/"
public class LinkedHashSetDemo {

public static void main(String[] args) {
LinkedHashSet<Integer> link = new LinkedHashSet<Integer>();
link.add(123);
link.add(44);
link.add(33);
link.add(33);
link.add(66);
link.add(11);
System.out.println(link);
}
}

17ArrayList,HashSet判断对象是否重复的原理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
ArrayList,HashSet判断对象是否重复的原理

1"ArrayList的【contains方法】判断元素是否重复"
a:"ArrayList的【contains方法】原理:【底层依赖于】【equals方法】"
ArrayList的【contains方法】会使用调用方法时,
"传入的元素的equals方法依次与集合中的旧元素所比较"
从而根据返回的布尔值判断是否有重复元素。

b:"当ArrayList存放【自定义类型】时",由于自定义类型在"未重写equals方法"之前,
"判断是否重复的依据是内存地址值""所以如果想根据【内容】判断是否为重复元素,需要【重写】元素的equals方法"

——————————————————————————————————————————————————————————————————————————————————————————

2: "HashSet的add/contains等方法判断元素是否重复"
"HashSet的【add()方法和contains方法()】【底层】都【依赖】 hashCode()方法与equals方法()"

HashSet集合"判断两个元素相等的标准"是:
两个对象通过"equals()方法"比较"相等",返回true"并且"两个对象的"hashCode()方法"返回值"也相等"
"则 HashSet集合中的 两个元素【相同】,【不予添加】"

Set集合"不能"存放重复元素,"其add()方法在添加时会判断是否有重复元素,"有重复【不】添加","没重复则添加""
HashSet集合由于是"无序"的,其判断唯一的依据是元素类型的"hashCode与equals方法的返回结果"

规则如下:
先判断新元素与集合内已经有的旧元素的"HashCode值"
1): 如果"不同",说明是"不同元"素,"添加到集合"
2): 如果"相同",再"判断equals比较结果"。返回true"相同元素""不予添加"
返回false"不同元素""添加到集合"
即:两个对象通过"equals()方法"比较"相等",并且两个对象的"hashCode()方法"返回值"也相等"
"不予添加"
——————————————————————————————————————————————————————————————————————————————————————————
总结:
使用"HashSet""存储【自定义类型】"时,
如果"没有重写"该类的hashCode与equals方法,则判断重复时,使用的是"内存地址值"
如果想通过"【内容】比较元素是否相同""需要重写"该元素类的hashcode与equals方法。

——————————————————————————————————————————————————————————————————————————————————————————

!!!!!!!!!!!!!!"注意"
如果向HashSet中添加一个"可变对象"后,
后面程序修改了该"可变对象""实例变量",则可能导致它与集合中"其他对象""元素相同"
(即两个对象通过equals()方法比较返回true,两个对象的 hashCode 值也相等),
这就可能导致HashSet 中包含"两个相同的对象"

public class Example {
int count;
public Example(int count) {
this.count = count;
}

public Example() {
}

@Override
public int hashCode() {
return this.count;
}

@Override
public boolean equals(Object obj) {
if(this == obj){
return true;
}
if(obj != null && obj.getClass()==Example.class){
Example ex = (Example) obj;
return this.count ==ex.count;
}
return false;
}
@Override
public String toString(){
return "Example[count: "+ this.count + "]";
}
}

public class ExampleTest {
public static void main(String[] args) {
HashSet<Example> hs = new HashSet<>();
hs.add(new Example(5));
hs.add(new Example(-3));
hs.add(new Example(9));
hs.add(new Example(-2));
//打印HashSet集合,集合元素没有重复
System.out.println(hs);
Iterator<Example> it = hs.iterator();
it.next().count = -3;

//为第一个元素的count实例变量赋值
System.out.println(hs);
//删除值为-3的Example对象
hs.remove(new Example(-3));

System.out.println(hs);
System.out.println("hs是否包含count为-3的Example对象? "+ hs.contains(new Example(-3)));
System.out.println("hs是否包含count为-2的Example对象? "+ hs.contains(new Example(-2)));
}
}

结果:
[Example[count: -2], Example[count: -3], Example[count: 5], Example[count: 9]]
[Example[count: -3], Example[count: -3], Example[count: 5], Example[count: 9]]
[Example[count: -3], Example[count: 5], Example[count: 9]]
hs是否包含count为-3的Example对象? false
hs是否包含count为-2的Example对象? false

分析:
正如结果所见到的,
HashSet集合的第1个元素和第2个元素"完全相同",这表明两个元素"已经重复"。此时HashSet会比较混乱:
当试图删除count为-3的Example对象时,
HashSet会计算出该对象的hashCode值,从而找出该对象在集合中的保存位置,
然后把此处的对象与count为-3的Example对象时通过equals()方法进行比较,如果相等则删除该对象:
HashSet"只有"2个元素才满足该条件(第1个元素
"实际"上保存在count为-2的Example对象对应的位置),
所以"第2个元素被删除"。至于第一个count为-3的Example对象,它保存在count为-2的Example对象对应的位置,
但使用equals()方法拿它和count为-2的R对象比较时又返回false—这将导致HashSet"不能""准确"访问该"元素"

18hashCode和equals方法的面试题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
 A:hashCode和equals的面试题

两个对象 Person p1 p2
问题:
(1)"如果两个对象的哈希值相同" p1.hashCode()==p2.hashCode()
两个对象的equals一定返回true吗 p1.equals(p2) 一定是true
正确答案:"p1.equals(p2)不一定"true

(2)"如果两个对象的equals方法返回true",p1.equals(p2)==true
两个对象的"哈希值一定相同"
正确答案: "哈希值一定相同"


——————————————————————————————————————————————————————————————————————————————————————————
在 Java 应用程序执行期间,"规定"
1."如果根据 equals(Object) 方法""两个对象是相等"的,
那么对这两个对象中的每个对象调用 "hashCode 方法""必须生成相同的整数结果"
2.如果根据 equals(java.lang.Object) 方法,"两个对象【不相等】"
那么对这两个对象中的任一对象上调用" hashCode 方法" "不要求"一定"生成不同的整数结果"
此时,hashCode值(可以"相同""可以不同")

2.1 两个对象不同(对象属性值不同) equals返回false=====>两个对象调用hashCode()方法"哈希值""可相同"

两个对象调用hashCode()方法哈希值不同=====>equals返回true


2.2 两个对象不同(对象属性值不同) equals返回false=====>两个对象调用hashCode()方法"哈希值""可不同"

两个对象调用hashCode()方法哈希值相同=====>equals返回true

"所以说两个对象【哈希值】无论【相同】还是【不同】,equals都可能返回"true

19TreeSet类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
TreeSet 类与散列集HashSet十分类似, 不过, 它比HashSet有所改进。
TreeSet是一个"有序集合"( sorted collection) 。可以以任意顺序将元素插入到集合中。
在对集合进行遍历时, 每个值将"自动地按照排序后""顺序"呈现。
TreeSet是SortedSet接口的实现类。

"HashSet集合"采用"hash算法"来决定元索的"存储位置"不同,
"TreeSet"采用"红黑树"的数据结构来存储集合元素。
TreeSet支持两种排序方法:"自然排序""定制排序"

public Comparator<? super E> comparator():
如果TreeSet采用了"定制排序",则该方法返回定制排序所使用的
如果TreeSet采用了"自然排序",则返回null

public E first(): 返回集合中的第一个元素。
public E last(): 返回集合中的最后一个元素。
public E lower(E e):返回集合中位于指定元素之前的元素(即小于指定元素的最大元素,
参考元素不需要是TreeSet集合里的元素)
public E higher(E e):返回集合中位于指定元素之后的元素(即大于指定元素的最小元素,
参考元素不需要是TreeSet集合里的元素)
public SortedSet<E> subSet(E fromElement,E toElement):
返回此Set的子集合,范围从fromElement (包含)toElement(不包含)
public SortedSet<E> headSet(E toElement):
返回此Set的子集,由小于toElement的元素组成。
public SortedSet<E> tailSet(E fromElement):
返回此Set的子集,由大于或等于fromElement的元素组成。

TreeSet并【不是】根据元素的【插入顺序】进行排序的,而是根据元素【实际值的大小】来进行排序的。
public class TreeSetDemol {
public static void main(String[] args) {
TreeSet<Integer> tset = new TreeSet<>();
tset.add(12);
tset.add(-9);
tset.add(19);
tset.add(78);
tset.add(3);
System.out.println(tset);//[-9, 3, 12, 19, 78]
System.out.println(tset.first());//-9
System.out.println(tset.last());//78
System.out.println(tset.lower(5));//3
System.out.println(tset.higher(7));//12
System.out.println(tset.headSet(4));//[-9, 3]
System.out.println(tset.tailSet(8));//[12, 19, 78]
System.out.println(tset.subSet(2,13));//[3, 12]
}
}

20TreeSet类的自然排序和定制排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
1. 自然排序
TreeSet会调用集合元素的compareTo(Object obj)方法来比较元素之间的大小关系,
然后将集合元素按升序排列,这种方式就是自然排序。
如果试图把个对象添加到TreeSet时,则该对象的类必须实现Comparable接口,否则程序将会抛出异常。
例如:
class Err{}
public class TreeSetErrorTest
{
public static void main (String「1args)
{
TreeSet ts=new TreeSet();
//向TreeSet集合中添加两个Err对象
ts.add(aew Err());
ts.add (nest Err());//(1)
}
}

上面的程序试图向TreeSet集合中添加两个Err对象,添加第一个对象时,TreeSet里没有任何元素,
所以不会出现任何问题;但是添加第二个Err对象时,
TreeSet就会调用该对象的compareTo(Object obj)方法与集合中的其他元素进行比较:
如果其对应的类没有实现Comparable接口,则会引发
CIassCastException异常。因此,上面的程序会在(1)处引发该异常。

此外:
"如果希望TreeSet能正常运作,TreeSet【只能】添加【同一种类型】的对象"
对于TreeSet集合,判断两个对象是否相等的唯一标准是:
两个对象通过"compareTo(Object obj)方法"比较"是否返回0"
如果通过compareTo(Object obj)方法比较"返回0",TreeSet则会认为它们"相等";否则就认为它们不相等


—————————————————————————————————————————————————————————————————————————————————————————————

2. 定制排序
TreeSet 的 自然排序是根据集合元素的大小, TreeSet将它们以升序排列。
如果需要实现定制排序 ,例如以降序排列 ,则可以通过" Comparator 接口 "的帮助 。
该接 口 里包含一个 int compare(T 01, T 02)方法 ,
该方法用于 比较 01 和 02 的大小:
如果该方法返回正整数,则表 明 01 大于 02; 如果该方法返回 0,则表 明 0 1 等于 02;
如果该方法返回负整数,则表 明 01 小于 02 ;

如果需要实现"定制排序" ,则需要在创建 TreeSet 集合对象时,提供一个 "Comparator 对象"与该 "TreeSet
集合""关联" ,由该" Comparator 对象""负责"集合元素的"排序逻辑"
由于 Comparator 是一个函数式接口 , 因此可使用 Lambda 表达式来代替 Comparator 对象 。

//TreeSet定制排序
class M{
int age;

public M(int age) {
this.age = age;
}

@Override
public String toString() {
return "M{" +
"age=" + age +
'}';
}
}
public class Test {
public static void main(String[] args) {
//此处 Lambda 表达式的目标类型是 Comparator
TreeSet ts = new TreeSet((t1, t2) ->
{
M m1 = (M) t1;
M m2 = (M) t2;
//根据"对象的 a归属性来决定大小, ag. 越大, M 对象反而越小
return m1.age> m2.age ? -1
: m1.age<m2.age ? 1 : 0;
});
ts.add(new M(5));
ts.add(new M(-3));
ts.add(new M(9));
System.out.println(ts);
}
}

结果:
降序排列: [M{age=9}, M{age=5}, M{age=-3}]

21TreeSet类判断对象是否重复的原理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
对于TreeSet集合,判断"两个对象是否相等""唯一标准"是:
两个对象通过"compareTo(Object obj)方法""比较"是否返回0
如果通过compareTo(Object obj)方法比较"返回0",TreeSet则会认为它们"相等";"否则"就认为它们"不相等"
只和"compareTo(Object obj)方法""结果有关"

举例:
public class Exampale implements Comparable {
int age;
public Exampale(){

}
public Exampale(int age){
this.age =age;
}
@Override
public boolean equals(Object obj){
return true; "equals()方法总是返回 true"
}

public int compareTo(Object obj){
return 1; "compareTo(Object obj)方法总是返回 1"
}
}

public class TreeSetDemo {
public static void main(String[] args) {
TreeSet<Exampale> ts= new TreeSet<>();
Exampale e1 = new Exampale(10);
ts.add(e1); (1)
//第二次添加同一个对象 , 输出 true , 表明添加成功
boolean bb = ts.add(e1); (1)

//下面输出 set 集合,将看到有两个元素
System.out.println(ts);
//修改 set 集合的第一个元素的 age 变量
ts.first().age = 88;
//输出 set 集合的最后一个元素的 age 变量,将看到也变成了88
System.out.println(ts.last().age);//88
}
}
结果:
[demo10.Exampale@4554617c, demo10.Exampale@4554617c]
88

分析:
程序中(1)代码行把同一个对象再次添加到 TreeSet 集合中 ,
"因为 e1 对象的compareTo(Object obj)方法总是返回 1 ""不返回0 "
虽然它的 equals()方法总是返回 true ,但 "TreeSet "
会认为 " e1 对象 ""它自己 ""不相等 "
因此,TreeSet 可以添加两个e1对象。

从图可以看到 TreeSet 对象保存的两个元素(集合里的元素总是"引用"
但习惯上把被引用的对象称为集合元素) , 实际上是同一个元素("同一个引用") 。
所以当"修改" TreeSet 集合里"第一个元素"的 age 变量后,
该 TreeSet 集合里"最后一个元素"的 age 变量也"随之改变"

由此应该注意一个问题 :
当需要把一个对象放入 TreeSet 中,重写该对象对应类的" equals方法"时 ,
应保证该方法与 "compareTo(Object obj)方法""一致""结果".
其规则是 : 如果两个对象通过" equals()方法"
比较返回 true 时,这两个对象通过 "compareTo(Object obj)方法 "比较应"返回 0 "

方法调用的内存图

22各Set实现类的性能分析

1
2
3
4
5
6
7
8
9
HashSet 和 TreeSet 是 Set 的两个典型实现 ,到底如何选择HashSet 和 TreeSet 呢? 
"HashSet "的性能总是"比" "TreeSet" "好"(特别是最常用的"添加、查询元素等操作" ) ,
因为 TreeSet 需要额外的"红黑树算法"来维护集合元素的"次序"
只有当需要一个保持"排序"的 Set 时,才应该使用 TreeSet , "否则都应该使用 HashSet"


LinkedHashSet,对于普通的"插入、删除操作", LinkedHashSet 比 HashSet要"略微慢一点"
这是由"维护链表"所带来的额外开销造成的 ,
"由于有了链表""遍历" LinkedHashSet 会"比"HashSet"更快"

23 Queue(队列)集合

1
2
3
4
5
6
7
8
9
10
11
12
13
Queue 用于模拟队列这种数据结构 , 队列通常是指"先进先出" (FIFO ) 的容器 。 
队列的头部保存在队列中存放时间最长的元素 ,队列的尾部保存在队列中存放时间最短的元素。
新元素插入 (offer ) 到队列的尾部,访问元素 (poll) 操作会返回队列头部的元素 。
通常 ,队列不允许随机访问队列中的元素。

Queue 接口中定义了如下儿个方法。
~ void add(Object e): 将指定元素加入此队列的【尾部】 。
~ Object element(): 获取队列【头部】 的元素,但是不删除该元素 。
~ boolean offer(Object e): 将指定元素加入此队列的【尾部】。当使用有容量 限制的队列时,此方法通
常比 add(Object e)方法更好 。
~ Object peek(): 获取队列【头部】的元素,但是【不删除】该元素。如果此队列为空,则返回 null
~ Object poll(): 获取队列【头部】的元素 , 并【删除】该元素 。如果此队列为 空 ,则返回 null
~ Object remove(): 获取 队列【头部】的元素,并删除该元素 。

24 Priority Queue 优先级队列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
"PriorityQueue" 是一个比较标准的队列实现类 ,但"不是绝对标准""队列"实现,
是因为 PriorityQueue 保存队列元素的顺序并"不是""加入队列""顺序"
而是按"队列元素""大小"进行"重新排序"(堆结构)。
因此当调用 "peek()方法或者 poll()方法"取出队列中的元素时,
"不是取出""最先进入队列的元素""而是"取出队列中"最小的元素"
从这个意义上来看 , PriorityQueue 已经违反了队列的最基本规则 : 先进先出 (FIFO )

优先级队列使用的是"堆(heap)数据结构"
堆是一个可以"自我调整的二叉树",对树执行"添加(add) ""删除(remore) "操作,
可以让"最小的元素(优先级最高)""移动到根"
都将"优先级最高"的任务从"队列""删除"(由于"习惯上将1设为最高优先级",所以会将"最小的元素删除" )
而不必花费时间对元素进行排序。

优先级队列(priority queue) 中的元素可以"按照任意的顺序插人"
却总是"按照排序的顺序进行检索"
也就是说,无论何时调用 remove 方法,"总会获得"当前"优先级队列""最小的元素(优先级最高的元素)"
然而,优先级队列并没有对所有的元素进行排序

public class QueueDemo {
public static void main(String[] args) {
Queue<Integer> pq =new PriorityQueue<>();
pq.offer(12);
pq.offer(-10);
pq.offer(118);
pq.offer(5);
pq.offer(120);
System.out.println(pq);//[-10, 5, 118, 12, 120]
System.out.println(pq.poll());//-10
System.out.println(pq);//[5, 12, 118, 120]
System.out.println(pq.poll());//5
System.out.println(pq.poll());//12
System.out.println(pq.poll());//118
System.out.println(pq.poll());//120


}
}

运行上面程序"直接输出" PriorityQueue 集合时,可能看到该队列里的元素"并没有"很好地"按大小进行排序"
但这只是受到 PriorityQueue 的 toString()方法的返回值的影响 。
实际上 ,
程序"多次调用" PriorityQueue集合对象的" poll()方法",即可看到元素"按从小到大"的顺序"移出队列"


PriorityQueue "不允许插入" null 元素,
它还"需要""队列元素"进行"排序" ,PriorityQueue 的元素有两种排序方式。
"自然排序" : 采用自然顺序的 PriorityQueue 集合中的元素必须实现了 "Comparable 接口"
而且应该是"同一个类的多个实例",否则可能导致 ClassCastException 异常 。

"定制排序":创建 PriorityQueue 队列时,传入一个 "Comparator 对象"
该对象负责对队列中的所有元素进行排序 。采用定制排序时不要求队列元素实现 Comparable 接口 。
PriorityQueue 队列对元素的要求与 TreeSet 对元素的要求基本 一致

25 Deque 接口(双端队列)与 ArrayDeque 实现类、LinkedList 实现类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
~ void addFirst(Object e): 将指定元素插入该双端队列的开头。
~ void addLast(Object e): 将指定元素插入该双端队列的末尾。
~ Iterator descendingIterator(): 返回该双端队列对应的迭代器,该迭代器将以逆向顺序来法代队列
中的元素。
~ Object getFirst(): 获取但不删除双端队列的第一个元素。
~ Object getLast() : 获取但不删除双端队列的最后 一个元素 。
~ boolean offerFirst(Object e): 将指定元素插入该双端队列的开头 。
~ boolean offerLast(Object e): 将指定元素插入该双端队列的末尾 。
~ Object peekFirst(): 获取但不删除该双端队列的第一个元素;如果此双端队列为空,则返回 null
~ Object peekLast(): 获取但不删除该双端队列的最后 一个元素;如果此双端队列为空,则返回 null
~ Object pollFirst(): 获取并删除该双端队列的第一个元素 :如果此双端队列为 空 ,则返回 null o
~ Object pollLast(): 获取并删除该双端队列的最后一个元素 ; 如果此双端队列为空,则返回 null
~ Object pop() (栈方法) : pop 出该双端队列所表示的栈的栈顶元素 。 相当于 removeFirst()
~ void push(Object e) (栈方法) : 将 一个元素 push 进该双端队列所表 示 的栈的栈顶 。 相当于
addFirst(e)
~ Object removeFirst(): 获取并删除该双端队列的第一个元素 。
~ Object removeFirstOccurrence(Object 0): 删 除该双端队列的第一次出现的元素 。 。
~ Object removeLast(): 获取并删除该双端队列的最后一个元素 。
~ boolean removeLastOccurrence(Object 0): 删除该双端队列的最后一次出现的元素。;

从上面方法中可以看出," Deque" 不仅可以 当成"双端队列"使用,而且可以被当成"栈"来使用 , 因为 该类
里还包含了 pop (出栈〉、 push (入栈)两个方法。

————————————————————————————————————————————————————————————————————————————————————
Deque 接口提供了 一个典型的实现类: "ArrayDeque"
从该名称就可以看出,它是一个"基于数组实现的双端队列"
创建 Deque 时同样可指定一个 numElements 参数 ,该参数用于指定 Object[]数组的长度:
如果不指定 numElements 参数, Deque 底层数组的长度为 16
"
————————————————————————————————————————————————————————————————————————————————————
Queue 方法 等效 Deque 方法
add(e) addLast(e)
offer(e) offerLast(e)
remove() removeFirst()
poll() pollFirst()
element() getFirst()
peek() peekFirst()
————————————————————————————————————————————————————————————————————————————————————
堆栈方法 等效 Deque 方法
push(e) addFirst(e)
pop() removeFirst()
peek() peekFirst()

————————————————————————————————————————————————————————————————————————————————————
"
当然 "ArrayDeque" 也可以 当成"队列使用",此处 ArrayDeque 将按"先进先出"的方式操作集合元素
public class ArrayDequeDemo1 {
public static void main(String[] args) {
ArrayDeque<Integer> ad = new ArrayDeque<>();
ad.offer(23);
ad.offer(1);
ad.offer(56);
ad.offer(-70);
ad.offer(8);

"//输出 :[23, 1, 56, -70, 8]"
System.out.println(ad);

////访问队列头部的元素,但并不将其 poll 出队列"钱 ", 输出 : 23
System.out.println(ad.peek());

// poll 出第一个元素,输出 23
System.out.println(ad.poll());

//输出 :[1, 56, -70, 8]
System.out.println(ad);
}
}

展示 "ArrayDeque" 作为"栈"的行为 ,"后进先出",
因此当程序中需要使用"栈" 这种数据结构时,推荐使用 ArrayDeque
public class ArrayDequeDemo {
public static void main(String[] args) {
ArrayDeque<Integer> ad = new ArrayDeque<>();
ad.push(42);
ad.push(205);
ad.push(-30);
ad.push(78);
ad.push(3);
ad.push(11);

"//输出 :[11, 3, 78, -30, 205, 42]"
System.out.println(ad);

//访问第一个元素,但并不将其 pop 出"栈,输出 :11
System.out.println(ad.peek());

//输出 :[11, 3, 78, -30, 205, 42]
System.out.println(ad);

//第一个元素将其 pop 出"栈,输出 :11
System.out.println(ad.pop());

//输出 :[ 3, 78, -30, 205, 42]
System.out.println(ad);
System.out.println(ad.peekLast());

}
}


————————————————————————————————————————————————————————————————————————————————————
LinkedList 类是 "List 接口 "的 实现类,LinkedList 还实现了 "Deque 接口",
可以被当成"双端队列"来使用 , 因此既可以被当成"栈"来使用,也可以 当成"队列"使用 。

public class LinkedListDemo {
public static void main(String[] args) {
LinkedList<Integer> lds = new LinkedList<>();
"//将元素加入队列的尾部"
lds.offer(23);

"//将一个元素加入【栈】的顶部"
lds.push(111);

"//将元素加入【队列】的尾部"
lds.offer(407);

"//将元素添加到【队列】的头部(相当于【栈】的顶部〉"
lds.addFirst(56);

"//输出:[56, 111, 23, 407]"
System.out.println(lds);

"//以 List 的方式(按【索引访问】的方式〉来遍历集合元素"
for(int ee:lds){
System.out.println(ee);
}

//访问并不删除顶的元素:56
System.out.println(lds.peekFirst()) ;
//访问并不删除队列的最后一个元素:407
System.out.println(lds.peekLast()) ;

"//将【栈】顶的元素弹出 ,输出 56"
System.out.println(lds.pop());

//输出:[111, 23, 407]
System.out.println(lds);

//访问并删除【队列】的最后一个元素: 407
System.out.println(lds.pollLast());

//输出:[111, 23]
System.out.println(lds);

}
}

小结

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
	List与Set集合的区别?
List:
它是一个有序的集合(元素存与取的顺序相同)
它可以存储重复的元素
Set:
它是一个无序的集合(元素存与取的顺序可能不同)
它不能存储重复的元素

 List集合中的特有方法
void add(int index, Object element) 将指定的元素,添加到该集合中的指定位置上
 Object get(int index)返回集合中指定位置的元素。
 Object remove(int index) 移除列表中指定位置的元素, 返回的是被移除的元素
 Object set(int index, Object element)用指定元素替换集合中指定位置的元素,返回值的更新前的元素

 ArrayList:
底层数据结构是数组,查询快,增删慢
 LinkedList:
底层数据结构是链表,查询慢,增删快

 HashSet:
元素唯一,不能重复
底层结构是 哈希表结构
元素的存与取的顺序不能保证一致
如何保证元素的唯一的?
重写hashCode()equals()方法

 LinkedHashSet:
元素唯一不能重复
底层结构是 哈希表结构 + 链表结构
元素的存与取的顺序一致
-------------本文结束感谢您的阅读-------------
感谢您的支持,我会继续努力的!
0%