常用类的方法总结

类的各种常用方法

字符串String类中的常用方法

方法名返回值作用
length()int得到字符串长度
toLowerCase()String转换为小写
toUpperCase()String转换为大写
trimString去除字符串首尾全部空格
isEmpty()boolean判断字符串长度是否为0
getBytes()byte[]转换为字节数组
toCharArray()char[]转换为字符数组
equalsIgnoreCase(String  str)boolean忽略大小写比较字符串是否相等
equals(String str)boolean判断两个字符串是否相等
charAt(int index)char得到某个索引上的字符
indexOf(String str)int得到某个字符串第一次出现的索引,不存在返回-1
lastIndexOf(String str)int得到某个字符串最后一次出现的索引,不存在返回-1
contains(String str)boolean判断是否存在某个字符串
startsWith(String str)boolean判断是否以某个字符串开头
endsWith(String str)boolean判断是否以某个字符串结尾
concat(String str)String将指定字符串评级到原字符串末尾
substring(int index)String从索引index开始截取字符串末尾
substring(int begin,int end)String截取[begin,end)范围内的字符串
split(Sting regex)String[]根据字符串或者正则表达式切分原字符串
replace(String oldStr,String newStr)String将原字符串的oldStr替换为newStr
String.valueOf(参数)String将参数转换为字符串,参数可以是任何数据,通常用于原始类型转换为字符串
String.format(String式,Object…obj)String根据指定格式转换参数。常用与将浮点数据保留指定小数位数。\n如String.format(“%4.2f”,2.345)表示将 2.345保留2位小数,整体占4位,输出为字符串格式。 如果实际数字总位数大于4,原样输出,如果实际数字 总位数小于4,会在最前补充空格。

可变字符串

  • StringBuilder

    用于表示可变字符串的一个类,是非线程安全的,在单线程环境下使用,效率更高。

  • StringBuffer

    用于表示可变字符串的一个类,是线程安全的,在多线程环境下使用。

    StringBuilder和StringBuffer中的方法都一致,只不过StringBuffer中的方法使用了synchronized关键字修饰,表示是一个同步方法,在多线程环境下不会出现问题。

    常用方法

    常用方法作用
    append(Object obj)将任意数据添加带原可变字符串的末尾
    delete(int start,int end)删除[start,end)范围内的字符
    deleteCharAt(int index)删除index索引上的字符
    insert(int index,Object obj)将obj添加到index上
    repalce(int start,int end,String str)将[start,end)范围内的字符替换为str
    reverse()反转字符串

注意

  • String类中的所有方法调用后,都会创建一个新的String对象,即原本的String字符串不会改变

  • StringBuilder类中的所有方法都是在操作同一个字符串对象,每次调用方法,都会让原字符串发生变化

  • StringBuilder类中没有重写equals方法,所以判断两个可变字符串对象是否相同时,如果调用equals方法,实际调用的是Object类中未重写的方法,即==判断。所以判断可变字符串是否相同时,需要将其转换为String对象再调用equals方法。

任意类型对象转换为String

  • String.valueOf(Object obj)

  • toString()方法

  • 拼接空字符串

可变字符串相关面试题

比较String、StringBuilder和StringBuffer的区别
相同点:
  • 这三个类都可以表示字符串。都提供了一些操作字符串的方法。

  • 这三个类中有相同的方法,如charAt(),indexOf()等。

  • 这三个类都是被final修饰的类,不能被继承

不同点:
  • String定义的字符串是一个常量。可变字符串定义的字符串是一个变量。

  • String类中的方法调用后,不会改变原本字符串的值。可变字符串中的方法调用后,会改变原本字符串的值

  • StringBuilder是非线程安全的可变字符串类,StringBuffer是线程安全的可变字符串类,其中的方法被synchronized修饰。

System类

常用方法

常用方法与属性
System.ou获取标准输出流对象,用于打印信息
System.in获取标准输入流对象,用于获取输入的信息
System.err获取错误输出流对象,用于打印异常信息
System.exit(int statues)终止虚拟机运行,参数0表示正常终止
System.currentTimeMills()获取从1970/1/1 0:0:0至今经过了多少毫秒。中国是UTC(+8),所以实际是从1970/1/1 8:0:0至今经过了多少毫秒。返回值为long类型。通常称为时间戳。
System.arraycopy(原数组,原数组的起始位置,目标数组,目标数组的起始位置,要复制的元素数量)将原数组中指定数量的元素复制到新数组中

Runtime类

这个类不是一个抽象类,但不能创建对象,因为它的构造方法是私有的。

这个类提供了一个静态方法getRuntime(),通过该方法,可以获取一个Runtime类的对象。

这种方式可以保证该类只能创建一个对象,是Java中的一种设计模式:单例模式。

public class Runtime{
//定义了一个私有的静态成员,创建一个当前类的对象
private static Runtime currentRuntime = new Runtime();
//将构造方法私有,无法在外创建对象
private Runtime();
//定义了一个公开的静态方法,用于获取创建的唯一的当前类的对象
public static Runtime getRuntime(){
return currentRuntime;
}
}

Date类

常用方法

常用方法作用
getTime()得到Date对应对象的毫秒数
after(Date  when)判断参数是否是调用日期之后
before(Date when)判断参数是否再调用日期之前

SimpleDateFormat类

日期模板

特殊字符作用
yyyy
MM
dd
hh12小时制
HH24小时制
mm
ss
E星期
yyyy/MM/dd HH:mm:ss E2023/03/09 14:05:16 星期四

常用方法

常用方法返回值作用
format(Date date)String将Date对象按日期模板转换为字符串
parse(String str)Date将满足日期模板的字符串转换为Date对象

Calendar类

常用方法

get(int field)根据日历字段获取对应的值
getMaximum(int field)获取指定日历字段的最大值,如日期最大值为31
getActualMaximum(int field)获取指定日历字段的实际最大值,如11月的日期最大为30
getTime()将Calendar对象转换为Date对象
set(int field,int value)将指定的日历字段设置为指定值
set(int year,int month,int date)同时设置日历的年月日
setTime(Date date)将Date对象作为参数设置日历的信息

使用Calendar类实现万年历

