文章目录
- 1.堆栈/队列/数组/链表:数据结构即计算机组织管理数据的方式,堆栈指的是内存图中的栈,不是堆
- 2.红黑树:二查,二查平,二查平1倍
- 3.List子接口:集合,IndexOutOfBoundsException
- 4.ArrayList的扩容原理:Stringbuild默认长度=16,扩容2倍
- 5.LinkedList:push堆栈
- 6.set子接口:单例=keySet
- 7.Object类的hashcode方法:对象的真正的内存地址(无限种)->哈希码 (43亿不到的可能)。极端下多个明文 -> 同一密文 (哈希碰撞)。打印对象,.toString(),.hashCode()
- 8.String类的hashcode方法:a97,碰撞
- 9.哈希表(HashSet)原理:元素不重复
- 10.linkedHashset和Hashset区别:A a = new A(){},coll.iterator().hasNext()
- 11.Map:Map和Collection是并列关系,Map.Entry < Integer,String > 是数据类型
- 12.内部接口:除了Inner访问受限于Outer,这两各自独立
- 13.统计字符出现个数:.containsKey(.charAt)
- 14.斗地主:list.addAll(set)
- 15.Collections类和TreeSet:return o1-o2是升序
- 16.错误和异常区别:Arrays.toString(array)
- 17.编译和运行异常:SimpleDateFormat
- 18.处理异常:方法声明抛出
- 19.finally关键字:catch相当于else if,finally相当于else,return
- 20.自定义异常:extends
- 21.线程两种创建方式:new Thread(new Runnable() {}),extends Thread,implements Runable
- 22.卖票:原子性
- 23.线程同步:synchronized关键字/方法,Lock接口,ThreadLocal
- 24.卖包子:wait,notify
- 25.创建和启动2个子线程:一个打印1-10之间奇数,一个打印1-10之间偶数
- 26.使用三个线程循环打印出1~100
- 27.账户类:synchronized 方法
1.堆栈/队列/数组/链表:数据结构即计算机组织管理数据的方式,堆栈指的是内存图中的栈,不是堆
如下查询慢:知道张三在哪,不能马上知道王五在哪,挨个查。如下增删虽然不用整个动(如删除李四,只需要将箭头指向王五就行),但是还是要先查找到再删除
,效率还是慢。但是直接删除张三或马六头尾元素快。
2.红黑树:二查,二查平,二查平1倍
二叉树(二)
:每个节点最多两个子节点。查找树(查)
:左小右大(二分法)。平衡树(平)
:左右尽量相等(一边的节点层次不会超过另一边的两倍)。
二叉搜索树BST(二查)
:插入或查询一节点时,树的每一行只需要和这一行的一个节点比较(因为左小右大),复杂度完全依赖树深度。树的行数即高度=logn【n为节点总数,2的树行数次方为n】,BST读和写的复杂度都为logn。
有序数组
:查找时用二分查找法(和BST像),时间复杂度也为logn。有序数组查询最差情况logn,而当BST为单边时(最差情况),BST查询和插入都为o(n)。为什么很多情况下用BST,而不是有序数组二分查找?因为有序数组查找用二分查找logn,但是插入(不是查)要移动,插入时间复杂度为o(n)。
BST很少在语言内部数据结构存储里用(因为下面直线情况),自平衡二叉树AVL(二查平)
是BST(二查)
的继承优化:左子树和右子树都是平衡二叉树,而且左子树和右子树深度之差绝对值不会超过1(左旋和右旋),AVL读和写的复杂度最差情况都为O(logn)。
AVL平衡左右子树相差1,这个条件很苛刻,导致很多情况下都不满足这个平衡条件,需要旋转变换,变换的话需要浪费时间。红黑树(二平查1倍)
平衡条件更加宽松些左右深度差一倍
即> = 节点数相同,< = 节点数差一倍(因为红节点的子节点
必须为黑即黑红相间)。叶子节点(最后一个)和null节点都为黑节点
这样宽松条件导致我们在插入节点时候变化更少的,所以红黑树写的性能会高一些,所以treemap/hashmap底层采用红黑树(BST会变直线,AVL左右只能差1)。
如下是红黑树的插入变色
流程:最上面根节点必须为黑,插入节点(为叶子节点)必为红节点(看插入节点的父节点和父节点的兄弟节点即叔节点)。null节点算叶子节点即黑节点
,当前插入的003是爷爷节点001
的右右。
如下左旋+变色。
先序(先根)
:根左右
中序
:投影
后序
:左右
根(从下到上)
3.List子接口:集合,IndexOutOfBoundsException
package com.itheima01.list;
import java.util.ArrayList;
import java.util.List;
/*
Collection子接口:List
1. List的特点:重索序
1. 有先后顺序:元素存储的顺序和取出的顺序相同
2. 具有整数索引,就是下标
3. 允许重复元素
2. List的方法(带索引)(List特有的,共有的在Collection讲过)
1. add(int index, E element) :往索引位置添加一个元素
1. Java中的 三个越界异常
1. IndexOutOfBoundsException 集合
2. ArrayIndexOutOfBoundsException 数组
3. StringIndexOutOfBoundsException 字符串越界
2. get(int index):获取指定索引的元素
3. remove(int index):移除指定索引的元素
4. set(int index, E element) :修改指定索引的元素值
*/
public class ListDemo {
public static void main(String[] args) {
// add();
List<String> list = new ArrayList<>();
list.add("周楠");
list.add("王凤枝");
list.add("王凯");
String s = list.get(2);
System.out.println(s); //王凯
list.remove(2);
System.out.println(list); //[周楠, 王凤枝]
list.set(1,"昌老师");
System.out.println(list); //[周楠, 昌老师]
}
//11111111111111111111111111111111111111111111111111111111111111111111111111111
private static void add() {
List<String> list = new ArrayList<>();
list.add("周楠");
list.add("王凤枝");
list.add("王凯");
/*
add(int index, element)
往指定索引位添加元素
index = list.size()
IndexOutOfBoundsException: : 索引越界异常
*/
list.add(3,"田锁"); //不越界,4越界
System.out.println(list);
String[] array = {};
//System.out.println(array[0]); //ArrayIndexOutOfBoundsException : 数组索引越界
String str = "abc"; // 字符串底层也是数组
// char c = str.charAt(3); //索引0,1,2
// System.out.println(c); //StringIndexOutOfBoundsException:字符串索引越界
}
}
4.ArrayList的扩容原理:Stringbuild默认长度=16,扩容2倍
ArrayList底层是存Object数组,扩容新建一个长度为原来1.5倍新数组(空)。
如下10进制的4就是2进制的0100(8421),3/2=1,ArrayList.java源码中出现左右移(二进制右移一位相当于十进制/2)
。
package com.itheima01.list;
import java.util.ArrayList;
/*
* ArrayList: 数组
* 1. 最常用: 适合 查询需求比较多的场景
* 2. 原理: ArrayList扩容原理
* ArrayList底层是数组,数组长度不可变,为什么ArrayList又可变呢? 因为数据迁移
*/
public class ArrayListDemo {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("xx");
System.out.println(3 >> 1); // 1 //除2取整
System.out.println(4 >> 1); // 2
System.out.println(10 >> 1); // 5
System.out.println(3 << 2); //12 //3*2*2,左移2位
}
}
5.LinkedList:push堆栈
package com.itheima01.list;
import java.util.LinkedList;
/*
LinkedList特点
1. 底层数据结构: 双向链表
2. 查询速度慢,增删快(增删需求多而且增删首尾用LinkedList)
3. 特有方法(不能使用多态,父类不能调子类特有方法)
1. addFirst 元素添加在链表开头
2. addLast(add相同) 元素添加在链表结尾
3. getFirst 获取链表开头
4. getLast 获取链表结尾
5. removeFirst 移除并返回链表开头
6. removeLast 移除并返回链表结尾
//下面两个不需要掌握
7. pop 从此列表所表示的堆栈处弹出一个元素(最顶部元素弹出,removeFirst)
8. push 将元素推入此列表所表示的堆栈(元素存储到集合顶部,addFirst)
*/
public class LinkedListDemo {
public static void main(String[] args) {
// method01();
LinkedList<String> list = new LinkedList<>();
list.add("张三"); // 是链表,不是按堆栈结构添加元素
list.add("李四");
list.add("王五");
// 链表 -> 堆栈 ,张三在栈顶
// String pop = list.pop(); // 弹栈: 栈顶元素()
// String removeFirst = list.removeFirst();//效果同上
// System.out.println(pop);
list.push("王二"); //栈顶添加,效果等同于add
System.out.println(list);
}
private static void method01() {
LinkedList<String> list = new LinkedList<>();
list.add("张三");
list.add("李四");
list.add("王五");
list.addFirst("王二");
list.addLast("马六");
System.out.println(list);
String first = list.getFirst();
String last = list.getLast();
System.out.println(first + "," + last);
System.out.println(list);
list.removeFirst();
list.removeLast();
System.out.println(list);
}
}
6.set子接口:单例=keySet
package com.itheima02.set;
import java.util.HashSet;
import java.util.Set;
public class SetDemo {
public static void main(String[] args) {
Set<String> set = new HashSet<>();
set.add("张三");
set.add("李四");
boolean result = set.add("王五");
System.out.println(result); //true
boolean result2 = set.add("王五");
System.out.println(result2); //false //元素不可重复
System.out.println(set);//[李四,张三,王五] //存取不保证顺序
}
}
7.Object类的hashcode方法:对象的真正的内存地址(无限种)->哈希码 (43亿不到的可能)。极端下多个明文 -> 同一密文 (哈希碰撞)。打印对象,.toString(),.hashCode()
package com.itheima03.hash;
/*
* Object类有一个方法: int hashCode() : 返回该对象的哈希码值。
* 1. 原理: 将对象的真正的内存地址(明文) 进行 哈希算法 加密之后产生的 哈希码值(密文)
* 2. 加密 :
* 明文 : 大家都看的懂东西 I love you
* 密文 : 明文经过加密算法变成密文 J mpwf zpv
* 加密算法: 数学 (凯撒加密: 字母按字母表右移动一位)
* 破解: 频率分析法 (e i -> d h),截获大量数据进行大数据分析e,i出现频率最高,密文中出现最多的是d,h
*
* 哈希算法: 公开
* 基本保证 一个明文 -> 一个密文 不同明文不同的密文
* 告诉你算法,告诉你密文, 算不出明文
*
* 3. 源码: public native int hashCode(); 本地方法
* native(本地关键字) 修饰的方法没有java方法体 (方法实现在JVM底层, 用C语言写的)
* 返回值 int (43亿不到的可能)
*/
public class HashcodeDemo {
public static void main(String[] args) {
Person p = new Person();
System.out.println(p); //com.itheima03.hash.Person@14ae5a5
// return getClass().getName() + "@" + Integer.toHexString(hashCode());
System.out.println(p.toString());//com.itheima03.hash.Person@14ae5a5
// 上面两个打印结果都一样
// 如下内存地址: 16进制哈希码值14ae5a5 和下面10进制相等
System.out.println(p.hashCode()); // 10进制: 21685669
}
}
class Person{
}
8.String类的hashcode方法:a97,碰撞
String s1 = “abc”,如下h就是hash值
(只看最后一个h),b是98,c是99。
package com.itheima03.hash;
// String类重写了Object的hashcode方法 (31算法)
public class StringHashCodeDemo {
public static void main(String[] args) {
String s1 = "abc";
String s2 = "acD";
String s3 = "重地";
String s4 = "通话";
System.out.println(s1.hashCode());//96354
System.out.println(s2.hashCode());//96354 //和上面哈希碰撞
System.out.println(s3.hashCode()); //1179395
System.out.println(s4.hashCode()); //1179395
}
}
9.哈希表(HashSet)原理:元素不重复
hashcode(密文)为HashSet(HashSet底层数据结构是hash表)做铺垫。如下三个abc字符串都为96354。问题:HashSet如何判定这个元素是否跟已存在的元素是重复即如下[重地,通话,abc,acD]?Set不存重复元素。S3因为是new出来的,和S1,S2明文即真正的内存地址不一样。
下面S1,S4,S5,S6都是不重复元素。竖:暗文或暗文%16同(因为数组长度为16
),equal不一样。横:明文和暗文和equal都不一样。HashSet是效率最高的set且元素不重复,同一链表(竖)hashcode一样,但是链表如果太长查询慢,所以假如同一hash值(hashcode)碰撞了8次,链表重构为红黑树
。
同一链表上都hash碰撞,数组的第一个位置余数=0,第二个位置余数=1。。。16的容量为什么到16*0.75=12
就扩容了?再哈希rehash(余数重新算)这段时间内,16没满,我还有的用,如果rehash非常快就不用提前。
package com.itheima02.set;
import java.util.HashSet;
import java.util.Objects;
/*
* HashSet: 判定重复元素:(明文地址【内】,hash值【外】,equals【名字】)。
* 内同-》重复不插, 内不同 外不同 默认e不同-》横插, 内不同 外同 e不同-》竖插
*
* person类父类的Object: 1. hashcode:明文内存地址加密得到密文hash值
*(不同明文产生不同密文,刘亦菲明文即两个内存地址不一样,密文hash值基本不会相同,万一碰撞了,还有下面2进 行保障)
* 2. equals == 比较真正内存地址
*
* 需求: 两个对象就算地址不同, 但是所有属性一一相同, 就认为是同一元素
* 解决: 重写hashcode和equals方法 -> 类中的所有属性,重写规范见文章:https://blog.csdn.net/weixin_43435675/article/details/112604089
*/
public class HashSetDemo02 {
public static void main(String[] args) {
HashSet<Person> set = new HashSet<>();
set.add(new Person("高圆圆",18));
set.add(new Person("刘亦菲",19));
set.add(new Person("刘亦菲",19));//Person类继承Object类,new新地址
System.out.println(set);
}
}
class Person{
String name;
int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public boolean equals(Object o) { //alt+insert选equals()and hashCode() //每个属性一一比对
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o; //o是外部传入,转成person
return age == person.age &&
Objects.equals(name, person.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age); //工具类Objects.java中hash方法中hashCode方法就是31算法,也是逐一遍历
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
没有重写Person类的hashcode和equals方法。
如下重写…如下就是hash表的应用:元素不重复,效率高。
10.linkedHashset和Hashset区别:A a = new A(){},coll.iterator().hasNext()
package com.itheima00.question;
import java.util.HashSet;
import java.util.LinkedHashSet;
/*
* Set: 不保证 存入和取出顺序一致
* HashSet : 无序
* LinkedHashSet : 多个另一个链表, 来记录存入的顺序,有序即取出有序,所以效率变低(少用)
*/
public class Demo01 {
public static void main(String[] args) {
HashSet<String> set = new LinkedHashSet<>(); //向上转型
set.add("张三");
set.add("李四");
set.add("王五");
set.add("马六");
System.out.println(set); //打印出有序的,LinkedHashSet不同于HashSet
}
}
package com.itheima00.question;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
public class Demo02 {
public static void main(String[] args) {
Collection<String> coll = new ArrayList<String>(); //Collection是接口
coll.add("张三");
coll.add("张三2");
coll.add("张三3");
// Iterator接口类型 变量 = 其实现类对象 (多态的向上转型)
/* Iterator<String> it = coll.iterator(); //Collection即coll是接口,接口调用方法执行子类ArrayList重写的iterator()
while(it.hasNext()){
String name = it.next();
System.out.println(name); //张三 张三2 张三3
}*/
// while(coll.iterator().hasNext()){ //不能这样把it换了链式编程,原因如下图
// String name = coll.iterator().next();
// System.out.println(name);
// }
}
//11111111111111111111111111111111111111111111111111111111111111111111111111111111111111
public static void method01(){
MyClass mc = new MyClass();
A a = mc.test(); //右边返回必然是A接口实现类对象即向上转型,不需要new
//上行等同于Iterator<String> it = coll.iterator(); 不一定需要看到new
A a2 = new A() { //java中对象不一定看到new才放心 //new一个实现接口的匿名内部类A,使用{}具体实现接口
@Override
public void show() {
}
} ;
}
}
interface A{
void show();
}
class MyClass{
public A test(){ //返回A接口,不写void
// A a = new A(){}; //匿名内部类
// return a;
return new A() { //下面等同于上面两行,return A接口的子类对象
@Override
public void show() {
}
};
}
}
11.Map:Map和Collection是并列关系,Map.Entry < Integer,String > 是数据类型
package com.itheima01.map;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
/*
Map中的方法
1. Map<K,V> <泛型>: K 表示作为键的类型,V表示值的类型
2. put: 存储键值对
1. 键值对存储到集合中 V put (K,V)
2. 如果存储了相同的键,覆盖原有的值
3. 返回值:一般返回null,如果存储了重复的键,返回被覆盖之前的值
3. get:通过键,取出键对应的值
1. V get(K),传递键,返回对应的值
2. 如果集合中没有这个键,返回null
4. remove:移除键值对
1. V remove(K),传递键,移除这个键值对
2. 返回值:移除之前的值(无此键,则返回null)
5. keySet: 将集合中所有的键,存储到Set集合中
6. entrySet:获取到Map集合中所有的键值对存入Set接中
7. size:获取map集合的大小
* Map:
* 1. key不可以重复
* 2. value可以重复
* 如果key存在,那么新value覆盖旧value
*/
public class MapDemo {
public static void main(String[] args) {
Map<Integer,String> map = new HashMap<>();
map.put(1,"张三");
map.put(2,"李四");
map.put(3,"王五");
map.put(4,"王五");
map.put(3,"马六"); //覆盖王五
System.out.println(map);//{1=张三,2=李四,3=马六,4=王五}
String name = map.get(5); //null,不是越界异常,无索引
String name = map.get(3); //从key获取value
System.out.println(name);//马六
//根据key删除key-value
map.remove(3);
System.out.println(map);
System.out.println(map.size()); //3,几个k
}
}
如下key不可重复,所以放到set集合(单例)。如下两种遍历方式都涉及set:
package com.itheima01.map;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class MapLoopDemo01 { //loop循环
public static void main(String[] args) {
Map<Integer,String> map = new HashMap<>();
map.put(1,"张三");
map.put(2,"李四");
map.put(3,"王五");
Set<Integer> keySet = map.keySet(); //1.把key这一列取出来放到set集合中
for (Integer key : keySet) { //2.遍历这个set集合,取出每个key。keySet.for回车
String value = map.get(key); //3. 根据key获取value
System.out.println(key + "->" + value);
}
}
}
package com.itheima01.map;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class MapLoopDemo02 {
public static void main(String[] args) {
Map<Integer,String> map = new HashMap<>();
map.put(1,"张三");
map.put(2,"李四");
map.put(3,"王五");
Set<Map.Entry<Integer,String>> entrySet = map.entrySet(); // 1. 把map转化成 Set<Entry> set
for(Map.Entry<Integer,String> entry : entrySet){ // 2. 遍历这样的set, 取出每个entry
//Entry是Map的内部接口,Map有很多Entry,Entry相当于Map属性一样。Map.是接口名直接调用
//如果import java.util.Map.Entry,则Map.Entry可换成Entry
Integer key = entry.getKey(); //3. 从这个键值对中,取键,再取值
String value = entry.getValue();
System.out.println(key + "--" + value);
}
}
}
package com.itheima03.impl;
import java.util.*;
/*
* HashMap是最常用的map实现类,因为快。
* 1. key不可以重复,但是value可以重复
* 2. key如何判定重复? 先判断hashcode ,再判断equals
* Object: hashcode 和 equals 跟对象真正地址有关
* 重写了hashcode 和 equals,张三山东 覆盖 张三山西 新覆盖旧
*/
public class HashMapDemo {
public static void main(String[] args) {
// method01();
// method02(); //较method01交换了k和v
new LinkedHashSet<>(); //点进源码,底层是LinkedHashMap
new TreeSet<>(); //TreeMap
new HashSet<>(); //HashMap
//如下有序存取
LinkedHashMap<Person,String> map = new LinkedHashMap<>();
map.put(new Person("张三",18),"山西");
map.put(new Person("吴彦祖",20),"福州");
map.put(new Person("李四",19),"广东");
// map.put(new Person("张三",18),"山东");
Set<Person> keySet = map.keySet();
for (Person key : keySet) {
String value = map.get(key);
System.out.println(key + "---" + value);
}
}
private static void method02() {
HashMap<Person,String> map = new HashMap<>();
map.put(new Person("张三",18),"山西"); //key=Person 自定义类型
map.put(new Person("吴彦祖",20),"福州");
map.put(new Person("李四",19),"广东");
map.put(new Person("张三",18),"山东"); //new出来地址不同
Set<Person> keySet = map.keySet();
for (Person key : keySet) {
String value = map.get(key);
System.out.println(key + "---" + value);
}
// HashSet<Object> set = new HashSet<>(); //点进HashSet看源码
//HashSet【collection接口】的底层是HashMap【Map接口】 ,只不过hashset只使用了HashMap key这一列,value这一列不用
}
private static void method01() {
HashMap<String, Person> map = new HashMap<>();
map.put("1号",new Person("张三",18)); //value=Person 自定义类型
map.put("2号",new Person("李四",19));
map.put("3号",new Person("李四",19));
map.put("1号",new Person("王五",20));
//System.out.println(map);
Set<Map.Entry<String,Person>> entrySet = map.entrySet();
for (Map.Entry<String, Person> entry : entrySet) {
String key = entry.getKey();
Person value = entry.getValue();
System.out.println(key + "-" + value);
}
}
}
class Person{
String name;
int age;
public Person(String name, int age) {
this.name = name;
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);
}
}
如下吴彦祖应该在第二个,存取无序,解决:HashMap换成LinkedHashMap。
12.内部接口:除了Inner访问受限于Outer,这两各自独立
package com.itheima02.inner;
import java.lang.reflect.Field;
import java.util.Date;
import com.itheima02.inner.Outer.Inner;
/*
* inner class : 访问受限, 受限于外部类
* 有两个包是不用导入:1. java.lang (String,Object)
* 2. 当前类所在的包
* 1和2的子包都要导(如内部接口就在当前包的子包下)
*/
public class InnerDemo {
public static void main(String[] args) {
// new Date()
new String("");
new Object();
// new Field(); //java.lang包的子包,要导包
}
}
//1111111111111111111111111111111111111111111111111111111111111111111111111111111111111
interface Outer{
//public static final //因为Outer是接口,所以不能实例化,只能接口.。所以final让I变为常量,static让I可用接口名.直接调用
int I = 1;
//public abstract
void outerMethod();
//new外部类对象不需要new内部类对象,同理实现Outer接口不需要实现Inner接口
//public static //外部接口不能创建实例来调用,所以接口名调用,所以静态。上面Map.Entry即外接口.内接口(Entry是Map的内接口)
interface Inner{
void innerMethod();
}
}
class A implements Outer{ //不需要实现Inner
@Override
public void outerMethod() {
}
}
class B implements Outer.Inner{
@Override
public void innerMethod() {
}
}
class C implements Outer,Inner{//上面导过包了,Inner不用写成Outer.Inner
@Override
public void outerMethod() {
}
@Override
public void innerMethod() {
}
}
13.统计字符出现个数:.containsKey(.charAt)
统计字符串中:大小写字母及数字字符个数:https://blog.csdn.net/weixin_43435675/article/details/107434867
package com.itheima03.impl;
import java.util.HashMap;
/*
* 需求: 计算一个字符串中每个字符出现次数。
* 0. 弄一个Map : 记录 字符=次数 (就像画正字选票)
* char int
* 1. 遍历这个字符串,取出每个字符
* 2. 判断Map中是否存在这个字符-> boolean containsKey(Object key) 如果此映射包含指定键的映射关系,则返回 true。
* 3. 有: 在对应的次数+1 。没有 : 字符char=1 存进去
*/
public class CountDemo {
public static void main(String[] args) {
String str = "abcaba";
HashMap<Character, Integer> map = new HashMap<>();//泛型不接收基本类型,所以char写成Character,int的包装类Integer
for(int i=0; i<str.length();i++){
char c = str.charAt(i); //相应索引访问到相应字符
boolean result = map.containsKey(c); //一开始map为空,下面依次写进去
if(result){ // 存在,次数value+1
Integer value = map.get(c); //c是key即char
// value = value + 1;
// map.put(c,value);
// value++;
// map.put(c,value);
// int count = ++value;
// map.put(c,count);
map.put(c,++value); //不能写成map.put(c,value++);
}else{ // 不存在, 存入c=1
map.put(c,1);
}
}
System.out.println(map);
}
}
14.斗地主:list.addAll(set)
package com.itheima04.Poker;
import java.util.ArrayList;
import java.util.Collections;
// 写一个规则: 2 > A > K > Q > J ...(不行,组合方式太多2也要>K) //上次写的没有排序 //黑红梅方
public class SortDemo {
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<>(); //重索序
list.add(3);
list.add(5);
list.add(4);
list.add(1);
list.add(2);
System.out.println(list); //[3,5,4,1,2],有序
Collections.sort(list);
System.out.println(list);//[1,2,3,4,5]
}
}
15发给第一个,13发给第二个。。。
package com.itheima04.Poker;
import java.util.*;
class PokerDemo {
public static void main(String[] args) {
HashMap<Integer, String> map = new HashMap<>(); //key=编号, value=牌面
String[] colors = {"♠","♥","♣","♦"};
String[] numbers = {"3","4","5","6","7","8","9","10","J","Q","K","A","2"};
int index=0;
for (String number : numbers) {//数字写外面,外一次内一周,数字3和4种花拼接,并index标号依次从0对应到3,好按标号排序。
for (String color : colors) {
String poker = color + number;
map.put(index,poker);
index++;
}
}
// System.out.println(map); //{0=黑桃3,1=红心3,2=梅花3,...}
map.put(52,"小☺");
map.put(53,"大☺");
Set<Integer> set = map.keySet(); //0-53
ArrayList<Integer> list = new ArrayList<>();
list.addAll(set); //将set集合中每个元素都放进list中
//下面等同于上面
/*for (int i = 0; i < 54; i++) { //0-53编号放进去
list.add(i);
}*/
//洗牌
Collections.shuffle(list); //只能洗list集合
//发牌
ArrayList<Integer> p1 = new ArrayList<>();
ArrayList<Integer> p2 = new ArrayList<>();
ArrayList<Integer> p3 = new ArrayList<>();
ArrayList<Integer> dp = new ArrayList<>();
for (int i = 0; i < list.size(); i++) { //list里全是编号
Integer number = list.get(i);
int mod = i % 3;
if(i < 3){
dp.add(number);
}else if(mod == 1){
p1.add(number);
}else if(mod == 2){
p2.add(number);
}else if(mod == 0){
p3.add(number);
}
}
//排序(Integer:从小到大)
Collections.sort(p1);
Collections.sort(p2);
Collections.sort(p3);
Collections.sort(dp);
// lookPoker(map,p1); //因为list即p里都是编号,所以还需要map
// lookPoker(map,p2);
// lookPoker(map,p3);
// lookPoker(map,dp);
lookPoker(map,p1,p2,p3,dp);
}
private static void lookPoker(HashMap<Integer, String> map, ArrayList<Integer>... lists) { //lists为数组名
for (ArrayList<Integer> list : lists) {
for (Integer number : list) {
String poker = map.get(number); //从map中根据number 取出 poker
System.out.print(poker + "\t");
}
System.out.println();
}
}
// private static void lookPoker(HashMap<Integer,String> map,ArrayList<Integer> list){
// for(Integer number : list){
// String poker = map.get(number);
// System.out.print(poker+"\t");
// }
// System.out.println();
// }
}
15.Collections类和TreeSet:return o1-o2是升序
package com.itheima05.collections;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
/*
Arrays数组工具类, Objects对象工具类(命名规则最后+s)
- java.utils.Collections是集合工具类,用来对集合进行操作。 常用方法如下:
- public static void shuffle(List<?> list):打乱集合顺序。
- public static <T> void sort(List<T> list):将集合中元素按照默认规则排序。
- public static <T> void sort(List<T> list,Comparator<? super T> ):将集合中元素按照指定规则排序。
*/
public class CollectionsDemo01 {
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<>(); //Integer源码实现了Comparable接口
Collections.addAll(list,5,3,2,4,1); //System.out.println(list); //[3,4,5,1,2]
// Collections.sort(list); // 默认: 升序
// 如下第二个参数类型是Comparator接口,必须传入接口实现类对象:new Comparator...后面全是匿名内部类
Collections.sort(list, new Comparator<Integer>() { // 自定义升降序规则 , 任何排序都是两两比较
@Override
public int compare(Integer o1, Integer o2) {
// return o1 - o2;//升序: 从小到大
return o2 - o1;//降序: 从大到小
}
});
System.out.println(list);
}
}
15.1 自定义MyCollections类来模拟完成addAll()和sort()方法
package com.itheima05.collections;
import java.util.ArrayList;
import java.util.Comparator;
public class MyCollections {
// 1. addAll方法
public static void addAll(ArrayList<Integer> list, Integer... args){
for (Integer arg : args) {
list.add(arg);
}
}
//2. sort方法: 这里采用冒泡排序。collections底层实际是Timsort排序。
//注意: 第二个参数是接口,必须传入接口实现类(这将形成多态: 父接口引用调用方法执行的是子类重写方法)
public static void sort(ArrayList<Integer> list, Comparator<Integer> comparator) {
int temp;
for (int i = 0; i < list.size() - 1; i++) {
for (int j = 0; j < list.size() - i - 1; j++) {
int result = comparator.compare(list.get(j + 1), list.get(j));//j+1和j相邻比较,父类接口调用方法执行子类重写的方法即MyCollections.sort(list, new Comparator<Integer>() {子类重写的方法})。
if(result < 0){ //子类重写方法中rerurn o1-o2保证result<0即o1后小,rerurn o2-o1保证result<0即o1后大。
temp = list.get(j); //交换
list.set(j, list.get(j + 1));
list.set(j+1,temp);
}
}
}
}
}
package com.itheima05.collections;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
public class CollectionsDemo02 {
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<>();
Collections.addAll(list,5,3,2,4,1);
MyCollections.sort(list, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1 - o2;
}
});
System.out.println(list);
}
}
package com.itheima05.collections;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.TreeSet;
public class CollectionsDemo03 {
public static void main(String[] args) {
ArrayList<Student> list = new ArrayList<>(); //ArrayList底层就是数组,数组和集合差不多。
list.add(new Student("张三",20));
list.add(new Student("李四",18));
list.add(new Student("王五",22));
/* Collections.sort(list, new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
return o1.age - o2.age; //按年龄升序
}
});
System.out.println(list);*/
//1111111111111111111111111111111111111111111111111111111111111111111111111111111111111
// sort(list) : 这个方法要求集合元素类型需要实现 Comparable接口 (Integer已实现,自定义类型必须手动实现)
// Collections.sort(list); //如果就写class Student不实现Comparable接口,这行会报错
// System.out.println(list);
//111111111111111111111111111111111111111111111111111111111111111111111111111111111111
TreeSet<Student> set = new TreeSet<>(); //TreeSet判定元素重复的原理:compare方法 : 两数相减=0(不是hashcode和equals)
//TreeSet底层红黑树(红黑树就是排序,左小右大),和哈希表无关,和Collections.sort一样必须实现Comparable接口
//Set重点是hashset,TreeSet底层和比较器完全一样,TreeSet具备排序功能但效率不如hashset。TreeMap的key和TreeSet底层一样
set.add(new Student("张三",20));
set.add(new Student("李四",18));
set.add(new Student("王五",22));
set.add(new Student("马六",22)); //添不进去,因为compareTo比较的是age,不重复
System.out.println(set);
}
}
//11111111111111111111111111111111111111111111111111111111111111111111111111111111111111
class Student implements Comparable<Student>{
String name;
int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public int compareTo(Student o) {
return this.age - o.age; //升序,// this = o1,o = o2
}
}
16.错误和异常区别:Arrays.toString(array)
package com.itheima01.throwable;
import java.util.Arrays;
//错误(Error) : 从程序角度 只能避开,不能解决。 异常(Exception) : 从程序角度 可以解决的问题
public class ThrowableDemo {
public static void main(String[] args) {
// int[] array = new int[3];
//System.out.println(array); //[I@6d6f6e28
//System.out.println(array.toString()); //[I@6d6f6e28
//System.out.println(Arrays.toString(array)); //[0, 0, 0]
// int[] array = new int[2_000_000_000]; //20亿撑不住,内存条不够
// System.out.println(Arrays.toString(array)); //java.lang.OutOfMemoryError: Java heap(堆) space //OOM :内存溢出【错误】
int[] array = {};
System.out.println(array[1]); //java.lang.ArrayIndexOutOfBoundsException【异常】
}
}
package com.itheima02.jvm;
import java.util.ArrayList;
/*
* throw 关键字(全小写): 1. 效果等同于return,后续代码不再执行
* 2. throw + Throwable对象(只能跟异常对象); return 返回值;
* 3. 运用: 我们一般不使用, JDK方法的设计者使用 (抛出异常: api设计者 和 使用者交流方式)
*/
public class ThrowDemo {
public static void main(String[] args) {
// new ArrayList<String>(-1); //IllegalArgumentException
int[] array = {0,1};
int element = getElement(array);
System.out.println(element);
}
private static int getElement(int[] array) {
int index = 2;
if(index > array.length - 1){ //>2-1
//访问了数组不存在的索引
// ArrayIndexOutOfBoundsException e = new ArrayIndexOutOfBoundsException("you are a stupid bird,you access a wrong index:" + index);
// throw e; //抛出异常,下面代码不会执行。抛出去之后也没人处理
throw new ArrayIndexOutOfBoundsException(index); //这一行等同上两行
//throw new Throwable(""); //也可以,因为是编译异常,声明getElement方法后要加throws Throwable
}
int element = array[index]; //上面有throw,这里两行都不执行
return element;
}
}
17.编译和运行异常:SimpleDateFormat
public class ExceptionDemo {
public static void main(String[] args) throws IOException {
String str = "1996-01-01";
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
System.out.println(sdf); //java.text.SimpleDateFormat@f67a0200
// sdf.parse(str); //解析异常ParseException也叫编译异常,和IOException并列关系,main声明需抛出。
Date parse = sdf.parse(str); //加上Date parse不报错
System.out.println(parse); //Mon Jan 01 00:00:00 CST 1996
// FileOutputStream fos = new FileOutputStream(""); //FileNotFoundException是IOException子类
// fos.write(97); //IOException是最经典的编译异常
//111111111111111111111111111111111111111111111以下都为RuntimeException的子类
// 1. NullPointerException 空指针
String s = null;
// System.out.println(s.length());
// 2. IndexOutOfBoundsException 索引越界
int[] array = {};
// System.out.println(array[1]);
// 3. ClassCastException 类转换
// 4. IllegalArgumentException 非法参数
new ArrayList<String>(-1);
}
}
18.处理异常:方法声明抛出
package com.itheima03.throwsd;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class ThrowsDemo {
public static void main(String[] args) throws IOException {
method01(); //表哥又抛出去叫大哥(jvm,因为main方法调用者是jvm),jvm出错又打印终止
}
/*
* throws 关键字
* 1. 用在方法声明上: 修饰符 返回值类型 方法名(参数列表) throws 异常类型(A)
* 2. 这是处理异常的一种方式: 声明将异常抛出给调用者处理(假手与人)
* 3. 注意: 这里的异常类型A 必须跟方法里要抛出的异常B一致, 或者A是B的父类 (向上转型)
*
* 语法: throws 异常1,异常2{ }
* 运用: 我们可以在某一方法的设计上先声明抛出异常,可以方法的调用处进行处理
* 切记有有一环节必须处理, 不然到JVM中, 出现异常就崩溃。如果明知不会错的异常,直接throws。
*/
private static void method01() throws IOException { //交给表哥
FileOutputStream fos = new FileOutputStream("a.txt");
fos.write(97);
}
}
package com.itheima04.trycatch;
public class TrycatchDemo {
public static void main(String[] args) {
int[] array = {0,1};
try{
int element = array[2];
System.out.println(element);
}catch (Exception e){
//catch关键字监视try中代码块,我们程序自己把异常处理了,所以不会传给JVM,程序不会终止。
e.printStackTrace();//打印异常信息: 打印栈中追溯,案发地点在方法栈中。jvm不会打印了,我们自己手动打印。这行注释了,下面红字不会打印,但“出现异常..继续运行...”都会打印出。
System.out.println("出现异常,并被捕获了");
}
System.out.println("程序继续执行");
}
}
package com.itheima04.trycatch;
import java.util.Scanner;
/*
* try...catch运用: 1. 如果代码都是由我们自己编写的,我们一般都不会出现异常
* 2. 有些代码需要用户参与编写 (交互: 我们程序输出,用户输入)
*思想: 我们预知代码可能运行结果(如果了包含了异常, 提前try...cath并给出提示或者解决方案)
*/
public class TryCatchDemo02 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println("请输入一个被除数:");
int a = sc.nextInt();
System.out.println("请输入一个除数:");
int b = sc.nextInt();
try {
int result = a/b; //java.lang.ArithmeticException: / by zero 算术异常
System.out.println(result);
}catch (Exception e){
System.out.println("你个傻鸟,除数不能为0");
}
System.out.println("软件继续让你玩");
}
}
package com.itheima04.trycatch;
/*
try{
}catch (NullPointerException e){
//1. 如果出现了空指针,应该提示...
}catch (IndexOutOfBoundsException e2){
//2. 如果出现了索引越界, 应该提示...
}
执行顺序:如果try中代码发生异常, 那么多个catch会从上到下逐一尝试捕获, 如果被A捕获了,后续的catch不再执行。
注意: 1. 前面catch中的异常类型不能是后面catch中的异常类型的父类或者相同 (因为这样的话,后续catch执行不到没有意义) 2. 后面catch中的异常类型可以是前面的父类。
*/
public class TryCatchCatchDemo {
public static void main(String[] args) {
try{
method01(2);
}catch (NullPointerException e){
System.out.println("发生了空指针异常");
}catch (IndexOutOfBoundsException e2){
System.out.println("发生了索引越界异常");
}
System.out.println("代码继续执行");
}
//模拟: 这段代码可能有两个异常(一段代码里面不论可能有多少个异常,一次运行最多抛出一个异常)
private static void method01(int a) {
if(a == 1){
throw new NullPointerException("空指针"); //throw天生与其他代码互斥,一旦发生throw,其他代码不再运行
}else if(a == 2){
throw new IndexOutOfBoundsException("越界异常");
}else{
System.out.println("什么事都没发生");
}
}
}
19.finally关键字:catch相当于else if,finally相当于else,return
package com.itheima04.trycatch;
/*
try{
}catch (Exception e){
}finally {
// 无论如何,一定最后执行。 作用: (IO流)释放资源
}
*/
public class FinallyDemo {
public static void main(String[] args) {
// method();
int number = method2(); //运行 :3
System.out.println(number); //1
}
//111111111111111111111111111111111111111111111111111111111111111111111
private static int method2() {
int i = 1;
try{
// System.exit(0); //拔电源阻止finally
return i;
//一般return后续代码不会执行了,但finally会抢夺try...catch中的return执行权,finally会先执行,执行完又回到return
//return 安全机制: 把i的值给记录下来了1 ,所以return 1
}catch (Exception e){ //没有异常,i永远不会=2
i = 2;
}finally {
i = 3;
System.out.println("运行 :" + i);
}
return i; //不执行,因为try catch finally有return了
}
//11111111111111111111111111111111111111111111111111111111111111111111
private static void method() {
try{
int i = 4/0;
System.out.println(i);
}catch (NullPointerException e){
System.out.println("异常发生了");
}finally {
System.out.println("无论如何一定执行");
}
System.out.println("程序继续执行");
}
}
20.自定义异常:extends
package com.itheima05.custom;
/*
* JavaBean : 标准类 (封装)
* 1. private属性
* 2. public get set方法
* 3. public 空参构造,不声明构造会有一个空参构造。 写满参构造就得写空参构造。
* 封装: 1. private属性 为了不对外直接暴露 -> 安全
* 2. public get set方法【方法跟属性最大不同, 方法可以加条件判断(健壮性校验)】
* 继承: 自定义异常
*/
public class Student {
private String name;
private int age;
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", 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) {
if(age < 0){
// throw new IllegalArgumentException("你个傻鸟,age没有负数");
throw new NoAgeException("你个傻鸟,age没有负数"); //不报错,因为下面有定义class NoAgeException extends 。。。
}
this.age = age;
}
}
package com.itheima05.custom;
/*
* 自定义异常: IllegalArgumentException : 非法参数异常
* NoAgeException : 非法年龄异常(框架: 很多JDK没有的异常,自定义)
*/
public class CustomDemo {
public static void main(String[] args) {
Student s = new Student();
s.setName("张三");
// s.age = -18; // 非法访问,安全隐患,所以private int age;
s.setAge(-18); //在方法里加安全机制
System.out.println(s);
}
}
class NoAgeException extends IllegalArgumentException{
public NoAgeException(String msg){
super(msg); //调用IllegalArgumentException,Ill..又调用自己的super。。
}
}
package com.itheima06.notice;
import java.io.IOException;
public class NoticeDemo {
public static void main(String[] args) throws IOException {
Fu fu = new Zi(); //编译看左边fu(上行已经抛出编译异常throws IOException), 运行看右边Zi(下面已经抛出运行异常throws RuntimeException)
fu.method();
}
}
class Fu{
void method() {
}
}
class Zi extends Fu{
@Override
void method() throws RuntimeException{ //运行异常 不报错
}
}
package com.atguigu.test01.review;
// 编写代码演示栈内存溢出 StackOverflowError(递归导致内存溢出)
public class TestError1 {
public static void main(String[] args) {
Son s = new Son();
s.test(); //自己调用自己,不调用父类test()方法。java.lang.StackOverflowError
}
}
class Father{
public void test(){
System.out.println("父类的");
}
}
class Son extends Father{
public void test(){
//调用父类的test();要用super.test()
test();
System.out.println("子类的");
}
}
package com.atguigu.test01.review;
import java.util.ArrayList;
// 请编写代码演示OOM:OutOfMemoryError
public class TestError2 {
public static void main(String[] args) {
//1、答案一:创建一个超级大数组,
//数组的长度的类型是int,Integer是int类型的一个包装类,Integer.MAX_VALUE是2的31次方-1
// int[] arr = new int[Integer.MAX_VALUE];
//2、答案二:不断的创建对象
ArrayList list = new ArrayList();//容器,用来装对象
while(true){
list.add(new Object());
}
}
}
public class TestFinallyNoReturn2 {
public static void main(String[] args) {
int num = getNum(4);
System.out.println(num);//0,不是30
}
public static int getNum(int a){
int result = 10;
try{
System.out.println(a/0); //直接跳到catch
if(a > 0){
result = 20;
return result;
}else if(a < 0){
result = -20;
return result;
}else{
return result;
}
}catch(Exception e){
System.out.println("exception");
result = 0;
return result;
}finally{
result = 30;
System.out.println("finally");
// return result;//如果有这句,最后一行结果就变成30
}
}
}
21.线程两种创建方式:new Thread(new Runnable() {}),extends Thread,implements Runable
如下FileOutputStream源码中抛出异常,为了让写代码人自己写try catch异常提示信息。
package com.itheim07.thread;
/*
* 进程和线程
* 1. 进程 : 航空母舰(资源: 燃油 弹药)
* 2. 线程 : 舰载机
* 一个软件运行: 一个军事活动, 必须有一艘航母出去,但执行具体任务的是航母上的舰载机
* 一个软件运行,至少一个进程, 一个进程中至少一个线程。谷歌浏览器是多进程,进程多了,占用资源多,速度快
*
* cpu: 4核 8线程。线程要运行,需要cpu授予执行权(指挥室),指挥室可以同时调度8架 飞机
* 1. 并行 : 同一时间,同时执行 (并行只能8线程)
* 2. 并发 : 同一段时间, 实际上是交替执行, 速度快的时候看起来像是同时执行(频率快)(常见: 并发1800线程)
*
* cpu调度算法(并发)
* 1. 分时调度 : 1800s, 每个线程1s
* 2. 抢占式调度 : 按照线程优先级进行分配, 优先级高(可以自己设置)一般就分配的多(随机性强) java
*
* 为什么需要多线程?
* 1. 默认java代码有两个线程
* 1. main方法线程 : 主线程
* 2. GC线程(jvm使用的,我们无法调度)
* 2. 一个线程可用, 有什么局限性?只能做一件事
* 3. 如果想要同时执行多个任务 -> 多线程
*/
public class ThreadDemo {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
while(true){
System.out.println("播放音乐...");
}
}
}).start(); //.start()不能改成.run()
boolean result = true;
while(result){
System.out.println("下载电影...");
}
/* while(result){ //虽然骗了编译器,但还是不能执行到这里,上面一直在死循环里出不来。
System.out.println("播放音乐...");
}*/
}
}
如下线程第一种创建方式。
package com.itheima01.thread;
/*
Thread:1. start() : 启动线程,jvm会创建线程,并调用run方法
2. static Thread currentThread(),返回对当前正在执行的线程对象的引用。
3. String getName() : 获取线程名称
!!! Thread.currentThread().getName() : 获取当前线程名称
线程默认命名规则:1. main线程 : main
2. 子线程(main线程创建的线程) : static int number;static被共享
Thread-0 , 1, 2 ...
*/
public class ThreadDemo02 {
public static void main(String[] args) {
// Thread thread = Thread.currentThread();
// String name = thread.getName();
// System.out.println(name); // main
//下面一行等同于上面
System.out.println("主:" + Thread.currentThread().getName());
YourThread yt = new YourThread();
yt.start(); //子:Thread-0
YourThread yt2 = new YourThread();
yt.run(); //子:main。 因为子线程YourThread还未执行起飞 ,被main飞机拖着走
YourThread yt3 = new YourThread();
yt3.start(); //子:Thread-2。 不是Thread-1是因为yt2未起飞但依旧new了yt2
// Person p = new Person(); //执行空参构造
// System.out.println(p.number); //0
// Person p2 = new Person();
// System.out.println(p2.number); //1
}
}
class YourThread extends Thread{
@Override
public void run() {
System.out.println("子:" + Thread.currentThread().getName());
}
}
class Person{
static int number=-1;
public Person(){
number++;
}
}
package com.itheima02.runnable;
/*
* 线程第二种创建方式: 1. 声明实现 Runnable 接口的类。
* 2. 该类然后实现 run 方法。
* 3. 然后可以分配该类的实例,在创建 Thread 时作为一个参数来传递并启动。
* Thread(Runnable target)
*/
public class RunnableDemo {
public static void main(String[] args) {
MyRunnable mr = new MyRunnable(); // 分配该类的实例
Thread t = new Thread(mr);
t.start(); //Thread-0
}
}
class MyRunnable implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
package com.itheima02.runnable;
//用匿名内部类简化上面代码
public class RunnableDemo02 {
public static void main(String[] args) {
/* Runnable mr = new Runnable(){ //用接口名Runnable代替子类类名,匿名对象。
//不用再写class MyRunnable implements Runnable{},Runnable mr = new MyRunable(); 向上转型
@Override
public void run() { //new一个接口()再{},是new这个接口的子类对象
System.out.println(Thread.currentThread().getName());
}
};
Thread t = new Thread(mr);
t.start();
// new Thread(mr).start(); */
//111111111111111111111111111111111111111111111111111111111111111111111111111111
new Thread(new Runnable() {
@Override
public void run() { //主要关注run
System.out.println(Thread.currentThread().getName());
}
}).start();
//new Thread(() -> System.out.println(Thread.currentThread().getName())).start();
}
}
22.卖票:原子性
package com.itheima03.ticket;
/*
* 需求假设某航空公司有三个窗口发售某日某次航班的100张票,100张票可以作为共享资源,三个售票窗口需要创建三个线程
* 好处: 多线程执行同一任务,比较快。
* 1. 程序(单线程) , 并发1600线程, cpu分配执行权: 1/1600
* 2. 程序(多线程 100) , 并发1700, cpu分配给我们的程序执行权更多:1/17
* 注意: 线程不是越多越好(线程本身很占内存, 慢。票数不多不需要用多线程)。
*/
public class TicketDemo01 {
public static void main(String[] args) {
MyWindow mw1 = new MyWindow(); //堆中开一块空间
mw1.setName("窗口壹");
MyWindow mw2 = new MyWindow(); //同上
mw2.setName("窗口222");
MyWindow mw3 = new MyWindow(); //同上
mw3.setName("窗口三三三");
mw1.start();
mw2.start();
mw3.start();
}
}
//11111111111111111111111111111111111111111111111111111111111111111111111111111
class MyWindow extends Thread{
static int number = 100; //去掉static,每创建一个MyWindow窗口在堆里开辟一块空间,三个窗口各卖100张
@Override
public void run() {
while(number > 0){
System.out.println(Thread.currentThread().getName() + "正在卖出第" + number + "张票");
number--;
}
}
}
/*
* 两种线程创建方式: 1.继承Thread 2.实现Runnbale 。第二种方案会更好一些,不需要加static,因为只new了一个对象。
* 1. 实现接口,而不是继承类(扩展性更强) 接口可以多实现,但是类只能单继承(MyWindow继承Thread后,就不能继承另外的类。MyTask可以继承其他类,实现其他接口)
* 2. 更符合 面向对象 (高内聚,低耦合:线程独立,和业务代码MyTask分离,传入卖猪肉任务也行)。封装(各干各的,有必要再进行合作)
*/
如下线程同步问题分析:两种创建方式3个窗口都总卖出102张票,而不是100张。原因:三个窗口同时卡在打印正在卖出第100张票。解决:t1在卖第100张票时,cpu可能会切到t3和t2,可以控制t2和t3不动,等t1的number- -完再动。
23.线程同步:synchronized关键字/方法,Lock接口,ThreadLocal
package com.itheima04.synchronizedd;
import java.io.IOException;
/*
* 1. 代码块
* synchronized(锁对象){
* 代码A
* }
* 1. 锁对象可以是任意对象,但必须唯一
* 2. 同步代码块中的 代码A 同一时间,只允许一个线程执行
*
* 使用同步锁的注意点:1. 在保证业务逻辑可用的情况,同步锁加的范围越小越好
* 2. 锁对象必须唯一:
* <1> 如果能保证当前对象唯一,this也可以作为锁对象 (更节省内存)
* <2> 当前类名.class(最好的锁对象) -> Class对象(一个类被加载,在内存都会有一个Class对象) 反射
*/
public class TicketDemo02 {
public static void main(String[] args) {
MyTask mt = new MyTask(); //只new了一个,可以用this
Thread t1 = new Thread(mt);
t1.setName("窗口壹");
Thread t2 = new Thread(mt);
t2.setName("窗口222");
Thread t3 = new Thread(mt);
t3.setName("窗口三三三");
t1.start();
t2.start();
t3.start();
}
}
class MyTask implements Runnable{
int number = 100;
// Object obj = new Object(); //锁对象
@Override
public void run() {
while(number > 0){
//1111111111111111111111111111111111111111111111111111111111111111111111111111111111111
synchronized(MyTask.class){ //MyTask.class也可以换成this
if(number <= 0){
break; //跳出while大循环
}
System.out.println(Thread.currentThread().getName() + "正在卖出第" + number + "张票");
number--;
}
//111111111111111111111111111111111111111111111111111111111111111111111111111111111111
//这边只能try catch不能throws,原因:父类Runnable中run方法没有声明抛出编译异常,所以子类也不能throws
try {
Thread.sleep(1); //线程啥事也不干,暂停1ms,cpu有空闲切换其他线程
} catch (InterruptedException e) { //这异常一般发生在线程中,当一个正在执行的线程被中断时就会出现这个异常
e.printStackTrace();
}
} //while里
}
}
如下t2卖到0张时出while,而t1和t3还在while里,此时number=0,所以变为0和-1。
如下把synchronized拖到外面也不行。
如下加if(number <= 0),没有加浪费时间代码,所以看不到交替效果,但不会出现0和-1。
obj是锁对象即钥匙,如下钥匙不能进run方法(每个线程一把即三把钥匙了),只能在成员位置。
用this,不用new object(),可以节约内存。
package com.itheima05.method;
/*
* synchronized 方法(同步方法)
* 1. 语法 : 方法声明 + synchronized
* 2. 同步方法有没有锁对象? 有
* 1. 普通方法: 是this
* 2. 静态方法: 静态不能和对象(this)有关。 是当前类名.class
*/
public class TicketDemo02 {
public static void main(String[] args) {
MyTask mt = new MyTask();
Thread t1 = new Thread(mt);
t1.setName("窗口壹");
Thread t2 = new Thread(mt);
t2.setName("窗口222");
Thread t3 = new Thread(mt);
t3.setName("窗口三三三");
t1.start();
t2.start();
t3.start();
}
}
class MyTask implements Runnable{
static int number = 100;
@Override
public void run() {
while(number > 0){
method(); //非静态方法可以调用静态方法
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private static synchronized void method() { //静态方法不能和对象关键字如this相关 //同步方法效果 等价于 同步代码块
if(number <= 0){
return; //break只能写在循环和switch里
}
System.out.println(Thread.currentThread().getName() + "正在卖出第" + number + "张票");
number--;
}
}
package com.itheima06.lock;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/*
* Lock接口: 1. 实现类 ReentrantLock
* 2. lock() 获取锁(获取钥匙)
* 3. unlock() 释放锁 (还钥匙)
*/
public class TicketDemo02 {
public static void main(String[] args) {
MyTask mt = new MyTask();
Thread t1 = new Thread(mt);
t1.setName("窗口壹");
Thread t2 = new Thread(mt);
t2.setName("窗口222");
Thread t3 = new Thread(mt);
t3.setName("窗口三三三");
t1.start();
t2.start();
t3.start();
}
}
class MyTask implements Runnable{
int number = 100;
Lock lock = new ReentrantLock(); //创建lock对象
@Override
public void run() {
while(number > 0){
//1111111111111111111111111111111111111111111111111111111111111111111111111
lock.lock();
if(number <= 0){
// System.out.println(Thread.currentThread().getName());
lock.unlock(); // 注意: lock提供了锁的可视化操作(线程执行结束,要记得手动释放。厕所上完不能带走钥匙)//同步代码块return或break后是jvm自动释放锁。//这里不加lock.unlock()程序停不下来。
break;
}
System.out.println(Thread.currentThread().getName() + "正在卖出第" + number + "张票");
number--;
lock.unlock();
}
}
}
如下t1和t2两个线程隔离。
t1(…,User),每次传User对象显得麻烦。用static每个线程会改变,所以可将User放入ThreadLocal中,每次在每个函数中通过ThreadLocal.get拿到线程的User。
24.卖包子:wait,notify
package com.itheima07.bz;
public class Demo {
public static void main(String[] args) throws InterruptedException {
Object obj = new Object();
// obj.wait(); //IllegalMonitorStateException : 非法的监视状态异常,因为.wait()必须锁对象调用如下
synchronized (obj){ //对象变成锁对象
obj.wait(); //不会报错,一直等待。在锁对象中
}
}
}
如下两个方法wait和notify不是给线程调用的,而是给锁对象【锁对象可以是任意对象】调用的如上所示。BaoZi只能一个线程对其操作。
package com.itheima07.bz;
public class BaoZi {
boolean isHave=false; //默认没有包子
}
package com.itheima07.bz;
public class BaoziPu extends Thread {
BaoZi bz;
public BaoziPu(BaoZi bz){
this.bz = bz;
}
@Override
public void run() {
while(true){ //不停生产包子
//111111111111111111111111111111111111111111111111111111111111111111111111111111
synchronized (bz){ //加锁: 同步代码,生产包子时不让别人打扰我。注意下面wait和notify
if(bz.isHave){
try {
bz.wait(); //包子铺有包子就等待(此时吃货正在吃包子)
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("包子铺生产包子..."); //没包子
bz.isHave = true;
bz.notify(); //唤醒吃货
}
} //while里
}
}
package com.itheima07.bz;
public class ChiHuo extends Thread{
BaoZi bz;
public ChiHuo(BaoZi bz){
this.bz = bz;
}
@Override
public void run() {
while(true){ //不停吃包子
//1111111111111111111111111111111111111111111111111111111111111111111111111111
synchronized (bz){
if(!bz.isHave){
try {
bz.wait(); //吃货没有包子就等待(此时包子铺正在生产包子)
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("吃货吃包子"); //有包子
bz.isHave = false;
bz.notify(); //唤醒包子铺
}
}
}
}
package com.itheima07.bz;
public class BzDemo {
public static void main(String[] args) {
BaoZi bz = new BaoZi();
BaoziPu bzp = new BaoziPu(bz); //和下面一行共同操作一个包子对象
ChiHuo ch = new ChiHuo(bz);
bzp.start();
ch.start();
}
}
如下第一次没有包子,所以绕过2中if到1。运行完1后就有包子了,1时间很短,cpu不切换线程,切换了也没用,因为2中syn…(bz)包子被锁住,就算切换到吃货线程进不去syn…(bz)里,所以1中notify唤不醒吃货线程。
1和2都在sy…(bz)里,bzp线程bz.wait()【有3个好处】进入等待状态即进入监视队列即等待包子被吃
,吃货线程的synchronized锁被打开,有包子不会wait,执行3。
一个线程wait把自己停下来放入堆(监视队列)
中,来年开春,另一个线程中3叫我起来干活。2和3对应,1和4对应。3唤醒了2中wait,但2没钥匙(锁)动不了(鬼压床),钥匙在吃货手上,所以3往后4执行释放锁,1234不停循环执行
。
生产消费者模型:用户发请求来
相当于包子铺生产包子即生产者
。服务器
24小时开着相当于消费者
一天24小时等包子吃。不会让消费者线程空转浪费cpu资源,所以没包子设置消费者线程为wait状态不占用cpu资源
。
package com.atguigu.test14;
// 线程通信是用来解决生产者与消费者问题。
public class Test14 {
public static void main(String[] args) {
Workbench tai = new Workbench(); //相当于包子
Cook c = new Cook("崔志恒", tai); //生产者
Waiter w = new Waiter("翠花", tai); //消费者
c.start();
w.start();
}
}
//11111111111111111111111111111111111111111111111111111111111111111111111111
class Workbench{
private static final int MAX = 10; //假设工作台上最多能够放10盘
private int count; //count是共用的,要考虑线程安全
public synchronized void put(){ //同步方法,非静态方法来说,锁对象就是this //往工作台上放一盘菜
if(count >= MAX){
try {
//生产者停下来,等待
wait();//默认是this.wait(),所以上面必须加锁对象synchronized
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//上面是安全校验
count++;
System.out.println(Thread.currentThread().getName() + "放了一盘菜,剩余:" + count);
this.notify(); // 包子/工作台.notify() //唤醒消费者
}
//1111111111111111111111111111111111111111111111111111111111111111111111111111
public synchronized void take(){//从工作台上取走一盘菜
if(count<=0){
try {
wait(); //工作台没有菜,消费者应该停下来
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//上面是安全校验
count--;
System.out.println(Thread.currentThread().getName() + "取走一盘菜,剩余:" + count);
this.notify(); //唤醒生产者
}
}
//1111111111111111111111111111111111111111111111111111111111111111111111111
class Cook extends Thread{
private Workbench tai;
public Cook(String name, Workbench tai) {
super(name);
this.tai = tai;
}
public void run(){
while(true){
tai.put(); //封装了
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//111111111111111111111111111111111111111111111111111111111111111111111
class Waiter extends Thread{
private Workbench tai;
public Waiter(String name, Workbench tai) {
super(name); //name属性在父类中已声明
this.tai = tai;
}
public void run(){
while(true){
tai.take();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
如下一直交替运行,不停。
如下线程6态:锁就是钥匙上厕所,限时等待就是sleep,记住blocked,waitting,runnable。
如下B进不去不执行。
25.创建和启动2个子线程:一个打印1-10之间奇数,一个打印1-10之间偶数
package com.atguigu.test03.homework02;
//(1)要求每个线程要么不打印,要么就连续打印5个数,每个数打印间隔500毫秒 。(2)但两个线程不要求交替打印。
public class Test02 {
public static void main(String[] args) {
Odd o = new Odd();
Even e = new Even();
o.start();
e.start();
}
}
class Odd extends Thread{
private int num = 1;
public void run(){
while(true){
synchronized (Thread.class) {
for (int i = 1; i <=5; i++) {
System.out.println("奇数线程,第" + i + "个:" + num);
num += 2;
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
class Even extends Thread{
private int num = 0;
public void run(){
while(true){
synchronized (Thread.class) {
for (int i = 1; i<=5; i++) {
System.out.println("偶数线程,第" + i + "个:" + num);
num += 2;
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
26.使用三个线程循环打印出1~100
package com.itheima.demo01;
class TestThread2 {
public static void main(String[] args) throws Exception{
Thread t1 = new Thread(new MyThread1(0));
Thread t2 = new Thread(new MyThread1(1));
Thread t3 = new Thread(new MyThread1(2));
t1.start();
t2.start();
t3.start();
}
static class MyThread1 implements Runnable {
private static Object lock = new Object();
private static int count = 0;
int no;
public MyThread1(int no) {
this.no = no;
}
@Override
public void run() {
while (true) {
synchronized (lock) {
//11111111111111111111111111111111111111111111111111111111111111111111
if (count >= 100) {
break;
}
if (count % 3 == this.no) {
count++;
System.out.println(this.no + "--->" + count);
} else {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
lock.notifyAll();
}
}
}
}
}
27.账户类:synchronized 方法
package com.atguigu.test06.homework04;
/*
案例:1、创建一个银行账户类:(1)属性:账号,余额。(2)get/set。(3)toString():返回:账户:xxx,余额:xxx
2、创建一个丈夫类:负责往里存钱,每次存款[0,10000)以内不等
3、创建一个妻子类:负责取钱,每次取款[0,10000)以内不等,如果余额不足,要等丈夫存够了才能取
*/
public class Test04 {
public static void main(String[] args) {
Account a = new Account("1122", 0);
AccountManager am = new AccountManager(a);
Husband h = new Husband("崔志恒",am);
Wife w = new Wife("甄玉禄",am);
h.start();
w.start();
}
}
//1.11111111111111111111111111111111111111111111111111111111111111111111111
class Husband extends Thread{
private AccountManager am;
public Husband(String name,AccountManager am) {
super(name);
this.am = am;
}
public void run(){
while(true){
am.save();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//2.11111111111111111111111111111111111111111111111111111111111111111111111
class Wife extends Thread{
private AccountManager am;
public Wife(String name,AccountManager am) {
super(name);
this.am = am;
}
public void run(){
while(true){
am.withdraw();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//3.11111111111111111111111111111111111111111111111111111111111111111111111
class AccountManager{
private Account account;
public AccountManager(Account account) {
super();
this.account = account;
}
public synchronized void save(){
double money = Math.random() * 10000;
System.out.println(Thread.currentThread().getName() + "开始存钱,目前账户状态:" + account); //自动调用account.tostring方法
System.out.println("本次存钱的数量是:" + money);
account.setBalance(account.getBalance() + money);
System.out.println(Thread.currentThread().getName() + "存钱结束,目前账户状态: " + account);
this.notify();
}
public synchronized void withdraw(){
double money = Math.random() * 10000;
System.out.println(Thread.currentThread().getName() + "开始取钱,目前账户状态:" + account);
while(money > account.getBalance()){
try {
System.out.println("本次想取钱的数量是:" + money + ",余额不足....");
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
account.setBalance(account.getBalance() - money);
System.out.println(Thread.currentThread().getName() + "取钱结束,目前账户状态: " + account);
}
}
//4.111111111111111111111111111111111111111111111111111111111111111111111111
class Account{
private String id;
private double balance;
public Account(String id, double balance) {
super();
this.id = id;
this.balance = balance;
}
public Account() {
super();
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
@Override
public String toString() {
return "账户: " + id +"余额:" + balance; //账户:xxx,余额:xxx
}
}