课前回顾:
1.Lock锁:接口-> 实现类 ->ReentractLock
2.方法: lock()获取锁 unlock()释放锁
3.实现原理:乐观锁,比较并交换->CAS
4.Condition:线程阻塞队列->和Lock锁结合使用
获取: Lock类中的newCondition()
方法: await()->线程等待
signal()->线程唤醒
5.线程池:Executors
获取: ExecutorService newFixedThreadPool(int n)->获取线程池对象,并且指明池子中最多能都n个线程
submit(Runnable r)
submit(Callable c)
call方法和run方法的区别:
相同点:都是设置线程任务
不同点:
call()有返回值,可以直接throws异常
run()没有返回值,不能直接throws异常
Future接口:用于接收run方法或者call方法的返回值,但是run方法没有返回值,所以,不用Future接收
get()->将call方法的返回值获取到
6.定时器:Timer->可以让线程任务每隔多长时间执行一次
构造:Timer()
方法:void schedule(TimerTask task, Date firstTime, long period)
task:设置线程任务
firsttime:从哪个时间开始计算
period:间隔多长时间
7.集合
a.概述:容器
b.作用:存储元素
c.特点:
长度可变
只能存储引用数据类型
8.Collection:单列集合的顶级接口
方法:add(E e)->添加元素
addAll(Collection c)->合并集合
clear()清空集合元素
contains(E e)集合中是否包含指定的元素
remove(E e):删除指定元素
size():获取几个元素个数
isEmpty() : 判断集合中是否有元素
toArray() :将集合转成数组
9.迭代器:Iterator接口
a.获取:Collection下的方法 Iterator<E> iterator()
b.方法:hashNext()->判断集合中有没有下一个元素
next()->获取下一个元素
c.并发修改异常:
什么原因出现:当add之后,再次调用next方法,导致了实际操作次数和预期操作次数不相等
10.数据结构:
栈:先进后出
队列:先进先出
数组:查询快(有索引),增删慢(数组定长)
链表:查询慢,增删快
单向链表:前面的元素知道后面的元素;但是后面的不知道前面的->不能保证元素有序
双向链表:前面的记录后面的元素,后面的元素记录前面的元素-> 能保证元素有序
11.List接口(有索引,元素有序,元素可重复)
12.实现类:ArrayList
特点: 有索引,元素有序,元素可重复
数据结构: 数组
方法:
add(E)
add(int index,E e)->往指定索引位置上添加元素
remove(Object o)->删除指定的元素
remove(int index)->按照指定索引删除元素
get(int index)->获取元素
set(int index, E element) ->将指定索引位置上的元素,修改成element
底层:
利用空参构造newArraylist集合时,底层会创建一个长度为10的列表
不是一new就创建出来了,而是第一次add的时候,底层会创建一个长度为10的列表
ArrayList长度为什么可变:数组复制
扩容多少: 1.5倍
今日重点:
1.会使用LinkedList的方法操作元素
2.会使用Collections集合工具类的方法
3.会使用增强for遍历集合
4.知道HashSet和LinkedHashSet的特点以及使用
5.知道HashSet元素去重复的过程
6.如果HashSet存储自定义类型如何去重
第一章.LinkedList集合
1.LinkedList集合
1.概述:
是List接口的实现类
2.特点:
有索引,元素有序,元素可重复
底层数据结构:链表(双向链表)
3.方法:
- public void addFirst(E e):将指定元素插入此列表的开头。
- public void addLast(E e):将指定元素添加到此列表的结尾。
- public E getFirst():返回此列表的第一个元素。
- public E getLast():返回此列表的最后一个元素。
- public E removeFirst():移除并返回此列表的第一个元素。
- public E removeLast():移除并返回此列表的最后一个元素。
- public E pop():从此列表所表示的堆栈处弹出一个元素。
- public void push(E e):将元素推入此列表所表示的堆栈。
- public boolean isEmpty():如果列表不包含元素,则返回true。
public class Demo01LinkedList {
public static void main(String[] args) {
LinkedList<String> linkedList = new LinkedList<>();
linkedList.add("李云龙");
linkedList.add("丁伟");
linkedList.add("楚云飞");
linkedList.add("魏和尚");
linkedList.add("大彪");
linkedList.add("沈泉");
linkedList.add("秀琴");
linkedList.addFirst("赵刚");
System.out.println(linkedList);
linkedList.addLast("田雨");
System.out.println(linkedList);
String first = linkedList.getFirst();
System.out.println(first);
System.out.println("=============================");
//- public E pop():从此列表所表示的堆栈处弹出一个元素。
//String pop = linkedList.pop();
//System.out.println(pop);
//System.out.println(linkedList);
//- public void push(E e):将元素推入此列表所表示的堆栈。
//linkedList.push("涛哥");
//System.out.println(linkedList);
System.out.println("==========================");
for (int i = 0; i < linkedList.size(); i++) {
System.out.println(linkedList.get(i));
}
System.out.println("=======================");
Iterator<String> iterator = linkedList.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
}
}
1.1 LinkedList底层成员解释说明
1.LinkedList底层成员
transient int size = 0; 元素个数
transient Node<E> first; 第一个结点对象
transient Node<E> last; 最后一个结点对象
2.Node代表的是结点对象
private static class Node<E> {
E item;//结点上的元素
Node<E> next;//记录着下一个节点地址
Node<E> prev;//记录着上一个结点地址
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
1.2 LinkedList中add方法源码分析
LinkedList<String> linkedList = new LinkedList<>();
linkedList.add("a");
linkedList.add("b");
System.out.println(linkedList);
=========================================================
public boolean add(E e) {
linkLast(e);
return true;
}
void linkLast(E e) {
final Node<E> l = last; //null
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
由此证明,LinkedList底层是双向链表存储,前面的记录后面的,后面的记录前面的
1.3.LinkedList中get方法源码分析
public E get(int index) {
checkElementIndex(index);
return node(index).item;
}
Node<E> node(int index) {
// assert isElementIndex(index);
if (index < (size >> 1)) {
Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
index < (size >> 1)采用二分法,先将index与长度size的一半比较,如果index<size/2,就只从位置0往后遍历到位置index处,而如果index>size/2,就只从位置size往前遍历到位置index处。这样可以减少一部分不必要的遍历
第二章.Collections工具类
1.概述:专门操作集合的工具类
2.方法都是静态的
类名直接调用
3.方法:
- public static void shuffle(List<?> list):打乱集合顺序。
- public static <T> void sort(List<T> list):将集合中元素按照默认规则排序。->ASCII
- public static <T> void sort(List<T> list,Comparator<? super T> ):将集合中元素按照指定规则排序。
4.比较器:
Comparator:比较器
compare(o1,o2)-> o1-o2(升序) -> o2-o1(降序)
Comparable:比较器
compareTo(o)-> this-o(升序) -> o-this(降序)
public class Test01 {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("宋江");
list.add("晁盖");
list.add("武松");
list.add("林冲");
System.out.println(list);
//- public static void shuffle(List<?> list):打乱集合顺序。
Collections.shuffle(list);
System.out.println(list);
}
}
public class Test02 {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("DDaaa");
list.add("Accc");
list.add("Bbbb");
list.add("Cddd");
System.out.println(list);
//- public static <T> void sort(List<T> list):将集合中元素按照默认规则排序。->ASCII
Collections.sort(list);
System.out.println(list);
}
}
//Comparator比较器
public class Test03 {
public static void main(String[] args) {
ArrayList<Person> list = new ArrayList<Person>();
list.add(new Person("柳岩",36));
list.add(new Person("杨幂",32));
list.add(new Person("金莲",40));
/*
- public static <T> void sort(List<T> list,Comparator<? super T> ):
将集合中元素按照指定规则排序。
*/
Collections.sort(list, new Comparator<Person>() {
@Override
public int compare(Person o1, Person o2) {
return o2.getAge()-o1.getAge();
}
});
System.out.println(list);
}
}
public class Person {
private String name;
private int age;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = 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;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
Comparable比较器接口:
public class Person implements Comparable<Person>{
private String name;
private int age;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = 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;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public int compareTo(Person o) {
return o.age-this.age;
}
}
public class Test04 {
public static void main(String[] args) {
ArrayList<Person> list = new ArrayList<Person>();
list.add(new Person("柳岩",36));
list.add(new Person("杨幂",32));
list.add(new Person("金莲",40));
Collections.sort(list);
System.out.println(list);
}
}
第三章.增强for
1.增强for的介绍以及基本使用
1.作用: 用于遍历集合
2.格式:
for(元素类型 变量名:要遍历的集合或者数组){
变量名就代表的是每一个元素
}
3.快捷键:
集合名或者数组名.for
public class Test01 {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("宋江");
list.add("晁盖");
list.add("武松");
list.add("林冲");
System.out.println("========普通for==========");
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
System.out.println("=======迭代器========");
Iterator<String> iterator = list.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
System.out.println("=======增强for======");
for (String s : list) {
System.out.println(s);
}
}
}
public class Test02 {
public static void main(String[] args) {
int[] arr = {1,2,3,4,5,6};
for (int i : arr) {
System.out.println(i);
}
}
}
2.使用增强for时要注意的事项
1.注意:
在使用增强for的时候尽量不要改变集合长度,否则会出现并发修改异常
2.如果使用增强for遍历集合,实现原理是迭代器
如果使用增强for遍历数组,实现原理是普通for
第四章.泛型
1.为什么要使用泛型
1.如果不使用泛型的话:集合类型默认为Object
很可能会出现类型转换异常
2.使用泛型好处:
a.限制数据类型,防止类型转换异常
2.泛型的定义
2.1含有泛型的类
1.格式:
public class 类名<E>{
}
2.什么时候确定泛型的类型呢?
new对象是确定E的具体类型
public class ArrayListMyself <E>{
public void add(E e){
System.out.println(e);
}
}
public class Test01 {
public static void main(String[] args) {
ArrayListMyself<Integer> list = new ArrayListMyself<>();
list.add(1);
}
}
2.2含有泛型的方法
1.格式:
修饰符 <E> 返回值 方法名(E e){}
2.什么时候确定类型
被调用的时候确定类型
public class Person{
public <E> void eat(E e){
System.out.println(e);
}
}
public class Test {
public static void main(String[] args) {
Person person = new Person();
person.eat("hahahah");//传递什么类型的数据,eat中的E类型就代表什么类型的数据
}
}
2.3含有泛型的接口
1.定义格式:
修饰符 interface 接口名<E>{}
2.什么时候确定类型
a.在实现类的时候直接确定了类型-->Scanner
b.在实现类的时候还不确定类型,等到new的时候确定类型-->ArrayList
public interface IteratorMyself<E> {
public E next(E e);
}
public class ScannerMySelf implements IteratorMyself<String>{
@Override
public String next(String s) {
return "金莲叫涛哥起床";
}
}
public interface ListMyself <E>{
public void add(E e);
}
public class ArrayListMySelf<E> implements ListMyself<E>{
@Override
public void add(E e) {
System.out.println(e);
}
}
public class Test {
public static void main(String[] args) {
ArrayListMySelf<Integer> list = new ArrayListMySelf<>();
list.add(1);
}
}
3.泛型的高级使用
3.1泛型通配符->?
1.什么时候用泛型通配符:当不知道接收什么类型时->集合方面
2.用在什么位置:一般用在参数上
public class Test01 {
public static void main(String[] args) {
Collection<String> collection = new ArrayList<>();
collection.add("张三");
collection.add("李四");
Collection<Integer> collection1 = new ArrayList<>();
collection1.add(1);
collection1.add(2);
method(collection);
method(collection1);
}
//定义一个方法,此方法遍历以上两个集合
public static void method(Collection<?> collection){
for (Object o : collection) {
System.out.println(o);
}
}
}
3.2泛型的上限下限
通配符高级使用----受限泛型
上限:
- 格式: 类型名称 <? extends 类 > 对象名称
- 意义: 只能接收该类型及其子类
下限:
- 格式: 类型名称 <? super 类 > 对象名称
- 意义: 只能接收该类型及其父类型
public class Test01 {
public static void main(String[] args) {
Collection<Integer> list1 = new ArrayList<Integer>();
Collection<String> list2 = new ArrayList<String>();
Collection<Number> list3 = new ArrayList<Number>();
Collection<Object> list4 = new ArrayList<Object>();
//getElement1(list1);
//getElement1(list2);//报错
//getElement1(list3);
//getElement1(list4);//报错
//getElement2(list1);//报错
//getElement2(list2);//报错
//getElement2(list3);
//getElement2(list4);
}
// 泛型的上限:此时的泛型?,必须是Number类型或者Number类型的子类
public static void getElement1(Collection<? extends Number> coll) {
}
// 泛型的下限:此时的泛型?,必须是Number类型或者Number类型的父类
public static void getElement2(Collection<? super Number> coll) {
}
}
第五章.红黑树(了解)
1.底层数据结构是红黑树的原因:为了查询快
第六章.Set集合
1.Set集合介绍
1.Set接口 是Collection接口的子接口
2.特点:
无索引
元素不可重复
无序
3.Set集合没有对Collection集合中的方法做扩充
2.HashSet集合介绍以及使用
1.概述:HashSet是Set接口的实现类
2.特点:
无索引
元素不可重复(如果存储了重复的元素,后面的会把前面的覆盖掉)
无序(存进去的元素是什么顺序,第一次取出来有可能和存进去的顺序不一样)
3.数据结构:
哈希表结构
jdk8之前:哈希表 = 数组+链表
jdk8之后:哈希表 = 数组+链表+红黑树
4.注意:
HashSet集合本身没有功能,HashSet操作元素的功能都是依靠HashMap实现的
public class Demo01HashSet {
public static void main(String[] args) {
HashSet<String> hashSet = new HashSet<>();
hashSet.add("张三");
hashSet.add("李四");
hashSet.add("王五");
hashSet.add("赵六");
hashSet.add("赵六");
System.out.println(hashSet);
System.out.println("==========迭代器==========");
Iterator<String> iterator = hashSet.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
System.out.println("=========增强for=========");
for (String s : hashSet) {
System.out.println(s);
}
}
}
3.LinkedHashSet的介绍以及使用
1.概述: LinkedHashSet extends HashSet
2.特点:
没有索引
元素不可重复
有序
3.数据结构:
链表+哈希表 -> 双向链表
4.方法:
和HashSet使用上没区别
public class Demo02HashSet {
public static void main(String[] args) {
LinkedHashSet<String> hashSet = new LinkedHashSet<>();
hashSet.add("张三");
hashSet.add("李四");
hashSet.add("王五");
hashSet.add("赵六");
hashSet.add("赵六");
System.out.println(hashSet);
}
}
4.哈希值
1.概述:由计算机计算出来的一个十进制的数,可以看做是一个地址值(不是内存中分配)
1.结论:
a.如果想获取的是内容的哈希值,我们需要重写Object类中的hashCode()方法
b.内容一样,哈希值一定一样
内容不一样,哈希值也有可能一样
public class Test01 {
public static void main(String[] args) {
Person p1 = new Person("柳岩", 36);
Person p2 = new Person("柳岩", 36);
System.out.println(p1);
System.out.println(p1.hashCode());
System.out.println(p2.toString());
System.out.println("===============");
String s1 = "abc";
String s2 = new String("abc");
String s3 = new String("abc");
System.out.println(s1.hashCode());//96354
System.out.println(s2.hashCode());//96354
System.out.println(s3.hashCode());//96354
System.out.println("==============");
String s4 = "通话";
String s5 = "重地";
System.out.println(s4.hashCode());//1179395
System.out.println(s5.hashCode());//1179395
}
}
5.字符串的哈希值是如何算出来的
public class Test02 {
public static void main(String[] args) {
String s = "abc";
System.out.println(s.hashCode());
}
}
String s = "abc" -> char[] value = {'a','b','c'}
public int hashCode() {
int h = hash;
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;
}
第一圈循环: h = 31*h+'a' = 31*0+97 = 97
第二圈循环: h = 31*h+'b' = 31*97+98 = 3105
第三圈循环: h = 31*h+'c' = 31*3105+99 = 96354
小细节:
为什么String中重写的hashCode用31去参与运算呢?
31是一个质数,31参与运算就是为了减少字符串不同但是哈希值相同的情况
内容不同,但是算出来的哈希值相同-> 哈希碰撞(哈希冲突)
6.HashSet的存储去重复的过程
1.先算元素的哈希值,先比较元素哈希值,再比较元素内容
2.先比较哈希值,如果哈希值不一样,直接存储到HashSet集合中
3.如果哈希值一样,再比较内容
如果哈希值一样,内容不一样,也直接存储
如果哈希值一样(hashcode),内容也一样(equals),被认定为是同一个元素,直接去重复
注意:
我们比较哈希值时,我们比较的是对象内容的哈希值(重写hashcode方法)
我们比较内容是,我们比较的是对象的内容(重写equals方法)
public class Test03 {
public static void main(String[] args) {
HashSet<String> hashSet = new HashSet<>(10);
hashSet.add("abc");
hashSet.add("通话");
hashSet.add("重地");
hashSet.add("abc");
System.out.println(hashSet);
}
}
7.HashSet存储自定义类型如何去重复
1.重写hashCode和equals方法
我们比较哈希值时,我们比较的是对象内容的哈希值(重写hashcode方法)
我们比较内容是,我们比较的是对象的内容(重写equals方法)
public class Person {
private String name;
private int age;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = 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;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age &&
Objects.equals(name, person.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
public class Test04 {
public static void main(String[] args) {
HashSet<Person> hashSet = new HashSet<>();
hashSet.add(new Person("柳岩",36));
hashSet.add(new Person("涛哥",16));
hashSet.add(new Person("柳岩",36));
System.out.println(hashSet);
}
}