package com.hqyj.dateTest;
import java.util.Calendar;
import java.util.Scanner;
public class Test2 {
public static void main(String[] args) {
//实现"万年历"
//输入年份和月份,输出
Calendar c = Calendar.getInstance();
Scanner sc = new Scanner(System.in);
System.out.println("输入年份");
int year = sc.nextInt();
System.out.println("输入月份");
int month = sc.nextInt();
c.set(year, month - 1, 1);
//得到当月最大日期
int maxDate = c.getActualMaximum(Calendar.DATE);
//换行计数
int count = 0;
//输出空格
/*
* DAY_OF_WEEK 星期 空格数量
* 2 一 0
* 3 二 1
* 4 三 2
* 5 四 3
* 6 五 4
* 7 六 5
* 1 天 6
* //周天空6 其余空DAY_OF_WEEK-2
* */
System.out.println("一\t二\t三\t四\t五\t六\t日");
//获取当月1号是一周中的第几天
int week = c.get(Calendar.DAY_OF_WEEK);
//周天空6格
if (week == 1) {
System.out.print("\t\t\t\t\t\t");
//空格也需要计数
count += 6;
} else {
//其他情况空星期-2个格
for (int j = 1; j <= week - 2; j++) {
System.out.print("\t");
//空格也需要计数
count++;
}
}
//输出数字
for (int i = 1; i <= maxDate; i++) {
System.out.print(i + "\t");
//计数+1
count++;
//计数到7换行
if (count % 7 == 0) {
System.out.println();
}
}
}
}

包装类

Java是纯面向对象语言,宗旨是将一切事物视为对象处理。

但原始类型不属于对象,不满足面向对象的思想。但原始类型无需创建对象,保存在栈中,效率更高。

为了既保证效率又让原始类型也有对应的类类型,达到"万物皆对象"的理念,所以就有了包装类的概念。

包装类就是原始类型对应的类类型。

包装类常用于字符串与原始类中之间的转换。

在web应用中,从浏览器页面中获取数据提交到服务器,全部都是String类型,所以一定要使用字符串转换为原始类型的方法。

包装类原始类型
Bytebyte
Shortshort
Integerinteger
Longlong
Floatfloat
Doubledouble
Chartacterchar
Booleanboolean
特点
  • 八个原始类型中,除了int和char,其余包装类都是将原始类型的首字母改为大写。

int对应Integer,char对应Character

  • 包装类都是被final修饰的,不能被继承

  • 除了Character类,其余包装类都有两个过时的构造方法,参数为对应的原始类型或字符串

Character只有一个参数为char类型的构造方法

构造方法的目的都是将原始类型的数据转换为包装类的对象

  • 除了Character类,其余包装类都有静态方法"parse原始类型单词(String str)",用于将字符串转换为相应的原始类型

    • 数值型的包装类parseXXX()方法,如果参数不是对应的数字,就会抛出NumberFormat异常,如"123a"或"123.4"都会报错

    • boolean的包装类Boolean的parseBoolean()方法,如果参数不是"true"这个单词的四个字母,转换结果都是false

  • 除了Boolean类,其余包装类都有MAX_VALUE和MIN_VALUE这两个静态属性,用于获取对应类型支持的最大最小值

  • 所有包装类都重写了toString(),用于将包装类对象转换为String对象

装箱和拆箱

装箱
拆箱

自动装箱和拆箱

  • 自动装箱缓冲区

    //i1和i2保存的数字在byte范围[-127,127]内,这个值会共享,只会有一个"100"对象
    Integer i1 = 100;
    Integer i2 = 100;
    System.out.println(i1 == i2);//i1和i2引用同一个地址,结果为true
    //i3和i4保存的数字不在byte范围[-127,127]内,会创建对象
    Integer i3 = 128;//128对象
    Integer i4 = 128;//128对象
    System.out.println(i3 == i4);//i3和i4引用不同的地址,结果为false
    System.out.println(i3.equals(i4));//包装类重写了equals,会拆箱后判断,结果为ture
    
    • 使用自动装箱给包装类对象赋值,值的范围在byte范围[-127,127]内,这个值会保存在缓冲区中,如果多个对象都使用这个值,共享这一个数据,使用同一个地址,==判断结果true;值的范围不在byte范围[-127,127]内,就会创建新的包装类对象,会有不同的地址,==判断结果false

    • 引用类型对象比较相同时,不要使用==,包括包装类的对象。比较相同时,使用包装类重写的equals方法.

异常

当程序没有按开发人员的意愿正常执行,中途出现错误导致程序中断,这种情况,就称为异常。

学习异常就是认识异常的种类,如何处理异常和避免异常出现。

异常的产生

异常在程序中以对象的形式存在。当代码执行过程中出现异常,虚拟机会自动创建一个异常对象,如果没有对该异常对象进行处理,就会导致程序中断,不再执行后续内容。

异常的分类

异常在程序中以对象的形式存在,就有相应的类。

所有的异常类,组成了"异常家族"。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BB4l6SCY-1679309728418)(C:\Users\23266\AppData\Roaming\marktext\images\2023-03-10-15-26-11-image.png)]

Error错误

如果出现xxxError,如StackOverFlowError,栈溢出,无法通过额外的代码解决,只能修改源代码。

Exception异常
  • RunTimeExcetpion运行时异常

    如果一个异常类属于RunTimeExcetpion异常类的子类,称这个异常为运行时异常,可以通过编译,运行时可能抛出异常对象

    常见运行时异常说明出现的情景
    NullPointerExceptionNullPointerException 空指针异 常如用空对象null调用属性或方法
    IndexOutOfBoundsExceptionn 索引越界异常如当使用某个带有索引的对象超出范围
    NumberFormatException数字格式异常如调用包装类的parseXX()方法,如果参数不 能转换
    InputMismatchException输入不匹配异常如使用Scanner接收控制台输入时,如果输 入的数据不是对应的类型
    ClassCastException对象转型异常如Person p = (Person)Dog dog;
    ArithmeticException算术运算 异常如0当分母
  • 编译时异常

    如果一个异常类属于RunTimeExcetpion异常类的子类,称这个异常为运行时异常,可以通过编译,运行时可能抛出异常对象

    常见编译时异常说明出现的情景
    IOException输入输出流异常使用流对象
    FileNotFoundException文件未找到以方法的参数为文件对象时
    SQLException数据库相关异常操作数据库时

处理异常

通常所说的处理异常,指的是处理Exception类的子类异常。

处理异常的目的,就是保证程序正常执行。

方式一:try-catch-finally语句

这种方式处理异常,无论会不会抛出异常,都能让程序正常执行

try{
//可能出错的代码
}catch(异常类 异常对象){
//如果try中的代码抛出异常,异常对象属于catch中的异常类型,就会执行这里的代码
}catch(异常类 异常对象){
//如果try中的代码抛出异常,异常对象属于catch中的异常类型,就会执行这里的代码
}...{
}finally{
//无论程序是否会抛出异常,都要执行这里的代码
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wsUzXVtl-1679309728419)(C:\Users\23266\AppData\Roaming\marktext\images\2023-03-10-19-09-04-image.png)]

