javaSE

idea的项目结构

主要结构是project-module-package

快捷键与快速写法

快捷键功能
alt + insert快速生成代码,toString,get,set
alt + enter导入包自动修正代码
ctrl + D(我推荐改成 ctrl+shif+上下箭头)复制光标所在行的内容,插入光标位置下面
ctrl + Alt + L格式化代码
Ctrl + /单选注释
ctrl + Y删除光标所在行
Alt + shift + 上下箭头移动当前代码行
ctrl + space补全代码与windows输入法冲突,需要修改
shift + F6包重命名
ctrl + alt + T选中代码后使用,增加包裹代码块如 「if,try,catch」
alt + 左右箭头切换窗口中的类
ctrl + p提示方法中的参数
ctrl + 1返回当前窗口的包目录
Alt + Enter根据光标所在问题,提供快速修复选择,光标放在的位置不同提示的结果也不同
Ctrl + Shift + F12关闭左边的包目录
Ctrl + Alt + O优化导入的类,可以对当前文件和整个包目录使用
Ctrl + Alt + 左方向键退回到上一个操作的地方
Ctrl + Alt + 右方向键前进到上一个操作的地方
Ctrl + Shift + R根据输入内容替换对应内容,范围为整个项目 或 指定目录内文件
Ctrl + Shift + /代码块注释
ctrl + F12对一个类的方法进行搜索
ctrl + N对所有的类进行搜索
ctrl+alt+M将当前选中的代码生成一个方法
ctrl+alt+shift+j选中多行相同的内容
ctrl+shift+f12隐藏或者恢复所有的窗口
ctrl+shift+i快速查看方法的定义
F7跳入方法内,断点
F8逐行执行代码,断点
shift+F8跳出方法,断点
F9执行下一处断点
ctrl+r选中相同的内容
ctrl+shift+z还原
tab 两次后回车生成无参构造
ctrl+B定位到方法
ctrl+H查看一个类的层级关系
ctrl+alt+T生成测试类
home行首
end行尾
ctrl+k剪切一行
shift+enter直接开始下一行
ctrl+enter上起一行
ctrl+j尽可能的提示你现在能做的操作
ctrl+o重写里面的任意方法
ctrl+e打开最近打开的文件
shift+f6选中当前选中的变量
alter+1idea文件树和编辑区移动
ctrl+f4关闭当前文件

写法功能
iter快速生成foreach
查看此类所有的方法alt+7或者ctrl+F12然后直接按键盘字母可以搜索如果你知道是哪个类的方法名,Ctrl+f查找即可;如果不知道是哪个类的,但是知道方法名,你直接Ctrl+Shift+f全局查找即可
https://www.cnblogs.com/xiaomingzaixian/p/9683744.html自动生成函数注释

程序设计的追求

所有的错误尽可能让编译器找出来,所有的错误越早发现越好.

能省的资源,能省就省.

程序的时间复杂度尽量降到n

enum

枚举类型

  1. 枚举是一种引用数据类型

  2. 枚举类型怎么定义,语法是?

    enum 枚举类型名{
        枚举值1,枚举值2...
    }
    
  3. 只有两种情况建议使用bollean结果超过两种并且还是可以一枚列举出来的,建议使用枚举

案例

 public static void main(String[] args) throws Exception {
        Result a = divide(2,3);
        System.out.println(a);//ERR
      switch (a){
            case FATL: System.out.println("失败");break;
            case SUCCESS: System.out.println("成功");break;
            case OVER: System.out.println("溢出");break;
            case ERR: System.out.println("错误");break;

        }//错误switch支持enum


    }
    public static Result divide(int a,int b){
        if(b>a){
            return Result.ERR;
        }else if(a>b){
            return Result.SUCCESS;
        }else if(b==0){
            return Result.FATL;
        }else{
            return Result.OVER;
        }
    }


} 
enum Result{
    SUCCESS,FATL,ERR,OVER
}

异常

java语言中异常是以什么形式存在的呢?

  1. 异常在java中以了类的形式存在,每一个异常类都可以创建一个异常对象

  2. 异常分为两类一类是编译时异常一类是运行时异常.

  3. 其实都是在运行时产生的异常

  4. 编译时异常发生的概率比较大,运行时异常发生的概率比较小

  5. 编译时异常是编写代码时必须要处理的,不然过不去编译

  6. 处理异常一共两种方法

    1. throws exception
    2. try catch
      • catch后面的小括号中的类型可以是具体的异常类型,也可以是该异常的父类型
      • catch写多个.建议catch的时候,精确的一个一个处理,有利于调试
      • catch写多个的时候,从上到下,必须遵守从小到大.
    try{
        new FileInputStream("c://ject.json");
    }catch (FileNotFoundException e){
        e.printStackTrace();
        //建议使用 printStackTrace()对代码进行一个维护
        //需要注意的是,只需要看自己写的代码就行了
    }finally{
     //这里的代码一定会执行,除非你在 try 中 System.exit(0) 退出Java虚拟机    
    }
    

自定义异常

开发中异常是不够用的,所以我们需要自定义异常。

自定义异常需要走两步:

  1. 编写一个类继承 Exception 或者是 RuntimeException
  2. 提供两个构造方法,一个无参数的一个带有 String 参数的。
public class WrongNameEXception  extends Exception{
    //继承RunTimeException与Exception全看你自己的需求
    public WrongNameEXception(){
        super();
    }
    public WrongNameEXception(String s ){
        super(s);
    }
}


//主程序中调用
 public static void main(String[] args) throws WrongNameEXception {
//       WrongNameEXception a = new WrongNameEXception("用户名错误");
//       a.printStackTrace();
//       String b = a.getMessage();
//        System.out.println(b);
        throw new WrongNameEXception();



    }

在工具类中的异常处理

static {
        Properties properties = new Properties();
        try {
            properties.load(new FileInputStream("src\\mysql.properties"));
            user = properties.getProperty("user");
            password = properties.getProperty("password");
            url = properties.getProperty("url");
            driver = properties.getProperty("driver");
        } catch (IOException e) {
//        在实际开发中往往会将这个异常转成运行时异常再抛出去
//        1. 将编译异常转成一个运行时异常
//        2. 调用者可以选择捕获,也可以选择默认处理该异常比较方便
            throw new RuntimeException(e);
        }
    }

方法覆盖

重写后的方法不能比重写之前的代码抛出更多的异常,可以更少。

final,finally,finalize 有什么区别

final 是一个关键字表示最终的、不变的,在变量上表示这个变量不可变

finally 也是一个关键字与try联合使用,在异常处理机制中,在其中的语句一定会执行。

finalize 是 Object 类中的一个方法。作为一个标识符

基本Dos命令

命令作用
mkdir新建一个目录
cd进入当前目录下的一个子目录或者文件
cls清屏
dir查看当前目录下面的文件
exit退出Dos窗口
cd …回到上级路径
cd \回到根路径

Java常用的一些方法

Object类

equals

java中基本数据类型直接使用 == 进行一个比较

java中所有的引用数据类型使用 equals方法来判断

所以推荐我们在写一个类的时候最好将其的 toStringequals 方法一起重写了

finalize

这个方法是系统对无用的对象进行垃圾回收的时候进行调用的

写法

 protected void finalize() throws Throwable{
        System.out.println("即将被销毁");
    }

用处如有需求是让我们记录一下所有的对象在 JVM 中被释放的时间。

这个代码就写到 finalize 方法中

关于在String与int还有Integer内转换

idea的项目结构

主要结构是project-module-package

快捷键与快速写法

快捷键功能
alt + insert快速生成代码,toString,get,set
alt + enter导入包自动修正代码
ctrl + D(我推荐改成 ctrl+shif+上下箭头)复制光标所在行的内容,插入光标位置下面
ctrl + Alt + L格式化代码
Ctrl + /单选注释
ctrl + Y删除光标所在行
Alt + shift + 上下箭头移动当前代码行
ctrl + space补全代码与windows输入法冲突,需要修改
shift + F6包重命名
ctrl + alt + T选中代码后使用,增加包裹代码块如 「if,try,catch」
alt + 左右箭头切换窗口中的类
ctrl + p提示方法中的参数
ctrl + 1返回当前窗口的包目录
Alt + Enter根据光标所在问题,提供快速修复选择,光标放在的位置不同提示的结果也不同
Ctrl + Shift + F12关闭左边的包目录
Ctrl + Alt + O优化导入的类,可以对当前文件和整个包目录使用
Ctrl + Alt + 左方向键退回到上一个操作的地方
Ctrl + Alt + 右方向键前进到上一个操作的地方
Ctrl + Shift + R根据输入内容替换对应内容,范围为整个项目 或 指定目录内文件
Ctrl + Shift + /代码块注释
ctrl + F12对一个类的方法进行搜索
ctrl + N对所有的类进行搜索
ctrl+alt+M将当前选中的代码生成一个方法
ctrl+alt+shift+j选中多行相同的内容
ctrl+shift+f12隐藏或者恢复所有的窗口
ctrl+shift+i快速查看方法的定义
F7跳入方法内,断点
F8逐行执行代码,断点
shift+F8跳出方法,断点
F9执行下一处断点
ctrl+r选中相同的内容
ctrl+shift+z还原
tab 两次后回车生成无参构造
ctrl+B定位到方法
ctrl+H查看一个类的层级关系
ctrl+alt+T生成测试类
home行首
end行尾
ctrl+k剪切一行
shift+enter直接开始下一行
ctrl+enter上起一行
ctrl+j尽可能的提示你现在能做的操作
ctrl+o重写里面的任意方法
ctrl+e打开最近打开的文件
shift+f6选中当前选中的变量
alter+1idea文件树和编辑区移动
ctrl+f4关闭当前文件

写法功能
iter快速生成foreach
查看此类所有的方法alt+7或者ctrl+F12然后直接按键盘字母可以搜索如果你知道是哪个类的方法名,Ctrl+f查找即可;如果不知道是哪个类的,但是知道方法名,你直接Ctrl+Shift+f全局查找即可
https://www.cnblogs.com/xiaomingzaixian/p/9683744.html自动生成函数注释

程序设计的追求

所有的错误尽可能让编译器找出来,所有的错误越早发现越好.

能省的资源,能省就省.

程序的时间复杂度尽量降到n

enum

枚举类型

  1. 枚举是一种引用数据类型

  2. 枚举类型怎么定义,语法是?

    enum 枚举类型名{
        枚举值1,枚举值2...
    }
    
  3. 只有两种情况建议使用bollean结果超过两种并且还是可以一枚列举出来的,建议使用枚举

案例

 public static void main(String[] args) throws Exception {
        Result a = divide(2,3);
        System.out.println(a);//ERR
      switch (a){
            case FATL: System.out.println("失败");break;
            case SUCCESS: System.out.println("成功");break;
            case OVER: System.out.println("溢出");break;
            case ERR: System.out.println("错误");break;

        }//错误switch支持enum


    }
    public static Result divide(int a,int b){
        if(b>a){
            return Result.ERR;
        }else if(a>b){
            return Result.SUCCESS;
        }else if(b==0){
            return Result.FATL;
        }else{
            return Result.OVER;
        }
    }


} 
enum Result{
    SUCCESS,FATL,ERR,OVER
}

异常

java语言中异常是以什么形式存在的呢?

  1. 异常在java中以了类的形式存在,每一个异常类都可以创建一个异常对象

  2. 异常分为两类一类是编译时异常一类是运行时异常.

  3. 其实都是在运行时产生的异常

  4. 编译时异常发生的概率比较大,运行时异常发生的概率比较小

  5. 编译时异常是编写代码时必须要处理的,不然过不去编译

  6. 处理异常一共两种方法

    1. throws exception
    2. try catch
      • catch后面的小括号中的类型可以是具体的异常类型,也可以是该异常的父类型
      • catch写多个.建议catch的时候,精确的一个一个处理,有利于调试
      • catch写多个的时候,从上到下,必须遵守从小到大.
    try{
        new FileInputStream("c://ject.json");
    }catch (FileNotFoundException e){
        e.printStackTrace();
        //建议使用 printStackTrace()对代码进行一个维护
        //需要注意的是,只需要看自己写的代码就行了
    }finally{
     //这里的代码一定会执行,除非你在 try 中 System.exit(0) 退出Java虚拟机    
    }
    

自定义异常

开发中异常是不够用的,所以我们需要自定义异常。

自定义异常需要走两步:

  1. 编写一个类继承 Exception 或者是 RuntimeException
  2. 提供两个构造方法,一个无参数的一个带有 String 参数的。
public class WrongNameEXception  extends Exception{
    //继承RunTimeException与Exception全看你自己的需求
    public WrongNameEXception(){
        super();
    }
    public WrongNameEXception(String s ){
        super(s);
    }
}


//主程序中调用
 public static void main(String[] args) throws WrongNameEXception {
//       WrongNameEXception a = new WrongNameEXception("用户名错误");
//       a.printStackTrace();
//       String b = a.getMessage();
//        System.out.println(b);
        throw new WrongNameEXception();



    }

在工具类中的异常处理

static {
        Properties properties = new Properties();
        try {
            properties.load(new FileInputStream("src\\mysql.properties"));
            user = properties.getProperty("user");
            password = properties.getProperty("password");
            url = properties.getProperty("url");
            driver = properties.getProperty("driver");
        } catch (IOException e) {
//        在实际开发中往往会将这个异常转成运行时异常再抛出去
//        1. 将编译异常转成一个运行时异常
//        2. 调用者可以选择捕获,也可以选择默认处理该异常比较方便
            throw new RuntimeException(e);
        }
    }

方法覆盖

重写后的方法不能比重写之前的代码抛出更多的异常,可以更少。

final,finally,finalize 有什么区别

final 是一个关键字表示最终的、不变的,在变量上表示这个变量不可变

finally 也是一个关键字与try联合使用,在异常处理机制中,在其中的语句一定会执行。

finalize 是 Object 类中的一个方法。作为一个标识符

基本Dos命令

命令作用
mkdir新建一个目录
cd进入当前目录下的一个子目录或者文件
cls清屏
dir查看当前目录下面的文件
exit退出Dos窗口
cd …回到上级路径
cd \回到根路径

Java常用的一些方法

Object类

equals

java中基本数据类型直接使用 == 进行一个比较

java中所有的引用数据类型使用 equals方法来判断

所以推荐我们在写一个类的时候最好将其的 toStringequals 方法一起重写了

finalize

这个方法是系统对无用的对象进行垃圾回收的时候进行调用的

写法

 protected void finalize() throws Throwable{
        System.out.println("即将被销毁");
    }

用处如有需求是让我们记录一下所有的对象在 JVM 中被释放的时间。

这个代码就写到 finalize 方法中

String

目标索引的字符

str.charAt(index)
 //java中的字符串无法像c一样直接使用[]需要取索引时只能使用这个函数

比较两个字符串

str1.comparto(str2);
//用ascll码来进行比较两个字符串直到出现不相等
//然后对两个字符算差值返回一个int,如果是正数说明第一个子串更大,如果是负数就是第二个字符更大

一个字符串是否包含另一个字符串

str1.contains(str2);
//看源字符串是否包含后面的字符返回一个boolean
//包含返回true不包含返回一个false

一个字符串是否以另外一个字符串结尾

str1.endWith(str2);
//返回一个bollean如果以它结尾返回true否则返回false

一个字符串是否以另外一个字符串开始

"http://www.baidu.com".startsWith("http")
    //返回一个bollean如果是相等返回一个true如果不相等返回一个false

关于两个字符串是否相等

str1.equals(str2)
//返回一个bollean如果相等返回true不相等返回一个false不能忽略大小写

将一个字符串转换成一个byte数组

  byte [] bytes ="fdsa".getBytes();
  for (int i = 0; i < bytes.length; i++) {
     System.out.println(bytes[i]);
 }
//需要注意的是byte数组里面是用ascll码来进行存储的

找到子字符串在当前字符串的索引

 System.out.println("中国人".indexOf("国"));
//返回int表示出现的位置如果没有返回-1

这个字符串是否为空

 System.out.println("".isEmpty());
//返回bollean空为true不空为false

字符串的长度

//面试题数组的length与字符串的length有什么区别
//判断数组的length是length属性,字符串的length是length()方法
str1.length()

匹配子串的最后一个位置

"enterterterlastter".lastIndexOf("ter")
//如果没有返回-1

替换字符中的一部分子串

 String str1 = "中国人的血".replace("中国","强大的中国");
// 将中国替换成强大的中国并且返回一个String

拆分字符串

 String[] sr1="c-t-r-l".split("-");
//将其根据正则来拆分返回一个String数组

截取字符串

"https://www.baidu.com".substring(8)
 //从指定位置开始到结尾截取一个子字符串
"https://www.baidu.com".substring(8,11)
//从指定位置开始到索引结束,左闭右开

将字符串转换成一个char数组

 char[] chars = "我是中国人".toCharArray();

转换成小写

"DFDSJK".toLowerCase(Locale.ROOT)

转换成大写

"fds".toUpperCase(Locale.ROOT)

去除前后空白

"   fds   ".trim()

将"非字符串"转成"字符串"

String s1 = String.valueOf(432);
//如果目标是一个对象对调用对象的toString方法
//如果没有重写toString方法则返回对象的地址

StringBuffer类

我们在实际开发中,如果需要进行字符串的频繁拼接,会有什么问题?

因为java中的字符串是不可变的,每一次拼接都会产生新字符串. 这样会占用大量方法区的内存.造成内存空间的浪费.

String s = “abc”;

s+=“hello”

这两行代码在字符串常量池中创建了3个对象.

conclusions如果以后需要进行大量字符串的拼接操作,建议直接使用JDK中自带的

java.lang.StringBuffer

java.lang.StringBuilder

字符串拼接函数

StringBuffer s1  = new StringBuffer();
        s1.append("fdsafds");

Array

一维数组扩容

在Java开发中数组长度一但确定不可变,如果数组满了就需要扩容.

Java对数组的扩容内部实现只是

新建一个大容量的数组,然后将小容量数组中的数据一个个扩容到大数组中

结论:Java扩容的效率比较低尽量不要使用扩容

数组拷贝

System.arraycopy(src,0,dest,0,3);
// 参数1.源数组 参数2.开始拷贝的位置 参数3.目标数组 参数4.存放的位置 参数5.拷贝的长度

Date

获取当前时间

 Date d1  =new Date(); //Sun Aug 22 15:34:57 CST 2021

如果需要对Date进行一个格式化需要用SimpleDateFormat类

SimpleDateFormat

字母日期或时间元素表示示例
GEra 标志符TextAD
yYear1996; 96
M年中的月份MonthJuly; Jul; 07
w年中的周数Number27
W月份中的周数Number2
D年中的天数Number189
d月份中的天数Number10
F月份中的星期Number2
E星期中的天数TextTuesday; Tue
aAm/pm 标记TextPM
H一天中的小时数(0-23)Number0
k一天中的小时数(1-24)Number24
Kam/pm 中的小时数(0-11)Number0
ham/pm 中的小时数(1-12)Number12
m小时中的分钟数Number30
s分钟中的秒数Number55
S毫秒数Number978
z时区General time zonePacific Standard Time; PST; GMT-08:00
Z时区RFC 822 time zone-0800

将Date进行格式化

  SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
String nowDate =sdf.format(d1);

将String转成Date

 String date = "2008-8-8 08:08:08 888";
 SimpleDateFormat fds = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
        Date s1 = fds.parse(date);

统计一个方法执行所耗费时长

  long s1 = System.currentTimeMillis();
      int a1 = 2;
        for (int i = 0; i < 1000; i++) {
            System.out.println(a1);
        }
      long s2 =  System.currentTimeMillis();
        System.out.println(s2-s1);//这里就是所用的总时长了

DecimalFormat对数字进行格式化

在java.text包下

符号位置本地化?含义
0数字阿拉伯数字如果不存在显示为0
#数字字阿拉伯数字,如果不存在则不显示
.数字小数分隔符或货币小数分隔符
-数字减号
,数字分组分隔符
E数字分隔科学计数法中的尾数和指数。在前缀或后缀中无需加引号。
;子模式边界分隔正数和负数子模式
%前缀或后缀乘以 100 并显示为百分数
\u2030前缀或后缀乘以 1000 并显示为千分数
¤ (\u00A4)前缀或后缀货币记号,由货币符号替换。如果两个同时出现,则用国际货币符号替换。如果出现在某个模式中,则使用货币小数分隔符,而不使用小数分隔符。
'前缀或后缀用于在前缀或或后缀中为特殊字符加引号,例如 "'#'#" 将 123 格式化为 "#123"。要创建单引号本身,请连续使用两个单引

数字格式化转成String

DecimalFormat df = new DecimalFormat("###,###.0000");
String a = df.format(324.2);

BigDecimal

https://blog.csdn.net/haiyinshushe/article/details/82721234

大数运算用这个类准没有错

Random

产生限定范围内的随机数

 Random v1 = new Random();
int a =  v1.nextInt(34);

产生int取值范围内的随机数

 int c = v1.nextInt();

Java各种类的细节

关于String类的创建的方法

String a1 = "abcdf";
String b1 = "rew";
//这两行代码表示在底层创建了两个字符串对象都在字符串常量池中
//这是用new 方式创建的字符串对象
//凡是双括号括起来的都在常量池中有个位置
//用new创建的时候一定是在堆里面开辟空间
String c1 = new String("c1");

String类的构造方法

输出一个引用时会自动调用这个引用的toString方法,默认的object类会默认输出内存地址

但输出String的时候我们发现并没有得到地址说明String类重写了toString方法

使用byte数组转换成字符串

  1. byte [] bytes={97,98,99};
    //byte数组内用ascll码
    String s2 = new String(bytes)
     //所以我们这里是直接用   
    
String s3 = new String(bytes,1,2);
//bytes数组,起始的位置,长度

使用char数组转换成字符串

  1. char [] chars={'我','是','中','国','人'};
           String s4 = new String(chars);
    
  2.  String s5 = new String(chars,2,3);
    

使用常量池中的字符来构造字符串

String s6 = new String("我是中国人");

println函数细节

println底层还是使用valueof来对对象进行处理,valueof如果目标调用的是toString方法所以如果是一个对象调用println的话就是调用toString方法.

所以本质上println这个函数就是将任何数据先转换成字符串再来输出

StringBuffer类

底层使用的是byte字节大小为16的数组,如果容量不够会使用arrycopy自动扩容,其实String类的本质也是一个byte数组但前面有一个final关键字导致它不可变.

如何忧化StringBuffer的性能?

在创建StringBufffer的时候尽可能给定一个初始化容量

最好减少底层数组的扩容次数.预先估计一下,给一个大一些初始化容量.

StringBuffer sb = new StringBuffer(100);
//创建了一个容量是100的StringBuffer数组

StringBuilder

与StringBuffer一样都是为了字符串拼接这个目的产生的

StringBuffer中的方法都有synchronized关键字修饰表示其在多线程环境下的安全性

StringBuilder与StringBuffer的区别是StringBuffer是线程安全的,StringBuilder是非线程安全的

8种基本包装类

  1. java中为8种基本数据类型又对应准备了8种包装类型.8种包装类属于引用数据类型,父类是object

  2. 思考为什么要再提供8种包装类?

    因为8种基本数据类型不够用.所以又提供对应的8种包装类

8种基本数据类型对应包装类型名是什么?

基本数据类型包装类型
bytejava.lang.Byte
shortjava.lang.Short
intjava.lang.Integer
longjava.lang.Long
floatjava.lang.Float
doublejava.lang.Double
booleanjava.lang.Boolean
charjava.lang.Character
//        基本数据类型转换成引用数据类型(装箱)
       Integer s1 =  new Integer(234);
//       引用数据类型转换成基本数据类型(拆箱)
       float a1  =s1.floatValue();

所有的数字包装类的子类都有

方法摘要
byte**[byteValue] 以 byte` 形式返回指定的数值。
abstract doubledouble 形式返回指定的数值。
abstract float**[floatValue]float 形式返回指定的数值。
abstract int**[intValue] 以 int` 形式返回指定的数值。
abstract long**[longValue] 以 long` 形式返回指定的数值。
short**[shortValue] 以 short` 形式返回指定的数值。

关于构造方法一般都有两个一个是用对应的基本数据类型,一个是使用String

通过访问包装类的常量来获取最大值与最小值

System.out.println(Integer.MAX_VALUE);
System.out.println(Integer.MIN_VALUE);

我们进行±*/等运算的时候就会触发自动装箱与自动拆箱

java中为了提高程序效率,将[-128,127]之间所有的包装类提前创建好,放到一个常量池中目地是这个区间的数据不需要再new了直接取

 Integer a=127;
        Integer b = 127;
        System.out.println(a==b);//true他们两是同一个内存地址
        Integer c = 128;
        Integer d = 128;
        System.out.println(c==d);//false

Integer类加载的时候会初始化整数型常量池:256个对象

将String转换成int静态方法
int retValue = Integer.parseInt("123");

各种异常

中文名英文名
空指针异常NullPointerException
类型转换导演ClassCastException
数组下标越界异常ArrayIndexOutOfBoundsException
数字格式化异常NumberFomatException

Arrays

java.utils.Arrays 类是一个工具类,让我们更方便的操作数组。
这里介绍本人认为的几个比较实用的方法。

  1. sort
  2. fill

sort

该方法是用于数组排序,在 Arrays 类中有该方法的一系列重载方法,能对7种基本数据类型,包括 byte,char,double,float,int,long,short 等都能进行排序,还有 Object 类型(实现了Comparable接口),以及比较器 Comparator 。内部算法的复杂度应该是在 n^2到 nlongn,

默认升序排序.

升序
    int scores[] = new int[]{1,2,3,89,4};
        Arrays.sort(scores);
        for (int score : scores) {
            System.out.println(score);
        }
降序
    Integer integers[] = {1,2,3,89,4};
	Arrays.sort(integers, Collections.reverseOrder());//需要注意的是 不能使用基本类型(int,double, char)需要改成对应的包装类,如果是int型需要改成Integer,float要改成Float...
	for (Integer integer : integers) {
            System.out.println(integer);
        }

这里插入一个操作如何将 int 数组转成 Integer 包装类

 		int scores[] = new int[]{1,2,3,89,4};
//		先将int 数组转换成数组流
        IntStream stream = Arrays.stream(scores);
//		流中的元素全部装箱,转换为流 ---->int转为Integer
        Stream<Integer> boxed = stream.boxed();
//		将流转换为数组
        Integer[] integers = boxed.toArray(Integer[]::new);
        Arrays.sort(integers);
        for (Integer integer : integers) {
            System.out.println(integer);
        }

简化

	Integer[]integers = Arrays.stream(scores).boxed().toArray(Integer[]::new);//简化操作
	Arrays.sort(integers, Comparator.reverseOrder());

fill

从指定位置插入固定的值,从 fromIndextoIndex填充的位置左闭右开

int[] ints ={1,2,3,489,23};
        Arrays.fill(ints,0,2,5);//填充值为5
        for (int anInt : ints) {
            System.out.println(anInt);
        }

分配指定数值给数组内的每个元素

	   int[] ints ={1,2,3,489,23};
        Arrays.fill(ints,5);
        for (int anInt : ints) {
            System.out.println(anInt);
        }

当为二维数组赋值时需要注意,不能直接加数值,否则会报异常

		int[][] map =new int[3][4];
        Arrays.fill(map,5);//ArrayStroeException 当试图将类型不兼容类型的对象存入一个Object[]数组时将引发异常
        for (int i = 0; i < 3; i++) {
            for (int j : map[i]) {
                System.out.print(j);
            }
        }

需要将一个一维数组填入

		int[][] map =new int[3][4];
        int [] ten = {1,2,3};
        Arrays.fill(map,ten);
        for (int i = 0; i < 3; i++) {
            for (int j : map[i]) {
                System.out.print(j);
            }
        }

包装类

概述:

基本数据类型,使用起来非常方便,但没有对应的方法来操作,可以使用一个类,把基本类型的数据装起来,在类中定义一个方法,这个类叫包装类,我们可以使用类中的方法来操作这些基本类型对象

基本类型对应的包装类(位于java.lang包中)
byteByte
shortShort
intInteger
longLong
floatFloat
doubleDouble
charCharacter
booleanBoolean

自动拆箱与装箱

装箱:把基本类型的数据,包装到包装类中(基本类型的数据->包装类)

构造方法:

Integer(int value)构造一个新分配的Integer对象,表示指定的int

这里涉及到一个valueOf方法

valueOf() 方法用于返回给定参数的原生 Number 对象值,参数可以是原生数据类型, String等。

  • **Integer valueOf(int i):**返回一个表示指定的 int 值的 Integer 实例。
  • **Integer valueOf(String s):**返回保存指定的 String 的值的 Integer 对象。
  • Integer valueOf(String s, int radix): 返回一个 Integer 对象,该对象中保存了用第二个参数提供的进制进行解析时从指定的 String 中提取的值。

调用valueOf函数之后,确定i是否在缓存范围内,如果在返回已经缓存下来的,如果不在返回一个新的Integer对象所以合理的利用valueOf函数可以减少内存的开销

这种缓存行为不仅适用于Integer对象。我们针对所有整数类型的类都有类似的缓存机制。
ByteCache 用于缓存 Byte 对象
ShortCache 用于缓存 Short 对象
LongCache 用于缓存 Long 对象
CharacterCache 用于缓存 Character 对象
Byte,Short,Long 有固定范围: -128 到 127。对于 Character, 范围是 0 到 127。除了 Integer 可以通过参数改变范围外,其它的都不行。