执行流程:先执行try中的内容,当出现异常,与后续每个catch中的异常类型进行匹配,如果匹配到对应的类型或异常父类时,执行后续大括号中的内容,最终一定执行finally中的内容。

try-catch-finally使用时注意
  • 如果代码会抛出多个异常,可以使用多个catch进行捕获。需要将子类异常放在最前,父类异常放在最后

  • try、catch、finally都不能单独使用,try必须配合catch或finally或一起使用

  • 无论try中的代码是否会抛出异常,finally中的代码一定会执行

  • 执行try中的代码是,如果出现异常,就不再执行try中剩余代码

  • try中定义的内容,无法在try之外的地方使用

  • try中如果有return,不影响finally的执行,finally优先于return执行

方式二:throws关键字

这种方法,可以让编译时异常通过编译。

在定义方法的时候,通过该关键字声明方法可能抛出的异常

用法:方法的参数部分后,添加 throws ****异常类型1,****异常类型2.

public class Test{
//这时该方法就会有一个声明:该方法可能会抛出InterruptedException异常
public void fun() throws InterruptedException{
//sleep()方法在源码中声明了可能会抛出InterruptedException异常,
//InterruptedException异常不是RuntimeException的子类异常,必须要处理才能通过编译
//要么使用try-catch处理,要么继续声明有异常
Thread.sleep(500);
}
}

throw和throws

  • throws表示用于声明方法有可能出现的异常。使用时写在方法的小括号之后

    public void fun() throws InterruptedException{
    Thread.sleep(500);
    }
    
  • throw用于手动抛出异常对象。使用时,写在方法体中,“throw 异常对象”。

常用于满足某种条件时,强制中断程序。

public void fun(){
throw
}

自定义异常

如果需要在某种情况下中断程序,可以自定义一个异常类。再通过throw关键字手动抛出自定义异常。

自定义异常步骤

1.创建一个类,继承某个异常类

  • 如果继承的是RuntimeException,表示自定义的异常类属于运行时异常,该异常对象可以不用处理

  • 如果继承的是非RuntimeException,表示自定义的异常属于编译时异常,该异常对象必须要处理

2.可选操作。定义带参构造方法,参数为String类型的异常信息,调用父类中的构造方法

/*
* 自定义异常
* 只需继承某个异常类即可
* 是否定义构造方法根据实际情况决定
* */
public class MyException extends NullPointerException {
/*
* 带参构造,参数为异常信息
* */
public MyException(String msg){
super(msg);
}
/*
* 无参构造
* */
public MyException(){
super();
}
}

数组和集合

数组的特点

  • 数组中保存的元素都是有序的,可以通过索引快速访问。

  • 数组中保存的元素都是一种类型。

  • 数组的长度在定义后,无法改变。

  • 数组中无法获取其中实际保存的元素数量

集合的特点

  • 能保存一组数据,元素可以有序和无序(存入的顺序和读取的顺序不一致)

  • 集合中保存的元素的数据类型可以不同

  • 集合的容量可以改变

  • 可以获取集合中实际存在的元素数量

集合家族

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xzFjcbME-1679309728420)(C:\Users\23266\AppData\Roaming\marktext\images\2023-03-13-10-08-29-image.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VIu7L7jV-1679309728420)(C:\Users\23266\AppData\Roaming\marktext\images\2023-03-13-10-10-24-image.png)]

图上的所有实现类,都是非线程安全的,在多线程环境下使用以上任何集合,都会出现数据不准确的情况。

Cellection接口

该接口有两个核心接口:List和Set。

这两个接口都可以保存一组元素,List接口保存原始有序可重复,Set接口保存元素无需不可重复。

Collection接口有一个父类接口Iterable,它不是一个集合,而是用于遍历集合的工具接口,包含forEach()和iterator()方法。

常用方法返回值作用
add(Object obj)boolean将元素添加到集合中
size()int获取集合中的元素数量
isEmpty()boolean判断集合是否为空
clear()void清空集合
contains(Object obj)boolean判断集合中是否包含指定元素
remove()boolean移除集合中的指定元素
toArray()Object[]将集合转换为数组
stream()Streanm获取集合流对象
iterator()Iterator得到集合的迭代器对象,用于遍历集合
List接口(有序可重复)

有序集合,元素可以重复,允许保存null,可以通过索引获取对应的元素。

List接口在继承Colletion接口后,又拓展了一些操作元素的方法。

拓展方法返回值作用
get(int index)Object得到指定索引的元素
set(int index,Object obj)Object使用obj替换index上的元素,返回被替换的元素
add(int index,Object obj)void将obj添加到index上
remove(int index)Object移除指定索引上的元素,返回被移除的元素
index(Object obj)int得到obj第一次出现的索引
lastindex(Object ojb)int得到obj最后一次出现的索引
subList(int from,int to)List截取[from,to)区间的元素,返回子集合
ArrayList实现类(重点)
  • 采用数组实现集合。

  • 可以通过索引访问元素,可以改变集合大小,可以要在其中插入和删除元素时,会影响后续元素。

  • 该集合查询效率很高,在其中间删除或加入元素效率很低。

  • 任何集合中保存的都是引用类,如集合中保存123,保存的不是int类型的123,二十integer类型的123.

构造方法说明
ArrayList()创建一个Object类型的空数组,在调用后添加方法,才会初始化数组大小为10.
ArrayList(int initialCapacity)创建一个指定容量的Object类型数组,如果参数为负值,会抛出IllegalArgumentException异常
ArrayList(Collection c)根据指定集合创建Object类型数组
常用方法

主要以List接口和Collection接口中的方法为主。

LinkedList实现类
  • 采用双向链表实现集合

  • 集合中保存的每个元素称为节点,除首尾节点外,其他节点即保存了自己的数据,还保存了其前后节点的地址。

  • 如果在双向链表的结构中进行插入和删除节点的操作时,不会影响其他节点现在的保存位置。

  • 如果要查询某个节点的地址时,需要从头节点或则尾节点开始搜索目标节点的位置。

  • 双向链表在中间插入和删除数据效率高,随机读取的效率低。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dJ7BH4Zk-1679309728421)(C:\Users\23266\AppData\Roaming\marktext\images\2023-03-14-18-08-16-image.png)]

构造方法
常用构造方法说明
LinkedList()
常用方法

主要以List接口和Collection接口中的方法为主,由于还实现了Deque接口,所以还有一些Deque接口中的方法。

常用来自于Deque接口中的方法作用
addFirst(Object obj)添加obj为头节点
addLast(Object obj)添加obj为尾节点
getFirst()得到头节点
gerLast()得到尾巴节点
removeFirst()删除头节点
removeLast()删除尾节点
ArrayList和LinkedList的区别
  • 这两个类都是List接口的实习类,保存的元素有序可重复,允许保存null。

  • ArrayList采用数组实现,随机读取效率高,插入和删除效率低,适用于查询。

  • LinkedList采用双向链表实现,插入和删除效率高,随机读取效率低,适用于频繁更新集合。

Set接口(无序不重复)

无序集合,元素不重复,允许保存null,没有索引。

Set接口中的方法都是继承于Collection接口。

哈希表hash table

哈希表,也成为散列表,是一种数据结构,能更快地访问数据.

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kVdFDuNb-1679309728422)(C:\Users\23266\AppData\Roaming\marktext\images\2023-03-14-18-21-54-image.png)]

哈希表的特点
  • 采用哈希表实现

  • 元素不重复,无序保存,允许保存一个null

  • 本质是HashMap对象

  • 使用HashSet集合时,通常要吃重写实体类中的equals和hashcode方法

构造方法
常用构造方法说明
HashSet()创建一个空集合,实际是创建了一个HashMap对象
常用方法

HashSet中没有定义属于自定的方法,都是父接口Set和Collection中的方法。

equals和hashcode的关系
  • 如果两个对象的equals方法结果为true,在没有重写equals方法的前提下,hashcode相同吗?

    • 如果没有重写equals方法,默认使用Object中的方法,使用==判断,如果结果为true,说明是同一个地址,同一个对象,hashcode一定相同
  • 如果两个对象的hashcode不同,在没有重写equals方法的前提下,equals方法的结果为?

    • hashcode不同,说明不是同一个对象,没有重写equals,说明使用Object中的方法,使用==判断,结果为false。
  • 如果两个对象的hashcode相同,equals方法的比较结果为?

    • 可能为true或false

      String str1="hello";
      String str2="hello";
      //str1和str2使用同一个地址,hashcode相同,equals结果为true
      String str3="通话";
      String str4="重地";
      //str3和str4不是同一个地址,但hashcode相同,这种情况称为哈希冲突,equals结果为
      false
      
TreeSet实现类
  • 特殊的Set实现类,数据可以有序保存,可以重复,不能添加null

  • 采用红黑树(自平衡二叉树)实现的集合

    • 二叉树表示某个节点最多有两个节点

    • 某个节点右侧节点值都大于左侧节点值

    • 红黑树会经过"变色"和旋转达到二叉树的平衡

  • 只能添加同一种类型的对象且该对象实现了Comparable接口

    • 每次调用add方法添加元素时,会自动调用CompareTo()方法,用于决定新添加的元素放在旧元素之前还是之后
  • CompareTo()方法的返回值决定了能否添加新元素和新元素的位置

    • 如果返回,视为每次添加的元素都是同一个,不再重复添加

    • 如果为正数,将新的元素添加在现有元素之后

    • 如果为负数,将新的元素添加在现有元素之前

  • 添加的元素是自动排序

构造方法
常用构造方法说明
TreeSet()常见一个空集合
常用方法
独有方法作用
first()得到集合中第一个元素
last()得到集合中的最后一个元素
celliling(Object obj)得到集合中比参数大的元素中的最小元素
floor(Object obj)得到集合中比参数小的元素中的最大元素
Map接口

Map成为映射,该集合中保存的数据时以键值对的形式保存,保存的键与值的映射关系。

键成为Key值成为Value,键不能重复,允许出现一个null作为键,值没有限制

常用方法作用
size()得到键值对的数量
isEmty()判断是否为空集合
clear()清空所有键值对
put(Object key,Object Value)添加一组键值对
get(Object key)根据键得到对应的值
containsKey(Object key)判断是否存在某个键
containValue(Object value)判断是否存在某个值
keyset()得到键的集合
values()得到值的集合
entrySet()得到键值对的集合
remove(Object key)删除指定的键值对
HashMap实现类(重点)
构造方法
常用构造方法说明
HashMap()创建一个大小为16,加载因子为0.75的空集合
常用方法

使用Map接口中的方法

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HmMO0m5Y-1679309728422)(C:\Users\23266\AppData\Roaming\marktext\images\2023-03-14-19-16-12-image.png)]

  • JDK1.8之后,HashMap采用"数组+链表+红黑树"实现

    • 当没有出现哈希表冲突时,元素保存在数组中

    • 如果出现哈希冲突,在对应的位置上创建链表,元素保存在链表中

遍历集合的方式

遍历LIst集合

  • 普通for循环

    for(int i =0,i<集合.size(),i++){
        s
    }
    
  • 增强for循环

    for(数据类型 变量名 : 集合){
    元素 变量 = 集合.get(i);
    }
    
  • forEach()方法

    使用该方法遍历集合时,不要使用add或remove操作,遍历会抛出异常。

    集合.forEach(obj -> {
    元素 变量 = 集合.get(i);
    });
    
  • 迭代器

//Collection接口有一个父接口Iterable,其中有一个iterator方法用于获取迭代器对象遍历集合
//所有Collection的子实现类都能调用该方法
Iterator it = Collection集合.iterator();
//hasNext()判断是否还有下一个元素
while(it.hasNext()){
//next()方法读取该元素
元素 变量 = it.next();
}

遍历Set集合

  • 普通for循环无法遍历Set集合,因为元素没有索引

  • 增强for循环

    for(数据类型 变量名 : 集合){
    元素 变量 = 集合.get(i);
    }
    
  • forEach()方法

    集合.forEach(obj -> {
    元素 变量 = 集合.get(i);
    });
    
  • 迭代器

    //Collection接口有一个父接口Iterable,其中有一个iterator方法用于获取迭代器对象遍历集合
    //所有Collection的子实现类都能调用该方法
    Iterator it = Collection集合.iterator();
    //hasNext()判断是否还有下一个元素
    while(it.hasNext()){
    //next()方法读取该元素
    元素 变量 = it.next();
    }
    

遍历HashMap集合

Set keySet = 集合.keySets();
for(Object key :keySet){
Object value=集合.get(key);
}
泛型

一种规范,常用于限制集合中的元素类型,省去遍历集合时转换Object对象的过程。

//定义只能保存任意类型的元素,即Object类型
List list = new ArrayList();
list.add(123);
list.add("hello");
//遍历时只能使用Object类型获取
for(Object obj : list){
}  