@Override//下面的是equals方法的源码
 public boolean equals(Object o) {
     return (o instanceof Integer) && (((Integer) .value == value);
}

自动拆箱与自动装箱

  1. 需要知道什么时候会引发装箱和拆箱

  2. 装箱操作会创建对象,频繁的装箱操作会消耗许多内存,影响性能,所以可以避免装箱的时候应该尽量避免。

  3. equals(Object o) 因为原equals方法中的参数类型是封装类型,所传入的参数类型(a)是原始数据类型,所以会自动对其装箱,反之,会对其进行拆箱

  4. 当两种不同类型用比较时,包装器类的需要拆箱, 当同种类型用比较时,会自动拆箱或者装箱?


基本类型与字符串类型之间的相互转换

基本类型->字符串(String)

  1. 基本类型的值+“”

  2. 包装类的静态方法toStirng

  3. String类的静态方法valueOf
    static String valueOf(int i)返回i参数的字符串表示

  		int i1 = 100;
        String s1 = i1+"";
        System.out.println(s1+200);//100200
        String s2 = Integer.toString(100);
        System.out.println(s2+200);//100200
        String s3 = String.valueOf(100);
        System.out.println(s3+200);//100200

String->基本类型
使用包装类的静态方法parsexxx(“字符串内为数值”)
Integer类:static int parseInt(String s)
Double类:static double parseDouble(String s)
int i = Integer.parseInt("2134"); System.out.println(i);//成功 int bc = Integer.parseInt("rewq"); System.out.println(bc);//报错NumberFormatException

重写与重载之间的区别

区别点重载方法重写方法
参数列表必须修改一定不能修改
返回类型可以修改一定不能修改
异常可以修改可以减少或删除,一定不能抛出新的或者更广的异常
访问可以修改一定不能做更严格的限制(可以降低限制)

第一章Collection集合

1.1集合概述

  • 集合是一种java中提供的一种容器用来存储多个数据
  • 数组的长度是固定的,集合的长度是可变的
  • 数组中存储的都是同一类型的元素,可以存储基本数据类型。集合存储的都是对象,而且对象的类型可以不一致

主要的集合类

  • ArrayList

  • LinkedList

  • HashSet(底层 哈希表 存储在这个集合的元素需要同时重写 hashCode + equals 方法)

  • TreeSet

  • properties

  • TreeMap

    • //     遍历方法1.得到所有的key 用 key 再来得到 Map 里面的value
           Set<Integer> b1 = a1.keySet();
              for (Integer integer : b1) {
                  System.out.println(a1.get(integer));
              }
      //       遍历方法2.将Map直接转换成Set,Set里面的每一个元素是Map.Entry
              Set<Map.Entry<Integer,String>> nodes = a1.entrySet();
              for (Map.Entry<Integer, String> node : nodes) {
                  System.out.println(node.getValue());
                  System.out.println(node.getKey());
              }
      

1.2集合框架


java提供了满足各种需要的API,在使用这些API前,先了解其继承与接口操作架构,才能了解何时采用哪个类,以及类之间如何彼此合作,从而达到灵活运用
集合按照基存储结构可以分为两大类,分别是单列集合java.util.Collection与双列集合java.util.Map

  • Collection:单列集合类的根接口,用于存储一系列符合某种规则的元素两个重要的子接口分别是java.util.Listjava.util.Set其中,

List接口

  1. 有序的集合(存储和取出元素顺序相同)

  2. 允许存储重复的元素

  3. 有索引,可以使用普通的for循环遍

List接口方法

以下是list接口特有的常见方法

intindexOf(Object o) 返回此列表中第一次出现的指定元素的索引;如果此列表不包含该元素,则返回 -1。
voidadd (int index, E element) 在列表的指定位置插入指定元素(可选操作)使用较少,效率较低
intlastIndexOf (Object o) 返回此列表中最后出现的指定元素的索引;如果列表不包含此元素,则返回 -1。
Eremove (int index) 移除列表中指定位置的元素(可选操作)。
Eset (int index, E element) 用指定元素替换列表中指定位置的元素(可选操作)。
Eget (int index) 返回列表中指定位置的元素。

ArrayList

最常用的集合

优点:检索速度快,向数据末尾添加元素不受影响

缺点:随机增删数据较慢

LinkList

底层双向链表

随机增删效率较高,检索效率较低。

public boolean add(Object element)

向链表末尾添加一个新节点,该节点中的数据是参数element指定的对象

public void add(int index,Object element)

向链表指定位置添加一个新节点,该节点中的数据是参数element指定的对象

public void addFirist(Object element)

向链表表头添加一个新节点,该节点中的数据是参数element指定的对象

public void addLast(Object element)

向链表表尾添加一个新节点,该节点中的数据是参数element指定的对象

public  Object removeFirst()

删除第一个节点并返回这个节点中的对象

public  Object removeLast()

删除最后一个节点并返回这个节点中的对象

public Object remove(int index)

删除指定位置的节点

public Object get(int index)

得到指定位置的节点

public Object getFirst()

得到链表第一个节点的对象

public Object getLast()

得到链表最后一个节点的对象

int indexOf(Object element) 

返回节点对象element在链表中首次出现的位置,如果链表中无此节点的对象则返回-1

public int lastIndexOf(Object element)

返回节点对象element在链表中最后出现的位置,如果链表中无此节点的对象则返回-1

public Object set(int index,Object element)

将当前链表index位置节点中的对象替换成参数element指定的对象,返回被替换对象

public int size()

返回链表的长度即节点个数

public boolean contains(Object element)

判断链表节点对象中是否含有element

如何将一个线程不安全的集合转换成线程安全的?

使用集合工具类:java.util.Collections 包下

List<String> MYList = new ArrayList<String>();
  Collections.synchronizedCollection(MYList); //使用这个方法

Set接口

  1. 不允许存储重复元素
  2. 没有索引(不能使用普通的for循环遍历)底层大多调用 Map 接口下的一些类

map

  1. Map 与 COllection 没有继承关系
  2. Map集合以 key 和 value 方式存储数据:键值对
    • 存储的都是对象的内存地

两种遍历方法

  1. 直接转成类型为Map.Entry<key,value>的Set
Map<Integer,String> MyMap = new HashMap<>();
        MyMap.put(1,"神奇");
        MyMap.put(2,"真实");
        Set<Map.Entry<Integer,String>> it2 =  MyMap.entrySet();
        for (Map.Entry<Integer, String> integerStringEntry : it2) {
            System.out.println(integerStringEntry.getKey()+ integerStringEntry.getValue());
        }
//这种方法大数据量的时候效率较高,因为 key 和 value都是直接从 node 对象内取
  1. 用 keyset 方法得到Set
Set<Integer> keys = MyMap.keySet();
        for (Integer key : keys) {
            System.out.println(key + "="+ MyMap.get(key));
        }

使用 HashSet 底层需要重写对象的 hashCode 与 equals

HashMap 使用不当时无法发挥性能

假设所有的 HashCode 返回的都是同一个值导致 HashMap变成一个纯单向链表,我们称之为散列分步不均匀。

什么 是散列分步均匀?

假设有10个链表,每个单链表上有 10 个结点,这是最好的。

假设所有的hashCode 返回值都不一样,可以吗?

不行,这样会导致hashMap成为一个数组,没有链表的概念了,也是散列分步不均匀。

散列分步需要你在重写 hashCode 时有一定的技巧。

因为HashSet底层调用的还是 HashMap 所以和 HashMap 一样都需要重写 equanls 与 hashCode 方法

HashMap 集合默认初始化容量是16,默认加载因子是 0.75

默认加载因子是 HashMap底层的数组达到 75% 的容量的时候就会进行一个扩容。

HashMap 初始容量是 2 的倍数,为了提高HashMap 集合的存取容量, 所必须的。

放在 HashMap 集合key部分的,以及放在HashSet 集合中的元素,需要同时 hashCode 与 equals 方法

如果哈希表单向链表中元素超过 8 个,单向链表这种数据结构就会变成红黑树。当红黑树节点小于 6 又会转成单向链表

常用方法

voidclear() 从此映射中移除所有映射关系(可选操作)。
booleancontainsKey (Object key) 如果此映射包含指定键的映射关系,则返回 true
booleancontainsValue (Object value) 如果此映射将一个或多个键映射到指定值,则返回 true
Vget (Object key) 返回指定键所映射的值;如果此映射不包含该键的映射关系,则返回 null
booleanisEmpty() 如果此映射未包含键-值映射关系,则返回 true
Vput (K key, V value) 将指定的值与此映射中的指定键关联(可选操作) 相当于集合的 add。
Set<K>keySet() 返回此映射中包含的键的 Set 视图。
Vremove(Object key) 如果存在一个键的映射关系,则将其从此映射中移除(可选操作)。
intsize() 返回此映射中的键-值映射关系数。
Collection<V>values() 返回此映射中包含的值的` Collection 视图。将所有的 value 转成Collection
Set<Map.Entry<K,V>>entrySet() 返回此映射中包含的映射关系的 Set 视图。 返回 Map.Entry 这个内部类组成的 Set

contains底层所用的都是equals方法


1.3 Collection常用功能

Collection是所有单列集合的父接口因此在Collection接口定义着单列集合中一些通用的方法

  • public boolean add(E e): 把给定的对象添加到当前集合中 。返回一个true和false表示添加成功或者失败

  • public void clear():清空集合中所有的元素。

  • public boolean remove(E e): 把给定的对象在当前集合中删除。同样返回一个布尔值表示添加成功或者失败

  • public boolean contains(Object obj): 判断当前集合中是否包含给定的对象。同样返回一个布尔值表示包含不包含

    • 源码使用equals方法对两个数据进行比较
    • 所以存放在一个集合中的类型一定要重写 equals 方法
  • public boolean isEmpty(): 判断当前集合是否为空。

  • public int size(): 返回集合中元素的个数。

  • public Object[] toArray(): 把集合中的元素,存储到数组中


第二章迭代器 iterator

  • 迭代:即Collection集合元素的通用获取方式。在取出元素前先判断有没有元素如果有就取,继续判断,然后往复专业术语称为迭代。

    • 注意我们每当集合的结构发生改变的时候,我们都需要重新获取迭代器,如果还是用之前的老迭代器,就会报异常 : java.util.conCurrentModificationException
    • 当然我们可以用 iterator 本身的 remove、add 等方法

两个常用方法

boolean hasNext()如果仍有元素可以迭代则返回true

E next()返回迭代的下一个元素

Iterator迭代器,是一个接口,我们无法直接使用,需要使用Iterator接口实现类对象获取实现类的方式比较特殊Collection接口中有一个方法,叫Iterator()这个方法返回的就是迭代器的实现类对象

***迭代器的使用步骤(重点)***

1. 使用集合中的iterator()获取迭代器的实现类对象用Iterator接口来接收(多态)

2. 使用Iterator接口中的方法`hasNext()`判断还有没有下一个元素

3. 使用Iterator接口中的方法`next()`取出下一个元素 

   ```java
   while (it.hasNext()){
                System.out.println(it.next());
            }
   
           for(Iterator<String> it2 = coll.iterator();it2.hasNext();){
               System.out.println(it2.next());
           }
   ```

   `coll.iiterator()`获取迭代器的实现类对象,并且会把指针指向集合的-1索引

   `hasNext()`判断有没有下一个元素

   `next()`做了两件事

   1. 取出下一个元素
   2. 会把指针向后移动一位

foreach

底层用的也是迭代器所以不能对元素进行一个增删

Collecttion<E>extends Iterable<E>:所有的单列集合都可以用foreach主要用来遍历集合和数组

格式:for(集合或者数组 变量名:集合名/数组名)

		ArrayList<String> arr = new ArrayList<>();
        arr.add("aaa");
        arr.add("ccc");
        arr.add("eee");
        arr.add("nnbn");
        for (String i :
                arr) {
            System.out.println(i);
        }
//或者是直接用iter+table键

HashSet

HashSet如何传入自定义类

  1. 使用HashSet 自定义类实现 Comparable 接口

    • class Person implements Comparable<Person>{
          int age;
          public Person(int age){
              this.age = age;
          }
      
          @Override
          public String toString() {
              return "Person{" +
                      "age=" + age +
                      '}';
          }
      
          @Override
          public int compareTo(Person o) {
              return this.age-o.age;
          }
      }
      
  2. 写一个类实现 Comparator 接口,并在 new HashSet 时传入这个比较类

    //主函数  
    TreeSet<Person> b1 = new TreeSet<>(new PersonComparator());
    //自定义类
    class PersonComparator implements Comparator<Person>{
    
        @Override
        public int compare(Person o1, Person o2) {
            return o1.age-o2.age;
        }
    }
    

Comparable与Comparator如何选择

如果排序的规则固定就使用 Comparable 如果 排序的规则会改变就使用 Comparator

Collections 工具类

对集合进行排序

Collections.sort(List类对象)

如果是自定义类需要将这个实例对象实现 Compleable 接口

如何对 Set 集合进行排序

 Set<String> ss= new HashSet<>();
      ss.add("ewaqr");
      ss.add("wqrt");
      ss.add("fds");
      ss.add("rew");
//转成 List 集合
      List<String> s2 = new ArrayList<>(ss);
//再进行排序
      Collections.sort(s2);
        for (String s : s2) {
            System.out.println(s);
        }


抽象类

  1. 抽象类如何定义?在class前加 abstract 关键字就行

  2. 抽象类是无法补全化的,无法创建对象

  3. final 和 abstract 不能联合使用,这两具关键字是对立的。

  4. 抽象类的子类可以是抽象类,也可以是非抽象类。

  5. 抽象类虽然无法补全化,但是抽象类有构造方法,这个构造方法是为子类准备的。

  6. 抽象类中不一定有抽象方法,抽象方法必须出现在抽象类中

  7. 抽象方法怎么定义?

    • abstract public class Animal
      
  8. 一个非抽象的类,继承抽象类,必须将抽象类中的抽象方法进行覆盖重写。

接口

  1. 接口也是一种引用数据类型。
  2. 接口是完全抽象的。
  3. 接口怎么定义,语法是什么
    • 「修饰符列表」 interface 接口名{}
  4. 接口支持多继承。
  5. 接口中只包含两部分内容,常量 + 抽象方法。
  6. 接口中所有的元素都是 public 修饰的。(都是公开的)
  7. 接口中抽象方法定义时:public abstract 修饰里可以省略。
  8. 接口中的方法不能有方法体。
  9. 接口中的 public static final 可以省略。
  10. 一个类可以实现多个接口
    • 这种机制弥补了 java 中类和类只支持单继承。实际上单继承是为了简单而出现的,现实世界中存在多继承,实现多个接口,其实就类似于多继承。
  11. extends 和 implements 可以共存, extends 在前,implements 在后。

重写覆盖一个方法不能分配给这个方法比原先方法更低的权限

接口中是public,你写的方法就必须是public

类与类之间如果没有继承关系不能使用强制转型。

如果能够使用多态就尽量使用多态

多态的口诀,编译看左边,运行看右边。

类型与类型的关系

  • is a:
    • Cat is a Animal
    • 凡是满足 is a 的表示“继承关系”
    • A extends B
  • has a:
    • I has a pen
    • 凡是能够满足 has a 关系的表示“关联关系”
    • A{
    • B b;
    • }
  • like a:
    • Cooker like a FoodMenu
    • 凡是能够满足 like a 关系的表示“实现关系”
    • A implements B

接口与抽象类的区别

  1. 抽象类是半抽象的。接口是完全抽象的。
  2. 抽象类中有构造方法。接口中没有构造方法。
  3. 接口和接口之间支持多继承。类和类之间只能单继承。
  4. 一个类可以同时实现多个接口。一个抽象类只能继承一个类。

final 关键字

  1. final 修饰的类无法继承
  2. final 修饰的方法无法覆盖
  3. final 修饰的变量只能赋值一次
  4. final 的引用一旦指向某个对象,便不能重新指向其它对象,但该引用指向的对象内部的数据是可以修改的。
  5. final 修饰的补全变量必须手动初始化。
  6. final 修饰的实例变量一般和 static 联合使用,称为常量

static关键字

  1. static修饰的方法是静态方法
  2. static修饰的变量是静态变量
  3. 所有static修饰的元素都称为静态,都可以使用“类名.”的方式来访问。当然也可以引用.的方式来访问
  • 什么时候成员变量声明为实例变量?
    • 所有对象都有这个属性,但是这个属性的值会随着对象的变化面变化
  • 什么时候成员变量声明为静态变量
    • 所有对象都有这个属性,并且所有对象的这个属性的值是一样的,建议定义为静态变量
  • 静态变量在类加载的时候初始化,内存在方法区中开辟。访问的时候不需要创建对象,直接使用“类名.变量名”的方式来访问

静态代码块

  1. 语法格式:

    static{

    }

  2. 静态代码块在类加载时执行,并且只执行一次。

  3. 静态代码块在一个类中可以编写多个。

泛型

3.1泛型 概述

泛型是一种未知的数据类型,当我们不知道用什么数据类型的时候就用泛型

创建对象的时候就会确定泛型的数据类型

/*创建集合对象不使用泛型
    好处:集合不使用泛型默认就是Object可以存储任意类型的数据
    弊端:不安全会引发异常
    * */
        //ArrayList<String> arr = new ArrayList<>();
        ArrayList list = new ArrayList<>();
        list.add("adc");
        list.add(1);
        Iterator it = list.iterator();
        while(it.hasNext()){
            Object obj  = it.next();
            System.out.println(obj);
            //我们这想要用String类特有的方法,length获取字符串的长度:不能使用多态
            //需要向下转型
            String  s = (String) obj;
            System.out.println(s.length());//java.lang.Integer cannot be cast to java.lang.String
        }
```java
ArrayList<String>  list = new ArrayList<>();
                list.add("fdsa");
                //报错list.add(1);
        /*创建集合对象使用泛型
        好处:
        1. 避免了类型转换的麻烦存储的是什么类型,取出的就是什么类型
        2. 把运行期异常,提升到了编译期
        弊端:
        泛型是什么类型,只能存储什么类型的数据
        * */
```
泛型的定义与使用
//泛型函数的写法
package climb;

public class GennericClass<E> {
    private E name;

    public E getName() {
        return name;
    }

    public void setName(E name) {
        this.name = name;
    }
}
泛型方法的使用
//这个泛型的名字可以随便命名
package climb;

public class GennericClass<E> {
    private E name;
    public <M> void method(M c){
        System.out.println(c);
    }
    public static <S> void method2(S s){
        System.out.println(s);
    }
    //静态方法,通过类名.方法名(参数)可以直接使用

    public E getName() {
        return name;
    }

    public void setName(E name) {
        this.name = name;
    }
}
泛型接口的使用及继承
package climb;

public interface GenericInterface<I> {
    public  void method(I c);
}

//继承泛型接口可改写这个泛型为具体的类型
public class GenericImple implements GenericInterface <String>{

    @Override
    public void method(String c) {
        System.out.println(c);
    }
}


package climb;
/*
第二种方式就是:接口使用什么泛型,实现类就使用什么泛型,类跟着接口走,就相当于
定义了一个含有泛型的类创建对象的时候确定泛型
* */
public class Genericlmple2 <I> implements GenericInterface<I>{
    @Override
    public void method(I c) {

    }
}

泛型通配符

泛型通配符
?:代表任意的数据类型
使用方式
不能创建对象使用
只能作为方法的参数使用

import java.util.ArrayList;
import java.util.Collection;

public class hello_world {
    public static void main(String[] args) {
        ArrayList<String> it1 = new ArrayList<>();
        it1.add("wqe");
        it1.add("fds");
        ArrayList<Integer> it2 = new ArrayList<>();
        it2.add(23);
        it2.add(2);
        PrintArry(it1);
        PrintArry(it2);
    }
     static void PrintArry(ArrayList<?> Arr){
        for (Object o : Arr) {
            System.out.println(o);
        }
    }
泛型限定
  • 泛型的上限限定 ?extends E:表示使用的泛型只能是E的子类/本身
  • 泛型的下限限定 ? super E:表示使用的泛型只能是E类型的父类/本身
package climb;

import java.util.ArrayList;
import java.util.Collection;

public class hello_world {
    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);//报错因为String不是Number的子类或者本身
        getElement1(list3);
        getElement1(list4);//报错Objcect是Number的父类而不是子类
        getElement2(list1);//报错Integer是Number的子类
        getElement2(list2);//报错String与Number类都是Object类的子类
        getElement2(list3);
        getElement2(list4);
    }
    public static void getElement1(Collection<? extends Number> call){};
    public static void getElement2(Collection<? super Number> call){};


}

package climb;

import java.util.ArrayList;

public class hello_world {
    public static void main(String[] args) {
        ArrayList<String> c = new ArrayList<>();
        c.add("wq");
        c.add("er");
        ArrayList<Integer> List = new ArrayList<>();
        List.add(23);
        List.add(25);
        PrintArry(c);
        PrintArry(List);

    }
    public static void PrintArry(ArrayList<?> list){
        for (Object o : list) {
            System.out.println(o);
        }
    }
}

//斗地主实例
package climb;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;

public class hello_world {
    public static void main(String[] args) {
        ArrayList<String> poker = new ArrayList<>();
        String[] colors = {"♥","♣","♠","♦"};
        String[] numbers = {"2","A","K","Q","J","10","9","8","7","6","5","4","3"};
        poker.add("大王");
        poker.add("小王");
        for (String number : numbers   ) {
            for (String color : colors) {
                poker.add(color+number);
            }
        }
        /*对其进行一个洗牌
        使用集合Collection中的方法
        static void shuffle(List<?> list)
        * */
        Collections.shuffle(poker);
        /*3.发牌
         * */
        ArrayList<String> player01 = new ArrayList<>();
        ArrayList<String> player02 = new ArrayList<>();
        ArrayList<String> player03 = new ArrayList<>();
        ArrayList<String> a_hand = new ArrayList<>();
        for (int i = 0; i < poker.size(); i++) {
            String p = poker.get(i);
            if(i>=51) {
                //给底牌发牌
                a_hand.add(p);
            }else if(i%3==0){
                player01.add(p);
            }else if(i%3==1){
                player02.add(p);
            }else if(i%3==2){
                player03.add(p);
            }
        }
        //4.看牌
        System.out.println("刘德华"+player01);
        System.out.println("周润发"+player02);
        System.out.println("周星驰"+player03);
        System.out.println("底牌"+a_hand);
    }
}

数据结构

红黑树

首先知道红黑树并不是AVI(二叉平衡树——左右子树高度差不大于一)趋近于

list接口

java.util.list接口 extends Collection接口
List接口的特点

  1. 有序的集合 存储元素与取出元素的顺序一致

  2. 有索引,包含了一些带索引的方法

  3. 允许存储重复元素
    List接口中带索引的方法(特有)

    • set(int index, E element) 指定索引值修改元素
    • add(int index, E element) 指定索引值添加元素
    • remove(int index) 指定索引值删除元素并返回被移除的元素
    • get(int index) 根据索引值获取元素
    • subList(int fromIndex, int toIndex) 指定开始于结束的索引值截取集合中的元素,返回
      注意:
      操作索引的时候一定不要越界:.
      IndexOutOfBoundsException:索引越界集合会报
      ArrayIndexOutofBoundsException:数组索引越界异常
      StringIndexOutOfBoundsException:字符串索引越界异常
    list集合三种遍历的方式
 /*使用普通的for循环
        * */
        for (int i = 0; i < list.size(); i++) {
            String s = list.get(i);
            System.out.println(s);
        }
        /*使用迭代器
        * */
        Iterator<String> it = list.iterator();
        while (it.hasNext()){
            String i = it.next();
            System.out.println(i);
        }
        /*
        使用增强for
        * */
        for (String s : list) {
            System.out.println(s);
        }

List的子类

ArrayList集合

List 接口大小可变数组的实现,此实现不是同步的(即多线程效率高速度快)
增删慢查找快底层源码用数组实现,每次增加都调用的是数组复制函数

//grow底层源码
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
        @SuppressWarnings("unchecked")
        T[] copy = ((Object)newType == (Object)Object[].class)
            ? (T[]) new Object[newLength]
            : (T[]) Array.newInstance(newType.getComponentType(), newLength);
        System.arraycopy(original, 0, copy, 0,
                         Math.min(original.length, newLength));
        return copy;
    }
LinkedList集合

底层为链表实现增删快,查询慢。
LinkedList是一个双向链表,

addFirst(E e)将指定元素插入此列表的开头等效于push
addLast(E e)将指定元素添加到此列表的结尾等效于add
getFirst()返回此列表中的第一个元素。 没有
E getLast()返回此列表中的最后一个元素。
removeFirst(E e)移除并返回此列表的第一个元素等效于Pop
removeLast(E e)移除并返回此列表的最后一个元素
pop() 从此列表表示的堆栈中弹出一个元素。
isEmpty() 返回这个链表是否为空
void push(E e) 将元素推送到由此列表表示的堆栈上。
注意写LinkedList集合不要使用多态因为多态看不到子类特有的方法

Set

Set接口extends Collection接口
Set

  1. 不允许存储重复的元素

  2. 没有索引,没有带索引的方法,也不能通过普通的for
    HashSet特点

  3. 不允许存储重复的元素

  4. 没有索引,没有带索引的方法,也不能通过普通的for

  5. 一个无序的集合,存储与取出的顺序可能不一致

  6. 此类实现Set接口,由哈希表(实际为HashMap实例)支持
    java.util.HashSet集合 implements Set接口

遍历由iterator与foreach完成

HashSet里的Hash表

hashCode() 方法用于返回字符串的哈希码。
字符串对象的哈希码根据以下公式计算:
hasCode源码
public native int hashCode();
native:代表该方法调用的是本地的本地操作系统的方法
toString方法的源码


public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}

同时哈希值也是对象的地址值同时我们可以在对象
重写hascode方法
同时String类重写了hascode方法返回的值都是 96354
String类的本质是字符数组char[],其次String类是final的,是不可被继承的,这点可能被大多数人忽略,再次String是特殊的封装类型,使用String时可以直接赋值,也可以用new来创建对象,但是这二者的实现机制是不同的。还有一个String池的概念,Java运行时维护一个String池,池中的String对象不可重复,没有创建,有则作罢。String池不属于堆和栈,而是属于常量池(应该位于方法区)。

下面分析上方代码的真正含义

String strA = “abc”;

String strB = “abc”;

第一句的真正含义是在String池中创建一个对象”abc”,然后引用时strA指向池中的对象”abc”。第二句执行时,因为”abc”已经存在于String池了,所以不再创建,则strAstrB返回true就明白了。strB”abc”肯定正确了,在String池中只有一个”abc”,而strA和strB都指向池中的”abc”,就是这个道理。
jdk.1.8版本之后:
哈希表=数组+链表
哈希表=数组+红黑树(提高查询速度)

HashSet存储不重复数据的流程

先为这个对象用hascode生成一个值
如果没有就放进去如果有就用equals方法比较是否相等如果不相等就在后面继续添加元素如果相等就添加

存储流程为我们存储自定义类型元素提供了解决方案


finally代码块

格式:

 try{

可能产生异常的代码块

}catch(定义一个异常的变量,用来接收try中抛出的异常对象){

异常的处理逻辑,异常对象之后怎么处理异常对象,一般在工作中会把异常的信息记录到一个日志中

}finally  {

无论是否出现异常都会执行

}

**注意 **

  1. finally不能单独使用,必须和try一起使用
  2. finally类一般用于资源释放(资源回收),无论程序是否出现异常都要释放(io)

关于java中的package和import机制

  1. 为什么要使用 package
    • package是 java 中包机制。 包机制是为了方便程序的管理。不机功能的类分别存放在不同的包下。
  2. package怎么用?
    • package 是一关键字,后面加上包名。例如:package com.bjpowernode.javase.chapter17
    • 注意:package语句只鸡毛出现在java源代码的第一行。
  3. 包名有没有命名规范?
    • 有,一般都采用公司域名倒序的方式
    • 公司域名倒序 + 项目名 + 模块名 + 功能名

访问控制权限

  1. 访问控制权限都有哪几个,分别是什么。
    • 4个
    • privete
    • public
    • protected
    • 默认
  2. 以上4个访问控制权限:控制的范围是什么?
访问控制修饰符本类同包子类任意位置
public可以可以不行可以
private可以不行不行不行
protected可以可以可以不行
默认可以可以不行不行

public > protected >默认 >privete

面向对象

面向过程和面向对象的区别

  • 面向过程:主要关注点是实现的具体过程,因果关系。
    • 优点: 对于业务逻辑比较简单的程序,可以快速开发,投入成本低。
    • 缺点:采用面向过程的方式开发很难解决非常复杂的业务逻辑,面向过程导致软件元素之间的“耦合度”非常高,只要一环出了问题整个系统受到影响导致最终的软件扩展力低。
  • 面向对象:

JDK、JRE、JVM三者之间的关系

JDK : Java 开发工具箱

JRE:java 运行环境

JVM:Java 虚拟机

JDK包括 JRE,JRE 包括 JVM。

安装 JDK 的时候:JRE就自动安装,同时JRE内部的 JVM 也就自动安装了。

项目部署一下,跑个项目,我们只需要安装JRE就行了。

Java体系的技术被划分为三大块:

JavaSE:标准版

JavaEE:企业版

JavaME:微型版

编译生成的字节码文件扩展名:xxx.class

Java文件就是源文件,这个文件中编写源代码。

另外需要注意的是:1个 Java 源文件是可以编译生成多个 class文件的。最终运行的是class文件

问题:字节码文件是二进制文件吗?

字节码文件不是二进制文件

运行期

如果是在Linux上运行,需要将window上生成的class文件拷贝过去。

使用 JDK 自带的一个命令/工具 : java(负责运行命令)执行字节码

往下的步骤就全部交给 JVM 了,然后进行解释生成二进制文件。

操作系统直接识别二进制文件,与硬件进行交互。

在以上运行过程中,需要使用两个非常重要的命令。

javac 命令,负责编译。

java 命令,负责运行

IO 流

什么是 IO 流

I: input

O: output

通过流的分类可以完成硬盘的读和写

IO流的分类

  1. 以流的方向分类

    • 输入流
    • 输出流
  2. 按照读取数据的方式来分

    • 一次读一个 byte,什么文件都能读
    • 一次读一个字符,为了读取纯文本文件存在。

    java 所有的流都在 java.io.* 下

    java 中以 Stream 结尾的都是字节流 以「Reader、writer」结尾的都是字符流

    所有的流都实现了 Closeable 这个接口, 都是可关闭的,都有 close() 方法。

    每个流都会占用很多资源,所以我们用完流必须关闭

    所有的输出流都实现了 Flushable 这个接口,所以所有的输出流都是可刷新的。

    养成一个好习惯,**输出流在最终输出后一定要记得 flush() 刷新一下,**这个刷新表示将这个管道中未输出的强行输出完——清空管道。

    如果没有 flush 可能会导致数据的丢失

需要掌握的16个流

  1. 文件专属

    FileReader

    FileReader reader  =null;
    try {
        reader = new FileReader("D:\\Typora\\temp.txt");
        //使用 char 数组来接收
        char [] chars = new  char[4];
        int readCount = 0;
        while ((readCount = reader.read(chars))!=-1){
            System.out.print(new String(chars,0,readCount));
        }
    } catch (IOException e) {
        e.printStackTrace();
    }finally {
        if (reader != null) {
            try {
                reader.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
    }
    

    FileWriter 只能操作纯文本

     FileWriter fw = null;
    try {
        fw = new FileWriter("D://Typora//temp.txt");
        char [] chars = {'我','是','中','国','人'};
        fw.write(chars);
        //这个类的write可以直接使用 String
        fw.write("我是一名java软件工程师");
        fw.write("\n");
        fw.write("hello_world");
    } catch (IOException e) {
        e.printStackTrace();
    }finally {
        if (fw != null) {
            try {
                fw.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    

    FilterInputStream 主要方法 reade(),available(),skip() 重点

    int readeData=1;
            FileInputStream fis = null;
            try {
                fis = new FileInputStream("D:\\Typora\\temp.txt");
                byte[] bytes = new byte[1024];
                int readCount = 0; 
                while((readCount= fis.read(bytes))!=-1){
                    System.out.print(new String(bytes,0,readCount));
                }
    
            } catch (IOException e) {
                e.printStackTrace();
            }finally {
    //            在 finally 代码块中关闭流
                if (fis != null) {
    //                避免空指针异常
                    try {
                        fis.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
    

    FilterOutputStream 主要方法 write() 重点

      FileOutputStream fis = null;
            try {
                //当这个文件不存在的时候会自动新建、
                //这种方式谨慎使用会原文件清空再重新写入
    //            fis = new FileOutputStream("D://Typora//temp.txt");
    //            以追加的方式在文件末尾写入,不会清空原文件的内容
                fis = new FileOutputStream("D://Typora//temp.txt",true);
                String s = "我是一个中国人,我骄傲!";
                byte[] bytes = s.getBytes();
    
                fis.write(bytes);
                fis.flush();
    
            } catch (IOException e) {
                e.printStackTrace();
            }finally {
                if (fis != null) {
                    try {
                        fis.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
    

    文件拷贝

     FileOutputStream fos = null;
           FileInputStream fis = null;
            try {
                fis = new FileInputStream("D://Typora//temp.txt");
    
                fos = new FileOutputStream("D:\\temp.txt");
                byte[] bytes = new byte[1024*1024];
                int readCount = 0;
                while((readCount = fis.read(bytes))!=-1){
                    fos.write(bytes,0,readCount);
                }
    //            刷新这个输出流
                fos.flush();
            } catch (IOException e) {
                e.printStackTrace();
            }finally {
    //分开try 不然当其中一个出了异常可能会影响另一个流的关闭
                if (fis != null) {
                    try {
                        fis.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if (fos == null) {
                    try {
                        fos.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
    
  2. 转换流(将字节流转换成字符流)

    InputStreamReader

    //       字节流
            FileInputStream fi = null;
            InputStreamReader ir = null;
            BufferedReader br = null;
            try {
    //            fi = new FileInputStream("D://Typora//temp.txt");
    //            通过流转换,将字节流转换成字符流
    //            ir = new InputStreamReader(fi);
    //            这个构造方法只能传一个字符流不能传字节流
    //            合并
                br = new BufferedReader(new InputStreamReader(new FileInputStream("D://Typora//temp.txt")));
                String line = null;
                while((line=br.readLine())!=null){
                    System.out.println(line);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }finally {
                if (br == null) {
                    try {
                        br.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
    

    OutputStreamWriter

  3. 缓冲流专属

    使用这个流不需要自定义 char 数组或 byte 数组,自带缓冲。并且关闭这个流内部的节点流会自动关闭

    BufferedInputStream
    BufferedOutputStream
    BufferedReader
    BufferedWriter

  4. 数据流专属

    DataInputStream 这两个流的读写顺序需要一致才能正常读写,并且两个流都是包装流
    DataOutputStream

  5. 标准输出流

    PrintStream 重点

    package climb;
    
    import java.io.FileNotFoundException;
    import java.io.FileOutputStream;
    import java.io.PrintStream;
    import java.text.SimpleDateFormat;
    import java.util.Date;
    
    public class Logger {
    //    记录日志的方法
        public static void log(String msg) {
            try {
                PrintStream ps = new PrintStream(new FileOutputStream("D://Typora//temp.txt"));
                System.setOut(ps);
                Date nowTime = new Date();
                SimpleDateFormat  sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
                String strTime = sdf.format(nowTime);
                ps.println(strTime+":"+msg);
    
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            }
        }
    }
    

    主函数中调用

     Logger.log("你好");
    

    PrintWriter

  6. 对象专属

    ObjectInputStream 重点
    ObjectOutputStream 重点

idea 默认当前路径是默认当前项目的根

File 类

File 类本身并不能完成文件的读和写。

File 类对象可以是一个文件也可以是一个目录 File 只是一个路径名抽象的表示形式

需要掌握 File 类的常用方法

  • getParent()
  • getAbsolutePath()
  • getName()
  • isDirctory()
  • isFile()
  • lastModifyed()
  • mkdir()
  • mkdirs()
  • length()
 File f1 = new File("D://Typora//temp.txt");
//    判断是否存在
//        获取父路径
        String parent = f1.getParent();
        System.out.println(parent);
//        获取父文件
       File f2 =  f1.getParentFile();
      String c2 =  f2.getAbsolutePath();
        System.out.println("获取绝对路径"+ c2);
//        获取文件名
        System.out.println("获取文件名"+f1.getName());
//        判断是否是一个目录
        System.out.println(f1.isDirectory());
//        判断是否是一个文件
        System.out.println(f1.isFile());
//        返回这个文件最后一次修改的时间 这个毫秒是从格林日期至今的时间
        long l1 = f1.lastModified();
        System.out.println(l1);
//        如何将这个毫秒转换成日期?
        Date D2 = new Date(l1);
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
        String modifyedTime  = sdf.format(D2);
        System.out.println(modifyedTime);
//        如果不存在以文件的形式创建出来
        /*if(!f1.exists()){
            try {
                f1.createNewFile();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }*/
        如果不存在以目录的形式创建出来
//        if (!f1.exists()) {
//            f1.mkdir();  
//        } 
//        可以创建一个多重目录,用 mkdirs 命令
//        获取文件大小
        System.out.println(f1.length());//返回的单位是字节
//        获取当前目录下的所有的子文件
        File[] f5 = f2.listFiles();
        for (File file : f5) {
            System.out.println(file.getName());
        }

作业目录拷贝

package climb;


import java.io.*;
import java.text.SimpleDateFormat;
import java.util.*;

public class hello_world {

    public static void main(String[] args) {
        File srcFile = new File("D:\\notebook\\back end");
        File DestFile = new File("D:\\idm\\file");
        copyDir(srcFile,DestFile);
        }

   private static void   copyDir(File srcFile, File destFile){
       if(srcFile.isFile()){
//           如果是一个文件递归结束并且传输文件
            FileInputStream fileInputStream = null;
            FileOutputStream fileOutputStream = null;
           try {
               fileInputStream = new FileInputStream(srcFile);
               String destDir =destFile.getAbsolutePath().endsWith("\\")?destFile.getAbsolutePath(): destFile.getAbsolutePath()+"\\"+ srcFile.getAbsolutePath().substring(3);
               fileOutputStream = new FileOutputStream(destDir);
               int readCount =0;
               byte[] bytes = new byte[1024*1024];
               while((readCount = fileInputStream.read(bytes))!=-1){
                   fileOutputStream.write(bytes,0,readCount);
               }
               //               刷新输出流
               fileOutputStream.flush();
           } catch (IOException e) {
               e.printStackTrace();
           }finally {
//               关闭流
               if (fileOutputStream != null) {
                   try {
                       fileOutputStream.close();
                   } catch (IOException e) {
                       e.printStackTrace();
                   }
               }
               if (fileInputStream != null) {
                   try {
                       fileInputStream.close();
                   } catch (IOException e) {
                       e.printStackTrace();
                   }
               }
               return ;
           }

       }
       File[]   files   = srcFile.listFiles();
       for (File file : files) {
           if(file.isDirectory()){
//               如果是一个目录的话
              String srcDir = file.getAbsolutePath();
              String destDir =destFile.getAbsolutePath().endsWith("\\")?destFile.getAbsolutePath(): destFile.getAbsolutePath()+"\\"+ srcDir.substring(3);
              File newFile = new File(destDir);
//              如果这个目录不存在就新建一个目录
              if(!newFile.exists()) {
                  newFile.mkdirs();
              }
           }
//           递归调用
           copyDir(file,destFile);
       }
    }


}

序列化与反序列化

如果需要将某个对象保存到磁盘上或者通过网络传输,那么这个类应该实现 Serializable 接口或者 Externalizable 接口之一。

  1. java.io.NotSerializableException 对象不支持序列化,

  2. 参与序列化的对象必须实现 Serializable 接口

  3. Serializable 这个接口通过源码发现,它只是一个标志性的接口

    • public interface Serializable {
      

    }

     
    + 那么它起到一个什么待遇? java 虚拟机看到这个类实现了这个接口,会自动生成一个序列化版本号 
    
    
  4. 序列化多个对象,我们可以将这多个对象放在一个集合中然后把这个集合进行序列化

    • List<Student> list = new ArrayList<>();
             list.add(new Student("唐三",23));
             list.add(new Student("小舞",22));
             list.add(new Student("王胖子",24));
             ObjectOutputStream objectOutputStream = null;
             ObjectInputStream objectInputStream = null;
              try {
                  objectOutputStream = new ObjectOutputStream(new FileOutputStream("斗罗"));
                  objectOutputStream.writeObject(list);
                  objectInputStream = new ObjectInputStream(new FileInputStream("斗罗"));
                  Object  obj = objectInputStream.readObject();
                  System.out.println(obj);
              } catch (IOException | ClassNotFoundException e) {
                  e.printStackTrace();
              }finally {
                  if (objectOutputStream != null) {
                      try {
                          objectOutputStream.close();
                      } catch (IOException e) {
                          e.printStackTrace();
                      }
                  }
              }
      
    • transient 关键字表示游离,用其修饰的属性的不参与序列化

  5. java 使用什么机制进行区分类的?

    • 通过类名进行比对,如果类名不一样,肯定不是同一个类
    • 如果类名一样,再怎么进行类的区分?靠序列化版本号进行区分
  6. 但如果我之前写一个类实现了 Serializable 接口,后续想改它就会重新生成一个全新的序列化版本号,conclusion:凡是一个类实现了这个接口,建议给这个类提供一个固定不变的序列化版本号

    •  private static final long serialVersionUID = 1L;
      

Io + Properties 联合使用

一个非常好的理念:

​ 以后经常改变的数据,可以单独写到一个文件中,使用程序动态读取将来只需要修改这个文件的内容,java 代码不需要改动,不需要重新编译,服务器也不需要重启就可以使使用动态信息。

​ 类似于以上机制的这种文件被称为配置文件:

​ 并且当配置文件中的内容格式是:

​ key = value

​ key = value

的时候我们把这种配置文件叫做属性配置文件

java 规范中有要求:属性配置文件建议以 .properties 结尾,但这不是必须的。

其中 Properties 对象是专门存放属性配置文件内容的一个类

 FileInputStream fileInputStream = null;
        Properties properties = new Properties();
        try {
            fileInputStream = new FileInputStream("2021demon\\UserInfo");
            properties.load(fileInputStream); //文件数据顺着管道进入 Map 集合中 其中等号左边的做 key
//            右边的做 value
            for (Object o : properties.keySet()) {

                System.out.println(properties.getProperty(o.toString()));
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (fileInputStream != null) {
                try {
                    fileInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

注意事项

建议使用 key 和 value 这间使用 = 的方式

属性配置文件 key 重复的话 value 自动覆盖

key 与 valu 之间最好不要有空格

多线程

什么是进程什么是线程

进程是一个应用程序,线程是一个进程中的执行场景/执行单元,一个进程可以启动多个线程。

进程A与进程B的内存独立不共享,线程A和线程B,堆内存和方法区内存共享。

java 中之所以有多线程机制,目的就是为了提高程度的处理速度。

使用了多线程机制后,main 方法结束只是主线程结束了,主栈空了,其它的栈可能依然在弹栈压栈。

对于单核 cpu 来说做不到真正的多线程并发,但是可以给人一种多线程并发的感觉

实现线程有三种方式

  1. 编写一个类,直接继承 java.lang.Thread,重写 run 方法

    •     public static void main(String[] args) {
              MyThread myThread = new MyThread();
      //        myThread.run(); 不会启动线程,不会分配新栈(这种方式就是单线程)
      
              myThread.start();//start 的作用启动一个分支线程,在jvm中开辟一个空间只要空间开辟出来了它的任务就完成了
      //        注意: 亘古不变的道理 方法体中的代码永远都是自上而下的顺序依次运行的
              for (int i = 0; i < 100; i++) {
                  System.out.println("主线程开始了"+i);
              }
          }
      }
      class  MyThread extends Thread{
          @Override
          public void run() {
              for (int i = 0; i < 100; i++) {
                  System.out.println("分支线程"+i);
              }
          }
      }
      
  2. 写一个类实现 Runnable 接口

    • //定义一个可运行的类
      public class Myrunnable implements Runnable{
          public void run (){
              
          }
      }
      //创建线程对象
      Thread t = new Thread(new MYrunnable());
      //启动线程
      t.start();
      

第二种更常用

  1. 实现 Callable 接口

    •  FutureTask task = new FutureTask(new Callable() {
                  @Override
                  public Object call() throws Exception {
      //                模拟执行
                      System.out.println("call method begin");
                      Thread.sleep(1000*10);
                      int a = 10;
                      int b=20;
                      System.out.println("call method over");
                      return a+b;
                  }
              });
              Thread t = new Thread(task);
              t.start();
              try {
                   Object obj = task.get();
                  System.out.println(obj);
              } catch (InterruptedException e) {
                  e.printStackTrace();
              } catch (ExecutionException e) {
                  e.printStackTrace();
              }
              //get 方法执行会导致当前线程阻塞
              //优点 可以拿到当前线程的结果
              //缺点 阻塞当前线程,效率较低
              System.out.println("hello world");
      

线程常用方法

  1. 获取当前线程对象

    • Thread t = Thread.currentThread()
      
  2. 获取线程名字

    • myrunnalbe.getName();
      
  3. 修改线程名字

    • myrunnalbe.setName("张三")
      
  4. 线程的sleep方法

    • Thread.sleep(1000*5);
      
    • 可以实现间隔特定的时间,执行特定的代码

  5. 中断睡眠

    • Thread t1 = new Thread(new MyThread());
      t1.start();
       t1.interrupt();//这种方式是依靠 java 中的异常的方式
      
  6. 终止线程

    • public class hello_world {
      
          public static void main(String[] args) {
              MyThread myThread = new MyThread();
              Thread t1 = new Thread(myThread);
              t1.start();
      
              try {
                  Thread.sleep(1000*5);
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
      //        需要什么时候终止这个类的执行,将这具标记改为 false 就可以了
              myThread.run = false;
      
          }
      }
      class  MyThread implements Runnable{
          boolean run = true;
          @Override
          public void run() {
              for (int i = 0; i < 10; i++) {
                  if(run){
                      try {
                          System.out.println(Thread.currentThread().getName()+"--->");
                          Thread.sleep(1000);
                      } catch (InterruptedException e) {
                          e.printStackTrace();
                      }
                  }else{
                      return ;
                  }
      
              }
      
          }
      }
      

线程调度

Thread.yield()方法作用是:暂停当前正在执行的线程对象,并执行其他线程。

yield()应该做的是让当前运行线程回到可运行状态,以允许具有相同优先级的其他线程获得运行机会。因此,使用yield()的目的是让相同优先级的线程之间能适当的轮转执行。但是,实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。

结论:yield()从未导致线程转到等待/睡眠/阻塞状态。在大多数情况下,yield()将导致线程从运行状态转到可运行状态,但有可能没有效果

线程调度有两种一种是抢占式,谁的优先度高的就先用谁.一种是均分式,平均分配时间.java 采用抢占式.

实例方法

​ void setPriority(int newPriority) 设置线程的优先级

​ int getPriority() 获取优先级

​ void join() 合并线程 当前线程陷入堵塞,实例线程执行,直到实例线程执行完毕,当前线程才可以继续执行.

Thread thread = new Thread(new MyThread());
try {
            thread.join();//当前线程受到阻塞,我们开辟的线程执行
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

​ 最低优先级1

​ 最高优先级10

​ 默认优先级5

​ 优先级高的获得 cpu的时间片大概率会多一些

静态方法

​ static void yield() 暂停当前线程,执行其它线程,这个方法并不是阻塞而是让给其它的线程使用.这个方法的执行会让它从运行状态回到就 绪状态,但仍有机会抢到

多线程并发环境下,数据的安全问题

我们编写的代码都将放在一个多线程的环境下运行,我们更加需要关注数据的安全问题

什么时候我们需要关心数据在多线程并发环境下会存在安全问题?

三个条件

  1. 多线程并发
  2. 有共享数据
  3. 共享数据有修改的行为

如何解决线程安全问题?

线程排除执行,解决并发,这种机制被称为线程同步机制,同样这种机制会牺牲一部分效率.

说到线程同步涉及到两个专业术语.

异步编程模型,

同步编程模型.

这两个模型直接按照前端的同步异步来理解就可以了

线程同步机制的语法

synchronized (){
        //线程同步机制
    }
//这个小括号填的内容相当关键,这个数据必须是多线程共享的数据,才能达到多线程排队
//假设一共有5个线程 t1,t2,t3,t4,t5 我们只想要t1,t2,t3 进行排队我们就填 t1,t2,t3 共享的数据但对于 t4,t5 来说不是共享的
//如果实在想不到有什么共享的数据直接填 this

在 java 中任何对象都有一把锁,其实这把锁就是标记(只是将它叫做锁) 100 个对象就有 100 把锁, 1 个对象一把锁.

synchronize 语法解析

假设 t1 与 t2 并发执行synchronized 代码块的时候肯定一个前一个后,

假设 t1 先执行的遇到 synchronized 先找共享对象这把锁,找到后并占有这把锁开始执行代码,在执行过程中一直都是占有这把锁,直到同步代码块结束这把锁释放.

当 t1 占有这把锁的时候,t2 没有这把锁只能排队等待,直到 t1 将这个同步代码块执行完成归还了这锁, t2 开始占用这把锁执行程序.

  1. 作为代码块
```java
synchronized (){
        //如果括号内填写 this 是 new 同一个对象的线程同步,
    //如果填写 "abc" 是所有的线程都要同步
    //如果直接传一个 null 会报 nullPointException
    }
```
  1. 作为关键字

    synchronized 出现在实例方法作为关键字的话一定锁的是 this 所以这种方法不灵活,并且这种方式表示整个方法体都需要同步,可能会无故扩大同步的范围,导致程序效率降低,所以这种方法不常用.

  2. 在静态方法上使用 synchronized 排它锁

    表示找类锁,类锁永远只有一把.就算创建了 100 个对象也只有一个类锁

如何解决线程安全问题

是一上来就使用 sythronized 吗? 不是使用 sythronized 会让执行效率降低,用户体验不好,系统的用户吞吐量降低,用户体验差,在不得已的情况下使用 sythronized 关键字

  1. 尽量使用局部变量代替实例变量与静态变量
  2. 如果必须是实例变量可以考虑创建多个对象,这样实例变量的内存就不共享了(一个线程对应一个对象,100个线程对应100 个对象,对象不共享就没有线程安全问题了)
  3. 如果不能使用局部变量,对象也不能创建多个这个时候就使用 sythronized .线程同步机制

java中有三大变量

  1. 局部变量:在栈中 在类的方法中定义,只有在方法中能够访问
  2. 实例变量:在堆中,声明在类里
  3. 静态变量:在方法区中 static 修饰

以上三大变量中局部变量永远都不会有线程安全问题,所以局部变量永远不共享

实例变量在堆中,堆只有一个

静态变量在方法区中,方法区只有一个所以它们的数据是共享的,可能存在线程安全问题

如果使用局部变量

String Buffer与String Builder 推荐使用 String Builder,因为String Buffer效率比较低.

另外注意 ArrayLIst 是非线程安全的.

Vector 是线程安全的.

HashSet,HashMap 是非线程安全的

Hashtable 是线程安全的

死锁

package climb;


import java.io.*;
import java.text.SimpleDateFormat;
import java.util.*;

public class hello_world {

    public static void main(String[] args) {
        Object o1 = new Object();
        Object o2 = new Object();
        Thread to1 = new MyThread(o1,o2);
        Thread to2 = new MyThread2(o1,o2);
        to1.start();
        to2.start();

    }
}
class  MyThread  extends Thread{
    Object o2;
    Object o1;
    public MyThread (Object o1,Object o2){
        this.o1 = o1;
        this.o2=o2;
    }
    @Override
    public void run() {
        synchronized (o1){//先锁的o1
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (o2){

            }
        }
    }
}
class MyThread2 extends Thread{
    Object o2;
    Object o1;
    public MyThread2 (Object o1,Object o2){
        this.o1 = o1;
        this.o2=o2;
    }
    @Override
    public void run() {
        synchronized (o2){//然后锁的o2 两边都不放行
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }synchronized (o1){
            }
        }

    }
}

所以开发后最好不要使用sychronized 嵌套否则一旦出现死锁就很难整

守护线程

java 中线程分为两类

  1. 一类是守护线程
  2. 一类是用户线程

其中有代表性的就是垃圾回收线程(守护线程)

守护线程的特点:

​ 一般守护线程就是一个死循环,所有的用户线程结束,守护线程自动结束.主线程 main 方法是一个用户线程.

BackDataThread backDataThread = new BackDataThread();
        Thread t1 = new Thread(backDataThread);
        t1.setName("备份数据的线程");
        t1.setDaemon(true);//这个是重要的,就是这句代码的作用将其变成守护者
        t1.start();
//下方类的实现
 @Override
    public void run() {
        while(true){
            System.out.println(Thread.currentThread().getName()+"---->");
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

定时器

使用Timer 类,但使用框架的时候我们不会用这个类,有更好的解决方案

 Timer time = new Timer();
       SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date firstime = null;
        try {
            firstime= simpleDateFormat.parse("2021-8-28 2:32:00");
        } catch (ParseException e) {
            e.printStackTrace();
        }
//LongTimeTask 类需要继承 TimerTask抽象类
        time.schedule(new LongTimeTask(),firstime,1000);

关于Object 的 notify 方法与 wait 方法

wait 方法与 notify 方法不是线程对象所特有的方法,是 java 中任何一个对象都有的方法,因为这两个方式是Object 自带的.

wait 方法

Object  o = new Object();
o.wait();
//表示让正在o对象上活动的线程进入等待状态,无期限等待,直到调用 o.notify() 方法
//o.wait 会让在 o上活动的当前线程进入等待状态,并且释放之前占有的 o状态的锁

notify 方法

表示唤醒正在 o 对象上等待的线程,还有一个 notifyAll 方法 这个方法是唤醒 o 对象上处于等待的所有的线程但会释放锁

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eVzZvoNw-1655898519058)(D:\notebook\文档截图\notify方法与wait方法.png)]

package climb;


import java.io.*;
import java.sql.Time;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;

public class hello_world {

    public static void main(String[] args) {
        List list = new ArrayList();
        Thread t1 = new Thread(new T1(list,0));
        Thread t2 = new Thread(new T2(list,1));
        t1.setName("t1");
        t2.setName("t2");
        t1.start();
        t2.start();

    }
}

//生产线程
class T1 implements Runnable{
    List list;
    Integer i ;

    public T1(List list, Integer i) {
        this.list = list;
        this.i = i;
    }

    @Override
    public void run() {
        while(true){
            synchronized (list){
                if(list.size()>0){
                    try {
                        list.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println(Thread.currentThread().getName()+"->"+i);
                i++;
                list.add(i);
                list.notify();
            }
        }

    }
}
class T2 implements Runnable{
    public T2(List list, Integer i) {
        this.list = list;
        this.i = i;
    }

    List list;
    Integer i ;
    @Override
    public void run() {
        while(true){
            synchronized (list){
                if(list.size()==0){
                    try {
                        list.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println(Thread.currentThread().getName()+"->"+i);
                i++;
                list.remove(0);
                list.notify();
            }
        }
    }
}

反射机制

相关的类有哪些

java.lang.Class 代表字节码文件 代表整个类

java.lang.reflect.Method 代表字节码中的方法字节码 代表类中的方法

java.lang.reflect.Constructor 代表字节码中的构造方法字节码 代表类中的构造方法

java.lang.reflect.Field 代表字节码中的属性字节码 代表类中的成员变量

利弊

  1. 优点动态的创建和使用对象,使用灵活,没有反射机制框架技术就失去底层支撑
  2. 使用反射基本是解释执行,会影响运行效率
    • 抬高调用优化,关闭访问检查
    • Method 和 Field 、construct 都有一个 setAccessible方法
    • 这个使用是启动和禁用访问安全检查的开头
    • 参数为 true 表示,反射的对象在使用时取消访问检查,提高反射效率,反射为 false 则表示反射的对象执行检查

class 类

  1. Class 也是类,因此也继承 Object
  2. Class 类对象不是 new 出来的,而是系统创建出来的
  3. 对于某一个类对象在内存中只有一份,因为类加载只有一次
  4. Class 对象存放在堆中
  5. 类的字节码了二进制数据,是放在方法区中(包括变量,方法,构造方法,变量名,方法名)

获取 class 的三种方式

  1. 通过 Class.forName 的方式

    这个方法会导致类加载,当一个类,类加载的时候,它的静态代码块就会执行

    如果你只是希望一个类的静态代码块执行,其它代码一律不执行你可以使用

    Class.forName(“完整类名”)

 Class c2; Class c3;Class c1;

    {
        try {
            c2 = Class.forName("java.util.Date");
            c1 = Class.forName("java.lang.String");
            c3 = Class.forName("java.lang.System");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
  1. 通过类型的 getClass 的方式

    String s = "2343";
    Class a = s.getClass();
    
  2. 使用 Class 属性

     Class b = String.class;
    

字节码文件装载到 JVM 的时候只装载一份

反射机制的灵活性.

java 代码写一遍,在不改变 java 源代码的基础上,可以做到不同对象的实例化

符合OCP 开门原则: 对扩展开放,对修改关闭.

后期的高级框架包括: ssh ssm Spring SpringMVC MyBatis Hibernate 都使用了反射机制,所以反射机制还是非常好用的.学会反射机制有利于你理解剖析框架底层源代码

类加载

  1. 静态加载:编译时加载相关的类,也就是通过反射机制来实现类加载,依赖性较强
  2. 动态加载:运行时加载需要的类,如果不运行就不用该类,则不报错

反射类中的属性

 try {
            Class StudentClass = Class.forName("climb.Student");
//            这里拿到的是 public 修饰的属性
           Field[] files =  StudentClass.getFields();
            System.out.println(files.length);
//            这时拿到是已经声明的属性
            files =  StudentClass.getDeclaredFields();
            System.out.println(files.length);
            for (Field file : files) {
//                获取属性的修饰符列表
                int files1 =  file.getModifiers();//返回的修饰符列表是一个数字,每一个数字是一个代号
                System.out.println(Modifier.toString(files1));
//                获取属性的类型
//                Class fileType = file.getType();
//                System.out.println(fileType.getName());
//                System.out.print(fileType.getSimpleName()+"=");
//                System.out.println(file.getName());
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

关于路径

fileInputStream = new FileInputStream("2021demon\\UserInfo.properties");

这种方式的路径缺点是:移植性差,在 IDEA 默认的当前的路径是 Project的根,

这个代码假设离开了 IDEA 换到了其它的位置可能当前的路径就不是 IDEA 的根了,这时这个路径就无效了.

使用以下通用方式的前提是:这个文件必须在类路径下,即在 src 路径下

image-20210828194434448
//        直接以流的形式返回
        InputStream inputStream = Thread.currentThread().getContextClassLoader().
                getResourceAsStream("climb\\Student.class");

取代原先的 properties + Io 流这种写法

//        资源绑定器只能绑定 xxx.properties 文件,并且这个文件必须在类路径下,并且在写路径的时候,路径后面的扩展名不能写
        ResourceBundle resourceBundle =ResourceBundle.getBundle("UserInfo");
        String className = resourceBundle.getString("password");
        System.out.println(className);

反编译实验

 StringBuilder stringBuilder = new StringBuilder();
        try {
            Class studentClass = Class.forName("climb.Student");
            stringBuilder.append(Modifier.toString(studentClass.getModifiers()) + " class "+studentClass.getSimpleName()+"{\n");
            Field[] fields = studentClass.getDeclaredFields();
            for (Field field : fields) {
                stringBuilder.append("\t");
                stringBuilder.append(Modifier.toString(field.getModifiers()));
                stringBuilder.append(field.getType().getSimpleName() );
                stringBuilder.append(" ");
                stringBuilder.append(field.getName()+";\n");


            }
            stringBuilder.append("}");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        System.out.println(stringBuilder);

通过反射机制访问一个对象的属性

try {
            Class studentClass = Class.forName("climb.Student");
            Object obj = studentClass.newInstance();

           Field NameFild =  studentClass.getDeclaredField("name");
//           打破封装,从而访问其它类的私有的成员变量
        NameFild.setAccessible(true);
        NameFild.set(obj,"张三");
        System.out.println(NameFild.get(obj));
        } catch (Exception e) {
            e.printStackTrace();
        }

通过反射机制访问一个类的方法

说一个可变长参数 int… args

可变长度参数在参数列表中只能出现一个并且只能出现在最后一个,可变长度参数的数量是 0-n

try {
    Class StudentClass=  Class.forName("climb.Student");
   Object obj =  StudentClass.newInstance();
   Method methods =  StudentClass.getDeclaredMethod("getName");
   /*methods 方法
   obj 对象
   实参为空
   retValue 返回值
   * */
    Object retValue = methods.invoke(obj);
   } catch (Exception e) {
       e.printStackTrace();
   }

通过反射机制访问一个类的构造方法

    Class c = Class.forName("climb.Student");
//    调用有参数的构造方法
    Object obj = c.newInstance();
//    先获取到有参数的构造方法
   Constructor constructor =  c.getDeclaredConstructor(String.class,Integer.class);
//   使用这个构造方法调用 newInstance 方法
   Object stt= constructor.newInstance("张三",23);
        System.out.println(stt instanceof Student);
        System.out.println(stt);

通过一个类访问这个类的父类以及实现了什么接口

       Class  string = Class.forName("java.lang.String");
//        获取它的父类
        Class superClass = string.getSuperclass();
        System.out.println(superClass.getName());
//        获取它实现的接口
       Class[] Interface =  string.getInterfaces();
        for (Class aClass : Interface) {
            System.out.println(aClass.getName());
        }

注解

注解是一种引用数据类型,它可以出现在一个类的任何地方,包括注解自身.

如何定义

[修饰符] @interface 注解类型名{
    
}

java 中实现了哪些注解

注释类型摘要
Deprecated 重要用 @Deprecated 注释的程序元素,不鼓励程序员使用这样的元素,通常是因为它很危险或存在更好的选择。
Override 重要表示一个方法声明打算重写超类中的另一个方法声明。 只能出现在方法上,标识性注解,编译器看到方法上有这个注解的时候,会自动检查该方法是否重写了父类的方法,如果没有报错.
SuppressWarnings指示应该在注释元素(以及包含在该注释元素中的所有程序元素)中取消显示指定的编译器警告。

什么是元注解

用来标志注解类型的的注解,就是元注解

常见的注解类型有哪些

@Target(ElementType.METHOD) 标注注解类型的注解,这个 Target 用来标注被标注的注解类型可以出现在哪些位置上 这里表示只能出现在方法上
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
//这里表示这个注解可以出现在构造方法上,字段上,局部字段上,方法上,包上...类上。。。
    如果注解中有属性,就必须给属性赋值除非该属性有default 默认值 如果只有一个属性名叫 value 的话那么(value=..)这是可以省略的 直接写成 ("value"@Retention(RetentionPolicy.SOURCE)  这个Retention 注解用来标注这个注解保存在哪 这里表示只能出现在java 源文件中
     CLASS 表示可以出现在 class 文件中
      RunTim 表示可以出现在 class 文件中并可以被反射机制所读取
    
           

关于 jdk 的类加载器

什么是类加载器?

专门负责了加载类的命令/工具

ClassLoader

JDk 中自带了三个类加载器

启动类加载器

扩展类加载器

应用类加载器

首先通过启动类加载器加载, C:\Program Files\Java\jdk1.8.0_281\jre\lib\rt.jar

这个目录下的都是 JDK 中最核心的类.

如果启动类加载不到的时候就通过扩展类加载器加载:C:\Program Files\Java\jdk1.8.0_281\jre\lib\ext? .jar

如果扩展类加载器加载不到就会通过"应用类加载器"加载

网络编程

域名

  1. www.baidu.com
  2. 好处为了方便记忆,解决记 ip 的困难
  3. 概念:将 ip 地址映射成域名,映射的方法是通过 http 协议

端口号

  1. 用于标识计算机上某个特定的网络程序
  2. 表示形式:以整数形式,范围 0~65535
  3. 0~1024已经被占用
  4. 常见的网络程序端口号:
    • tomcat:8080
    • mysql:3306
    • oracle:1521
    • sqlserver:1433

ip+端口就可以找到对应的网站服务

TCP 和 UDP

Tcp 协议:传输控制协议

  1. 使用 TCP 协议前,须先建立 TCP 连接,形成传输数据通道
  2. 传输前,采用"三次握手"方式,是可靠的
  3. 在连接中可进行大数据量的传输
  4. 传输完毕,需释放已建立的连接,效率低

UDP 协议:用户数据协议

  1. 将数据,源,目的封装成数据包,不需要建立连接
  2. 每个数据报的大小限制在 64k 内
  3. 因无需连接,故是不可靠的
  4. 发送数据结束时无需释放资源,速度快

重要的类

inetAddress 类

  1. 获取本机 inetAddress 对象 getLocalHost
  2. 根据指定主机名/域名获取 ip 地址对象 getByName
  3. 获取 inetAddress 对象的主机名 getHostName
  4. 获取 inetAddress 对象的地址 getHostAddress

Socket 类

  1. Socket 开发网络应用程序的被广泛的采用,以至于成为事实上的标准
  2. 通信的两端都要有 Socket 是两台机器间通信的端点
  3. 网络通信其实就是 Socket 间的通信
  4. Socket 允许程序把网络连接当成一个流,数据在两个流之间通过 IO 传输
字符流
client
//        连接客户端 两个参数分别是连接的主机名,与端口号
        OutputStream outputStream =null;
        Socket socket = null;
        InputStream inputStream = null;
        BufferedWriter bufferedWriter = null;
        BufferedReader bufferedReader = null;
        try {
//            1. 连接服务器端口 (ip,端口号)
            socket = new Socket(InetAddress.getLocalHost(),9999);
            System.out.println("客户端 Scoket="+socket.getClass());
//           2. 连接上后和 socket 对象关联的输出流对象
           outputStream =   socket.getOutputStream();
//            3. 通过输入流,写入数据到数据通到.
            bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream));
            bufferedWriter.write("hello,server 字符流");
            bufferedWriter.newLine();//插入一个换行符,表示写入的内容结束,注意,要求对方使用 readLine()
            bufferedWriter.flush();// 如果使用的字符流,需要手动冲一下,否则数据可能会不全
//            4. 获取和 socket 相关联的输入流
            inputStream = socket.getInputStream();
          bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
            String s = bufferedReader.readLine();
            System.out.println(s);

        } catch (IOException e) {
            e.printStackTrace();
        }finally {


            try {
                bufferedReader.close();

            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                bufferedWriter.close();
            } catch (IOException e) {
                e.printStackTrace();
            } try {
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            System.out.println("客户端退出...");
        }
server
Socket socket = null;
        BufferedReader bufferedReader = null;
        ServerSocket serverSocket = null;
        BufferedWriter bufferedWriter = null;
        try {
//            在9999端口监听,细节要求这个端口没有被占用
            serverSocket = new ServerSocket(9999);
            System.out.println("服务端在9999端口监听,等待连接...");
//            当没有客户连接时,程序会阻塞等待连接
//            如果有客户连接,会返回 Socket 对象,程序继续
            socket = serverSocket.accept();
//            得到 socket 对象关联的输出流对象

            System.out.println("socket " + socket.getClass());
//            通过 socket.getInputStream() 读取客户端写入到数据通道的数据
            InputStream inputStream = socket.getInputStream();


//            IO 读取 使用字符流,使用 InputStreamReader 将 InputStream 转成了一个 Reader
            bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
            String s = bufferedReader.readLine();
            System.out.println(s);

//            获取 socket 相关的输出流
            OutputStream outputStream = socket.getOutputStream();
            bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream));
            bufferedWriter.write("hello Client 字符流");
            bufferedWriter.newLine();//插入一个换行符,表示回复结束
            bufferedWriter.flush();

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //            关闭流和 socket
            try {
                bufferedReader.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                bufferedWriter.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                serverSocket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

字节流

 client
//        连接数据库
        Socket socket = null;
        OutputStream outputStream = null;
        try {
            socket = new Socket(InetAddress.getLocalHost(), 9999);
//            连接上后,生成 Socket ,通过 Socket.getOutputStream
            outputStream = socket.getOutputStream();
            outputStream.write("hello server".getBytes());
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                outputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
server
ServerSocket serverSocket = null;
        InputStream inputStream = null;
        Socket socket = null;
        try {
//            在本机的 9999端口监听
            serverSocket = new ServerSocket(9999);
            System.out.println("服务端在 9999 端口开启服务,等待连接");
//           服务端会阻塞,等待连接
            socket = serverSocket.accept();
            inputStream = socket.getInputStream();
//            Io 读取
            byte[] buf = new byte[1024];
            int readLen = 0;
            while ((readLen = inputStream.read(buf)) != -1) {
                System.out.println(new String(buf, 0, readLen));
            }
            System.out.println("服务端下线");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                inputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                serverSocket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

Homework

将客户端将一张图片发送到服务器端

服务器端
 //            服务器在8888端口
        ServerSocket serverSocket = new ServerSocket(8888);
//            开始接客
        Socket socket = serverSocket.accept();
//        读取客户端发送的数据
//        先将这个数据读入内存
        BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
        byte[] bytes = streamUtils.streamToByteArray(bis);
//        将这个 bytes 数据输入到指定的路径
        String destFilePath = "src\\missing.wedp";
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(destFilePath));
        bos.write(bytes);
        bos.flush();
        bos.close();
//        向客户端发送已经收到图片
        BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
        bufferedWriter.write("收到图片");
        bufferedWriter.flush();
        bufferedWriter.newLine();//写入一个结束标志
//        关闭流
        bufferedWriter.close();
        bis.close();
        socket.close();
        serverSocket.close();
客户端
    Socket socket = null;
    InputStream inputStream = null;
    OutputStream outputStream = null;

//            建立连接
        socket = new Socket(InetAddress.getLocalHost(), 8888);
//            准备传图片
        String filePath = "D:\\ps personal works\\your name\\missing.webp";
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream(filePath));
//       这里的 bytes 就是这个文件对应的字节
        byte[] bytes = streamUtils.streamToByteArray(bis);
        BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());
//        这里将我们的字节数组传给服务器端
        bos.write(bytes);
        bos.flush();
//        设置一个结束标志
        socket.shutdownOutput();
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        String s = bufferedReader.readLine();
        System.out.println(s);
        System.out.println("hell client");


//        关闭流
        bis.close();
        bos.close();
        socket.close()
工具类
    public class streamUtils {
/**
 * @author guojingbo
 * @date 2021-09-08 19:35
 * @param is
 * @return byte[]*/
    public static byte[] streamToByteArray (InputStream is) throws Exception{
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        byte[] b =  new byte[1024];
        int len;
//        循环读入文件,到缓冲层
        while((len=is.read(b))!=-1){
            bos.write(b,0,len);
        }
//        将这个缓冲层的数据转成字节数组再 return 出去
        byte[] array = bos.toByteArray();
        bos.close();
        return array;
    }
}

netstat 指令

  1. netstat-an 可以查看当前主机网络情况中,包括端口监听情况和网络连接情况
  2. netstat -an| more 可以分布显示

本地连接指的本地协议占用的端口号,外部地址连接的端口

netstat -anb 显示这个端口号及占用的程序名称

TCP 网络通讯不为人知的秘密

当客户端连接到端后,实际上客户端也是通过一个端口和服务器进行通讯的,只不过这个端口是TCP/IP 来分配的,是不确定的,是随机的

UDP 网络通信编程

基本介绍
  1. 类DatagramSocket 和 DatagramPack 实现了基于 UDP 协议网络
  2. UDP 数据报通过数据报套接字 DatagramSocket 发送的接收,系统并不这个数据一定能到达目的地
  3. UDP 协议中每个数据报都给出了完整的地址信息,所以无需建立发送方与接收方的连接
UDP 说明
  1. 没有明确的服务端与客户端,演变成数据的发送端与接收端
  2. 接收数据和发送数据是通过 DatagramSocket 对象来完成的
  3. 将数据封装到 DatagramPacket 对象/装包
  4. 当接收到 DatagramPacket 对象/拆包,取出数据
  5. DatagramSocket 可以指定在哪个端口接收数据
基本流程
  1. 核心的两个类/对象 DatagramSocket 和 DatagramPacket
  2. 建立发送端,接收端
  3. 发送数据前,建立数据包/ DatagramPacket
  4. 启用 DatagramSocket 的发送方法,接收方法
  5. 关闭DatagramSocket

Homework

  1. 编写一个接收端A和一个发送端B
  2. 接收端 A 在9999 端口等待接收数据
  3. 发送端 B 向接收端 A 发送数据 “hello,明天吃火锅”
  4. 接收端 A 收到 发送端 B 发送的数据,回复"好的,明天见"退出
  5. 发送端接收回复的数据,再退出
接收者
//    构建一个 DatagramSocket 对象,准备在9999 端口接收数据
    DatagramSocket socket =new  DatagramSocket(9999);
//    每个数据包的大小限制在 64k 以内,不适合传大数据
    byte[] bytes = new byte[1024*64];
    DatagramPacket packet = new DatagramPacket(bytes, bytes.length);
//    调用接收方法,将通过网络传输的 DatagramPacket 对象
//    当有数据包发送到 本机的 999 端口时,就会接收到数据 如果没有发送这时服务器就会阻塞
    socket.receive(packet);
// 可以把这个 packet 拆包,取出数据,并显示
    int length = packet.getLength();//得到实际的数据的大小
    byte[] data = packet.getData();//接收到数据
    String s = new String(data, 0, length);
    System.out.println(s);
    byte[] bytes1 = "好的明天见".getBytes();
    DatagramPacket packet1 = new DatagramPacket(bytes1, bytes1.length, InetAddress.getByName("192.168.1.104"), 9998);
    socket.send(packet1);
//    关闭资源
    socket.close();
    System.out.println("A端退出");
发送者
//        创建 DatagramSocket 对象,准备发送和接收数据
        DatagramSocket socket = new DatagramSocket(9998);
        byte[] data = "hello 明天吃火锅~".getBytes();
        //发送的数据,数据的长度,要发送的位置,以及端口号
        DatagramPacket packet =
                new DatagramPacket(data, data.length, InetAddress.getByName("192.168.1.104"),9999);
        socket.send(packet);
        byte[] bytes = new byte[1024*64];
        DatagramPacket packet1 = new DatagramPacket(bytes, bytes.length);
        socket.receive(packet1);
        int length = packet1.getLength();
        byte[] data1 = packet1.getData();
        String s = new String(data1, 0, length);
        System.out.println(s);
//        关闭资源
        socket.close();
        System.out.println("B端退出");

socket 实现文件的上传与接收

服务端 
ServerSocket serverSocket = new ServerSocket(9999);
        Socket socket = serverSocket.accept();
        InputStream inputStream =  socket.getInputStream();
        byte[] bytes = new byte[1024];
        int len = 0;
        String downloadFileName = "";
        while ((len=inputStream.read(bytes))!=-1){
            downloadFileName+=new String(bytes,0,len);
        }
        System.out.println("客户端希望下载的文件名是"+downloadFileName);
//        如果用户想下载的文件名是稻香就发送给用户,否则一律发送睛天
        String resFileName = "";
        if("稻香".equals(downloadFileName)){
            resFileName = "src\\稻香.mp3";
        }else{
            resFileName = "src\\sunny day.mp3";
        }
//        将这个文件读取到内存
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream(resFileName));
//        将内存中的这个文件输出给客户端
        BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());
        bos.write(streamUtils.streamToByteArray(bis));
        bos.flush();
        socket.shutdownOutput();
        inputStream.close();
//        释放资源
        bos.close();
        bis.close();
        socket.close();
        serverSocket.close();
        System.out.println("服务端退出");
客户端
//        输入需要下载的文件名
        Scanner scanner = new Scanner(System.in);
        String downloadName = scanner.next();
//        将这个文件名发送给服务端
        Socket socket = new Socket(InetAddress.getLocalHost(), 9999);
        BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());
        bos.write(downloadName.getBytes());
        socket.shutdownOutput();
//        4.读取服务端发来的文件
        BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
        byte[] bytes = streamUtils.streamToByteArray(bis);
//        将其写入硬盘
        String destFilenPath = "D:\\"+downloadName+".mp3";
        BufferedOutputStream bosF = new BufferedOutputStream(new FileOutputStream(destFilenPath));
        bosF.write(bytes);
        bosF.flush();

//        释放资源

        bis.close();
        bosF.close();
        socket.close();

多用户即时通信系统

需求分析
  1. 用户登录
  2. 摘取在线用户列表
  3. 无异常退出(客户端\服务端)
  4. 私聊
  5. 群聊
  6. 发文件
  7. 服务器摄像头新闻

项目结构

JDBC

com.guojingbo.dao_

  1. com.guojingbo.dao_.utils// 工具类
  2. com.guojingbo.dao_.domain //java bean 也就是对应表的操作类
  3. com.guojingbo.dao_.dao 存放xxxDao 和 BasicDao
  4. com.guojingbo.dao_.test //写测试类

其实在业务中还有一个 service 层来组织 sql 并调用相应的 xxx.Dao完成统合的需求

当多表联合查询

当多表联合查询时,创建一个MultixxxBean 存放多张表的字段名,生成一个 MultixxDao 给上层的 Service 调用。

如果字段过多,可以分成多个 multixxBean 来实现业务逻辑

如果字段名重复了,我们可以将 sql 语句字段名改成 MultixxBean 中存在的字段名操作如下

正则表达式

1. 底层原理

 String regStr = "\\d\\d\\d\\d";
        //1. 创建模式对象
        Pattern pattern = Pattern.compile(regStr);
        //2. 创建匹配器
        Matcher matcher = pattern.matcher(content);

        /**
         * mather.find() 完成任务
         * 1. 根据指定的规则,定位满足条件的子字符串
         * 2. 找到到将子字符串的索引记录到 mather 对象的属性中
         * groups[0] = 0 ,把该子字符串的结束的导引+1的值记录到 groups[1] 4
         *      2.2 记录第一组的匹配的字符 group[2]=0, group[3]=2;
         *      2.3 记录第二组匹配的字符 group[4]=2,group[5]=4
         * 3. 同时记录 oldList 的值为子字符串结束的索引+1的值即4,即下次执行 find 的时候就从4开始查找
         * 源码
         *   public String group(int group) {
         *         if (first < 0)
         *             throw new IllegalStateException("No match found");
         *         if (group < 0 || group > groupCount())
         *             throw new IndexOutOfBoundsException("No group " + group);
         *         if ((groups[group*2] == -1) || (groups[group*2+1] == -1))
         *             return null;
         *         return getSubSequence(groups[group * 2], groups[group * 2 + 1]).toString();
         *     }
         *     1. 根据 group[0]=0 group[1]=4 的记录的位置,从 content 开始截取字符串返回
         *   就是 [0,4)
         */
        while (matcher.find()){
            System.out.println("找到:"+ matcher.group(0));
        }

小结如下

  1. 如果正则表达式有 () 分组
  2. 取出的匹配的字符串的规则如下
  3. group(0) 表示匹配到的整体字符串
  4. group(1)或者group(2)表示接下来一个组匹配到的字符串

2. 元字符-限定符

符号含义
[]可接收的字符列表 在这个中括号的一切限定字符都会当成一个普通的字符不用转义
[^]不可接收的字符列表
-连字符表示从xx到xx
.匹配除\n以外的任何字符
\\d匹配单个数字字符
\\D匹配单个非数字字符
\\w匹配单个数字、大小写字母字符和下划线
\\W匹配单个非数字、大小写字母字符
\\s匹配任何空白字符(空格、制表符等)
\\S匹配任何非空白字符,与\\s相反
*指定字符重复 0 次或者 n 次
+指定字符重复 1 次或者 n 次
指定字符重复 0 次或者 1 次当此字符紧跟任何其他限定符*+?{n}{n,m}之后时匹配模式是非贪婪匹配
{n}只能输入 n 个字符
{n,m}表示至少 N 次最多 m 次(java 匹配模式是贪婪默认即尽可能的匹配多的)

java 正则表达式默认区分字母大小写的,如何不区分字母大小写

(?i)adb表示 abc 都不区分大小写

a(?i)bc表示 bc 不区分大小写

a((?i)b)c表示只有 b 不区分大小写

或者写Pattern pattern = Pattern.compile(regStr,Pattern.CASE_INSENSITIVE);

元字符-定位符

符号含义
^指定起始字符
$指定结束字符
\\b匹配目标字符的边界 指子串间有空格或者是目标字符串结束位置
\\B匹配目标字符串的非边界

分组、捕获、反向引用

常用分组构造说明
(pattern)编号为0的级是整个正则匹配的内容其余结果则根据左括号的顺序从 1 开始自动编号
(?<name>pattern)命名捕获,用于name 的字符串不能包含任何标点符号,并且不能以数字开头,可以使用单引号替代尖括号
(?:pattern)匹配 pattern 但不捕获该匹配的子表达式,即它是一个非捕获匹配,不存储以后使用的匹配。这对于用“or”字符的情况很有用。例如’industr(?:y|ies)'比‘industry|industies’更经济
(?=pattern)一个非捕获分组,例如 Windows(?=98|95|2000|NT)匹配这四个中的一个但不匹配windows3.1中的windows
(?!pattern)非捕获匹配,例如 windows(?!98|95|2000|NT)匹配windows2000 但不匹配 Windows2000中的window

反向引用

圆括号的内容被捕获后,可以在这个括号后使用,而写出一个比较实用的正则表达式,这个我们称为反向引用,这种引用既可以是在正则表达式内部,也可以是在正则表达式外部,内部反向引用\\分组号外部反向引用$分组号,注意这里 的外面指的是这个正则表达式本身的外面如在 reqlace填写的参数

  1. 要匹配两个连续的相同的数据:(\\d)\\1
  2. 要匹配五个连续的相同的数字:(\\d)\\1{4}
  3. 要匹配个位与千位相同,十位与百倍相同的数

正则表达式常用的类

  • pattern 类

pattern 对象是一个正则表达式对象,pattern 类没有公共构造方法,要创建一个对象,调用其公共静态方法,它返回一个 pattern 对象该对象接受一个正则表达式作为它的第一个参数,比如:Pattern r = Pattern.compile(pattern);

matches方法

返回一个一个正则是否可以整体匹配一个字符串

public static boolean matches(String regex, CharSequence input) {
        Pattern p = Pattern.compile(regex);
        Matcher m = p.matcher(input);
        return m.matches();//底层调用的还是Matcher类的matches
    }
  • Matcher 类

Matcher 对象是对输入字符串进行解释和匹配的引擎,与Pattern 类一样,也公共构造方法,你需要调用Pattern 对象的matcher 方法来获得一个 Mathcher 对象

start 返回匹配开始的索引

end 返回匹配结束的的索引

replaceAll(“想替换成的字符”) 对原来的字符串不做修改,返回一个符合匹配结果的字符串

String 类中使用正则表达式

  1. String 类自带 replaceAll 接受正则表达式
  2. public boolean matches(String regex) 自带的这个函数默认是整体匹配
  3. split

jdk.8 新特性

java 的内存情况

Java 有三个区域一个是堆,栈,方法区

方法区:主要用于存放信息、常量、静态变量

Java堆: 该区域主要放对象实例

栈:方法栈

Lambda 表达式

Lambda 是对匿名内部类的的一种简写方式,其本质还是调用对象的方法,当方法想要使用外部的变量的时候,需要将值传递过去,但方法无论怎么改变变量的值都不会改变这个变量在外部的值(值与引用传递的区别),所以让我们给这个参数加上 final 就是为了方便开发者,以免误以为可以更改这个变量的值。

Java 中局部内部类和匿名内部类访问的局部变量必须由 final 修饰,以保证内部类和外部类的数据一致性。但从 Java 8 开始,我们可以不加 final 修饰符,由系统默认添加,当然这在 Java 8 以前的版本是不允许的。Java 将这个功能称为 Effectively final 功能。

所以我们 Lambda 内中出现的任何变量都是 final 修饰的,也不可以做任何修改

  • 就和 es6 的箭头函数一样,括号左边是参数,右边的是函数体

函数式接口

  • Lambda 表达式需要函数式接口的支持

    • 函数式接口:接口中只有一个抽象方法的接口称为函数式接口,可以使用 @Function
  • Java8 四大内置的函数式接口

    • Consumer : 消费型接口

      • void accept(T t);
    • Supperlier:借给型接口

      • T get()
    • Function<T,R> : 函数型接口

      • R apply(T t)
    • Predicate: 断言型接口

      • boolean test(T t)

方法引用

方法引用: 若 Lambda 体中的内容有方法已经实现了,我们可以使用“方法引用”

2、方法引用的分类

类型语法对应的Lambda表达式
静态方法引用类名::staticMethod(args) -> 类名.staticMethod(args)
实例方法引用inst::instMethod(args) -> inst.instMethod(args)
对象方法引用类名::instMethod(inst,args) -> 类名.instMethod(args)
构建方法引用类名::new(args) -> new 类名(args)

若 Lambda 参数列表中的第一参数是实例方法的调用者,而第二个参数是实例方法的参数时可以用 ClassName::Method

Comparator<Integer> compare = Integer::compare;

stream API

1. 创建 Stream

  • 可以通过 Collection 系列集合提供的 stream() 或者parallelStream

  • 通过 Arrays 中的静态方法stream

  • 通过 Stream 类的中的静态方法 of()

  • 创建无限流 Stream s3 = Stream.iterate(0,(x)->x+2);

    • 迭代 `

      Stream<Integer> iterate = Stream.iterate(0, (x) -> x + 2);
      iterate.limit(10).forEach(System.out::println);
      
    • 生成

    • Stream.generate(()>Math.random()).limit(10).forEach(System.out::println);

2. 中间操作

筛选与切片

中间操作连接起来形成一个流水线,除非触发终止操作,否则中间操作不会进行任何操作,而在终止操作时一次性处理,称为惰性求值

  • filter- 接收
  • limit - 截断流,使其元素不超过给定数量
  • skip(n) - 路过元素,返回一个扔掉了前 n 个元素的流若不足前 n 个流返回一个空流
  • distinct 通过流所生成的 equals 与 hashCode 方法去重
映射

map - 接收Lambda ,将元素转换成其他形式或提取信息,接收一个函数作为参数,该函数会被应用到每一个元素上,并将其映射成一个新的元素

flatMap - 接收一个函数作为参数,将流中的每一个值都换成另一个流,然后将所有的流,连接成一个流

map -> 把一个流中的一个个流加入到当前元素

flatMap -> 把一个流中的所有的元素加入到当前的流中

排序

sorted() -> 自然排序(Comparable)

sorted(comparator com)- 定制排序(Comparator)

3. 终止操作

查找与匹配

allMatch - 检查是否匹配所有元素

anyMatch - 检查是否至少匹配一个元素

noneMatch - 检查是否没有匹配所有元素

findFirst - 返回第一个元素

findAny - 返回当前流中的任意元素

max 返回流中最大值

min 返回流中最小值

归约

reduce(T identity,BinaryOperator) 可以将流中的元素反复结合起来,得到一个值

List<Integer> integers = Arrays.asList(2, 23, 234, 54, 89, 90, 9);
System.out.println(integers.stream().reduce(0, (x, y) ->
        x + y
));

返回其总和,第一个参数是起始值

收集

collect 将流转换为其他形式,接收一个Collector 接口的实现类,用于给Stream 中的元素做汇总方法

List<Integer> collect = integers.stream().collect(Collectors.toList());
System.out.println(collect);
HashSet<Integer> collect1 = integers.stream().collect(Collectors.toCollection(HashSet::new));

并行流与顺序流

Java 8 中将并行进行了优化,我们可以很容易的对数据进行并行操作,Stream API 可以声明性地通过 parallel() 与 sequential() 在并行流与顺序流之间进行切换

并行流的效率是一定比顺序流高。

optional 容器类的常用方法

方法用处
optional.of(T t)创建一个 Optional 实例
Optional.empty()创建一个空的 Optional 实例
Optional.ofNullable(T t)若 t 不为 null 创建 Optional实例,否则创建空实例
orPresent()判断是否包含值
orElse(T t)如果调用对象包含值,返回该值否则返回 t
or

默认方法

在 java 8 之前接口只能有静态常量和抽象方法

现在还可以有默认方法

default String getName(){
	return "哈哈哈";
}

接口默认方法的类优先性原则

若一个接口中定义了一个默认方法,而另外一个父类或者接口中又定义了 一个同名的方法时

选择父类中的方法,如果父类中提供了一个具体的实现,那么接口中具有相同名称和参数的默认方法会被忽略

接口冲突,如果一个父接口提供一个默认方法,而另一个接口也提供了一个相同名称相同参数列表的方法,那么必须覆盖该方法来解决。

新日期时间 API

LocalDate、LocalTime、LocalDateTime

类的实例对象都是不可变的对象。给人看的API 接口

  LocalDateTime now = LocalDateTime.now();
        System.out.println(now);
        LocalDateTime of = LocalDateTime.of(2022, 9, 20, 12, 23, 50);
        System.out.println(of);
        LocalDateTime localDateTime = of.plusYears(32L);
        System.out.println(localDateTime);
        LocalDateTime localDateTime1 = localDateTime.minusYears(32L);
        System.out.println(localDateTime1);
        int dayOfYear = localDateTime1.getDayOfYear();
        System.out.println(dayOfYear);

Instance:时间戳

 Instant now = Instant.now();
        System.out.println(now);// 默认获取 UTC 时区
        OffsetDateTime offsetDateTime = now.atOffset(ZoneOffset.ofHours(8));//获取带偏移量的时差
        System.out.println(offsetDateTime);
        System.out.println(now.toEpochMilli());//返回时间戳

Duration、Period

Duration : 计算两个时间之间的间隔

Instant ins1 = Instant.now();
        Thread.sleep(1000);
        Instant now = Instant.now();
        Duration between = Duration.between(ins1, now);
        System.out.println(between.toMillis());
        System.out.println("=================================");
        LocalDateTime now1 = LocalDateTime.now();
        Thread.sleep(1000);
        LocalDateTime now2 = LocalDateTime.now();
        Duration between1 = Duration.between(now1, now2);
        System.out.println(between1);

Period: 计算两个日期之间的间隔

  LocalDate of = LocalDate.of(2016, 12, 3);
  LocalDate now = LocalDate.now();
  Period between = Period.between(of, now);
  System.out.println(between.getYears()+"年"+between.getMonths()+"月"+between.getDays()+"日");
  System.out.println(between.toTotalMonths());

TemporalAdjuster: 时间校正器

LocalDateTime now = LocalDateTime.now();
LocalDateTime localDateTime = now.withHour(23);//将小时改为 23 小时
System.out.println(localDateTime);
LocalDateTime with = now.with(TemporalAdjusters.lastDayOfYear());
System.out.println(with);
LocalDateTime with1 = now.with(TemporalAdjusters.next(DayOfWeek.MONDAY));
System.out.println(with1);
//自定义:下一个工作日
LocalDateTime with2 = now.with((l) -> {
    LocalDateTime now3 = (LocalDateTime) l;
    DayOfWeek dayOfWeek = now3.getDayOfWeek();
    if (dayOfWeek.equals(DayOfWeek.FRIDAY)) {
        return now3.plusDays(3L);
    } else if (dayOfWeek.equals(DayOfWeek.SATURDAY)) {
        return now3.plusDays(2L);
    } else {
        return now3.plusDays(1L);
    }
});

DataTimeFormatter 时间格式化

System.out.println(with2);
//直接使用给定的格式
DateTimeFormatter isoLocalDate = DateTimeFormatter.ISO_LOCAL_DATE;
System.out.println(now.format(isoLocalDate));
System.out.println("===============================");
//自定义格式
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH:mm:ss");
System.out.println(now.format(dateTimeFormatter));

ZonedDate、ZonedTime、ZonedDateTime 带时区的时间支持

java 面试题

双亲委派机制

当一个类接收到类加载请求,他首先不会尝试自己去加载这个类,而是向上委派查找缓存,如果找到则直接返回,没有继续往上委派,委派到顶层之后,缓存中还没有,则到加载路径中查找,有则加载返回,没有这向下查找,直到发起加载的加载器为止。

  • 安全性,避免用户自己编写的类动态替换java核心类,如String
  • 避免类的重复加载,JVM中区分不同类,不仅根据类名,相同的class文件被不同的ClassLoader加载就是不同的两个类

GC如何判断对象是否可以被回收

引用计数法:每个对象有个引用计数属性,新增一个引用计数+1,引用释放-1,计数为0可以回收
可达性分析:从GC ROOTS开始向下搜索,当一个对象没有任何引用链相连时,则证明此对象是不可用的,那么虚拟机就判断是可回收对象。

线程的生命周期?线程有几种状态
操作系统层面
1.线程有五种状态:创建、就绪、运行、阻塞、死亡
2.阻塞又分为三种:

等待阻塞:运行的线程执行wait()方法,释放占用的资源,JVM将其放入等待池中。不能自动唤醒,必须依靠其他线程调用notify或notifyAll方法才能唤醒,wait是Object类的方法
同步阻塞:运行中的线程在获取对象的同步锁时,如果该同步锁被别的线程占用,则将该线程放入锁池
其他阻塞:运行的线程调用sleep或join方法,或者发出I/O请求,JVM就会将该线程设为阻塞状态。当sleep状态超时、join等待线程终止、或者I/O请求处理完毕,线程重新转入就绪状态。sleep是Thread类的方法
1.新建状态(New):创建一个线程
2.就绪状态(Runnable):线程创建之后,其他线程调用该对象的start方法,该线程处于可运行线程池中,等待获取CPU执行权
3.运行状态(Running):就绪的线程获取CPU执行权,运行代码
4.阻塞(Blocked):由于某些原因放弃CPU使用权,暂时停止运行。当线程进入就绪状态,才有机会运行
5.死亡状态(Dead):线程执行结束或异常退出run方法,线程结束生命周期。

简述java线程的状态

操作系统层面
1.线程有五种状态:创建、就绪、运行、阻塞、死亡
2.阻塞又分为三种:

等待阻塞:运行的线程执行wait()方法,释放占用的资源,JVM将其放入等待池中。不能自动唤醒,必须依靠其他线程调用notify或notifyAll方法才能唤醒,wait是Object类的方法
同步阻塞:运行中的线程在获取对象的同步锁时,如果该同步锁被别的线程占用,则将该线程放入锁池
其他阻塞:运行的线程调用sleep或join方法,或者发出I/O请求,JVM就会将该线程设为阻塞状态。当sleep状态超时、join等待线程终止、或者I/O请求处理完毕,线程重新转入就绪状态。sleep是Thread类的方法
1.新建状态(New):创建一个线程
2.就绪状态(Runnable):线程创建之后,其他线程调用该对象的start方法,该线程处于可运行线程池中,等待获取CPU执行权
3.运行状态(Running):就绪的线程获取CPU执行权,运行代码
4.阻塞(Blocked):由于某些原因放弃CPU使用权,暂时停止运行。当线程进入就绪状态,才有机会运行
5.死亡状态(Dead):线程执行结束或异常退出run方法,线程结束生命周期

sleep()、wait()、join()、yield()

1.sleep是Thread类的方法,wait()是Object类的方法
2.sleep方法不会释放lock,但是wait会释放,而且会加入等待队列
3.sleep方法不需要依赖synchronized,可以在任何地方使用,但是wait方法需要依赖synchronized
4.sleep用于当前线程的休眠,wait用于多线程之间的通信
5.sleep会让出cpu并强制上下文切换,而wait不一定,wait后还是有机会重新竞争到锁继续执行。

yield()执行后直接进入就绪状态,释放cpu执行权,但保留cpu执行资格,有可能下次调度还会让该线程获取到执行权继续执行

join()执行后进入阻塞,例如在线程B中调用线程A的join(),线程B进入阻塞,直到线程A结束或中断线程

Thread和Runable的区别

Thread和Runable的实质是继承关系。用法上,如果有复杂线程操作需求,就选择继承Thread,如果只是简单执行一个任务,就实现Runnable。

Synchronized与Lock的区别

Synchronized是内置Java关键字,Lock是一个Java类
Synchronized无法判断获取锁的状态,Lock可以判断是否获取到了锁
Synchronized自动释放锁,Lock手动释放锁,如果不释放,就会死锁。
Synchronized 线程1(获得锁,阻塞)、线程2(等待,傻傻的等);Lock不一定会等待下去:tryLock()
Synchronized 可重入锁,不可中断,非公平;Lock 可重入锁,可以判断锁,非公平(可以设置)
Synchronized 适合锁少量的代码同步问题,Lock适合锁大量的同步代码

线程池的最大大小(maximumPoolSize)应该如何去设置?

1.CPU密集型:CPU有多少核,就是多少–>
Runtime.getRuntime().availableProcessors()
2.IO密集型: 判断程序中十分耗IO的线程,例如有15个大型任务耗IO资源,一般选择其两倍。

并发、并行、串行

并行:时间重叠,两个任务在同一时刻互不干扰执行
并发:允许两个任务互相干扰,两者交替执行,同一时间只能有一个任务执行
串行:多个任务挨个执行,时间不可能发生重叠

什么是指令重排

在程序执行时,为提高性能,编译器和处理器往往会对指令进行重排,而不是按照原来的顺序执行。
重排序分为以下三种:

  1. 编译器优化的重排序:编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序。
  2. 指令级并行的重排序:现代处理器采用了指令级并行技术,来将多条指令重叠执行。如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。
  3. 内在系统的重排序:由于处理器使用缓存和读/写缓冲区,这使得加载和存储操作看上去可能是在乱序执行。
    如何禁止指令重排?
  • 内存屏障:禁止上面的指令与下面的指令顺序进行交换

String

目标索引的字符

str.charAt(index)
 //java中的字符串无法像c一样直接使用[]需要取索引时只能使用这个函数

比较两个字符串

str1.comparto(str2);
//用ascll码来进行比较两个字符串直到出现不相等
//然后对两个字符算差值返回一个int,如果是正数说明第一个子串更大,如果是负数就是第二个字符更大

一个字符串是否包含另一个字符串

str1.contains(str2);
//看源字符串是否包含后面的字符返回一个boolean
//包含返回true不包含返回一个false

一个字符串是否以另外一个字符串结尾

str1.endWith(str2);
//返回一个bollean如果以它结尾返回true否则返回false

一个字符串是否以另外一个字符串开始

"http://www.baidu.com".startsWith("http")
    //返回一个bollean如果是相等返回一个true如果不相等返回一个false

关于两个字符串是否相等

str1.equals(str2)
//返回一个bollean如果相等返回true不相等返回一个false不能忽略大小写

将一个字符串转换成一个byte数组

  byte [] bytes ="fdsa".getBytes();
  for (int i = 0; i < bytes.length; i++) {
     System.out.println(bytes[i]);
 }
//需要注意的是byte数组里面是用ascll码来进行存储的

找到子字符串在当前字符串的索引

 System.out.println("中国人".indexOf("国"));
//返回int表示出现的位置如果没有返回-1

这个字符串是否为空

 System.out.println("".isEmpty());
//返回bollean空为true不空为false

字符串的长度

//面试题数组的length与字符串的length有什么区别
//判断数组的length是length属性,字符串的length是length()方法
str1.length()

匹配子串的最后一个位置

"enterterterlastter".lastIndexOf("ter")
//如果没有返回-1

替换字符中的一部分子串

 String str1 = "中国人的血".replace("中国","强大的中国");
// 将中国替换成强大的中国并且返回一个String

拆分字符串

 String[] sr1="c-t-r-l".split("-");
//将其根据正则来拆分返回一个String数组

截取字符串

"https://www.baidu.com".substring(8)
 //从指定位置开始到结尾截取一个子字符串
"https://www.baidu.com".substring(8,11)
//从指定位置开始到索引结束,左闭右开

将字符串转换成一个char数组

 char[] chars = "我是中国人".toCharArray();

转换成小写

"DFDSJK".toLowerCase(Locale.ROOT)

转换成大写

"fds".toUpperCase(Locale.ROOT)

去除前后空白

"   fds   ".trim()

将"非字符串"转成"字符串"

String s1 = String.valueOf(432);
//如果目标是一个对象对调用对象的toString方法
//如果没有重写toString方法则返回对象的地址

StringBuffer类

我们在实际开发中,如果需要进行字符串的频繁拼接,会有什么问题?

因为java中的字符串是不可变的,每一次拼接都会产生新字符串. 这样会占用大量方法区的内存.造成内存空间的浪费.

String s = “abc”;

s+=“hello”

这两行代码在字符串常量池中创建了3个对象.

conclusions如果以后需要进行大量字符串的拼接操作,建议直接使用JDK中自带的

java.lang.StringBuffer

java.lang.StringBuilder

字符串拼接函数

StringBuffer s1  = new StringBuffer();
        s1.append("fdsafds");

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-s0HqTZ5Y-1655898339945)(D:\notebook\文档截图\append源码.png)]