Collections集合工具类

  • Collection是集合的跟接口,定义了操作集合中元素的方法。

  • Collection是集合的工具,定义了操作集合中元素的静态方法。

Collections中的静态方法说明
Collections.shuffle(List list)打乱集合中元素的顺序
Collections.sort(List list)对集合中的元素进行排序。元素必须实现Comparable接
口。
Collections.swap(List list,int

a,int b)
将集合中索引a和b的元素交换位置
Collections.reverse(List list)反转集合中的元素
Collections.max(Collection c)得到集合中元素的最大值,元素必须实现Comparable接口
Collections.rotate(List list,int distance)将集合中第一个元素向后移动distance个元素
Collections.fill(List list,Object obj)使用obj填充集合

Arrays数组工具类

包含了一些操作数据的静态方法

常用静态方法说明
Arrays.sort()对数组的元素升序排序
Array.aList(T…obj)将可变参数转换为ArrayList集合

文件与流

文件File

java中的File类,表示本地硬盘中的文件file或我呢见驾directory的一个类。

通过这个类创建对象,可以读取文件信息或操作对应文件。

构造方法
常用构造方法说明
File(Strin pathName)根据文件的完整路径创建File对象
File(String parent,String child)根据文件的父目录的路径和自身创建File对象
File(File parent,String child)根据文件的父目录对应的Filed对象和自身的名称创建File对象
public class Demo {
    public static void main(String[] args) {
        //"C:\Users\23266\Desktop\test.txt"
        File file = new File("C:\\Users\\23266\\Desktop\\test.txt");
        System.out.println(file.exists());
        File file1 = new File("C:\\Users\\23266\\Desktop","test.txt");
        System.out.println(file.exists());
        File parent = new File("C:\\Users\\23266\\Desktop ");
        File file2 = new File(parent, "test.txt");
        System.out.println(file.exists());

    }
}

常用方法

常用方法(File)作用
exists()判断文件是否存在
isFile()判断是否为文件
isDirectory()判断是否为文件夹
getName()获取文件名
getPath()获取文件的相对路径
getAbsolutePath()获取文件的绝对路径
getParent()获取文件父目录路径
getParentFile()获取文件父目录对象
lastModfied()获取我呢见最后一次修改时间
length()获取文件字节大小
isHidden判断文件是否隐藏
delete()删除文件或空的目录
mkdir()新建指定文件夹
removeTo()移动且重命名文件
parent(文件夹).list()得到文件夹第一层文件名称的数组,返回String[]
parent.listFile()得到文件夹中的第一层文件对象的数组,返回File[]

斐波那契数列

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VlOVDAGQ-1679309728423)(C:\Users\23266\AppData\Roaming\marktext\images\2023-03-15-19-23-39-image.png)]

递归遍历文件夹

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OSlexVie-1679309728423)(C:\Users\23266\AppData\Roaming\marktext\images\2023-03-15-19-26-12-image.png)]

IO

  • I:input输入

  • O:output输出

流Stream

在java中,流表示计算机硬盘与内存之间传输数据的通道

内存中的数据存入到硬盘中,称为写write,也称为输出Output。

硬盘中的数据存入到内存中,称为读read,也称为输入Input

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fVy4Ije6-1679309728424)(C:\Users\23266\AppData\Roaming\marktext\images\2023-03-15-14-28-18-image.png)]

流的分类

java中的流也是类,以对象的实现表示流,流有”四大家族“,是所有流的父类。

字节输入流InputStream

FileInputStream、ObjectInputStream

字节输出流OutputStream

FileOutputStream、ObjectOutputStream

字符输入流Reader

FileReader、BufferedReader、InputStreamReader

字符输出流Writer

FileWriter、BufferedWriter、InputStreamWriter

按方向分类
  • 输入流:InputStream、OutputStream

    • 读写非文本类型文件。如图片、多媒体文件
  • 字符流:Reader、Writer

    • 读写纯文本我呢见。如txt、md等

流的四个父类的特点

  • 这四个父类都是java.io包下,都是抽象类,不能直接创建其对象,使用其子类对象

  • 这四个父类都有close()方法,用于关闭流对象,释放资源

  • 输入流(InputStream和Writer)都有flush()方法,用于将流中的数据冲刷到硬盘中

    • 在使用输出流对象时,一定要调用flush()方法后,才能真正将数据写入到硬盘中
  • 所有流中,以Stream结尾,都是字节流,数据以字节传输;以Reader或Writer结尾,都是字符流,数据以字符传输

  • 读取硬盘中的数据时,读取的文件必须存在;写入数据到硬盘中时,写入的文件可以不存在,但父目录必须存在。

FileInputStream文件字节输入流(重点)

以字节的形式读取文件

构造方法
常用构造方法作用
FileInputStream(String filePath)根据文件完整路径创建流对象
FileInputStream(File file)根据文件对象创建流对象
常用方法
常用方法作用
read()读取一个字节,返回读取到的字节
read(byte[] bytes)读取指定数组大小的字节,返回读取到的字节数量
read(byte[] bytes,int off,int len)读取指定数组大小的字节,从off索引开始读取len个字节,返回读取到的字节数量
available文件可读取的最大字节数量
close()关闭流对象

FileOutputStream文件字节输出流(掌握)

以字节的形式读取文件

构造方法
常用构造方法说明
FileOutputStream(String filePath)根据文件名创建流对象,覆盖写入
FileOutputStream(String filePath,boolean appen)根据文件名创建流对象,追加写入
FileOutputStream(File file)根据文件对象创建流对象,覆盖写入
FileOutputStream(File file,boolean appen)根据文件对象创建对象,追加写入
常用方法
常用方法作用
write(int b)写入一个字节
write(byte[] bytes)写入一个字节数组
write(byte[] bytes,int off,int  len)写入一个字节数组,从off开始到len个字节
flush()将流中的数据冲刷到硬盘中
close()关闭流对象
使用FileInputStream和FileOutputStream读写时的注意事项
  • 在通过FileInputStream对象使用ready(byte[] bytes)方法时,每次读取指定数组的字节,将读取到的字节保存在字节数组中。

    该方法的返回值是读取到的字节数量,如果最后一次读取的字节数量不足字节数组的大小时,只会将读取到的内容覆盖数组中最前的几个元素。

  • 在通过FileOutputStream对象使用write(byte[] bytes),会将字节数组中的所有内容写入到输出流中,在最后一次写入时,会写入多余的内容。

    所以在写入时,使用write(byte[],bytes,int off,int len)方法,表示将字节数组中的内容,从off开始写入len个

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VQeNxVql-1679309728424)(C:\Users\23266\AppData\Roaming\marktext\images\2023-03-15-20-05-48-image.png)]

单文件复制
public class Test2 {
public static void main(String[] args) throws IOException {//使用FileInputStream读取字节的同时使用FileOutputStream写入字节,实现单文件复制
//源文件
File source = new File("d:\\xxx.txt");
//读取
FileInputStream fis = new FileInputStream(source);
//写入到目标文件中
FileOutputStream fos = new FileOutputStream("f:\\copy.txt");
//定义字节数组,大小为8MB
byte[] bytes = new byte[1024*1024*8];
//读取指定数组大小的字节,返回读取到的字节数量
int count = fis.read(bytes);
while (count>0) {
//写入指定数组的字节
//fos.write(int b);写入一个字节
//fos.write(byte[] bytes);写入一个数组的字节
fos.write(bytes,0,count);//写入一个数组的字节,从0开始写入读取到的数量的字节
count = fis.read(bytes);
}
fis.close();
fos.close();
}
}

FileReader文件字符输入流

按字符读取文件

构造方法
常用构造方法说明
FileReader(String fileName)根据文件名创建文件字符输入流对象
FileReader(File file)根据文件对象创建文件字符输入流对象
常用方法
常用方法作用
ready()判断是否还有下一个字符
read()读取下一个字符,返回读取到的字符
read(char[] chars)按字符数组读取,返回读取到的字符数量,读取到的字符保存在字符数组中
close()关闭流对象

BufferedReader缓冲字符输入流(重点)

在读取纯文本文件(txt或md)时,首选该类。

构造方法
常用构造方法作用
BufferedReader(Reader in)创建一个带有缓冲区(大小为8912的char数组)的字符流对象,参数为Reader类型对象,Reader是抽象类,所以实际参数为Reader的子类,如FileReader,在FileReader对象中定义要读取到的文件
BufferedReader(Reader in ,int size)创建一个指定缓冲区(字符数组)大小的字符输入流对象
常用方法
常用方法作用
ready()判断是否还有字符
readLine()读取整行字符
close()关闭流对象

FileWriter文件字符输出流

按字符写入文件,必须在调用flush()或close()方法后才会写入

构造方法
常用构造方法说明
FileWriter(String filePath)根据文件路径创建流对象,覆盖写入
FileWriter(String filePath,boolean append)根据文件路径创建对象,append为true时追加写入
FileWriter(File file)根据文件对象创建流对象,覆盖写入
FileWriter(File file,boolean append)根据文件对象创建流对象,append为true时追加写入
常用方法
常用方法作用
writer(String str)写入字符串
flush()冲刷数据到硬盘
close()关闭对象流

BufferedWriter缓冲字符输出流(重点)

按字符写入,带有缓冲区的输出流。必须在调用flush()或flush()方法后才会写入。

构造方法
常用构造方法说明
BufferedWriter(Writer writer)根据Writer对象创建字符缓冲输出流
常用方法作用
writer(String str)写入字符串
newLine()写入空白行
flush()冲刷数据到硬盘
close()关闭流对象

序列化ObjectOutputStream对象字节输出流(重点)

序列化:将对象转换为文件的过程。

被序列化的对象,其类必须要是实现Serializable接口。

这个接口是一个特殊的接口,其中没有定义任何方法,只是给类将上标记,表示该类可以被序列化。

构造方法

构造方法说明
ObjectOutputStream(OutputStream os)根据字节输出流对象创建

常用方法

常用方法作用
writerObject(Object obj)将一个对象写入本地文件
flush()冲刷数据到硬盘中
close()关闭流对象

反序列化ObjectInputStream对象字节输入流(重点)

反序列化:将文件转换为对象的过程。

构造方法
构造方法说明
ObjectInputStream(InoutStream is)根据字节输入流对象创建

常用方法

常用方法作用
readObject()对于序列化的文件,返回Object类型
close()关闭流对象
序列化与反序列化案例

Person类,实现Serializable接口

/*
* 如果要序列化某个类的对象,该类必须要实现Serializable接口
* 该接口中没有定义任何方法,只是一个标记接口,表明该类可序列化
* */
public class Person implements Serializable {
private String name;
private int age;
private String sex;
//省略get/set/构造方法等
}

测试类

package com.hqyj.ObjectStreamTest;
import java.io.*;
import java.util.ArrayList;
public class Test {
public static void main(String[] args) throws IOException,
ClassNotFoundException {
Person p1 = new Person("王海", 22, "男");
Person p2 = new Person("刘玉梅", 29, "女");
Person p3 = new Person("白伟强", 21, "男");
//将程序运行中的数据(对象)保存到本地,这个过程就称为序列化
ArrayList<Person> list = new ArrayList<>();
list.add(p1);
list.add(p3);
list.add(p2);
//创建 "对象输出字节流" 对象,参数为字节输出流对象,这里使用FileOutputStream子类
ObjectOutputStream oos = new ObjectOutputStream(new
FileOutputStream("person.p"));
oos.writeObject(list);
oos.close();
//创建一个对象,将其保存为一个文件,再读取该文件中保存的对象
//ObjectInputStream 反序列化
ObjectInputStream ois = new ObjectInputStream(new
FileInputStream("person.p"));
//读取序列化文件中的对象
Object obj = ois.readObject();
//需要转换为相应的类型后使用
ArrayList<Person> pList = (ArrayList<Person> ) obj;
for (Person person : pList) {
System.out.println(person);
}
oos.close();
ois.close();
}
}

InputStreamReader

属于转换流(将字节流转换为字符流)

OutputStreamWriter

属于转换流(将字节转换为字符流)

进程与线程

进程Process

  • 进程就是操作系统中正在执行的程序.

  • 一个程序就是一个执行的进程实体对象。

  • 每个运行的进程,都有属于他独立的内存空间,各个进程之间互不影响。

线程Thread

  • 线程是一个进程的执行单元,一个进程可以多个线程。

  • 每个线程之间可以访问同一个进程中的资源。

  • 每个线程都有一块独立的栈空间,这些线程所在的栈空间位于同一个进程空间中。

多线程

  • 如果一个进程中,同时执行着多个线程,就称为多线程。

  • 多线程可以提高程序执行效率。

  • 其实每个执行的java程序都是多线程执行。所以main方法所在的线程称为主线程,还有GC线程(守护线程)在同时执行。

并行与并发

并行

各个进程同时运行,称为并行。