Array

一维数组扩容

在Java开发中数组长度一但确定不可变,如果数组满了就需要扩容.

Java对数组的扩容内部实现只是

新建一个大容量的数组,然后将小容量数组中的数据一个个扩容到大数组中

结论:Java扩容的效率比较低尽量不要使用扩容

数组拷贝

System.arraycopy(src,0,dest,0,3);
// 参数1.源数组 参数2.开始拷贝的位置 参数3.目标数组 参数4.存放的位置 参数5.拷贝的长度

Date

获取当前时间

 Date d1  =new Date(); //Sun Aug 22 15:34:57 CST 2021

如果需要对Date进行一个格式化需要用SimpleDateFormat类

SimpleDateFormat

字母日期或时间元素表示示例
GEra 标志符TextAD
yYear1996; 96
M年中的月份MonthJuly; Jul; 07
w年中的周数Number27
W月份中的周数Number2
D年中的天数Number189
d月份中的天数Number10
F月份中的星期Number2
E星期中的天数TextTuesday; Tue
aAm/pm 标记TextPM
H一天中的小时数(0-23)Number0
k一天中的小时数(1-24)Number24
Kam/pm 中的小时数(0-11)Number0
ham/pm 中的小时数(1-12)Number12
m小时中的分钟数Number30
s分钟中的秒数Number55
S毫秒数Number978
z时区General time zonePacific Standard Time; PST; GMT-08:00
Z时区RFC 822 time zone-0800

将Date进行格式化

  SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
String nowDate =sdf.format(d1);

将String转成Date

 String date = "2008-8-8 08:08:08 888";
 SimpleDateFormat fds = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
        Date s1 = fds.parse(date);

统计一个方法执行所耗费时长

  long s1 = System.currentTimeMillis();
      int a1 = 2;
        for (int i = 0; i < 1000; i++) {
            System.out.println(a1);
        }
      long s2 =  System.currentTimeMillis();
        System.out.println(s2-s1);//这里就是所用的总时长了

DecimalFormat对数字进行格式化

在java.text包下

符号位置本地化?含义
0数字阿拉伯数字如果不存在显示为0
#数字字阿拉伯数字,如果不存在则不显示
.数字小数分隔符或货币小数分隔符
-数字减号
,数字分组分隔符
E数字分隔科学计数法中的尾数和指数。在前缀或后缀中无需加引号。
;子模式边界分隔正数和负数子模式
%前缀或后缀乘以 100 并显示为百分数
\u2030前缀或后缀乘以 1000 并显示为千分数
¤ (\u00A4)前缀或后缀货币记号,由货币符号替换。如果两个同时出现,则用国际货币符号替换。如果出现在某个模式中,则使用货币小数分隔符,而不使用小数分隔符。
'前缀或后缀用于在前缀或或后缀中为特殊字符加引号,例如 "'#'#" 将 123 格式化为 "#123"。要创建单引号本身,请连续使用两个单引