并发

多个线程同时执行,称为并发

同步和异步

同步

所有任务排队执行,成为同步执行。

异步

在执行任务A的同时,执行任务B,称为异步执行。

Java中的线程Thread类

Java中,线程以对象的形式存在。

Thread类表示线程类。

获取线程对象

  • 获取当前正在运行的线程对象

    Thread thread =Tread.cuttentThread();
    
  • 创建一个线程对象

常用构造方法说明
Thread()创建一个线程对象
Thread(String name)创建一个指定线程名的线程对象
Thread(Runnable target)根据Runnable对象创建线程对象
Thread(Runnable target,String name)根据Runnable对象和指定线程名的线程对象

线程类常常用方法

常用方法作用
getId()获取线程Id
gatName()获取线程名
getPriority()获取线程优先级,默认为5
getState()获取线程状态
setName(String name)设置线程名
setPriority(int i )设置线程优先级,范围在1~10,数字越大优先级越高,越先执行完
isDaemon()判断是否为守护线程
setDaemon(boolean on )设置线程是否为守护线程
start()让线程进入就绪状态,等待执行
run()在线程获得执行权时要执行的方法(线程要做的事情)
Thread.sleep(long m)让线程休眠m毫秒后继续执行
Thread.currentThread()获取当前执行的线程对象

实现多线程

方式一:继承Thread类

  • 1.创建一个类,继承Thread类

  • 2.重写Thread类中run()方法,该方法

  • 3.创建自定义的线程对象后,调用start(方法,让线程就绪)

    自定义Thread类的子类(子线程)

    /*
    - 1.创建一个类,继承Thread类
    - 2.重写Thread类中的run()方法,该方法表示当该线程执行时要做的事情
    - 3.创建自定义的线程对象后,调用start()方法,让线程就绪
    */public class ThreadDemo extends  Thread{
        @Override
        public  void run(){
            for (int i = 1; i <= 100; i++) {
                System.out.println(Thread.currentThread().getName()+i);
         }                
       }
    } 
    

mian类(主线程)

 public static void main(String[] args) {
        new ThreadDemo().start();
        for (int i = 1; i <= 100; i++) {
            System.out.println(Thread.currentThread().getName()+i);
        }
    }

方式二:实现Runnable接口(建议使用)

由于Java中是单继承,如果某个类已经使用了extends继承了某个类,就无法在继承Thread类。

这时就可以自定义线程实现Runnable接口的方式实现多线程

  • 1.自定义一个类,实现Runnable接口

  • 2.重写run()方法,将多线程要执行的内容写在该方法中。

  • 3.创建Runnable接口的实现类对象

  • 4.使用Thread(Runnable)构造方法,将Runnable接口实现对象包装为Thread对象

自定义Runnable接口的实现类

/*
* 如果一个类已经是其他类的子类,无法再继承Thread
* 这时实现Runnable接口,重写Run方法
* 再将该类对象当做Thread(Runnable target)构造方法的参数,包装为线程类
* */
public class MyThread2 implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() +":"+ i);
}
}
}

mian类

public class Main{
public static void main(String[] args) {
//创建两个子线程对象
//使用new Thread(Runnable target)构造方法,将自定义线程子类包装为Thread
Thread t1 = new Thread(new MyThread2());
Thread t2 = new Thread(new MyThread2());
t1.start();
t2.start();
}
}
方式三:使用匿名内部类

如果不想创建Thread的子类或Runnable的实现类,就可以将匿名内部类当做Runnable接口的实现类来使用,甚至可以配合lambda表达式来更进一步简化。

public class ThreadDemo2 {
    public static void main(String[] args) {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 1; i <= 100; i++) {
                    System.out.println(Thread.currentThread().getName()+":"+i);
                }
            }
        },"线程A");
        t1.start();
        new Thread(()->{
            for (int i = 1; i <= 100; i++) {
                System.out.println(Thread.currentThread().getName()+":"+i);
            }
        },"线程B").start();
    }
}

线程生命周期

线程的初始化到终止的过程,成为线程的生命周期。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rAO28i3U-1679309728425)(C:\Users\23266\AppData\Roaming\marktext\images\2023-03-17-14-07-18-image.png)]

新生状态

当线程对象创建成功后,进入新生状态

就绪状态
  • 当某个线程对象调用了start()方法后,就进入就绪状态。

  • 在这个状态下,线程对象不会做任何事情,只是等待CPU分配时间片。

运行状态
  • 当某个线程对象得到CPU时间片(CPU执行该线程时分配得时间),进入运行状态,开始执行run()方法。

  • 不会等到run()方法执行完毕,只会在指定的时间内尽可能地执行run()方法。

  • 如果多线程下,某个线程对象在调用完run()方法后,就会进入就绪状态。

阻塞状态
  • 如果某个线程遇到了sleep()方法或wait()方法时,就会进入阻塞状态。

    • sleep()方法在指定时间后,就会让线程重新就绪。

    • wait()方法只有在被调用notifyAll()方法将其唤醒后才能重新就绪。

终止状态

当某个线程的run()方法中的所有内容执行完,就会进入终止状态。

守护线程

如果将一个线程设置为setDaemon(true),表示该线程为守护线程。

public class DaemonDemo {
    public static void main(String[] args) {
        //将含有死循环的线程设置为守护线程
        Thread t1 = new Thread(()->{
            while (true) {
                System.out.println("守护线程运行中......");
            }
        });
        t1.setDaemon(true);
        t1.start();
        /*
        * 普通线程,有限循环
        * */
        new Thread(()->{
            for (int i = 1; i <= 100; i++) {
                System.out.println("普通线程运行中......");
            }
        }).start();
        //随着有限循环结束,死循环线程也会接受
        //非守护线程结束,守护线程也会接受
    }
}

多线程访问同一个资源

可能出现的问题

如有10张票,三个窗口同时卖票

/*
* 模拟多窗口卖票
* */
public class TicketDemo implements Runnable{
    private Integer ticket=10;
    //如有10张票,三个窗口同时卖票
    public void sell() throws InterruptedException {
        while ((true)) {
            if ((ticket>0)) {
                Thread.sleep(1000);
                ticket =ticket-1;
                System.out.println(Thread.currentThread().getName()+"售出一张,剩余"+ticket+"张票");
            }else {
                System.out.println("已售罄");
                break;
            }
        }
    }
    @Override
    public void run(){
        try {
            sell();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
    public static void main(String[] args) {
        TicketDemo td = new TicketDemo();
        Thread t1 = new Thread(td,"A");
        Thread t2= new Thread(td,"B");
        Thread t3 = new Thread(td,"C");
        t1.start();
        t2.start();
        t3.start();
    }
}

运行结果

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yuZ1i2JV-1679309728425)(C:\Users\23266\AppData\Roaming\marktext\images\2023-03-17-15-03-44-image.png)]