数字格式化转成String

DecimalFormat df = new DecimalFormat("###,###.0000");
String a = df.format(324.2);

BigDecimal

https://blog.csdn.net/haiyinshushe/article/details/82721234

大数运算用这个类准没有错

Random

产生限定范围内的随机数

 Random v1 = new Random();
int a =  v1.nextInt(34);

产生int取值范围内的随机数

 int c = v1.nextInt();

Java各种类的细节

关于String类的创建的方法

String a1 = "abcdf";
String b1 = "rew";
//这两行代码表示在底层创建了两个字符串对象都在字符串常量池中
//这是用new 方式创建的字符串对象
//凡是双括号括起来的都在常量池中有个位置
//用new创建的时候一定是在堆里面开辟空间
String c1 = new String("c1");

String类的构造方法

输出一个引用时会自动调用这个引用的toString方法,默认的object类会默认输出内存地址

但输出String的时候我们发现并没有得到地址说明String类重写了toString方法

使用byte数组转换成字符串

  1. byte [] bytes={97,98,99};
    //byte数组内用ascll码
    String s2 = new String(bytes)
     //所以我们这里是直接用   
    
String s3 = new String(bytes,1,2);
//bytes数组,起始的位置,长度

使用char数组转换成字符串

  1. char [] chars={'我','是','中','国','人'};
           String s4 = new String(chars);
    
  2.  String s5 = new String(chars,2,3);
    

使用常量池中的字符来构造字符串

String s6 = new String("我是中国人");

println函数细节

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tlrQ8jal-1655898339946)(D:\notebook\文档截图\println源码.png)]

println底层还是使用valueof来对对象进行处理,valueof如果目标调用的是toString方法所以如果是一个对象调用println的话就是调用toString方法.

所以本质上println这个函数就是将任何数据先转换成字符串再来输出

StringBuffer类

底层使用的是byte字节大小为16的数组,如果容量不够会使用arrycopy自动扩容,其实String类的本质也是一个byte数组但前面有一个final关键字导致它不可变.

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Iyj7waoe-1655898339948)(D:\notebook\文档截图\String类源码.png)]

如何忧化StringBuffer的性能?

在创建StringBufffer的时候尽可能给定一个初始化容量

最好减少底层数组的扩容次数.预先估计一下,给一个大一些初始化容量.

StringBuffer sb = new StringBuffer(100);
//创建了一个容量是100的StringBuffer数组

StringBuilder

与StringBuffer一样都是为了字符串拼接这个目的产生的

StringBuffer中的方法都有synchronized关键字修饰表示其在多线程环境下的安全性

StringBuilder与StringBuffer的区别是StringBuffer是线程安全的,StringBuilder是非线程安全的

8种基本包装类

  1. java中为8种基本数据类型又对应准备了8种包装类型.8种包装类属于引用数据类型,父类是object

  2. 思考为什么要再提供8种包装类?

    因为8种基本数据类型不够用.所以又提供对应的8种包装类

8种基本数据类型对应包装类型名是什么?

基本数据类型包装类型
bytejava.lang.Byte
shortjava.lang.Short
intjava.lang.Integer
longjava.lang.Long
floatjava.lang.Float
doublejava.lang.Double
booleanjava.lang.Boolean
charjava.lang.Character
//        基本数据类型转换成引用数据类型(装箱)
       Integer s1 =  new Integer(234);
//       引用数据类型转换成基本数据类型(拆箱)
       float a1  =s1.floatValue();

所有的数字包装类的子类都有

方法摘要
byte**[byteValue] 以 byte` 形式返回指定的数值。
abstract doubledouble 形式返回指定的数值。
abstract float**[floatValue]float 形式返回指定的数值。
abstract int**[intValue] 以 int` 形式返回指定的数值。
abstract long**[longValue] 以 long` 形式返回指定的数值。
short**[shortValue] 以 short` 形式返回指定的数值。

关于构造方法一般都有两个一个是用对应的基本数据类型,一个是使用String

通过访问包装类的常量来获取最大值与最小值

System.out.println(Integer.MAX_VALUE);
System.out.println(Integer.MIN_VALUE);

我们进行±*/等运算的时候就会触发自动装箱与自动拆箱

java中为了提高程序效率,将[-128,127]之间所有的包装类提前创建好,放到一个常量池中目地是这个区间的数据不需要再new了直接取

 Integer a=127;
        Integer b = 127;
        System.out.println(a==b);//true他们两是同一个内存地址
        Integer c = 128;
        Integer d = 128;
        System.out.println(c==d);//false

Integer类加载的时候会初始化整数型常量池:256个对象

将String转换成int静态方法
int retValue = Integer.parseInt("123");
Object的toString方法

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dsAE1XC8-1655898339949)(D:\notebook\文档截图\object的toString方法.png)]

各种异常

中文名英文名
空指针异常NullPointerException
类型转换导演ClassCastException
数组下标越界异常ArrayIndexOutOfBoundsException
数字格式化异常NumberFomatException

Arrays

java.utils.Arrays 类是一个工具类,让我们更方便的操作数组。
这里介绍本人认为的几个比较实用的方法。

  1. sort
  2. fill

sort

该方法是用于数组排序,在 Arrays 类中有该方法的一系列重载方法,能对7种基本数据类型,包括 byte,char,double,float,int,long,short 等都能进行排序,还有 Object 类型(实现了Comparable接口),以及比较器 Comparator 。内部算法的复杂度应该是在 n^2到 nlongn,

默认升序排序.

升序
    int scores[] = new int[]{1,2,3,89,4};
        Arrays.sort(scores);
        for (int score : scores) {
            System.out.println(score);
        }
降序
    Integer integers[] = {1,2,3,89,4};
	Arrays.sort(integers, Collections.reverseOrder());//需要注意的是 不能使用基本类型(int,double, char)需要改成对应的包装类,如果是int型需要改成Integer,float要改成Float...
	for (Integer integer : integers) {
            System.out.println(integer);
        }

这里插入一个操作如何将 int 数组转成 Integer 包装类

 		int scores[] = new int[]{1,2,3,89,4};
//		先将int 数组转换成数组流
        IntStream stream = Arrays.stream(scores);
//		流中的元素全部装箱,转换为流 ---->int转为Integer
        Stream<Integer> boxed = stream.boxed();
//		将流转换为数组
        Integer[] integers = boxed.toArray(Integer[]::new);
        Arrays.sort(integers);
        for (Integer integer : integers) {
            System.out.println(integer);
        }

简化

	Integer[]integers = Arrays.stream(scores).boxed().toArray(Integer[]::new);//简化操作
	Arrays.sort(integers, Comparator.reverseOrder());

fill

从指定位置插入固定的值,从 fromIndextoIndex填充的位置左闭右开

int[] ints ={1,2,3,489,23};
        Arrays.fill(ints,0,2,5);//填充值为5
        for (int anInt : ints) {
            System.out.println(anInt);
        }

分配指定数值给数组内的每个元素

	   int[] ints ={1,2,3,489,23};
        Arrays.fill(ints,5);
        for (int anInt : ints) {
            System.out.println(anInt);
        }

当为二维数组赋值时需要注意,不能直接加数值,否则会报异常

		int[][] map =new int[3][4];
        Arrays.fill(map,5);//ArrayStroeException 当试图将类型不兼容类型的对象存入一个Object[]数组时将引发异常
        for (int i = 0; i < 3; i++) {
            for (int j : map[i]) {
                System.out.print(j);
            }
        }

需要将一个一维数组填入

		int[][] map =new int[3][4];
        int [] ten = {1,2,3};
        Arrays.fill(map,ten);
        for (int i = 0; i < 3; i++) {
            for (int j : map[i]) {
                System.out.print(j);
            }
        }

包装类

概述:

基本数据类型,使用起来非常方便,但没有对应的方法来操作,可以使用一个类,把基本类型的数据装起来,在类中定义一个方法,这个类叫包装类,我们可以使用类中的方法来操作这些基本类型对象

基本类型对应的包装类(位于java.lang包中)
byteByte
shortShort
intInteger
longLong
floatFloat
doubleDouble
charCharacter
booleanBoolean

自动拆箱与装箱

装箱:把基本类型的数据,包装到包装类中(基本类型的数据->包装类)

构造方法:

Integer(int value)构造一个新分配的Integer对象,表示指定的int

这里涉及到一个valueOf方法

valueOf() 方法用于返回给定参数的原生 Number 对象值,参数可以是原生数据类型, String等。

  • **Integer valueOf(int i):**返回一个表示指定的 int 值的 Integer 实例。
  • **Integer valueOf(String s):**返回保存指定的 String 的值的 Integer 对象。
  • Integer valueOf(String s, int radix): 返回一个 Integer 对象,该对象中保存了用第二个参数提供的进制进行解析时从指定的 String 中提取的值。

调用valueOf函数之后,确定i是否在缓存范围内,如果在返回已经缓存下来的,如果不在返回一个新的Integer对象所以合理的利用valueOf函数可以减少内存的开销

这种缓存行为不仅适用于Integer对象。我们针对所有整数类型的类都有类似的缓存机制。
ByteCache 用于缓存 Byte 对象
ShortCache 用于缓存 Short 对象
LongCache 用于缓存 Long 对象
CharacterCache 用于缓存 Character 对象
Byte,Short,Long 有固定范围: -128 到 127。对于 Character, 范围是 0 到 127。除了 Integer 可以通过参数改变范围外,其它的都不行。

@Override//下面的是equals方法的源码
 public boolean equals(Object o) {
     return (o instanceof Integer) && (((Integer) .value == value);
}

自动拆箱与自动装箱

  1. 需要知道什么时候会引发装箱和拆箱

  2. 装箱操作会创建对象,频繁的装箱操作会消耗许多内存,影响性能,所以可以避免装箱的时候应该尽量避免。

  3. equals(Object o) 因为原equals方法中的参数类型是封装类型,所传入的参数类型(a)是原始数据类型,所以会自动对其装箱,反之,会对其进行拆箱

  4. 当两种不同类型用比较时,包装器类的需要拆箱, 当同种类型用比较时,会自动拆箱或者装箱?


基本类型与字符串类型之间的相互转换

基本类型->字符串(String)

  1. 基本类型的值+“”

  2. 包装类的静态方法toStirng

  3. String类的静态方法valueOf
    static String valueOf(int i)返回i参数的字符串表示

  		int i1 = 100;
        String s1 = i1+"";
        System.out.println(s1+200);//100200
        String s2 = Integer.toString(100);
        System.out.println(s2+200);//100200
        String s3 = String.valueOf(100);
        System.out.println(s3+200);//100200

String->基本类型
使用包装类的静态方法parsexxx(“字符串内为数值”)
Integer类:static int parseInt(String s)
Double类:static double parseDouble(String s)
int i = Integer.parseInt("2134"); System.out.println(i);//成功 int bc = Integer.parseInt("rewq"); System.out.println(bc);//报错NumberFormatException

重写与重载之间的区别

区别点重载方法重写方法
参数列表必须修改一定不能修改
返回类型可以修改一定不能修改
异常可以修改可以减少或删除,一定不能抛出新的或者更广的异常
访问可以修改一定不能做更严格的限制(可以降低限制)

第一章Collection集合

1.1集合概述

  • 集合是一种java中提供的一种容器用来存储多个数据
  • 数组的长度是固定的,集合的长度是可变的
  • 数组中存储的都是同一类型的元素,可以存储基本数据类型。集合存储的都是对象,而且对象的类型可以不一致

主要的集合类

  • ArrayList

  • LinkedList

  • HashSet(底层 哈希表 存储在这个集合的元素需要同时重写 hashCode + equals 方法)

  • TreeSet

  • properties

  • TreeMap

    • //     遍历方法1.得到所有的key 用 key 再来得到 Map 里面的value
           Set<Integer> b1 = a1.keySet();
              for (Integer integer : b1) {
                  System.out.println(a1.get(integer));
              }
      //       遍历方法2.将Map直接转换成Set,Set里面的每一个元素是Map.Entry
              Set<Map.Entry<Integer,String>> nodes = a1.entrySet();
              for (Map.Entry<Integer, String> node : nodes) {
                  System.out.println(node.getValue());
                  System.out.println(node.getKey());
              }
      

1.2集合框架


java提供了满足各种需要的API,在使用这些API前,先了解其继承与接口操作架构,才能了解何时采用哪个类,以及类之间如何彼此合作,从而达到灵活运用
集合按照基存储结构可以分为两大类,分别是单列集合java.util.Collection与双列集合java.util.Map

  • Collection:单列集合类的根接口,用于存储一系列符合某种规则的元素两个重要的子接口分别是java.util.Listjava.util.Set其中,

List接口

  1. 有序的集合(存储和取出元素顺序相同)

  2. 允许存储重复的元素

  3. 有索引,可以使用普通的for循环遍

List接口方法

以下是list接口特有的常见方法

intindexOf(Object o) 返回此列表中第一次出现的指定元素的索引;如果此列表不包含该元素,则返回 -1。
voidadd (int index, E element) 在列表的指定位置插入指定元素(可选操作)使用较少,效率较低
intlastIndexOf (Object o) 返回此列表中最后出现的指定元素的索引;如果列表不包含此元素,则返回 -1。
Eremove (int index) 移除列表中指定位置的元素(可选操作)。
Eset (int index, E element) 用指定元素替换列表中指定位置的元素(可选操作)。
Eget (int index) 返回列表中指定位置的元素。

ArrayList

最常用的集合

优点:检索速度快,向数据末尾添加元素不受影响

缺点:随机增删数据较慢

LinkList

底层双向链表

随机增删效率较高,检索效率较低。

public boolean add(Object element)

向链表末尾添加一个新节点,该节点中的数据是参数element指定的对象

public void add(int index,Object element)

向链表指定位置添加一个新节点,该节点中的数据是参数element指定的对象

public void addFirist(Object element)

向链表表头添加一个新节点,该节点中的数据是参数element指定的对象

public void addLast(Object element)

向链表表尾添加一个新节点,该节点中的数据是参数element指定的对象

public  Object removeFirst()

删除第一个节点并返回这个节点中的对象

public  Object removeLast()

删除最后一个节点并返回这个节点中的对象

public Object remove(int index)

删除指定位置的节点

public Object get(int index)

得到指定位置的节点

public Object getFirst()

得到链表第一个节点的对象

public Object getLast()

得到链表最后一个节点的对象

int indexOf(Object element) 

返回节点对象element在链表中首次出现的位置,如果链表中无此节点的对象则返回-1

public int lastIndexOf(Object element)

返回节点对象element在链表中最后出现的位置,如果链表中无此节点的对象则返回-1

public Object set(int index,Object element)

将当前链表index位置节点中的对象替换成参数element指定的对象,返回被替换对象

public int size()

返回链表的长度即节点个数

public boolean contains(Object element)

判断链表节点对象中是否含有element

如何将一个线程不安全的集合转换成线程安全的?

使用集合工具类:java.util.Collections 包下

List<String> MYList = new ArrayList<String>();
  Collections.synchronizedCollection(MYList); //使用这个方法

Set接口

  1. 不允许存储重复元素
  2. 没有索引(不能使用普通的for循环遍历)底层大多调用 Map 接口下的一些类

map

  1. Map 与 COllection 没有继承关系
  2. Map集合以 key 和 value 方式存储数据:键值对
    • 存储的都是对象的内存地

两种遍历方法

  1. 直接转成类型为Map.Entry<key,value>的Set
Map<Integer,String> MyMap = new HashMap<>();
        MyMap.put(1,"神奇");
        MyMap.put(2,"真实");
        Set<Map.Entry<Integer,String>> it2 =  MyMap.entrySet();
        for (Map.Entry<Integer, String> integerStringEntry : it2) {
            System.out.println(integerStringEntry.getKey()+ integerStringEntry.getValue());
        }
//这种方法大数据量的时候效率较高,因为 key 和 value都是直接从 node 对象内取
  1. 用 keyset 方法得到Set
Set<Integer> keys = MyMap.keySet();
        for (Integer key : keys) {
            System.out.println(key + "="+ MyMap.get(key));
        }

使用 HashSet 底层需要重写对象的 hashCode 与 equals

HashMap 使用不当时无法发挥性能

假设所有的 HashCode 返回的都是同一个值导致 HashMap变成一个纯单向链表,我们称之为散列分步不均匀。

什么 是散列分步均匀?

假设有10个链表,每个单链表上有 10 个结点,这是最好的。

假设所有的hashCode 返回值都不一样,可以吗?

不行,这样会导致hashMap成为一个数组,没有链表的概念了,也是散列分步不均匀。

散列分步需要你在重写 hashCode 时有一定的技巧。

因为HashSet底层调用的还是 HashMap 所以和 HashMap 一样都需要重写 equanls 与 hashCode 方法

HashMap 集合默认初始化容量是16,默认加载因子是 0.75

默认加载因子是 HashMap底层的数组达到 75% 的容量的时候就会进行一个扩容。

HashMap 初始容量是 2 的倍数,为了提高HashMap 集合的存取容量, 所必须的。

放在 HashMap 集合key部分的,以及放在HashSet 集合中的元素,需要同时 hashCode 与 equals 方法

如果哈希表单向链表中元素超过 8 个,单向链表这种数据结构就会变成红黑树。当红黑树节点小于 6 又会转成单向链表

常用方法

voidclear() 从此映射中移除所有映射关系(可选操作)。
booleancontainsKey (Object key) 如果此映射包含指定键的映射关系,则返回 true
booleancontainsValue (Object value) 如果此映射将一个或多个键映射到指定值,则返回 true
Vget (Object key) 返回指定键所映射的值;如果此映射不包含该键的映射关系,则返回 null
booleanisEmpty() 如果此映射未包含键-值映射关系,则返回 true
Vput (K key, V value) 将指定的值与此映射中的指定键关联(可选操作) 相当于集合的 add。
Set<K>keySet() 返回此映射中包含的键的 Set 视图。
Vremove(Object key) 如果存在一个键的映射关系,则将其从此映射中移除(可选操作)。
intsize() 返回此映射中的键-值映射关系数。
Collection<V>values() 返回此映射中包含的值的` Collection 视图。将所有的 value 转成Collection
Set<Map.Entry<K,V>>entrySet() 返回此映射中包含的映射关系的 Set 视图。 返回 Map.Entry 这个内部类组成的 Set

contains底层所用的都是equals方法


1.3 Collection常用功能

Collection是所有单列集合的父接口因此在Collection接口定义着单列集合中一些通用的方法

  • public boolean add(E e): 把给定的对象添加到当前集合中 。返回一个true和false表示添加成功或者失败

  • public void clear():清空集合中所有的元素。

  • public boolean remove(E e): 把给定的对象在当前集合中删除。同样返回一个布尔值表示添加成功或者失败

  • public boolean contains(Object obj): 判断当前集合中是否包含给定的对象。同样返回一个布尔值表示包含不包含

    • 源码使用equals方法对两个数据进行比较
    • 所以存放在一个集合中的类型一定要重写 equals 方法
    • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-O2jPNB0y-1655898339950)(D:\notebook\文档截图\ArrayList contains 源码.png)]
  • public boolean isEmpty(): 判断当前集合是否为空。

  • public int size(): 返回集合中元素的个数。

  • public Object[] toArray(): 把集合中的元素,存储到数组中


第二章迭代器 iterator

  • 迭代:即Collection集合元素的通用获取方式。在取出元素前先判断有没有元素如果有就取,继续判断,然后往复专业术语称为迭代。

    • 注意我们每当集合的结构发生改变的时候,我们都需要重新获取迭代器,如果还是用之前的老迭代器,就会报异常 : java.util.conCurrentModificationException
    • 当然我们可以用 iterator 本身的 remove、add 等方法

两个常用方法

boolean hasNext()如果仍有元素可以迭代则返回true

E next()返回迭代的下一个元素

Iterator迭代器,是一个接口,我们无法直接使用,需要使用Iterator接口实现类对象获取实现类的方式比较特殊Collection接口中有一个方法,叫Iterator()这个方法返回的就是迭代器的实现类对象

***迭代器的使用步骤(重点)***

1. 使用集合中的iterator()获取迭代器的实现类对象用Iterator接口来接收(多态)

2. 使用Iterator接口中的方法`hasNext()`判断还有没有下一个元素

3. 使用Iterator接口中的方法`next()`取出下一个元素 

   ```java
   while (it.hasNext()){
                System.out.println(it.next());
            }
   
           for(Iterator<String> it2 = coll.iterator();it2.hasNext();){
               System.out.println(it2.next());
           }
   ```

   `coll.iiterator()`获取迭代器的实现类对象,并且会把指针指向集合的-1索引

   `hasNext()`判断有没有下一个元素

   `next()`做了两件事

   1. 取出下一个元素
   2. 会把指针向后移动一位

foreach

底层用的也是迭代器所以不能对元素进行一个增删

Collecttion<E>extends Iterable<E>:所有的单列集合都可以用foreach主要用来遍历集合和数组

格式:for(集合或者数组 变量名:集合名/数组名)

		ArrayList<String> arr = new ArrayList<>();
        arr.add("aaa");
        arr.add("ccc");
        arr.add("eee");
        arr.add("nnbn");
        for (String i :
                arr) {
            System.out.println(i);
        }
//或者是直接用iter+table键

HashSet

HashSet如何传入自定义类

  1. 使用HashSet 自定义类实现 Comparable 接口

    • class Person implements Comparable<Person>{
          int age;
          public Person(int age){
              this.age = age;
          }
      
          @Override
          public String toString() {
              return "Person{" +
                      "age=" + age +
                      '}';
          }
      
          @Override
          public int compareTo(Person o) {
              return this.age-o.age;
          }
      }
      
  2. 写一个类实现 Comparator 接口,并在 new HashSet 时传入这个比较类

    //主函数  
    TreeSet<Person> b1 = new TreeSet<>(new PersonComparator());
    //自定义类
    class PersonComparator implements Comparator<Person>{
    
        @Override
        public int compare(Person o1, Person o2) {
            return o1.age-o2.age;
        }
    }
    

Comparable与Comparator如何选择

如果排序的规则固定就使用 Comparable 如果 排序的规则会改变就使用 Comparator

Collections 工具类

对集合进行排序

Collections.sort(List类对象)

如果是自定义类需要将这个实例对象实现 Compleable 接口

如何对 Set 集合进行排序

 Set<String> ss= new HashSet<>();
      ss.add("ewaqr");
      ss.add("wqrt");
      ss.add("fds");
      ss.add("rew");
//转成 List 集合
      List<String> s2 = new ArrayList<>(ss);
//再进行排序
      Collections.sort(s2);
        for (String s : s2) {
            System.out.println(s);
        }


抽象类

  1. 抽象类如何定义?在class前加 abstract 关键字就行

  2. 抽象类是无法补全化的,无法创建对象

  3. final 和 abstract 不能联合使用,这两具关键字是对立的。

  4. 抽象类的子类可以是抽象类,也可以是非抽象类。

  5. 抽象类虽然无法补全化,但是抽象类有构造方法,这个构造方法是为子类准备的。

  6. 抽象类中不一定有抽象方法,抽象方法必须出现在抽象类中

  7. 抽象方法怎么定义?

    • abstract public class Animal
      
  8. 一个非抽象的类,继承抽象类,必须将抽象类中的抽象方法进行覆盖重写。

接口

  1. 接口也是一种引用数据类型。
  2. 接口是完全抽象的。
  3. 接口怎么定义,语法是什么
    • 「修饰符列表」 interface 接口名{}
  4. 接口支持多继承。
  5. 接口中只包含两部分内容,常量 + 抽象方法。
  6. 接口中所有的元素都是 public 修饰的。(都是公开的)
  7. 接口中抽象方法定义时:public abstract 修饰里可以省略。
  8. 接口中的方法不能有方法体。
  9. 接口中的 public static final 可以省略。
  10. 一个类可以实现多个接口
    • 这种机制弥补了 java 中类和类只支持单继承。实际上单继承是为了简单而出现的,现实世界中存在多继承,实现多个接口,其实就类似于多继承。
  11. extends 和 implements 可以共存, extends 在前,implements 在后。

重写覆盖一个方法不能分配给这个方法比原先方法更低的权限

接口中是public,你写的方法就必须是public

类与类之间如果没有继承关系不能使用强制转型。

如果能够使用多态就尽量使用多态

多态的口诀,编译看左边,运行看右边。

类型与类型的关系

  • is a:
    • Cat is a Animal
    • 凡是满足 is a 的表示“继承关系”
    • A extends B
  • has a:
    • I has a pen
    • 凡是能够满足 has a 关系的表示“关联关系”
    • A{
    • B b;
    • }
  • like a:
    • Cooker like a FoodMenu
    • 凡是能够满足 like a 关系的表示“实现关系”
    • A implements B

接口与抽象类的区别

  1. 抽象类是半抽象的。接口是完全抽象的。
  2. 抽象类中有构造方法。接口中没有构造方法。
  3. 接口和接口之间支持多继承。类和类之间只能单继承。
  4. 一个类可以同时实现多个接口。一个抽象类只能继承一个类。

final 关键字

  1. final 修饰的类无法继承
  2. final 修饰的方法无法覆盖
  3. final 修饰的变量只能赋值一次
  4. final 的引用一旦指向某个对象,便不能重新指向其它对象,但该引用指向的对象内部的数据是可以修改的。
  5. final 修饰的补全变量必须手动初始化。
  6. final 修饰的实例变量一般和 static 联合使用,称为常量

static关键字

  1. static修饰的方法是静态方法
  2. static修饰的变量是静态变量
  3. 所有static修饰的元素都称为静态,都可以使用“类名.”的方式来访问。当然也可以引用.的方式来访问
  • 什么时候成员变量声明为实例变量?
    • 所有对象都有这个属性,但是这个属性的值会随着对象的变化面变化
  • 什么时候成员变量声明为静态变量
    • 所有对象都有这个属性,并且所有对象的这个属性的值是一样的,建议定义为静态变量
  • 静态变量在类加载的时候初始化,内存在方法区中开辟。访问的时候不需要创建对象,直接使用“类名.变量名”的方式来访问

静态代码块

  1. 语法格式:

    static{

    }

  2. 静态代码块在类加载时执行,并且只执行一次。

  3. 静态代码块在一个类中可以编写多个。

泛型

3.1泛型 概述

泛型是一种未知的数据类型,当我们不知道用什么数据类型的时候就用泛型

创建对象的时候就会确定泛型的数据类型

/*创建集合对象不使用泛型
    好处:集合不使用泛型默认就是Object可以存储任意类型的数据
    弊端:不安全会引发异常
    * */
        //ArrayList<String> arr = new ArrayList<>();
        ArrayList list = new ArrayList<>();
        list.add("adc");
        list.add(1);
        Iterator it = list.iterator();
        while(it.hasNext()){
            Object obj  = it.next();
            System.out.println(obj);
            //我们这想要用String类特有的方法,length获取字符串的长度:不能使用多态
            //需要向下转型
            String  s = (String) obj;
            System.out.println(s.length());//java.lang.Integer cannot be cast to java.lang.String
        }
```java
ArrayList<String>  list = new ArrayList<>();
                list.add("fdsa");
                //报错list.add(1);
        /*创建集合对象使用泛型
        好处:
        1. 避免了类型转换的麻烦存储的是什么类型,取出的就是什么类型
        2. 把运行期异常,提升到了编译期
        弊端:
        泛型是什么类型,只能存储什么类型的数据
        * */
```
泛型的定义与使用
//泛型函数的写法
package climb;

public class GennericClass<E> {
    private E name;

    public E getName() {
        return name;
    }

    public void setName(E name) {
        this.name = name;
    }
}
泛型方法的使用
//这个泛型的名字可以随便命名
package climb;

public class GennericClass<E> {
    private E name;
    public <M> void method(M c){
        System.out.println(c);
    }
    public static <S> void method2(S s){
        System.out.println(s);
    }
    //静态方法,通过类名.方法名(参数)可以直接使用

    public E getName() {
        return name;
    }

    public void setName(E name) {
        this.name = name;
    }
}
泛型接口的使用及继承
package climb;

public interface GenericInterface<I> {
    public  void method(I c);
}

//继承泛型接口可改写这个泛型为具体的类型
public class GenericImple implements GenericInterface <String>{

    @Override
    public void method(String c) {
        System.out.println(c);
    }
}


package climb;
/*
第二种方式就是:接口使用什么泛型,实现类就使用什么泛型,类跟着接口走,就相当于
定义了一个含有泛型的类创建对象的时候确定泛型
* */
public class Genericlmple2 <I> implements GenericInterface<I>{
    @Override
    public void method(I c) {

    }
}

泛型通配符

泛型通配符
?:代表任意的数据类型
使用方式
不能创建对象使用
只能作为方法的参数使用

import java.util.ArrayList;
import java.util.Collection;

public class hello_world {
    public static void main(String[] args) {
        ArrayList<String> it1 = new ArrayList<>();
        it1.add("wqe");
        it1.add("fds");
        ArrayList<Integer> it2 = new ArrayList<>();
        it2.add(23);
        it2.add(2);
        PrintArry(it1);
        PrintArry(it2);
    }
     static void PrintArry(ArrayList<?> Arr){
        for (Object o : Arr) {
            System.out.println(o);
        }
    }
泛型限定
  • 泛型的上限限定 ?extends E:表示使用的泛型只能是E的子类/本身
  • 泛型的下限限定 ? super E:表示使用的泛型只能是E类型的父类/本身
package climb;

import java.util.ArrayList;
import java.util.Collection;

public class hello_world {
    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);//报错因为String不是Number的子类或者本身
        getElement1(list3);
        getElement1(list4);//报错Objcect是Number的父类而不是子类
        getElement2(list1);//报错Integer是Number的子类
        getElement2(list2);//报错String与Number类都是Object类的子类
        getElement2(list3);
        getElement2(list4);
    }
    public static void getElement1(Collection<? extends Number> call){};
    public static void getElement2(Collection<? super Number> call){};


}

package climb;

import java.util.ArrayList;

public class hello_world {
    public static void main(String[] args) {
        ArrayList<String> c = new ArrayList<>();
        c.add("wq");
        c.add("er");
        ArrayList<Integer> List = new ArrayList<>();
        List.add(23);
        List.add(25);
        PrintArry(c);
        PrintArry(List);

    }
    public static void PrintArry(ArrayList<?> list){
        for (Object o : list) {
            System.out.println(o);
        }
    }
}

//斗地主实例
package climb;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;

public class hello_world {
    public static void main(String[] args) {
        ArrayList<String> poker = new ArrayList<>();
        String[] colors = {"♥","♣","♠","♦"};
        String[] numbers = {"2","A","K","Q","J","10","9","8","7","6","5","4","3"};
        poker.add("大王");
        poker.add("小王");
        for (String number : numbers   ) {
            for (String color : colors) {
                poker.add(color+number);
            }
        }
        /*对其进行一个洗牌
        使用集合Collection中的方法
        static void shuffle(List<?> list)
        * */
        Collections.shuffle(poker);
        /*3.发牌
         * */
        ArrayList<String> player01 = new ArrayList<>();
        ArrayList<String> player02 = new ArrayList<>();
        ArrayList<String> player03 = new ArrayList<>();
        ArrayList<String> a_hand = new ArrayList<>();
        for (int i = 0; i < poker.size(); i++) {
            String p = poker.get(i);
            if(i>=51) {
                //给底牌发牌
                a_hand.add(p);
            }else if(i%3==0){
                player01.add(p);
            }else if(i%3==1){
                player02.add(p);
            }else if(i%3==2){
                player03.add(p);
            }
        }
        //4.看牌
        System.out.println("刘德华"+player01);
        System.out.println("周润发"+player02);
        System.out.println("周星驰"+player03);
        System.out.println("底牌"+a_hand);
    }
}

数据结构

红黑树

首先知道红黑树并不是AVI(二叉平衡树——左右子树高度差不大于一)趋近于

list接口

java.util.list接口 extends Collection接口
List接口的特点

  1. 有序的集合 存储元素与取出元素的顺序一致

  2. 有索引,包含了一些带索引的方法

  3. 允许存储重复元素
    List接口中带索引的方法(特有)

    • set(int index, E element) 指定索引值修改元素
    • add(int index, E element) 指定索引值添加元素
    • remove(int index) 指定索引值删除元素并返回被移除的元素
    • get(int index) 根据索引值获取元素
    • subList(int fromIndex, int toIndex) 指定开始于结束的索引值截取集合中的元素,返回
      注意:
      操作索引的时候一定不要越界:.
      IndexOutOfBoundsException:索引越界集合会报
      ArrayIndexOutofBoundsException:数组索引越界异常
      StringIndexOutOfBoundsException:字符串索引越界异常
    list集合三种遍历的方式
 /*使用普通的for循环
        * */
        for (int i = 0; i < list.size(); i++) {
            String s = list.get(i);
            System.out.println(s);
        }
        /*使用迭代器
        * */
        Iterator<String> it = list.iterator();
        while (it.hasNext()){
            String i = it.next();
            System.out.println(i);
        }
        /*
        使用增强for
        * */
        for (String s : list) {
            System.out.println(s);
        }

List的子类

ArrayList集合

List 接口大小可变数组的实现,此实现不是同步的(即多线程效率高速度快)
增删慢查找快底层源码用数组实现,每次增加都调用的是数组复制函数

//grow底层源码
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
        @SuppressWarnings("unchecked")
        T[] copy = ((Object)newType == (Object)Object[].class)
            ? (T[]) new Object[newLength]
            : (T[]) Array.newInstance(newType.getComponentType(), newLength);
        System.arraycopy(original, 0, copy, 0,
                         Math.min(original.length, newLength));
        return copy;
    }
LinkedList集合

底层为链表实现增删快,查询慢。
LinkedList是一个双向链表,
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bcu7zwLG-1655898339952)(img.png)]
addFirst(E e)将指定元素插入此列表的开头等效于push
addLast(E e)将指定元素添加到此列表的结尾等效于add
getFirst()返回此列表中的第一个元素。 没有
E getLast()返回此列表中的最后一个元素。
removeFirst(E e)移除并返回此列表的第一个元素等效于Pop
removeLast(E e)移除并返回此列表的最后一个元素
pop() 从此列表表示的堆栈中弹出一个元素。
isEmpty() 返回这个链表是否为空
void push(E e) 将元素推送到由此列表表示的堆栈上。
注意写LinkedList集合不要使用多态因为多态看不到子类特有的方法

Set

Set接口extends Collection接口
Set

  1. 不允许存储重复的元素

  2. 没有索引,没有带索引的方法,也不能通过普通的for
    HashSet特点

  3. 不允许存储重复的元素

  4. 没有索引,没有带索引的方法,也不能通过普通的for

  5. 一个无序的集合,存储与取出的顺序可能不一致

  6. 此类实现Set接口,由哈希表(实际为HashMap实例)支持
    java.util.HashSet集合 implements Set接口

遍历由iterator与foreach完成

HashSet里的Hash表

hashCode() 方法用于返回字符串的哈希码。
字符串对象的哈希码根据以下公式计算:
hasCode源码
public native int hashCode();
native:代表该方法调用的是本地的本地操作系统的方法
toString方法的源码


public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}

同时哈希值也是对象的地址值同时我们可以在对象
重写hascode方法
同时String类重写了hascode方法返回的值都是 96354
String类的本质是字符数组char[],其次String类是final的,是不可被继承的,这点可能被大多数人忽略,再次String是特殊的封装类型,使用String时可以直接赋值,也可以用new来创建对象,但是这二者的实现机制是不同的。还有一个String池的概念,Java运行时维护一个String池,池中的String对象不可重复,没有创建,有则作罢。String池不属于堆和栈,而是属于常量池(应该位于方法区)。

下面分析上方代码的真正含义

String strA = “abc”;

String strB = “abc”;

第一句的真正含义是在String池中创建一个对象”abc”,然后引用时strA指向池中的对象”abc”。第二句执行时,因为”abc”已经存在于String池了,所以不再创建,则strAstrB返回true就明白了。strB”abc”肯定正确了,在String池中只有一个”abc”,而strA和strB都指向池中的”abc”,就是这个道理。
jdk.1.8版本之后:
哈希表=数组+链表
哈希表=数组+红黑树(提高查询速度)

HashSet存储不重复数据的流程

先为这个对象用hascode生成一个值
如果没有就放进去如果有就用equals方法比较是否相等如果不相等就在后面继续添加元素如果相等就添加

存储流程为我们存储自定义类型元素提供了解决方案


finally代码块

格式:

 try{

可能产生异常的代码块

}catch(定义一个异常的变量,用来接收try中抛出的异常对象){

异常的处理逻辑,异常对象之后怎么处理异常对象,一般在工作中会把异常的信息记录到一个日志中

}finally  {

无论是否出现异常都会执行

}

**注意 **

  1. finally不能单独使用,必须和try一起使用
  2. finally类一般用于资源释放(资源回收),无论程序是否出现异常都要释放(io)

关于java中的package和import机制

  1. 为什么要使用 package
    • package是 java 中包机制。 包机制是为了方便程序的管理。不机功能的类分别存放在不同的包下。
  2. package怎么用?
    • package 是一关键字,后面加上包名。例如:package com.bjpowernode.javase.chapter17
    • 注意:package语句只鸡毛出现在java源代码的第一行。
  3. 包名有没有命名规范?
    • 有,一般都采用公司域名倒序的方式
    • 公司域名倒序 + 项目名 + 模块名 + 功能名

访问控制权限

  1. 访问控制权限都有哪几个,分别是什么。
    • 4个
    • privete
    • public
    • protected
    • 默认
  2. 以上4个访问控制权限:控制的范围是什么?
访问控制修饰符本类同包子类任意位置
public可以可以不行可以
private可以不行不行不行
protected可以可以可以不行
默认可以可以不行不行

public > protected >默认 >privete

面向对象

面向过程和面向对象的区别

  • 面向过程:主要关注点是实现的具体过程,因果关系。
    • 优点: 对于业务逻辑比较简单的程序,可以快速开发,投入成本低。
    • 缺点:采用面向过程的方式开发很难解决非常复杂的业务逻辑,面向过程导致软件元素之间的“耦合度”非常高,只要一环出了问题整个系统受到影响导致最终的软件扩展力低。
  • 面向对象:

JDK、JRE、JVM三者之间的关系

JDK : Java 开发工具箱

JRE:java 运行环境

JVM:Java 虚拟机

JDK包括 JRE,JRE 包括 JVM。

安装 JDK 的时候:JRE就自动安装,同时JRE内部的 JVM 也就自动安装了。

项目部署一下,跑个项目,我们只需要安装JRE就行了。

Java体系的技术被划分为三大块:

JavaSE:标准版

JavaEE:企业版

JavaME:微型版

编译生成的字节码文件扩展名:xxx.class

Java文件就是源文件,这个文件中编写源代码。

另外需要注意的是:1个 Java 源文件是可以编译生成多个 class文件的。最终运行的是class文件

问题:字节码文件是二进制文件吗?

字节码文件不是二进制文件

运行期

如果是在Linux上运行,需要将window上生成的class文件拷贝过去。

使用 JDK 自带的一个命令/工具 : java(负责运行命令)执行字节码

往下的步骤就全部交给 JVM 了,然后进行解释生成二进制文件。

操作系统直接识别二进制文件,与硬件进行交互。

在以上运行过程中,需要使用两个非常重要的命令。

javac 命令,负责编译。

java 命令,负责运行

IO 流

什么是 IO 流

I: input

O: output

通过流的分类可以完成硬盘的读和写

IO流的分类

  1. 以流的方向分类

    • 输入流
    • 输出流
  2. 按照读取数据的方式来分

    • 一次读一个 byte,什么文件都能读
    • 一次读一个字符,为了读取纯文本文件存在。

    java 所有的流都在 java.io.* 下

    java 中以 Stream 结尾的都是字节流 以「Reader、writer」结尾的都是字符流

    所有的流都实现了 Closeable 这个接口, 都是可关闭的,都有 close() 方法。

    每个流都会占用很多资源,所以我们用完流必须关闭

    所有的输出流都实现了 Flushable 这个接口,所以所有的输出流都是可刷新的。

    养成一个好习惯,**输出流在最终输出后一定要记得 flush() 刷新一下,**这个刷新表示将这个管道中未输出的强行输出完——清空管道。

    如果没有 flush 可能会导致数据的丢失

需要掌握的16个流

  1. 文件专属

    FileReader

    FileReader reader  =null;
    try {
        reader = new FileReader("D:\\Typora\\temp.txt");
        //使用 char 数组来接收
        char [] chars = new  char[4];
        int readCount = 0;
        while ((readCount = reader.read(chars))!=-1){
            System.out.print(new String(chars,0,readCount));
        }
    } catch (IOException e) {
        e.printStackTrace();
    }finally {
        if (reader != null) {
            try {
                reader.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
    }
    

    FileWriter 只能操作纯文本

     FileWriter fw = null;
    try {
        fw = new FileWriter("D://Typora//temp.txt");
        char [] chars = {'我','是','中','国','人'};
        fw.write(chars);
        //这个类的write可以直接使用 String
        fw.write("我是一名java软件工程师");
        fw.write("\n");
        fw.write("hello_world");
    } catch (IOException e) {
        e.printStackTrace();
    }finally {
        if (fw != null) {
            try {
                fw.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    

    FilterInputStream 主要方法 reade(),available(),skip() 重点

    int readeData=1;
            FileInputStream fis = null;
            try {
                fis = new FileInputStream("D:\\Typora\\temp.txt");
                byte[] bytes = new byte[1024];
                int readCount = 0; 
                while((readCount= fis.read(bytes))!=-1){
                    System.out.print(new String(bytes,0,readCount));
                }
    
            } catch (IOException e) {
                e.printStackTrace();
            }finally {
    //            在 finally 代码块中关闭流
                if (fis != null) {
    //                避免空指针异常
                    try {
                        fis.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
    

    FilterOutputStream 主要方法 write() 重点

      FileOutputStream fis = null;
            try {
                //当这个文件不存在的时候会自动新建、
                //这种方式谨慎使用会原文件清空再重新写入
    //            fis = new FileOutputStream("D://Typora//temp.txt");
    //            以追加的方式在文件末尾写入,不会清空原文件的内容
                fis = new FileOutputStream("D://Typora//temp.txt",true);
                String s = "我是一个中国人,我骄傲!";
                byte[] bytes = s.getBytes();
    
                fis.write(bytes);
                fis.flush();
    
            } catch (IOException e) {
                e.printStackTrace();
            }finally {
                if (fis != null) {
                    try {
                        fis.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
    

    文件拷贝

     FileOutputStream fos = null;
           FileInputStream fis = null;
            try {
                fis = new FileInputStream("D://Typora//temp.txt");
    
                fos = new FileOutputStream("D:\\temp.txt");
                byte[] bytes = new byte[1024*1024];
                int readCount = 0;
                while((readCount = fis.read(bytes))!=-1){
                    fos.write(bytes,0,readCount);
                }
    //            刷新这个输出流
                fos.flush();
            } catch (IOException e) {
                e.printStackTrace();
            }finally {
    //分开try 不然当其中一个出了异常可能会影响另一个流的关闭
                if (fis != null) {
                    try {
                        fis.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if (fos == null) {
                    try {
                        fos.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
    
  2. 转换流(将字节流转换成字符流)

    InputStreamReader

    //       字节流
            FileInputStream fi = null;
            InputStreamReader ir = null;
            BufferedReader br = null;
            try {
    //            fi = new FileInputStream("D://Typora//temp.txt");
    //            通过流转换,将字节流转换成字符流
    //            ir = new InputStreamReader(fi);
    //            这个构造方法只能传一个字符流不能传字节流
    //            合并
                br = new BufferedReader(new InputStreamReader(new FileInputStream("D://Typora//temp.txt")));
                String line = null;
                while((line=br.readLine())!=null){
                    System.out.println(line);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }finally {
                if (br == null) {
                    try {
                        br.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
    

    OutputStreamWriter

  3. 缓冲流专属

    使用这个流不需要自定义 char 数组或 byte 数组,自带缓冲。并且关闭这个流内部的节点流会自动关闭

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rqDSGzsf-1655898339952)(D:\notebook\文档截图\BufferReader的close方法.png)]

    BufferedInputStream
    BufferedOutputStream
    BufferedReader
    BufferedWriter

  4. 数据流专属

    DataInputStream 这两个流的读写顺序需要一致才能正常读写,并且两个流都是包装流
    DataOutputStream

  5. 标准输出流

    PrintStream 重点

    package climb;
    
    import java.io.FileNotFoundException;
    import java.io.FileOutputStream;
    import java.io.PrintStream;
    import java.text.SimpleDateFormat;
    import java.util.Date;
    
    public class Logger {
    //    记录日志的方法
        public static void log(String msg) {
            try {
                PrintStream ps = new PrintStream(new FileOutputStream("D://Typora//temp.txt"));
                System.setOut(ps);
                Date nowTime = new Date();
                SimpleDateFormat  sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
                String strTime = sdf.format(nowTime);
                ps.println(strTime+":"+msg);
    
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            }
        }
    }
    

    主函数中调用

     Logger.log("你好");
    

    PrintWriter

  6. 对象专属

    ObjectInputStream 重点
    ObjectOutputStream 重点

idea 默认当前路径是默认当前项目的根

File 类

File 类本身并不能完成文件的读和写。

File 类对象可以是一个文件也可以是一个目录 File 只是一个路径名抽象的表示形式

需要掌握 File 类的常用方法

  • getParent()
  • getAbsolutePath()
  • getName()
  • isDirctory()
  • isFile()
  • lastModifyed()
  • mkdir()
  • mkdirs()
  • length()
 File f1 = new File("D://Typora//temp.txt");
//    判断是否存在
//        获取父路径
        String parent = f1.getParent();
        System.out.println(parent);
//        获取父文件
       File f2 =  f1.getParentFile();
      String c2 =  f2.getAbsolutePath();
        System.out.println("获取绝对路径"+ c2);
//        获取文件名
        System.out.println("获取文件名"+f1.getName());
//        判断是否是一个目录
        System.out.println(f1.isDirectory());
//        判断是否是一个文件
        System.out.println(f1.isFile());
//        返回这个文件最后一次修改的时间 这个毫秒是从格林日期至今的时间
        long l1 = f1.lastModified();
        System.out.println(l1);
//        如何将这个毫秒转换成日期?
        Date D2 = new Date(l1);
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
        String modifyedTime  = sdf.format(D2);
        System.out.println(modifyedTime);
//        如果不存在以文件的形式创建出来
        /*if(!f1.exists()){
            try {
                f1.createNewFile();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }*/
        如果不存在以目录的形式创建出来
//        if (!f1.exists()) {
//            f1.mkdir();  
//        } 
//        可以创建一个多重目录,用 mkdirs 命令
//        获取文件大小
        System.out.println(f1.length());//返回的单位是字节
//        获取当前目录下的所有的子文件
        File[] f5 = f2.listFiles();
        for (File file : f5) {
            System.out.println(file.getName());
        }

作业目录拷贝

package climb;


import java.io.*;
import java.text.SimpleDateFormat;
import java.util.*;

public class hello_world {

    public static void main(String[] args) {
        File srcFile = new File("D:\\notebook\\back end");
        File DestFile = new File("D:\\idm\\file");
        copyDir(srcFile,DestFile);
        }

   private static void   copyDir(File srcFile, File destFile){
       if(srcFile.isFile()){
//           如果是一个文件递归结束并且传输文件
            FileInputStream fileInputStream = null;
            FileOutputStream fileOutputStream = null;
           try {
               fileInputStream = new FileInputStream(srcFile);
               String destDir =destFile.getAbsolutePath().endsWith("\\")?destFile.getAbsolutePath(): destFile.getAbsolutePath()+"\\"+ srcFile.getAbsolutePath().substring(3);
               fileOutputStream = new FileOutputStream(destDir);
               int readCount =0;
               byte[] bytes = new byte[1024*1024];
               while((readCount = fileInputStream.read(bytes))!=-1){
                   fileOutputStream.write(bytes,0,readCount);
               }
               //               刷新输出流
               fileOutputStream.flush();
           } catch (IOException e) {
               e.printStackTrace();
           }finally {
//               关闭流
               if (fileOutputStream != null) {
                   try {
                       fileOutputStream.close();
                   } catch (IOException e) {
                       e.printStackTrace();
                   }
               }
               if (fileInputStream != null) {
                   try {
                       fileInputStream.close();
                   } catch (IOException e) {
                       e.printStackTrace();
                   }
               }
               return ;
           }

       }
       File[]   files   = srcFile.listFiles();
       for (File file : files) {
           if(file.isDirectory()){
//               如果是一个目录的话
              String srcDir = file.getAbsolutePath();
              String destDir =destFile.getAbsolutePath().endsWith("\\")?destFile.getAbsolutePath(): destFile.getAbsolutePath()+"\\"+ srcDir.substring(3);
              File newFile = new File(destDir);
//              如果这个目录不存在就新建一个目录
              if(!newFile.exists()) {
                  newFile.mkdirs();
              }
           }
//           递归调用
           copyDir(file,destFile);
       }
    }


}

序列化与反序列化

如果需要将某个对象保存到磁盘上或者通过网络传输,那么这个类应该实现 Serializable 接口或者 Externalizable 接口之一。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xb7fKTBz-1655898339954)(D:\notebook\文档截图\序列化与反序列化.png)]

  1. java.io.NotSerializableException 对象不支持序列化,

  2. 参与序列化的对象必须实现 Serializable 接口

  3. Serializable 这个接口通过源码发现,它只是一个标志性的接口

    • public interface Serializable {
      

    }

     
    + 那么它起到一个什么待遇? java 虚拟机看到这个类实现了这个接口,会自动生成一个序列化版本号 
    
    
  4. 序列化多个对象,我们可以将这多个对象放在一个集合中然后把这个集合进行序列化

    • List<Student> list = new ArrayList<>();
             list.add(new Student("唐三",23));
             list.add(new Student("小舞",22));
             list.add(new Student("王胖子",24));
             ObjectOutputStream objectOutputStream = null;
             ObjectInputStream objectInputStream = null;
              try {
                  objectOutputStream = new ObjectOutputStream(new FileOutputStream("斗罗"));
                  objectOutputStream.writeObject(list);
                  objectInputStream = new ObjectInputStream(new FileInputStream("斗罗"));
                  Object  obj = objectInputStream.readObject();
                  System.out.println(obj);
              } catch (IOException | ClassNotFoundException e) {
                  e.printStackTrace();
              }finally {
                  if (objectOutputStream != null) {
                      try {
                          objectOutputStream.close();
                      } catch (IOException e) {
                          e.printStackTrace();
                      }
                  }
              }
      
    • transient 关键字表示游离,用其修饰的属性的不参与序列化

  5. java 使用什么机制进行区分类的?

    • 通过类名进行比对,如果类名不一样,肯定不是同一个类
    • 如果类名一样,再怎么进行类的区分?靠序列化版本号进行区分
  6. 但如果我之前写一个类实现了 Serializable 接口,后续想改它就会重新生成一个全新的序列化版本号,conclusion:凡是一个类实现了这个接口,建议给这个类提供一个固定不变的序列化版本号

    •  private static final long serialVersionUID = 1L;
      

Io + Properties 联合使用

一个非常好的理念:

​ 以后经常改变的数据,可以单独写到一个文件中,使用程序动态读取将来只需要修改这个文件的内容,java 代码不需要改动,不需要重新编译,服务器也不需要重启就可以使使用动态信息。

​ 类似于以上机制的这种文件被称为配置文件:

​ 并且当配置文件中的内容格式是:

​ key = value

​ key = value

的时候我们把这种配置文件叫做属性配置文件

java 规范中有要求:属性配置文件建议以 .properties 结尾,但这不是必须的。

其中 Properties 对象是专门存放属性配置文件内容的一个类

 FileInputStream fileInputStream = null;
        Properties properties = new Properties();
        try {
            fileInputStream = new FileInputStream("2021demon\\UserInfo");
            properties.load(fileInputStream); //文件数据顺着管道进入 Map 集合中 其中等号左边的做 key
//            右边的做 value
            for (Object o : properties.keySet()) {

                System.out.println(properties.getProperty(o.toString()));
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (fileInputStream != null) {
                try {
                    fileInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

注意事项

建议使用 key 和 value 这间使用 = 的方式

属性配置文件 key 重复的话 value 自动覆盖

key 与 valu 之间最好不要有空格

多线程

什么是进程什么是线程

进程是一个应用程序,线程是一个进程中的执行场景/执行单元,一个进程可以启动多个线程。

进程A与进程B的内存独立不共享,线程A和线程B,堆内存和方法区内存共享。

java 中之所以有多线程机制,目的就是为了提高程度的处理速度。

使用了多线程机制后,main 方法结束只是主线程结束了,主栈空了,其它的栈可能依然在弹栈压栈。

对于单核 cpu 来说做不到真正的多线程并发,但是可以给人一种多线程并发的感觉

实现线程有三种方式

  1. 编写一个类,直接继承 java.lang.Thread,重写 run 方法

    •     public static void main(String[] args) {
              MyThread myThread = new MyThread();
      //        myThread.run(); 不会启动线程,不会分配新栈(这种方式就是单线程)
      
              myThread.start();//start 的作用启动一个分支线程,在jvm中开辟一个空间只要空间开辟出来了它的任务就完成了
      //        注意: 亘古不变的道理 方法体中的代码永远都是自上而下的顺序依次运行的
              for (int i = 0; i < 100; i++) {
                  System.out.println("主线程开始了"+i);
              }
          }
      }
      class  MyThread extends Thread{
          @Override
          public void run() {
              for (int i = 0; i < 100; i++) {
                  System.out.println("分支线程"+i);
              }
          }
      }
      
  2. 写一个类实现 Runnable 接口

    • //定义一个可运行的类
      public class Myrunnable implements Runnable{
          public void run (){
              
          }
      }
      //创建线程对象
      Thread t = new Thread(new MYrunnable());
      //启动线程
      t.start();
      

第二种更常用

  1. 实现 Callable 接口

    •  FutureTask task = new FutureTask(new Callable() {
                  @Override
                  public Object call() throws Exception {
      //                模拟执行
                      System.out.println("call method begin");
                      Thread.sleep(1000*10);
                      int a = 10;
                      int b=20;
                      System.out.println("call method over");
                      return a+b;
                  }
              });
              Thread t = new Thread(task);
              t.start();
              try {
                   Object obj = task.get();
                  System.out.println(obj);
              } catch (InterruptedException e) {
                  e.printStackTrace();
              } catch (ExecutionException e) {
                  e.printStackTrace();
              }
              //get 方法执行会导致当前线程阻塞
              //优点 可以拿到当前线程的结果
              //缺点 阻塞当前线程,效率较低
              System.out.println("hello world");
      

匿名内部类

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IMrC1zxg-1655898339955)(D:\notebook\文档截图\匿名内部类.png)]

线程常用方法

  1. 获取当前线程对象

    • Thread t = Thread.currentThread()
      
  2. 获取线程名字

    • myrunnalbe.getName();
      
  3. 修改线程名字

    • myrunnalbe.setName("张三")
      
  4. 线程的sleep方法

    • Thread.sleep(1000*5);
      
    • 可以实现间隔特定的时间,执行特定的代码

  5. 中断睡眠

    • Thread t1 = new Thread(new MyThread());
      t1.start();
       t1.interrupt();//这种方式是依靠 java 中的异常的方式
      
  6. 终止线程

    • public class hello_world {
      
          public static void main(String[] args) {
              MyThread myThread = new MyThread();
              Thread t1 = new Thread(myThread);
              t1.start();
      
              try {
                  Thread.sleep(1000*5);
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
      //        需要什么时候终止这个类的执行,将这具标记改为 false 就可以了
              myThread.run = false;
      
          }
      }
      class  MyThread implements Runnable{
          boolean run = true;
          @Override
          public void run() {
              for (int i = 0; i < 10; i++) {
                  if(run){
                      try {
                          System.out.println(Thread.currentThread().getName()+"--->");
                          Thread.sleep(1000);
                      } catch (InterruptedException e) {
                          e.printStackTrace();
                      }
                  }else{
                      return ;
                  }
      
              }
      
          }
      }
      

线程调度

Thread.yield()方法作用是:暂停当前正在执行的线程对象,并执行其他线程。

yield()应该做的是让当前运行线程回到可运行状态,以允许具有相同优先级的其他线程获得运行机会。因此,使用yield()的目的是让相同优先级的线程之间能适当的轮转执行。但是,实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。

结论:yield()从未导致线程转到等待/睡眠/阻塞状态。在大多数情况下,yield()将导致线程从运行状态转到可运行状态,但有可能没有效果

线程调度有两种一种是抢占式,谁的优先度高的就先用谁.一种是均分式,平均分配时间.java 采用抢占式.

实例方法

​ void setPriority(int newPriority) 设置线程的优先级

​ int getPriority() 获取优先级

​ void join() 合并线程 当前线程陷入堵塞,实例线程执行,直到实例线程执行完毕,当前线程才可以继续执行.

Thread thread = new Thread(new MyThread());
try {
            thread.join();//当前线程受到阻塞,我们开辟的线程执行
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

​ 最低优先级1

​ 最高优先级10

​ 默认优先级5

​ 优先级高的获得 cpu的时间片大概率会多一些

静态方法

​ static void yield() 暂停当前线程,执行其它线程,这个方法并不是阻塞而是让给其它的线程使用.这个方法的执行会让它从运行状态回到就 绪状态,但仍有机会抢到

多线程并发环境下,数据的安全问题

我们编写的代码都将放在一个多线程的环境下运行,我们更加需要关注数据的安全问题

什么时候我们需要关心数据在多线程并发环境下会存在安全问题?

三个条件

  1. 多线程并发
  2. 有共享数据
  3. 共享数据有修改的行为

如何解决线程安全问题?

线程排除执行,解决并发,这种机制被称为线程同步机制,同样这种机制会牺牲一部分效率.

说到线程同步涉及到两个专业术语.

异步编程模型,

同步编程模型.

这两个模型直接按照前端的同步异步来理解就可以了

线程同步机制的语法

synchronized (){
        //线程同步机制
    }
//这个小括号填的内容相当关键,这个数据必须是多线程共享的数据,才能达到多线程排队
//假设一共有5个线程 t1,t2,t3,t4,t5 我们只想要t1,t2,t3 进行排队我们就填 t1,t2,t3 共享的数据但对于 t4,t5 来说不是共享的
//如果实在想不到有什么共享的数据直接填 this

在 java 中任何对象都有一把锁,其实这把锁就是标记(只是将它叫做锁) 100 个对象就有 100 把锁, 1 个对象一把锁.

synchronize 语法解析

假设 t1 与 t2 并发执行synchronized 代码块的时候肯定一个前一个后,

假设 t1 先执行的遇到 synchronized 先找共享对象这把锁,找到后并占有这把锁开始执行代码,在执行过程中一直都是占有这把锁,直到同步代码块结束这把锁释放.

当 t1 占有这把锁的时候,t2 没有这把锁只能排队等待,直到 t1 将这个同步代码块执行完成归还了这锁, t2 开始占用这把锁执行程序.

  1. 作为代码块
```java
synchronized (){
        //如果括号内填写 this 是 new 同一个对象的线程同步,
    //如果填写 "abc" 是所有的线程都要同步
    //如果直接传一个 null 会报 nullPointException
    }
```
  1. 作为关键字

    synchronized 出现在实例方法作为关键字的话一定锁的是 this 所以这种方法不灵活,并且这种方式表示整个方法体都需要同步,可能会无故扩大同步的范围,导致程序效率降低,所以这种方法不常用.

  2. 在静态方法上使用 synchronized 排它锁

    表示找类锁,类锁永远只有一把.就算创建了 100 个对象也只有一个类锁

如何解决线程安全问题

是一上来就使用 sythronized 吗? 不是使用 sythronized 会让执行效率降低,用户体验不好,系统的用户吞吐量降低,用户体验差,在不得已的情况下使用 sythronized 关键字

  1. 尽量使用局部变量代替实例变量与静态变量
  2. 如果必须是实例变量可以考虑创建多个对象,这样实例变量的内存就不共享了(一个线程对应一个对象,100个线程对应100 个对象,对象不共享就没有线程安全问题了)
  3. 如果不能使用局部变量,对象也不能创建多个这个时候就使用 sythronized .线程同步机制

java中有三大变量

  1. 局部变量:在栈中 在类的方法中定义,只有在方法中能够访问
  2. 实例变量:在堆中,声明在类里
  3. 静态变量:在方法区中 static 修饰

以上三大变量中局部变量永远都不会有线程安全问题,所以局部变量永远不共享

实例变量在堆中,堆只有一个

静态变量在方法区中,方法区只有一个所以它们的数据是共享的,可能存在线程安全问题

如果使用局部变量

String Buffer与String Builder 推荐使用 String Builder,因为String Buffer效率比较低.

另外注意 ArrayLIst 是非线程安全的.

Vector 是线程安全的.

HashSet,HashMap 是非线程安全的

Hashtable 是线程安全的

死锁

package climb;


import java.io.*;
import java.text.SimpleDateFormat;
import java.util.*;

public class hello_world {

    public static void main(String[] args) {
        Object o1 = new Object();
        Object o2 = new Object();
        Thread to1 = new MyThread(o1,o2);
        Thread to2 = new MyThread2(o1,o2);
        to1.start();
        to2.start();

    }
}
class  MyThread  extends Thread{
    Object o2;
    Object o1;
    public MyThread (Object o1,Object o2){
        this.o1 = o1;
        this.o2=o2;
    }
    @Override
    public void run() {
        synchronized (o1){//先锁的o1
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (o2){

            }
        }
    }
}
class MyThread2 extends Thread{
    Object o2;
    Object o1;
    public MyThread2 (Object o1,Object o2){
        this.o1 = o1;
        this.o2=o2;
    }
    @Override
    public void run() {
        synchronized (o2){//然后锁的o2 两边都不放行
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }synchronized (o1){
            }
        }

    }
}

所以开发后最好不要使用sychronized 嵌套否则一旦出现死锁就很难整

守护线程

java 中线程分为两类

  1. 一类是守护线程
  2. 一类是用户线程

其中有代表性的就是垃圾回收线程(守护线程)

守护线程的特点:

​ 一般守护线程就是一个死循环,所有的用户线程结束,守护线程自动结束.主线程 main 方法是一个用户线程.

BackDataThread backDataThread = new BackDataThread();
        Thread t1 = new Thread(backDataThread);
        t1.setName("备份数据的线程");
        t1.setDaemon(true);//这个是重要的,就是这句代码的作用将其变成守护者
        t1.start();
//下方类的实现
 @Override
    public void run() {
        while(true){
            System.out.println(Thread.currentThread().getName()+"---->");
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

定时器

使用Timer 类,但使用框架的时候我们不会用这个类,有更好的解决方案

 Timer time = new Timer();
       SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date firstime = null;
        try {
            firstime= simpleDateFormat.parse("2021-8-28 2:32:00");
        } catch (ParseException e) {
            e.printStackTrace();
        }
//LongTimeTask 类需要继承 TimerTask抽象类
        time.schedule(new LongTimeTask(),firstime,1000);

关于Object 的 notify 方法与 wait 方法

wait 方法与 notify 方法不是线程对象所特有的方法,是 java 中任何一个对象都有的方法,因为这两个方式是Object 自带的.

wait 方法

Object  o = new Object();
o.wait();
//表示让正在o对象上活动的线程进入等待状态,无期限等待,直到调用 o.notify() 方法
//o.wait 会让在 o上活动的当前线程进入等待状态,并且释放之前占有的 o状态的锁

notify 方法

表示唤醒正在 o 对象上等待的线程,还有一个 notifyAll 方法 这个方法是唤醒 o 对象上处于等待的所有的线程但会释放锁

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RlGd6jqr-1655898339956)(D:\notebook\文档截图\notify方法与wait方法.png)]

package climb;


import java.io.*;
import java.sql.Time;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;

public class hello_world {

    public static void main(String[] args) {
        List list = new ArrayList();
        Thread t1 = new Thread(new T1(list,0));
        Thread t2 = new Thread(new T2(list,1));
        t1.setName("t1");
        t2.setName("t2");
        t1.start();
        t2.start();

    }
}

//生产线程
class T1 implements Runnable{
    List list;
    Integer i ;

    public T1(List list, Integer i) {
        this.list = list;
        this.i = i;
    }

    @Override
    public void run() {
        while(true){
            synchronized (list){
                if(list.size()>0){
                    try {
                        list.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println(Thread.currentThread().getName()+"->"+i);
                i++;
                list.add(i);
                list.notify();
            }
        }

    }
}
class T2 implements Runnable{
    public T2(List list, Integer i) {
        this.list = list;
        this.i = i;
    }

    List list;
    Integer i ;
    @Override
    public void run() {
        while(true){
            synchronized (list){
                if(list.size()==0){
                    try {
                        list.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println(Thread.currentThread().getName()+"->"+i);
                i++;
                list.remove(0);
                list.notify();
            }
        }
    }
}

反射机制

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sR4dpc7T-1655898339957)(D:\notebook\文档截图\类加载图.png)]

相关的类有哪些

java.lang.Class 代表字节码文件 代表整个类

java.lang.reflect.Method 代表字节码中的方法字节码 代表类中的方法

java.lang.reflect.Constructor 代表字节码中的构造方法字节码 代表类中的构造方法

java.lang.reflect.Field 代表字节码中的属性字节码 代表类中的成员变量

利弊

  1. 优点动态的创建和使用对象,使用灵活,没有反射机制框架技术就失去底层支撑
  2. 使用反射基本是解释执行,会影响运行效率
    • 抬高调用优化,关闭访问检查
    • Method 和 Field 、construct 都有一个 setAccessible方法
    • 这个使用是启动和禁用访问安全检查的开头
    • 参数为 true 表示,反射的对象在使用时取消访问检查,提高反射效率,反射为 false 则表示反射的对象执行检查

class 类

  1. Class 也是类,因此也继承 Object
  2. Class 类对象不是 new 出来的,而是系统创建出来的
  3. 对于某一个类对象在内存中只有一份,因为类加载只有一次
  4. Class 对象存放在堆中
  5. 类的字节码了二进制数据,是放在方法区中(包括变量,方法,构造方法,变量名,方法名)

获取 class 的三种方式

  1. 通过 Class.forName 的方式

    这个方法会导致类加载,当一个类,类加载的时候,它的静态代码块就会执行

    如果你只是希望一个类的静态代码块执行,其它代码一律不执行你可以使用

    Class.forName(“完整类名”)

 Class c2; Class c3;Class c1;

    {
        try {
            c2 = Class.forName("java.util.Date");
            c1 = Class.forName("java.lang.String");
            c3 = Class.forName("java.lang.System");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
  1. 通过类型的 getClass 的方式

    String s = "2343";
    Class a = s.getClass();
    
  2. 使用 Class 属性

     Class b = String.class;
    

字节码文件装载到 JVM 的时候只装载一份

反射机制的灵活性.

java 代码写一遍,在不改变 java 源代码的基础上,可以做到不同对象的实例化

符合OCP 开门原则: 对扩展开放,对修改关闭.

后期的高级框架包括: ssh ssm Spring SpringMVC MyBatis Hibernate 都使用了反射机制,所以反射机制还是非常好用的.学会反射机制有利于你理解剖析框架底层源代码

类加载

  1. 静态加载:编译时加载相关的类,也就是通过反射机制来实现类加载,依赖性较强
  2. 动态加载:运行时加载需要的类,如果不运行就不用该类,则不报错

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9TfedD8O-1655898339958)(D:\notebook\文档截图\类加载.png)]

反射类中的属性

 try {
            Class StudentClass = Class.forName("climb.Student");
//            这里拿到的是 public 修饰的属性
           Field[] files =  StudentClass.getFields();
            System.out.println(files.length);
//            这时拿到是已经声明的属性
            files =  StudentClass.getDeclaredFields();
            System.out.println(files.length);
            for (Field file : files) {
//                获取属性的修饰符列表
                int files1 =  file.getModifiers();//返回的修饰符列表是一个数字,每一个数字是一个代号
                System.out.println(Modifier.toString(files1));
//                获取属性的类型
//                Class fileType = file.getType();
//                System.out.println(fileType.getName());
//                System.out.print(fileType.getSimpleName()+"=");
//                System.out.println(file.getName());
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

关于路径

fileInputStream = new FileInputStream("2021demon\\UserInfo.properties");

这种方式的路径缺点是:移植性差,在 IDEA 默认的当前的路径是 Project的根,

这个代码假设离开了 IDEA 换到了其它的位置可能当前的路径就不是 IDEA 的根了,这时这个路径就无效了.

使用以下通用方式的前提是:这个文件必须在类路径下,即在 src 路径下

image-20210828194434448
//        直接以流的形式返回
        InputStream inputStream = Thread.currentThread().getContextClassLoader().
                getResourceAsStream("climb\\Student.class");

取代原先的 properties + Io 流这种写法

//        资源绑定器只能绑定 xxx.properties 文件,并且这个文件必须在类路径下,并且在写路径的时候,路径后面的扩展名不能写
        ResourceBundle resourceBundle =ResourceBundle.getBundle("UserInfo");
        String className = resourceBundle.getString("password");
        System.out.println(className);

反编译实验

 StringBuilder stringBuilder = new StringBuilder();
        try {
            Class studentClass = Class.forName("climb.Student");
            stringBuilder.append(Modifier.toString(studentClass.getModifiers()) + " class "+studentClass.getSimpleName()+"{\n");
            Field[] fields = studentClass.getDeclaredFields();
            for (Field field : fields) {
                stringBuilder.append("\t");
                stringBuilder.append(Modifier.toString(field.getModifiers()));
                stringBuilder.append(field.getType().getSimpleName() );
                stringBuilder.append(" ");
                stringBuilder.append(field.getName()+";\n");


            }
            stringBuilder.append("}");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        System.out.println(stringBuilder);

通过反射机制访问一个对象的属性

try {
            Class studentClass = Class.forName("climb.Student");
            Object obj = studentClass.newInstance();

           Field NameFild =  studentClass.getDeclaredField("name");
//           打破封装,从而访问其它类的私有的成员变量
        NameFild.setAccessible(true);
        NameFild.set(obj,"张三");
        System.out.println(NameFild.get(obj));
        } catch (Exception e) {
            e.printStackTrace();
        }

通过反射机制访问一个类的方法

说一个可变长参数 int… args

可变长度参数在参数列表中只能出现一个并且只能出现在最后一个,可变长度参数的数量是 0-n

try {
    Class StudentClass=  Class.forName("climb.Student");
   Object obj =  StudentClass.newInstance();
   Method methods =  StudentClass.getDeclaredMethod("getName");
   /*methods 方法
   obj 对象
   实参为空
   retValue 返回值
   * */
    Object retValue = methods.invoke(obj);
   } catch (Exception e) {
       e.printStackTrace();
   }

通过反射机制访问一个类的构造方法

    Class c = Class.forName("climb.Student");
//    调用有参数的构造方法
    Object obj = c.newInstance();
//    先获取到有参数的构造方法
   Constructor constructor =  c.getDeclaredConstructor(String.class,Integer.class);
//   使用这个构造方法调用 newInstance 方法
   Object stt= constructor.newInstance("张三",23);
        System.out.println(stt instanceof Student);
        System.out.println(stt);

通过一个类访问这个类的父类以及实现了什么接口

       Class  string = Class.forName("java.lang.String");
//        获取它的父类
        Class superClass = string.getSuperclass();
        System.out.println(superClass.getName());
//        获取它实现的接口
       Class[] Interface =  string.getInterfaces();
        for (Class aClass : Interface) {
            System.out.println(aClass.getName());
        }

注解

注解是一种引用数据类型,它可以出现在一个类的任何地方,包括注解自身.

如何定义

[修饰符] @interface 注解类型名{
    
}

java 中实现了哪些注解

注释类型摘要
Deprecated 重要用 @Deprecated 注释的程序元素,不鼓励程序员使用这样的元素,通常是因为它很危险或存在更好的选择。
Override 重要表示一个方法声明打算重写超类中的另一个方法声明。 只能出现在方法上,标识性注解,编译器看到方法上有这个注解的时候,会自动检查该方法是否重写了父类的方法,如果没有报错.
SuppressWarnings指示应该在注释元素(以及包含在该注释元素中的所有程序元素)中取消显示指定的编译器警告。

什么是元注解

用来标志注解类型的的注解,就是元注解

常见的注解类型有哪些

@Target(ElementType.METHOD) 标注注解类型的注解,这个 Target 用来标注被标注的注解类型可以出现在哪些位置上 这里表示只能出现在方法上
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
//这里表示这个注解可以出现在构造方法上,字段上,局部字段上,方法上,包上...类上。。。
    如果注解中有属性,就必须给属性赋值除非该属性有default 默认值 如果只有一个属性名叫 value 的话那么(value=..)这是可以省略的 直接写成 ("value"@Retention(RetentionPolicy.SOURCE)  这个Retention 注解用来标注这个注解保存在哪 这里表示只能出现在java 源文件中
     CLASS 表示可以出现在 class 文件中
      RunTim 表示可以出现在 class 文件中并可以被反射机制所读取
    
           

关于 jdk 的类加载器

什么是类加载器?

专门负责了加载类的命令/工具

ClassLoader

JDk 中自带了三个类加载器

启动类加载器

扩展类加载器

应用类加载器

首先通过启动类加载器加载, C:\Program Files\Java\jdk1.8.0_281\jre\lib\rt.jar

这个目录下的都是 JDK 中最核心的类.

如果启动类加载不到的时候就通过扩展类加载器加载:C:\Program Files\Java\jdk1.8.0_281\jre\lib\ext? .jar

如果扩展类加载器加载不到就会通过"应用类加载器"加载

网络编程

域名

  1. www.baidu.com
  2. 好处为了方便记忆,解决记 ip 的困难
  3. 概念:将 ip 地址映射成域名,映射的方法是通过 http 协议

端口号

  1. 用于标识计算机上某个特定的网络程序
  2. 表示形式:以整数形式,范围 0~65535
  3. 0~1024已经被占用
  4. 常见的网络程序端口号:
    • tomcat:8080
    • mysql:3306
    • oracle:1521
    • sqlserver:1433

ip+端口就可以找到对应的网站服务

TCP 和 UDP

Tcp 协议:传输控制协议

  1. 使用 TCP 协议前,须先建立 TCP 连接,形成传输数据通道
  2. 传输前,采用"三次握手"方式,是可靠的
  3. 在连接中可进行大数据量的传输
  4. 传输完毕,需释放已建立的连接,效率低

UDP 协议:用户数据协议

  1. 将数据,源,目的封装成数据包,不需要建立连接
  2. 每个数据报的大小限制在 64k 内
  3. 因无需连接,故是不可靠的
  4. 发送数据结束时无需释放资源,速度快

重要的类

inetAddress 类

  1. 获取本机 inetAddress 对象 getLocalHost
  2. 根据指定主机名/域名获取 ip 地址对象 getByName
  3. 获取 inetAddress 对象的主机名 getHostName
  4. 获取 inetAddress 对象的地址 getHostAddress

Socket 类

  1. Socket 开发网络应用程序的被广泛的采用,以至于成为事实上的标准
  2. 通信的两端都要有 Socket 是两台机器间通信的端点
  3. 网络通信其实就是 Socket 间的通信
  4. Socket 允许程序把网络连接当成一个流,数据在两个流之间通过 IO 传输
字符流
client
//        连接客户端 两个参数分别是连接的主机名,与端口号
        OutputStream outputStream =null;
        Socket socket = null;
        InputStream inputStream = null;
        BufferedWriter bufferedWriter = null;
        BufferedReader bufferedReader = null;
        try {
//            1. 连接服务器端口 (ip,端口号)
            socket = new Socket(InetAddress.getLocalHost(),9999);
            System.out.println("客户端 Scoket="+socket.getClass());
//           2. 连接上后和 socket 对象关联的输出流对象
           outputStream =   socket.getOutputStream();
//            3. 通过输入流,写入数据到数据通到.
            bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream));
            bufferedWriter.write("hello,server 字符流");
            bufferedWriter.newLine();//插入一个换行符,表示写入的内容结束,注意,要求对方使用 readLine()
            bufferedWriter.flush();// 如果使用的字符流,需要手动冲一下,否则数据可能会不全
//            4. 获取和 socket 相关联的输入流
            inputStream = socket.getInputStream();
          bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
            String s = bufferedReader.readLine();
            System.out.println(s);

        } catch (IOException e) {
            e.printStackTrace();
        }finally {


            try {
                bufferedReader.close();

            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                bufferedWriter.close();
            } catch (IOException e) {
                e.printStackTrace();
            } try {
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            System.out.println("客户端退出...");
        }
server
Socket socket = null;
        BufferedReader bufferedReader = null;
        ServerSocket serverSocket = null;
        BufferedWriter bufferedWriter = null;
        try {
//            在9999端口监听,细节要求这个端口没有被占用
            serverSocket = new ServerSocket(9999);
            System.out.println("服务端在9999端口监听,等待连接...");
//            当没有客户连接时,程序会阻塞等待连接
//            如果有客户连接,会返回 Socket 对象,程序继续
            socket = serverSocket.accept();
//            得到 socket 对象关联的输出流对象

            System.out.println("socket " + socket.getClass());
//            通过 socket.getInputStream() 读取客户端写入到数据通道的数据
            InputStream inputStream = socket.getInputStream();


//            IO 读取 使用字符流,使用 InputStreamReader 将 InputStream 转成了一个 Reader
            bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
            String s = bufferedReader.readLine();
            System.out.println(s);

//            获取 socket 相关的输出流
            OutputStream outputStream = socket.getOutputStream();
            bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream));
            bufferedWriter.write("hello Client 字符流");
            bufferedWriter.newLine();//插入一个换行符,表示回复结束
            bufferedWriter.flush();

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //            关闭流和 socket
            try {
                bufferedReader.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                bufferedWriter.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                serverSocket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

字节流

 client
//        连接数据库
        Socket socket = null;
        OutputStream outputStream = null;
        try {
            socket = new Socket(InetAddress.getLocalHost(), 9999);
//            连接上后,生成 Socket ,通过 Socket.getOutputStream
            outputStream = socket.getOutputStream();
            outputStream.write("hello server".getBytes());
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                outputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
server
ServerSocket serverSocket = null;
        InputStream inputStream = null;
        Socket socket = null;
        try {
//            在本机的 9999端口监听
            serverSocket = new ServerSocket(9999);
            System.out.println("服务端在 9999 端口开启服务,等待连接");
//           服务端会阻塞,等待连接
            socket = serverSocket.accept();
            inputStream = socket.getInputStream();
//            Io 读取
            byte[] buf = new byte[1024];
            int readLen = 0;
            while ((readLen = inputStream.read(buf)) != -1) {
                System.out.println(new String(buf, 0, readLen));
            }
            System.out.println("服务端下线");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                inputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                serverSocket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

Homework

将客户端将一张图片发送到服务器端

服务器端
 //            服务器在8888端口
        ServerSocket serverSocket = new ServerSocket(8888);
//            开始接客
        Socket socket = serverSocket.accept();
//        读取客户端发送的数据
//        先将这个数据读入内存
        BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
        byte[] bytes = streamUtils.streamToByteArray(bis);
//        将这个 bytes 数据输入到指定的路径
        String destFilePath = "src\\missing.wedp";
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(destFilePath));
        bos.write(bytes);
        bos.flush();
        bos.close();
//        向客户端发送已经收到图片
        BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
        bufferedWriter.write("收到图片");
        bufferedWriter.flush();
        bufferedWriter.newLine();//写入一个结束标志
//        关闭流
        bufferedWriter.close();
        bis.close();
        socket.close();
        serverSocket.close();
客户端
    Socket socket = null;
    InputStream inputStream = null;
    OutputStream outputStream = null;

//            建立连接
        socket = new Socket(InetAddress.getLocalHost(), 8888);
//            准备传图片
        String filePath = "D:\\ps personal works\\your name\\missing.webp";
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream(filePath));
//       这里的 bytes 就是这个文件对应的字节
        byte[] bytes = streamUtils.streamToByteArray(bis);
        BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());
//        这里将我们的字节数组传给服务器端
        bos.write(bytes);
        bos.flush();
//        设置一个结束标志
        socket.shutdownOutput();
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        String s = bufferedReader.readLine();
        System.out.println(s);
        System.out.println("hell client");


//        关闭流
        bis.close();
        bos.close();
        socket.close()
工具类
    public class streamUtils {
/**
 * @author guojingbo
 * @date 2021-09-08 19:35
 * @param is
 * @return byte[]*/
    public static byte[] streamToByteArray (InputStream is) throws Exception{
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        byte[] b =  new byte[1024];
        int len;
//        循环读入文件,到缓冲层
        while((len=is.read(b))!=-1){
            bos.write(b,0,len);
        }
//        将这个缓冲层的数据转成字节数组再 return 出去
        byte[] array = bos.toByteArray();
        bos.close();
        return array;
    }
}

netstat 指令

  1. netstat-an 可以查看当前主机网络情况中,包括端口监听情况和网络连接情况
  2. netstat -an| more 可以分布显示

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-199MzDzA-1655898339959)(D:\notebook\文档截图\netstat.png)]

本地连接指的本地协议占用的端口号,外部地址连接的端口

netstat -anb 显示这个端口号及占用的程序名称

TCP 网络通讯不为人知的秘密

当客户端连接到端后,实际上客户端也是通过一个端口和服务器进行通讯的,只不过这个端口是TCP/IP 来分配的,是不确定的,是随机的

UDP 网络通信编程

基本介绍
  1. 类DatagramSocket 和 DatagramPack 实现了基于 UDP 协议网络
  2. UDP 数据报通过数据报套接字 DatagramSocket 发送的接收,系统并不这个数据一定能到达目的地
  3. UDP 协议中每个数据报都给出了完整的地址信息,所以无需建立发送方与接收方的连接
UDP 说明
  1. 没有明确的服务端与客户端,演变成数据的发送端与接收端
  2. 接收数据和发送数据是通过 DatagramSocket 对象来完成的
  3. 将数据封装到 DatagramPacket 对象/装包
  4. 当接收到 DatagramPacket 对象/拆包,取出数据
  5. DatagramSocket 可以指定在哪个端口接收数据
基本流程
  1. 核心的两个类/对象 DatagramSocket 和 DatagramPacket
  2. 建立发送端,接收端
  3. 发送数据前,建立数据包/ DatagramPacket
  4. 启用 DatagramSocket 的发送方法,接收方法
  5. 关闭DatagramSocket

Homework

  1. 编写一个接收端A和一个发送端B
  2. 接收端 A 在9999 端口等待接收数据
  3. 发送端 B 向接收端 A 发送数据 “hello,明天吃火锅”
  4. 接收端 A 收到 发送端 B 发送的数据,回复"好的,明天见"退出
  5. 发送端接收回复的数据,再退出
接收者
//    构建一个 DatagramSocket 对象,准备在9999 端口接收数据
    DatagramSocket socket =new  DatagramSocket(9999);
//    每个数据包的大小限制在 64k 以内,不适合传大数据
    byte[] bytes = new byte[1024*64];
    DatagramPacket packet = new DatagramPacket(bytes, bytes.length);
//    调用接收方法,将通过网络传输的 DatagramPacket 对象
//    当有数据包发送到 本机的 999 端口时,就会接收到数据 如果没有发送这时服务器就会阻塞
    socket.receive(packet);
// 可以把这个 packet 拆包,取出数据,并显示
    int length = packet.getLength();//得到实际的数据的大小
    byte[] data = packet.getData();//接收到数据
    String s = new String(data, 0, length);
    System.out.println(s);
    byte[] bytes1 = "好的明天见".getBytes();
    DatagramPacket packet1 = new DatagramPacket(bytes1, bytes1.length, InetAddress.getByName("192.168.1.104"), 9998);
    socket.send(packet1);
//    关闭资源
    socket.close();
    System.out.println("A端退出");
发送者
//        创建 DatagramSocket 对象,准备发送和接收数据
        DatagramSocket socket = new DatagramSocket(9998);
        byte[] data = "hello 明天吃火锅~".getBytes();
        //发送的数据,数据的长度,要发送的位置,以及端口号
        DatagramPacket packet =
                new DatagramPacket(data, data.length, InetAddress.getByName("192.168.1.104"),9999);
        socket.send(packet);
        byte[] bytes = new byte[1024*64];
        DatagramPacket packet1 = new DatagramPacket(bytes, bytes.length);
        socket.receive(packet1);
        int length = packet1.getLength();
        byte[] data1 = packet1.getData();
        String s = new String(data1, 0, length);
        System.out.println(s);
//        关闭资源
        socket.close();
        System.out.println("B端退出");

socket 实现文件的上传与接收

服务端 
ServerSocket serverSocket = new ServerSocket(9999);
        Socket socket = serverSocket.accept();
        InputStream inputStream =  socket.getInputStream();
        byte[] bytes = new byte[1024];
        int len = 0;
        String downloadFileName = "";
        while ((len=inputStream.read(bytes))!=-1){
            downloadFileName+=new String(bytes,0,len);
        }
        System.out.println("客户端希望下载的文件名是"+downloadFileName);
//        如果用户想下载的文件名是稻香就发送给用户,否则一律发送睛天
        String resFileName = "";
        if("稻香".equals(downloadFileName)){
            resFileName = "src\\稻香.mp3";
        }else{
            resFileName = "src\\sunny day.mp3";
        }
//        将这个文件读取到内存
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream(resFileName));
//        将内存中的这个文件输出给客户端
        BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());
        bos.write(streamUtils.streamToByteArray(bis));
        bos.flush();
        socket.shutdownOutput();
        inputStream.close();
//        释放资源
        bos.close();
        bis.close();
        socket.close();
        serverSocket.close();
        System.out.println("服务端退出");
客户端
//        输入需要下载的文件名
        Scanner scanner = new Scanner(System.in);
        String downloadName = scanner.next();
//        将这个文件名发送给服务端
        Socket socket = new Socket(InetAddress.getLocalHost(), 9999);
        BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());
        bos.write(downloadName.getBytes());
        socket.shutdownOutput();
//        4.读取服务端发来的文件
        BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
        byte[] bytes = streamUtils.streamToByteArray(bis);
//        将其写入硬盘
        String destFilenPath = "D:\\"+downloadName+".mp3";
        BufferedOutputStream bosF = new BufferedOutputStream(new FileOutputStream(destFilenPath));
        bosF.write(bytes);
        bosF.flush();

//        释放资源

        bis.close();
        bosF.close();
        socket.close();

多用户即时通信系统

需求分析
  1. 用户登录
  2. 摘取在线用户列表
  3. 无异常退出(客户端\服务端)
  4. 私聊
  5. 群聊
  6. 发文件
  7. 服务器摄像头新闻

项目结构

JDBC

com.guojingbo.dao_

  1. com.guojingbo.dao_.utils// 工具类
  2. com.guojingbo.dao_.domain //java bean 也就是对应表的操作类
  3. com.guojingbo.dao_.dao 存放xxxDao 和 BasicDao
  4. com.guojingbo.dao_.test //写测试类

其实在业务中还有一个 service 层来组织 sql 并调用相应的 xxx.Dao完成统合的需求

当多表联合查询

当多表联合查询时,创建一个MultixxxBean 存放多张表的字段名,生成一个 MultixxDao 给上层的 Service 调用。

如果字段过多,可以分成多个 multixxBean 来实现业务逻辑

如果字段名重复了,我们可以将 sql 语句字段名改成 MultixxBean 中存在的字段名操作如下

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6UjojtLU-1655898339965)(D:\notebook\文档截图\多表联合查询字段名重复解决思路.png)]

正则表达式

1. 底层原理

 String regStr = "\\d\\d\\d\\d";
        //1. 创建模式对象
        Pattern pattern = Pattern.compile(regStr);
        //2. 创建匹配器
        Matcher matcher = pattern.matcher(content);

        /**
         * mather.find() 完成任务
         * 1. 根据指定的规则,定位满足条件的子字符串
         * 2. 找到到将子字符串的索引记录到 mather 对象的属性中
         * groups[0] = 0 ,把该子字符串的结束的导引+1的值记录到 groups[1] 4
         *      2.2 记录第一组的匹配的字符 group[2]=0, group[3]=2;
         *      2.3 记录第二组匹配的字符 group[4]=2,group[5]=4
         * 3. 同时记录 oldList 的值为子字符串结束的索引+1的值即4,即下次执行 find 的时候就从4开始查找
         * 源码
         *   public String group(int group) {
         *         if (first < 0)
         *             throw new IllegalStateException("No match found");
         *         if (group < 0 || group > groupCount())
         *             throw new IndexOutOfBoundsException("No group " + group);
         *         if ((groups[group*2] == -1) || (groups[group*2+1] == -1))
         *             return null;
         *         return getSubSequence(groups[group * 2], groups[group * 2 + 1]).toString();
         *     }
         *     1. 根据 group[0]=0 group[1]=4 的记录的位置,从 content 开始截取字符串返回
         *   就是 [0,4)
         */
        while (matcher.find()){
            System.out.println("找到:"+ matcher.group(0));
        }

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SrOGcNdu-1655898339966)(D:\notebook\文档截图\正则底层 debug.png)]

小结如下

  1. 如果正则表达式有 () 分组
  2. 取出的匹配的字符串的规则如下
  3. group(0) 表示匹配到的整体字符串
  4. group(1)或者group(2)表示接下来一个组匹配到的字符串

2. 元字符-限定符

符号含义
[]可接收的字符列表 在这个中括号的一切限定字符都会当成一个普通的字符不用转义
[^]不可接收的字符列表
-连字符表示从xx到xx
.匹配除\n以外的任何字符
\\d匹配单个数字字符
\\D匹配单个非数字字符
\\w匹配单个数字、大小写字母字符和下划线
\\W匹配单个非数字、大小写字母字符
\\s匹配任何空白字符(空格、制表符等)
\\S匹配任何非空白字符,与\\s相反
*指定字符重复 0 次或者 n 次
+指定字符重复 1 次或者 n 次
指定字符重复 0 次或者 1 次当此字符紧跟任何其他限定符*+?{n}{n,m}之后时匹配模式是非贪婪匹配
{n}只能输入 n 个字符
{n,m}表示至少 N 次最多 m 次(java 匹配模式是贪婪默认即尽可能的匹配多的)

java 正则表达式默认区分字母大小写的,如何不区分字母大小写

(?i)adb表示 abc 都不区分大小写

a(?i)bc表示 bc 不区分大小写

a((?i)b)c表示只有 b 不区分大小写

或者写Pattern pattern = Pattern.compile(regStr,Pattern.CASE_INSENSITIVE);

元字符-定位符

符号含义
^指定起始字符
$指定结束字符
\\b匹配目标字符的边界 指子串间有空格或者是目标字符串结束位置
\\B匹配目标字符串的非边界

分组、捕获、反向引用

常用分组构造说明
(pattern)编号为0的级是整个正则匹配的内容其余结果则根据左括号的顺序从 1 开始自动编号
(?<name>pattern)命名捕获,用于name 的字符串不能包含任何标点符号,并且不能以数字开头,可以使用单引号替代尖括号
(?:pattern)匹配 pattern 但不捕获该匹配的子表达式,即它是一个非捕获匹配,不存储以后使用的匹配。这对于用“or”字符的情况很有用。例如’industr(?:y|ies)'比‘industry|industies’更经济
(?=pattern)一个非捕获分组,例如 Windows(?=98|95|2000|NT)匹配这四个中的一个但不匹配windows3.1中的windows
(?!pattern)非捕获匹配,例如 windows(?!98|95|2000|NT)匹配windows2000 但不匹配 Windows2000中的window

反向引用

圆括号的内容被捕获后,可以在这个括号后使用,而写出一个比较实用的正则表达式,这个我们称为反向引用,这种引用既可以是在正则表达式内部,也可以是在正则表达式外部,内部反向引用\\分组号外部反向引用$分组号,注意这里 的外面指的是这个正则表达式本身的外面如在 reqlace填写的参数

  1. 要匹配两个连续的相同的数据:(\\d)\\1
  2. 要匹配五个连续的相同的数字:(\\d)\\1{4}
  3. 要匹配个位与千位相同,十位与百倍相同的数

正则表达式常用的类

  • pattern 类

pattern 对象是一个正则表达式对象,pattern 类没有公共构造方法,要创建一个对象,调用其公共静态方法,它返回一个 pattern 对象该对象接受一个正则表达式作为它的第一个参数,比如:Pattern r = Pattern.compile(pattern);

matches方法

返回一个一个正则是否可以整体匹配一个字符串

public static boolean matches(String regex, CharSequence input) {
        Pattern p = Pattern.compile(regex);
        Matcher m = p.matcher(input);
        return m.matches();//底层调用的还是Matcher类的matches
    }
  • Matcher 类

Matcher 对象是对输入字符串进行解释和匹配的引擎,与Pattern 类一样,也公共构造方法,你需要调用Pattern 对象的matcher 方法来获得一个 Mathcher 对象

start 返回匹配开始的索引

end 返回匹配结束的的索引

replaceAll(“想替换成的字符”) 对原来的字符串不做修改,返回一个符合匹配结果的字符串

String 类中使用正则表达式

  1. String 类自带 replaceAll 接受正则表达式
  2. public boolean matches(String regex) 自带的这个函数默认是整体匹配
  3. split

jdk.8 新特性

java 的内存情况

Java 有三个区域一个是堆,栈,方法区

方法区:主要用于存放信息、常量、静态变量

Java堆: 该区域主要放对象实例

栈:方法栈

Lambda 表达式

Lambda 是对匿名内部类的的一种简写方式,其本质还是调用对象的方法,当方法想要使用外部的变量的时候,需要将值传递过去,但方法无论怎么改变变量的值都不会改变这个变量在外部的值(值与引用传递的区别),所以让我们给这个参数加上 final 就是为了方便开发者,以免误以为可以更改这个变量的值。

Java 中局部内部类和匿名内部类访问的局部变量必须由 final 修饰,以保证内部类和外部类的数据一致性。但从 Java 8 开始,我们可以不加 final 修饰符,由系统默认添加,当然这在 Java 8 以前的版本是不允许的。Java 将这个功能称为 Effectively final 功能。

所以我们 Lambda 内中出现的任何变量都是 final 修饰的,也不可以做任何修改

  • 就和 es6 的箭头函数一样,括号左边是参数,右边的是函数体

函数式接口

  • Lambda 表达式需要函数式接口的支持

    • 函数式接口:接口中只有一个抽象方法的接口称为函数式接口,可以使用 @Function
  • Java8 四大内置的函数式接口

    • Consumer : 消费型接口

      • void accept(T t);
    • Supperlier:借给型接口

      • T get()
    • Function<T,R> : 函数型接口

      • R apply(T t)
    • Predicate: 断言型接口

      • boolean test(T t)

方法引用

方法引用: 若 Lambda 体中的内容有方法已经实现了,我们可以使用“方法引用”

2、方法引用的分类

类型语法对应的Lambda表达式
静态方法引用类名::staticMethod(args) -> 类名.staticMethod(args)
实例方法引用inst::instMethod(args) -> inst.instMethod(args)
对象方法引用类名::instMethod(inst,args) -> 类名.instMethod(args)
构建方法引用类名::new(args) -> new 类名(args)

若 Lambda 参数列表中的第一参数是实例方法的调用者,而第二个参数是实例方法的参数时可以用 ClassName::Method

Comparator<Integer> compare = Integer::compare;

stream API

1. 创建 Stream

  • 可以通过 Collection 系列集合提供的 stream() 或者parallelStream

  • 通过 Arrays 中的静态方法stream

  • 通过 Stream 类的中的静态方法 of()

  • 创建无限流 Stream s3 = Stream.iterate(0,(x)->x+2);

    • 迭代 `

      Stream<Integer> iterate = Stream.iterate(0, (x) -> x + 2);
      iterate.limit(10).forEach(System.out::println);
      
    • 生成

    • Stream.generate(()>Math.random()).limit(10).forEach(System.out::println);

2. 中间操作

筛选与切片

中间操作连接起来形成一个流水线,除非触发终止操作,否则中间操作不会进行任何操作,而在终止操作时一次性处理,称为惰性求值

  • filter- 接收
  • limit - 截断流,使其元素不超过给定数量
  • skip(n) - 路过元素,返回一个扔掉了前 n 个元素的流若不足前 n 个流返回一个空流
  • distinct 通过流所生成的 equals 与 hashCode 方法去重
映射

map - 接收Lambda ,将元素转换成其他形式或提取信息,接收一个函数作为参数,该函数会被应用到每一个元素上,并将其映射成一个新的元素

flatMap - 接收一个函数作为参数,将流中的每一个值都换成另一个流,然后将所有的流,连接成一个流

map -> 把一个流中的一个个流加入到当前元素

flatMap -> 把一个流中的所有的元素加入到当前的流中

排序

sorted() -> 自然排序(Comparable)

sorted(comparator com)- 定制排序(Comparator)

3. 终止操作

查找与匹配

allMatch - 检查是否匹配所有元素

anyMatch - 检查是否至少匹配一个元素

noneMatch - 检查是否没有匹配所有元素

findFirst - 返回第一个元素

findAny - 返回当前流中的任意元素

max 返回流中最大值

min 返回流中最小值

归约

reduce(T identity,BinaryOperator) 可以将流中的元素反复结合起来,得到一个值

List<Integer> integers = Arrays.asList(2, 23, 234, 54, 89, 90, 9);
System.out.println(integers.stream().reduce(0, (x, y) ->
        x + y
));

返回其总和,第一个参数是起始值

收集

collect 将流转换为其他形式,接收一个Collector 接口的实现类,用于给Stream 中的元素做汇总方法

List<Integer> collect = integers.stream().collect(Collectors.toList());
System.out.println(collect);
HashSet<Integer> collect1 = integers.stream().collect(Collectors.toCollection(HashSet::new));

并行流与顺序流

Java 8 中将并行进行了优化,我们可以很容易的对数据进行并行操作,Stream API 可以声明性地通过 parallel() 与 sequential() 在并行流与顺序流之间进行切换

并行流的效率是一定比顺序流高。

optional 容器类的常用方法

方法用处
optional.of(T t)创建一个 Optional 实例
Optional.empty()创建一个空的 Optional 实例
Optional.ofNullable(T t)若 t 不为 null 创建 Optional实例,否则创建空实例
orPresent()判断是否包含值
orElse(T t)如果调用对象包含值,返回该值否则返回 t
or

默认方法

在 java 8 之前接口只能有静态常量和抽象方法

现在还可以有默认方法

default String getName(){
	return "哈哈哈";
}

接口默认方法的类优先性原则

若一个接口中定义了一个默认方法,而另外一个父类或者接口中又定义了 一个同名的方法时

选择父类中的方法,如果父类中提供了一个具体的实现,那么接口中具有相同名称和参数的默认方法会被忽略

接口冲突,如果一个父接口提供一个默认方法,而另一个接口也提供了一个相同名称相同参数列表的方法,那么必须覆盖该方法来解决。

新日期时间 API

LocalDate、LocalTime、LocalDateTime

类的实例对象都是不可变的对象。给人看的API 接口

  LocalDateTime now = LocalDateTime.now();
        System.out.println(now);
        LocalDateTime of = LocalDateTime.of(2022, 9, 20, 12, 23, 50);
        System.out.println(of);
        LocalDateTime localDateTime = of.plusYears(32L);
        System.out.println(localDateTime);
        LocalDateTime localDateTime1 = localDateTime.minusYears(32L);
        System.out.println(localDateTime1);
        int dayOfYear = localDateTime1.getDayOfYear();
        System.out.println(dayOfYear);

Instance:时间戳

 Instant now = Instant.now();
        System.out.println(now);// 默认获取 UTC 时区
        OffsetDateTime offsetDateTime = now.atOffset(ZoneOffset.ofHours(8));//获取带偏移量的时差
        System.out.println(offsetDateTime);
        System.out.println(now.toEpochMilli());//返回时间戳

Duration、Period

Duration : 计算两个时间之间的间隔

Instant ins1 = Instant.now();
        Thread.sleep(1000);
        Instant now = Instant.now();
        Duration between = Duration.between(ins1, now);
        System.out.println(between.toMillis());
        System.out.println("=================================");
        LocalDateTime now1 = LocalDateTime.now();
        Thread.sleep(1000);
        LocalDateTime now2 = LocalDateTime.now();
        Duration between1 = Duration.between(now1, now2);
        System.out.println(between1);

Period: 计算两个日期之间的间隔

  LocalDate of = LocalDate.of(2016, 12, 3);
  LocalDate now = LocalDate.now();
  Period between = Period.between(of, now);
  System.out.println(between.getYears()+"年"+between.getMonths()+"月"+between.getDays()+"日");
  System.out.println(between.toTotalMonths());

TemporalAdjuster: 时间校正器

LocalDateTime now = LocalDateTime.now();
LocalDateTime localDateTime = now.withHour(23);//将小时改为 23 小时
System.out.println(localDateTime);
LocalDateTime with = now.with(TemporalAdjusters.lastDayOfYear());
System.out.println(with);
LocalDateTime with1 = now.with(TemporalAdjusters.next(DayOfWeek.MONDAY));
System.out.println(with1);
//自定义:下一个工作日
LocalDateTime with2 = now.with((l) -> {
    LocalDateTime now3 = (LocalDateTime) l;
    DayOfWeek dayOfWeek = now3.getDayOfWeek();
    if (dayOfWeek.equals(DayOfWeek.FRIDAY)) {
        return now3.plusDays(3L);
    } else if (dayOfWeek.equals(DayOfWeek.SATURDAY)) {
        return now3.plusDays(2L);
    } else {
        return now3.plusDays(1L);
    }
});

DataTimeFormatter 时间格式化

System.out.println(with2);
//直接使用给定的格式
DateTimeFormatter isoLocalDate = DateTimeFormatter.ISO_LOCAL_DATE;
System.out.println(now.format(isoLocalDate));
System.out.println("===============================");
//自定义格式
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH:mm:ss");
System.out.println(now.format(dateTimeFormatter));

ZonedDate、ZonedTime、ZonedDateTime 带时区的时间支持

java 面试题

双亲委派机制

当一个类接收到类加载请求,他首先不会尝试自己去加载这个类,而是向上委派查找缓存,如果找到则直接返回,没有继续往上委派,委派到顶层之后,缓存中还没有,则到加载路径中查找,有则加载返回,没有这向下查找,直到发起加载的加载器为止。

  • 安全性,避免用户自己编写的类动态替换java核心类,如String
  • 避免类的重复加载,JVM中区分不同类,不仅根据类名,相同的class文件被不同的ClassLoader加载就是不同的两个类

GC如何判断对象是否可以被回收

引用计数法:每个对象有个引用计数属性,新增一个引用计数+1,引用释放-1,计数为0可以回收
可达性分析:从GC ROOTS开始向下搜索,当一个对象没有任何引用链相连时,则证明此对象是不可用的,那么虚拟机就判断是可回收对象。

线程的生命周期?线程有几种状态
操作系统层面
1.线程有五种状态:创建、就绪、运行、阻塞、死亡
2.阻塞又分为三种:

等待阻塞:运行的线程执行wait()方法,释放占用的资源,JVM将其放入等待池中。不能自动唤醒,必须依靠其他线程调用notify或notifyAll方法才能唤醒,wait是Object类的方法
同步阻塞:运行中的线程在获取对象的同步锁时,如果该同步锁被别的线程占用,则将该线程放入锁池
其他阻塞:运行的线程调用sleep或join方法,或者发出I/O请求,JVM就会将该线程设为阻塞状态。当sleep状态超时、join等待线程终止、或者I/O请求处理完毕,线程重新转入就绪状态。sleep是Thread类的方法
1.新建状态(New):创建一个线程
2.就绪状态(Runnable):线程创建之后,其他线程调用该对象的start方法,该线程处于可运行线程池中,等待获取CPU执行权
3.运行状态(Running):就绪的线程获取CPU执行权,运行代码
4.阻塞(Blocked):由于某些原因放弃CPU使用权,暂时停止运行。当线程进入就绪状态,才有机会运行
5.死亡状态(Dead):线程执行结束或异常退出run方法,线程结束生命周期。

简述java线程的状态

操作系统层面
1.线程有五种状态:创建、就绪、运行、阻塞、死亡
2.阻塞又分为三种:

等待阻塞:运行的线程执行wait()方法,释放占用的资源,JVM将其放入等待池中。不能自动唤醒,必须依靠其他线程调用notify或notifyAll方法才能唤醒,wait是Object类的方法
同步阻塞:运行中的线程在获取对象的同步锁时,如果该同步锁被别的线程占用,则将该线程放入锁池
其他阻塞:运行的线程调用sleep或join方法,或者发出I/O请求,JVM就会将该线程设为阻塞状态。当sleep状态超时、join等待线程终止、或者I/O请求处理完毕,线程重新转入就绪状态。sleep是Thread类的方法
1.新建状态(New):创建一个线程
2.就绪状态(Runnable):线程创建之后,其他线程调用该对象的start方法,该线程处于可运行线程池中,等待获取CPU执行权
3.运行状态(Running):就绪的线程获取CPU执行权,运行代码
4.阻塞(Blocked):由于某些原因放弃CPU使用权,暂时停止运行。当线程进入就绪状态,才有机会运行
5.死亡状态(Dead):线程执行结束或异常退出run方法,线程结束生命周期

sleep()、wait()、join()、yield()

1.sleep是Thread类的方法,wait()是Object类的方法
2.sleep方法不会释放lock,但是wait会释放,而且会加入等待队列
3.sleep方法不需要依赖synchronized,可以在任何地方使用,但是wait方法需要依赖synchronized
4.sleep用于当前线程的休眠,wait用于多线程之间的通信
5.sleep会让出cpu并强制上下文切换,而wait不一定,wait后还是有机会重新竞争到锁继续执行。

yield()执行后直接进入就绪状态,释放cpu执行权,但保留cpu执行资格,有可能下次调度还会让该线程获取到执行权继续执行

join()执行后进入阻塞,例如在线程B中调用线程A的join(),线程B进入阻塞,直到线程A结束或中断线程

Thread和Runable的区别

Thread和Runable的实质是继承关系。用法上,如果有复杂线程操作需求,就选择继承Thread,如果只是简单执行一个任务,就实现Runnable。

Synchronized与Lock的区别

Synchronized是内置Java关键字,Lock是一个Java类
Synchronized无法判断获取锁的状态,Lock可以判断是否获取到了锁
Synchronized自动释放锁,Lock手动释放锁,如果不释放,就会死锁。
Synchronized 线程1(获得锁,阻塞)、线程2(等待,傻傻的等);Lock不一定会等待下去:tryLock()
Synchronized 可重入锁,不可中断,非公平;Lock 可重入锁,可以判断锁,非公平(可以设置)
Synchronized 适合锁少量的代码同步问题,Lock适合锁大量的同步代码

线程池的最大大小(maximumPoolSize)应该如何去设置?

1.CPU密集型:CPU有多少核,就是多少–>
Runtime.getRuntime().availableProcessors()
2.IO密集型: 判断程序中十分耗IO的线程,例如有15个大型任务耗IO资源,一般选择其两倍。

并发、并行、串行

并行:时间重叠,两个任务在同一时刻互不干扰执行
并发:允许两个任务互相干扰,两者交替执行,同一时间只能有一个任务执行
串行:多个任务挨个执行,时间不可能发生重叠

什么是指令重排

在程序执行时,为提高性能,编译器和处理器往往会对指令进行重排,而不是按照原来的顺序执行。
重排序分为以下三种:

  1. 编译器优化的重排序:编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序。
  2. 指令级并行的重排序:现代处理器采用了指令级并行技术,来将多条指令重叠执行。如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。
  3. 内在系统的重排序:由于处理器使用缓存和读/写缓冲区,这使得加载和存储操作看上去可能是在乱序执行。
    如何禁止指令重排?
  • 内存屏障:禁止上面的指令与下面的指令顺序进行交换
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值