出现问题的原因

由于线程调用start()方法后,就会进入就绪状态。当线程获得了CPU时间片,就开始执行run()方法

并不会等待run()方法执行完毕,所以很有可能在线程A执行run()方法过程中,线程B也开始执行run()方法,造成数据共享时出错。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ng5Cmdt2-1679309728425)(C:\Users\23266\AppData\Roaming\marktext\images\2023-03-17-14-57-26-image.png)]

如何解决

如果要防止在线程A执行过程中B插队的情况,可以让所有线程排队同步执行。

这样一来,某个线程执行run()方法的时候,其他线程就会等待run()执行完毕。

synchronized关键字

这个关键字可以修饰方法或代码块

修饰方法

写在方法的返回值前,这是该方法就称为同步方法

 public synchronized void sell(){}
修饰代码块
synchornized(要同步的对象){
    //该方法执行过程中,其他线程无法插队
}
原理

每个对象默认都有一个"锁",当某个线程运行时被synchornized修饰方法时,

该对象就会拥有这把锁,在拥有锁的过程,其他线程不能同时访问该方法,只能等待其他执行结束后,才能释放这把锁。

使用synchornized修饰后的锁被称为"悲观锁"。

方法被synchornized修饰后,称为同步方法,会让原本多线程执行变成了单线程执行(异步变同步).

多线程相关面试题

  • 实现多线程的方式

    • 继Thread类,创建对象

    • 实现Runnable接口后,包装成Thread对象

    • 匿名内部类

  • 为什么说StringBuilder或ArrayList,HashMap是非线性安全的

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-r5YvqVkP-1679309728426)(C:\Users\23266\AppData\Roaming\marktext\images\2023-03-17-15-38-30-image.png)]

  • 什么叫死锁?怎么产生?如何解决?

    如两个人吃西餐,必须要有刀和叉,当前只有一副刀叉。

    如果A拿到了刀,B拿到了叉,互相都在等待对象释放另一个工具,如果不释放,就会进入僵持局面,这个局面就叫做死锁,既不结束,也不继续。

模拟死锁出现的情况

定义两个线程类,线程A先获取资源A后,再获取资源B;线程B先获取资源B后,再获取资源A。

如果对资源A和资源B同步执行,线程A

PersonA类

public class PersonA implements Runnable{
    private Object knife;
    private Object fork;

    public PersonA(Object knife, Object fork) {
        this.knife = knife;
        this.fork = fork;
    }

    @Override
    public void run() {
        synchronized (knife){
            System.out.println("拿到了刀,三秒后拿到叉");
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            synchronized (fork){
                System.out.println("拿到了叉,就可以吃饭啦");
            }
        }
    }
}

personB类

public class PersonB implements Runnable{
    private Object knife;
    private Object fork;

    public PersonB(Object knife, Object fork) {
        this.knife = knife;
        this.fork = fork;
    }

    @Override
    public void run() {
        synchronized (fork){
            System.out.println("拿到了叉,三秒后拿到刀");
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            synchronized (knife){
                System.out.println("拿到了刀,就可以吃饭啦");
            }
        }
    }
}

Mian方法

public class Main {
    public static void main(String[] args) {
        Object knife = new Object();
        Object fork = new Object();
        new Thread(new PersonA( knife,fork)).start();
        new Thread(new PersonB( knife,fork)).start();
    }
}
死锁的解决方式
方式一:让两个线程获取资源的顺序一致

如两个线程都先获取knife,再获取fork

方式二:在两个线程获取的资源A和资源B之前,再获取第三个资源C,对这个资源C使用synchornized进行同步这样在某个线程获取资源C后,继续执行后续内容,指导执行完毕,其他线程才有机会开始执行。

如获取knife与fork之前,先获取paper

PersonA类

public class PersonA implements Runnable{
    private Object knife;
    private Object fork;
    private  Object paper;

    public PersonA(Object knife, Object fork,Object paper) {
        this.knife = knife;
        this.fork = fork;
        this.paper=paper;
    }

    @Override
    public void run() {
        synchronized (paper){
            synchronized (knife){
                System.out.println("拿到了刀,三秒后拿到叉");
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                synchronized (fork){
                    System.out.println("拿到了叉,就可以吃饭啦");
                }
            }
        }
    }
}

PersonB类

public class PersonB implements Runnable {
    private Object knife;
    private Object fork;
    private Object paper;

    public PersonB(Object knife, Object fork, Object paper) {
        this.knife = knife;
        this.fork = fork;
        this.paper = paper;
    }

    @Override
    public void run() {
        synchronized (paper) {
            synchronized (fork) {
                System.out.println("拿到了叉,三秒后拿到刀");
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                synchronized (knife) {
                    System.out.println("拿到了刀,就可以吃饭啦");
                }
            }
        }
    }
}

Main

public class Main {
    public static void main(String[] args) {
        Object knife = new Object();
        Object fork = new Object();
        Object paper = new Object();
        new Thread(new PersonA( knife,fork,paper)).start();
        new Thread(new PersonB( knife,fork,paper)).start();
    }
}

(Object knife, Object fork,Object paper) {
this.knife = knife;
this.fork = fork;
this.paper=paper;
}

@Override
public void run() {
    synchronized (paper){
        synchronized (knife){
            System.out.println("拿到了刀,三秒后拿到叉");
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            synchronized (fork){
                System.out.println("拿到了叉,就可以吃饭啦");
            }
        }
    }
}

}


**PersonB类**

```java
public class PersonB implements Runnable {
    private Object knife;
    private Object fork;
    private Object paper;

    public PersonB(Object knife, Object fork, Object paper) {
        this.knife = knife;
        this.fork = fork;
        this.paper = paper;
    }

    @Override
    public void run() {
        synchronized (paper) {
            synchronized (fork) {
                System.out.println("拿到了叉,三秒后拿到刀");
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                synchronized (knife) {
                    System.out.println("拿到了刀,就可以吃饭啦");
                }
            }
        }
    }
}

Main

public class Main {
    public static void main(String[] args) {
        Object knife = new Object();
        Object fork = new Object();
        Object paper = new Object();
        new Thread(new PersonA( knife,fork,paper)).start();
        new Thread(new PersonB( knife,fork,paper)).start();
    }
}
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值