〇、前沿背景

1. 互联网三高架构

高性能、高可用、高并发

面向互联网的三高系统,最关注的软件质量属性是:性能、可用性、伸缩性、扩展性、安全性。
而构建此类系统,最常见的架构模式有:横向分层、纵向分割、分布式化、集群化、使用缓存、使用异步模式、使用冗余、自动化(发布、部署、监控)。
具体来说,可以在不同层次常用的技术有:

前端架构

浏览器优化技术:合理布局,页面缓存,减少http请求数,页面压缩,减少 cookie 传输。

  1. CDN
    CDN的基本原理是广泛采用各种缓存服务器,将这些缓存服务器分布到用户访问相对集中的地区或网络中,在用户访问网站时,利用全局负载技术将用户的访问指向距离最近的工作正常的缓存服务器上,由缓存服务器直接响应用户请求。

  2. DNS负载均衡

  3. 动静分离,静态资源独立部署,动态图片独立提供服务

应用层架构

1. 业务拆分

2. 负载均衡

3. 虚拟化服务器、容器化

4. 无状态(以及分布式 Session)

分布式Session有如下几种实现方式:

1.Session复制机制

在支持Session复制的Web服务器上,通过修改Web服务器的配置,可以实现将Session同步到其它Web服务器上,达到每个Web服务器上都保存一致的Session。

  • 优点:代码上不需要做支持和修改。
  • 缺点:需要依赖支持的Web服务器,一旦更换成不支持的Web服务器就不能使用了,在数据量很大的情况下不仅占用网络资源,而且会导致延迟。
  • 适用场景:只适用于Web服务器比较少且Session数据量少的情况。
  • 可用方案:开源方案tomcat-redis-session-manager,暂不支持Tomcat8。
2.Session粘滞机制

将用户的每次请求都通过某种方法强制分发到某一个Web服务器上,只要这个Web服务器上存储了对应Session数据,就可以实现会话跟踪。

  • 优点:使用简单,没有额外开销。
  • 缺点:一旦某个Web服务器重启或宕机,相对应的Session数据将会丢失,而且需要依赖负载均衡机制。
  • 适用场景:对稳定性要求不是很高的业务情景。
3.Session集中管理机制

在单独的服务器或服务器集群上使用缓存技术,如Redis存储Session数据,集中管理所有的Session,所有的Web服务器都从这个存储介质中存取对应的Session,实现Session共享。

  • 优点:可靠性高,减少Web服务器的资源开销。
  • 缺点:实现上有些复杂,配置较多。
  • 适用场景:Web服务器较多、要求高可用性的情况。
  • 可用方案:开源方案Spring Session,也可以自己实现,主要是重写HttpServletRequestWrapper中的getSession方法,博主也动手写了一个,github搜索joincat用户,然后自取。
4.基于Cookie管理机制

这种方式每次发起请求的时候都需要将Session数据放到Cookie中传递给服务端。

  • 优点:不需要依赖额外外部存储,不需要额外配置。
  • 缺点:不安全,易被盗取或篡改;Cookie数量和长度有限制,需要消耗更多网络带宽。
  • 适用场景:数据不重要、不敏感且数据量小的情况。

最后的总结

以上四种方式,相对来说,Session集中管理更加可靠,使用也是最多的。

5. 分布式缓存

分布式缓存的典型应用场景可分为以下几类:

  1. 页面缓存.用来缓存Web 页面的内容片段,包括HTML、CSS 和图片等,多应用于社交网站等;
  2. 应用对象缓存.缓存系统作为ORM 框架的二级缓存对外提供服务,目的是减轻数据库的负载压力,加速应用访问;
  3. 状态缓存.缓存包括Session 会话状态及应用横向扩展时的状态数据等,这类数据一般是难以恢复的,对可用性要求较高,多应用于高可用集群;
  4. 并行处理.通常涉及大量中间计算结果需要共享;
  5. 事件处理.分布式缓存提供了针对事件流的连续查询(continuous query)处理技术,满足实时性需求;
  6. 极限事务处理.分布式缓存为事务型应用提供高吞吐率、低延时的解决方案,支持高并发事务请求处理,多应用于铁路、金融服务和电信等领域.

异步、事件驱动架构、消息队列
EDA是一种以事件为媒介,实现组件或服务之间最大松耦合的方式。传统面向接口编程是以接口为媒介,实现调用接口者和接口实现者之间的解耦,但是这种解耦程度不是很高,如果接口发生变化,双方代码都需要变动,而事件驱动则是调用者和被调用者互相不知道对方,两者只和中间消息队列耦合。

多线程
动态页面静态化
我们只需要编写这么一个HttpModule就可以了,当用户第一次请求asp处理时,我们可以在ihttpmodule中拦截到这个请求,然后获取到这次请求应该返回的html代码,然后我们返回这些html给用户,并保存刚才我们获取到的html到文件内,当用户下次请求时,我们只需要直接返回我们已经保存的html文件即可。

服务层架构

1)分布式微服务(分级管理,超时设置,异步调用,服务降级,幂等性设计。)
同应用层架构

存储层架构

1)DFS(分布式文件存储)
2)路由数据库
3)No SQL 数据库
4)数据同步
5)数据冗余

安全架构

1)Web攻击(XSS、Sql Injection)
2)数据加密
3)密钥管理
4)发布、运维

自动化测试与发布

1)灰度发布
2)浏览器数据采集
3)服务器业务数据采集
4)服务器性能数据采集
5)系统监控
6)系统报警
7)机房(散热、省电、定制服务器)

2. Java简介

Java 是由 Sun Microsystems 公司于 1995 年 5 月推出的 Java 面向对象程序设计语言和 Java 平台的总称。由 James Gosling和同事们共同研发,并在 1995 年正式推出。

后来 Sun 公司被 Oracle (甲骨文)公司收购,Java 也随之成为 Oracle 公司的产品。

Java分为三个体系:

  • JavaSE(J2SE)(Java2 Platform Standard Edition,java平台标准版)

    • JavaSE 可以开发和部署在桌面、服务器、嵌入式环境和实时环境中使用的 Java 应用程序。是EE,和ME的基础。一般就是指JDK。就是Java的基础语法(变量、方法、类之间的调用、关系,继承、接口、线程之类的),工具包(java.util.* ),或者其他的一些封装
  • JavaEE(J2EE)(Java 2 Platform,Enterprise Edition,java平台企业版)

    • JavaEE,其实是一套规范,就是用java语言做企业开发(目前看来就是开发一些动态网站,或者对外提供调用服务的网站)中的一整套规范,比如类怎么封装,网页的请求要用什么方法处理,语言编码一类的处理,拦截器啊什么的定义,请求返回得有什么信息…
    • 2018年2月,Eclipse 宣布正式将 JavaEE 更名为 JakartaEE
  • JavaME(J2ME)(Java 2 Platform Micro Edition,java平台微型版)。

    • JavaEE,其实是一套规范,就是用java语言做企业开发(目前看来就是开发一些动态网站,或者对外提供调用服务的网站,或者其他没接触过的。。。)中的一整套规范,比如类怎么封装,网页的请求要用什么方法处理,语言编码一类的处理,拦截器啊什么的定义,请求返回得有什么信息。。。(具体看servlet的接口就知道了)

2005 年 6 月,JavaOne 大会召开,SUN 公司公开 Java SE 6。此时,Java 的各种版本已经更名,以取消其中的数字 “2”:J2EE 更名为 Java EE,J2SE 更名为Java SE,J2ME 更名为 Java ME。

一、 基础知识

0. 安装

Java\jdk1.8.0_301

Java\jre1.8.0_301

  • jre

    Jre 是java runtime environment, 是java程序的运行环境。既然是运行,当然要包含jvm,也就是大家熟悉的虚拟机啦,还有所有java类库的class文件,都在lib目录下打包成了jar。大家可以自己验证。至于在windows上的虚拟机是哪个文件呢?学过MFC(Microsoft Fundamental Class, 微软基础类库)的都知道什么是dll文件吧,那么大家看看jre/bin/client里面是不是有一个jvm.dll呢?那就是虚拟机。

  • jdk

    Jdk 是java development kit,是java的开发工具包, 主要是给ide 用的,里面包含了各种类库和工具。当然也包括了另外一个Jre., 而且jdk/jre/bin 里面也有一个server文件夹, server文件夹下面也有一个jvm.dll 虚拟机。

0.1 JDK源文件

jdk-8u301-windows-x64.exe

在这里插入图片描述

0.2 配置环境变量

0.2.1 新建

  • JAVA_HOME -> Java\jdk1.8.0_301
  • JRE_HOME -> Java\jre1.8.0_301
  • CLASSPATH -> .;%JAVA_HOME%\lib\dt.jar;%JAVA_HOME%\lib\tools.jar;

0.2.2 Path

  • %JAVA_HOME%\bin

  • %JRE_HOME%\bin

0.3 验证

  • cmd
    • java
    • javac

1. HelloWorld

1
2
3
4
5
public class helloWorld{
public static void main(String[] args){
System.out.println("Hello World");
}
}

2. 数据类型

2.1 强/弱类型语言

  • 强类型语言
    • 要求变量的使用要严格符合规定,所有变量都必须先定义后才能使用
  • 弱类型语言

2.2 数据类型分类

  • 分类两大类

    1. 基本类型(Primitive type)

      • 数值类型
        • 整数类型(byte-1字节、short-2字节、int-4字节、long-8字节)
        • 浮点类型(float-4字节、double-8字节)
        • 字符类型(char-2字节)
      • boolean类型(1位)
    2. 引用类型(Reference type)

      类、接口、数组

2.3 进制

  • 进制

    二进制 - 0b

    八进制

    十进制 - 0

    十六进制 - 0x

2.4 大数类型

BigDecimal

2.5 强制类型转换

  1. 不能对boolean类型进行转换
  2. 不能把对象类型转换为不相干的类型

2.6 JDK7可对数字进行下划线分割

1
int money = 10_0000_0000;

3. JavaDoc

1
javadoc -encoding UTF-8 -charset UTF-8 filename.java

4. 流程控制

4.1 用户交互Scanner

Scanner s = new Scanner(System.in)

java.util.Scanner 是 java5 的新特性,可以通过 Scanner 类来获取用户的输入。

通过 Scanner 类的 next() 与 nextline() 方法获取输入的字符串,在读取前我们一般需要使用 hasNext() 与 hasNextLine() 判断是否还有输入的数据。

hasNextDouble()

4.2 switch多选择结构

从Java SE 7开始,switch支持String字符串类型

5. 方法

  • Java方法是语句的集合
    1. 方法是解决一类问题的步骤的有序组合
    2. 方法包含于类或对象中
    3. 方法在程序中被创建,在其他地方被引用
  • 设计原则:原子性

6. 可变参数(…)

不定项参数

只能指定一个可变参数,必须是方法最后一个形参

7. 内存分析

7.1 堆

  • 存放new的对象和数组
  • 可以被所有的线程共享,不会存放别的对象引用

7.2 栈

  • 存放基本变量类型(会包含这个基本类型的具体数值)
  • 引用对象的变量(会存放这个引用在堆里面的具体地址)

7.3 方法区

  • 可以被所有的线程共享
  • 包含了所有得class和static变量

8. 方法

  • 方法的定义
    • 修饰符
    • 返回类型
    • break:跳出switch,结束循环 和 return 的区别
    • 方法名:注意规范(见名知意)
    • 参数列表:(参数类型, 参数名)
    • 异常抛出*
  • 方法的调用:递归
    • 静态方法: 函数启动时创建
    • 非静态方法: 类使用时创建
    • 形参和实参: (方法内的变量名) -> (实际传递的变量值)
    • 值传递和引用传递: 类比指针
    • this 关键字

9. 类与对象的关系

  • 类是一种抽象的数据类型,是对某一类食物整体描述/定义,但是并不能代表某一个具体的事物.
  • 对象是抽象概念的具体实例.】

  • 面向对象编程(Object-Oriented Programming, OOP)

    • 本质:以类的方式组织代码,以对象的组织(封装)数据

    • 特性:==封装==、==继承==、==多态==

10. 创建与初始化对象

  • 使用 ==new== 关键字创建对象
  • 创建对象的同时,会分配内存空间,还会给对象进行默认的初始化以及对类中构造器的调用
  • 构造器(构造方法):
    1. 必须与类的名字相同
    2. 必须没有返回值,也不能写void

11. 封装

  • 高内聚、低耦合
    • 高内聚:类的内部数据操作细节自己完成,不允许外部干涉
    • 低耦合:仅暴露少量的方法给外部使用
  • 封装
    • 通常,应禁止直接访问一个对象中数据的实际表示,而应通过操作接口来访问,这称为信息隐藏
  • 属性私有 -> ==private==
  • get/set`

12. 继承

  • 本质:对某一批类的抽象,从而实现对世界更好的建模
  • extends -> 子类是父类的拓展
  • Java中类只有单继承,没有多继承
  • 继承是类和类之间的一种关系
    • 除此之外,还有依赖、组合、聚合等
  • 子类(派生类) extends 父类(基类)
  • : 查看继承关系

  • super -> 指向父类

  • 方法的重写: 子类重写了父类的方法,那么就执行子类的方法
    1. 方法名必须相同
    2. 参数列表必须相同
    3. 修饰符:范围可以扩大不可以缩小, public -> protected -> default -> private
    4. 抛出的异常:范围可以被缩小,但不能扩大

13. 多态

  • 同一方法可以根据发送对象的不同采用多种不同的行为方式
  • 一个对象的实际类型是确定的,但可以指向对象的引用类型又很多
  • 存在条件:

    • 有继承关系
    • 子类重写父类方法
    • ==父类引用指向子类对象==
  • 成员变量:编译和运行都看父类。

  • 非静态方法:编译看父类,运行看子类(指的是,编译时看父类是否存在此方法,运行时执行的是子类重写的方法)
    对于静态方法:编译和运行都看父类!

  • 多态是方法的多态

  • instanceof: son instanceos Father_class

14. static

  • 静态变量和静态方法随类一起加载,非静态方法随实例一起创建

    • 静态方法不能调用非静态方法
    • 静态方法允许直接访问静态成员
    • 静态方法中不允许使用this或super关键字,可以被继承但不能被重写,没有多态
    • 静态代码块只执行一次
    • 静态属性是类共有的属性,不论创建几个实例,属性仅此一份
  • 匿名代码块

    • ```java
      // 匿名代码块,每次创建实例都会执行,可用于初始化数据
      {

      // code 1
      

      }

      // 静态代码块,只随类的创建执行一次
      static{

      // code 2
      

      }

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10

      - 静态导入包

      - ```java
      // 静态导入包
      import static java.lang.Math.random;

      public static void main(String[] args){
      System.out.println(random());
      }
  • 类加载

    • JVM首次使用某个类时,需通过CLASSPATH查找该类的.class文件
    • 将.class文件中对类的描述信息加载到内存中,进行保存
      • 如:包名、类名、父类、属性、方法、构造方法…
    • 加载时机:
      • 创建对象
      • 创建子类对象
      • 访问静态属性
      • 调用静态方法
      • 主动加载:Class.forName(“全限定名”);
  • final 修饰的类无法被继承

15. 抽象类

  • abstract修饰符可以用来修饰方法也可以修饰类,如果修饰方法,那么该方法就是抽象方法;如果
    修饰类,那么该类就是抽象类。
  • 抽象类中可以没有抽象方法,但是有抽象方法的类一定要声明为抽象类。
  • 抽象类,不能使用new关键字来创建对象,它是用来让子类继承的
    • 可被子类继承,提供共性属性和方法
    • 可声明为引用,更自然的使用多态
  • 抽象方法,只有方法的声明,没有方法的实现,它是用来让子类实现的。
  • 子类继承抽象类,那么就必须要实现抽象类没有实现的抽象方法,否则该子类也要声明为抽象类。

16. 接口

普通类:只有具体实现
抽象类:具体实现和规范(抽象方法)都有!
接口:只有规范!

  • 接口的本质是契约
    • 只能定义:公开静态常量+公开抽象方法
  • OO的精髓,是对对象的抽象,最能体现这一点的就是接口。为什么我们讨论设计模式都只针对具备了抽象能力的语言(比如c++、java、c#等),就是因为设计模式所研究的,实际上就是如何合理的去抽象。
  • 接口的声明关键字 —> interface
    • 属性: public static final(默认)
    • 方法:public abstract(默认)
  • 类继承接口的关键字 —> implements
  • 接口引用
    • 同父类一样,接口也可以声明为引用,并指向实现类对象
    • 仅可调用接口中所声明的方法,进行独有方法调用
  • 常量接口:将多个常用于表示状态或固定值的变量,以静态常量的形式定义在接口中统一管理,提高代码可读性
  • 标记接口:没有包含任意成员,仅仅用作标记
    • Serializable : 可序列化的
    • Cloneable : 可克隆的
  • 接口回调:先有接口的使用者,后有接口的实现者(先有电脑,后有usb设备)
  1. 18. 异常

18.1 异常简介

  • Exception
  • 分类

    • 检查性异常:最具代表的检查性异常是用户错误或问题引起的异常,这是程序员无法预见的。例如要打开一个不存在文件时,一个异常就发生了,这些异常在编译时不能被简单地忽略。
    • 运行时异常:运行时异常是可能被程序员避免的异常。与检查性异常相反,运行时异常可以在编译时被忽略。
    • 错误:错误不是异常,而是脱离程序员控制的问题。错误在代码中通常被忽略。例如,当栈洁出时,一个错误就发生了,它们在编译也检查不到的。
  • Error

    • Error类对象由Java虚拟机生成并抛出,大多数错误与代码编写者所执行的操作无关。
    • Java虚拟机运行错误(Virtual MachineError),当JVM不再有继续执行操作所需的内存资源时,将出现OutOfMemoryError。这些异常发生时,Java虚拟机(JVM)一般会选择线程终止;
    • 还有发生在虚拟机试图执行应用时,如类定义错误(NoClassDefFoundError)、链接错误(LinkageError)。这些错误是不可查的,因为它们在应用程序的控制和处理能力之外,而且绝大多数是程序运行时不允许出现的状况。
  • Exception

    • 在Exception分支中有一个重要的子类RuntimeException(运行时异常)
      • ArraylndexOutOfBoundsException(数组下标越界)
      • NullPointerException(空指针异常)
      • ArithmeticException(算术异常)
      • MissingResourceException(丢失资源)
      • ClassNotFoundException(找不到类)等异常,这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理。
    • 这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生;
  • Error和Exception的区别:Error通常是灾难性的致命的错误,是程序无法控制和处理的,当出现这些异常时,Java虚拟机(JVM)一般会选择终止线程;Exception通常情况下是可以被程序处理的,并且在程序中应该尽可能的去处理这些异常。

18.2 异常处理机制

  • 抛出异常
  • 捕获异常
  • 异常处理关键字
    • try、catch、finally、throw、throws

18.3 自定义异常

  • 使用Java内置的异常类可以描述在编程时出现的大部分异常情况。除此之外,用户还可以自定
    义异常。用户自定义异常类,只需继承Exception类即可。
  • 在程序中使用自定义异常类,大体可分为以下几个步骤:
    1. 创建自定义异常类。
    2. 在方法中通过throw关键字抛出异常对象。
    3. 如果在当前抛出异常的方法中处理异常,可以使用try-catch语句捕获并处理;否则在方法的声明处通过throws关键字指明要抛出给方法调用者的异常,继续进行下一步操作。
    4. 在出现异常方法的调用者中捕获并处理异常。

18.4 使用经验

  • 处理运行时异常时,采用逻辑去合理规避同时辅助try-catch处理
  • 在多重catch块后面,可以加一个catch(Exception)来处理可能会被遗漏的异常
  • 对于不确定的代码,也可以加上try-catch,处理潜在的异常
  • 尽量去处理异常,切忌只是简单地调用 printStackTrace()去打印输出
  • 具体如何处理异常,要根据不同的业务需求和异常类型去决定
  • 尽量添加finally语句块去释放占用的资源

二、内部类和常用类

1. 内部类

在一个类的内部在定义一个完整的类

  • 编译后,内部类也会生成独立的字节码文件(.class文件)
    • Outer.class + Outer$Inner.class
  • 可以获得外部类的私有属性(不破坏封装性)
  1. 成员内部类

    • 创建
      • 创建外部类对象:Outer outer = new Outer()
      • 创建内部类对象:Inner inner = outer.new Inner()
      • —————: Inner inner = new Outer().new Inner()
    • 内部/外部类同名时访问外部类属性
      • Outer.this.name
    • 内部类不能定义静态成员
      • 可以包含静态常量
  2. 静态内部类

    • 只有静态内部类用static修饰
    • 调用外部类属性
      • 先创建外部类对象,再调用
    • 可以定义静态成员
    • 使用时可以直接创建内部类对象
      • Outer.Inner inner = new Outer.Inner()
  3. 局部内部类(在方法内定义类)

    • 访问局部常量:jdk1.7要求变量必须是常量才可以直接访问, jdk1.8之后自动添加final
      • 为了保证变量的生命周期与自身相同
  4. 匿名内部类

    • 没有类名的局部内部类(一切特征与局部内部类相同)

    • 必须继承一个父类或者一个接口

    • 定义类、实现类、创建对象的语法合并,只能创建一个该类的对象

    • ```java
      public static void main(String[] args){

      // Usb为接口
      // 局部内部类
      class Fan implements Usb{
          @Override
          public void service(){
              System.out.println("电脑连接成功了,风扇开始工作了...");
          }
      }
      // 使用局部内部类创建对象
      Usb usb=new Fan();
      usb.service();
      
      /* 等价于 */
      
      // 匿名内部类
      Usb usb=new Usb(){
           @Override
          public void service(){
              System.out.println("电脑连接成功了,U盘开始工作了...");
          }
      }
      usb.service();
      

      }

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73



      ## 2. Object类

      - 超类、基类,所有类的直接或间接父类,位于继承树的最顶层
      - 任何类,如没有书写extends显示继承某个类,都默认直接继承Object类,否则为间接继承
      - Object类中所定义的方法,是所有对象都具备的方法
      - Object类型可以存储任何对象
      - 作为参数,可接受任何对象
      - 作为返回值,可返回任何对象

      ### 2.1 getClass()方法

      - `public final Class<?> getClass()`
      - 返回引用中存储的实际对象类型
      - 应用:通常用于判断两个引用中实际存储对象类型是否一致

      ### 2.2 hashCode()方法

      - `public int hashCode () {}`
      - 返回该对象的哈希码值
      - 哈希值根据**对象的地址**或**字符串**或**数字**使用hash算法计算出来的int类型的数值
      - 一般情况下相同对象返回相同哈希码

      ### 2.3 toString()方法

      - public String toString () {}
      - 返回该对象的字符串表示(表现形式)
      - 可以根据程序需求==重写==该方法,如:展示对象各个属性值

      ### 2.4 equals()方法

      - public boolean equals(Object obj){}

      - 默认实现为(this==obj),比较两个对象地址是否相同

      - 可进行覆盖,比较两个对象的内容是否相同

      - equals()方法覆盖步骤

      - 比较两个引用是否指向同一个对象

      - 判断obj是否为null

      - 判断两个引用指向的实际对象类型是否一致(instanceof)

      - 强制类型转换

      - 依次比较各个属性值是否相同

      - ```java
      @override
      public boolean equals(Object obj){
      // 1.判断是否为同一对象
      if(this==obj){
      return true;
      }
      // 2.判断是否为空
      if(obj==null){
      return false;
      }
      // 3. 判断是否是同类型
      if(obj instanceof thisClass){
      // 4.向下转型
      thisClass o=(thisClass) obj;
      // 5.比较属性
      if(this.name.equals(o.gtetName()) && ...){
      return true;
      }
      }
      return false;
      }

2.5 finalize()方法

  • 程序员一般无需涉及,当对象被判定为垃圾对象时,由JVM自动调用此方法,用以标记垃圾对象,进入回收队列。
  • 垃圾对象:没有有效引用指向此对象时,为垃圾对象。
  • 垃圾回收:由GC销毁垃圾对象,释放数据存储空间。
  • 自动回收机制:JVM的内存耗尽,一次性回收所有垃圾对象。
  • 手动回收机制:使用System.gc();通知JVM执行垃圾回收。

3. 包装类

  • 基本数据类型所对应的引用数据类型(由栈转堆)

  • | 基本数据类型 | 包装类型 |
    | —————— | ————- |
    | byte | Byte |
    | short | Short |
    | int | Integer |
    | long | Lone |
    | float | Float |
    | double | Double |
    | boolean | Boolean |
    | char | Character |

  • 类型转换与装箱、拆箱

    • jdk1.5之后,提供自动装箱、拆箱
  • 字符串与基本数据类型互转

    • parseXXX() 字符串转为基本数据类型
    • toString() (也可以改变进制法)

3.1 Integer

==Integer缓冲区==: 预先创建了256个常用的整数包装类

[-128, 127]

1
2
3
4
5
6
7
8
9
10
11
Integer integer1 = new Integer(10);
Integer integer2 = new Integer(10);
System.out.println(integer1 == integer2); // false

Integer integer3 = 10;
Integer integer4 = 10;
System.out.println(integer3 == integer4); // true

Integer integer5 = 128;
Integer integer6 = 128;
System.out.println(integer5 == integer6); // false

3.2 String

3.2.1 简介

  • 字符串是常量,创建之后不可改变
  • 字符串字面值存储在字符串池中,可以共享(方法区)
  • 字符串赋值操作为重新开辟字符串值 / new操作会在池和堆中各创建一个字符串

3.2.2 常用方法

public int length()

public char charAt(int dex)

public boolean contains(String str)

public char[] toCharArray(): 将字符串转换成数组

public int indexOf(String str)

public int lastIndexOf(String str)

public String trim(): 去掉字符串前后的空格

public String toUpperCase()

public boolean endsWith(String str)

public String replace(char oldChar, char newChar)

public String[] split(String str): 根据str做拆分

3.2.3 可变长字符串

  • StringBuffer:可变长字符串,JDK1.0提供,运行效率慢、线程安全。
  • StringBuilder:可变长字符串,JDK5.0提供,运行效率快、线程不安全。

3.3 BigDecimal

1
2
3
BigDecimal d3 = new BigDecimal("1.0");
BigDecimal d4 = new BigDecimal("0.9");
System.out.println(d3.subtract(d4));

3.4 Date

3.5 Calendar

protected类型,无法通过new创建 —-> Calendar calendar = Calendar.getInstance();

3.6 SimpleDateFormat

SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");

String str=sdf.format(date)

3.7 System

  • arraycopy()
  • currentTimeMillis(): 获取当前系统时间,返回的是毫秒值
  • gc(): 建议JVM尽快启动垃圾回收垃圾
  • exit(int status): 退出jvm,如果参数为0表示正常退出jvm,非0表示异常退出jvm

三、集合框架

1. 集合

  • 概念:对象的容器,定义了对多个对象进行操作的常用方法。可实现数组功能。
  • 和数组区别:
    1. 数组长度固定,集合长度不固定
    2. 数组可以存储基本类型和引用类型,集合只能存储引用类型
  • 位置:java.util.*;

image-20230409220942058

2. Collection父接口

  • 特点:代表一组任意类型的对象,无序、无下标、不能重复
  • 方法:
    1. boolean add(Object obj)//添加一个对象。
    2. boolean addAll(Collection c)//将一个集合中的所有对象添加到此集合中。
    3. void clear()//清空此集合中的所有对象
    4. boolean contains(Object o)//检查此集合中是否包含o对象
    5. boolean equals(0bject o)//比较此集合是否与指定对象相等
    6. boolean isEmpty()//判断此集合是否为空
    7. boolean remove(Object o)//在此集合中移除o对象
    8. int size()//返回此集合中的元素个数。
    9. Object[]toArray()//将此集合转换成数组。

3. List

  • 特点:有序、有下标、元素可以重复
  • 方法:
    1. add
    2. addAll
    3. get
    4. subList
  • 列表迭代器:ListIterator

3.1 ArrayList

  • 特点:数组结构实现,查询快、增删慢
  • JDK1.2版本,运行效率快、线程不安全
  • 源码:
    • DEFAULT_CAPACITY=10;默认容量
      • 没有添加任何元素时(无参构造方法),容量0,添加任意一个元素后,容量为10 -> 15 (每次扩容为1.5倍)
    • size 实际元素个数
    • elementData 存放元素的数组

3.2 Vector

  • 特点:数组结构实现,查询快、增删慢
  • JDK1.0版本,运行效率慢、线程安全
1
2
3
4
Enumeration en=vector.elements();
while(en.hasMoreElements()){
String o=(String)en.nextElements();
}

3.3 LinkedList

  • 链表结构实现,增删快、查询慢

4. 泛型

4.1 泛型概念

  • Java泛型是JDK1.5中引入的一个新特性,其本质是参数化类型,把类型作为参数传递。
  • 常见形式有泛型类、泛型接口、泛型方法。
  • 语法: T称为类型占位符,表示一种引用类型。
  • 好处:
    1. 提高代码的重用性
    2. 防止类型转换异常,提高代码的安全性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* 泛型类
* 语法,类名后面加<T>
* T是类型占位符,表示一种引用类型,如果编写多个类型占位符,用逗号隔开
* @param <T>
*/
public class myGeneric<T> {
// 使用泛型
// 创建变量
T t;
// 泛型作为方法的参数
public void setT(T t) {
System.out.println(t);
}
// 泛型作为方法的返回值
public T getT() {
return t;
}
}

/* 使用 */
myGeneric<String> mygeneric=new myGeneric<String>()

4.2 泛型集合

  • 概念:参数化类型、类型安全的集合,强制集合元素的类型必须一致。
  • 特点:
    • 编译时即可检查,而非运行时抛出异常
    • 访问时,不必类型转换(拆箱)
    • 不同泛型之间引用不能相互赋值,泛型不存在多态。

5. Set

5.1 Set概念

  • 特点:无序、无下标、元素不可重复
  • 方法:全部继承自Collection中的方法

5.2 Set实现类

5.2.1 HashSet

  • 基于HashCode实现元素不重复
  • 当存入元素的哈希码相同时,会调用equals进行确认,如结果为true,则拒绝后者存入。

增加过程:1. 计算hashCode,无重复直接添加;2. equals,不相等链表连接

5.2.2 TreeSet

  • 基于排列顺序实现元素不重复

  • 基于排列顺序实现元素不重复

  • 实现了SortedSet接口,对集合元素自动排序
  • 元素对象的类型必须实现Comparable接口,指定排序规则
  • 通过CompareTo方法确定是否为重复元素

6. Map

6.1 Map父接口

  • 特点:存储一堆数据(Key-Value),无序、无下标,键不可重复,值可重复。
  • 方法
    • V put(K key, V value)
    • Object get(Object key)
    • keyset\
    • Collection\ values()
    • Set>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
        // 遍历Map集合
// keySet()方法
// Set<String> keyset = map.keySet();
for(String key: map.keySet()) {
String value = map.get(key);
System.out.println(key + "=" + value);
}
// entrySet()方法
// Set<Map.Entry<String, String>> entryset = map.entrySet();
for(Map.Entry<String, String> entry: map.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
System.out.println(key + "=" + value);
}

6.2 Map集合的实现类

6.2.1 HashMap

  • 特点:默认初始容量(16)、默认加载因子(0.75)

  • 存储结构:哈希表(数组+链表+红黑树)

  • 源码分析:

    1. HashMap刚创建时,table是null,节省空间,当添加第一个元素时,table容量调整为16
    2. 当元素个数大于阈值(16*0.75 = 12)时,会进行扩容,扩容后的大小为原来的两倍,目的是减少调整元素的个数
    3. jdk1.8 当每个链表长度 >8 ,并且数组元素个数 ≥64时,会调整成红黑树,目的是提高效率
    4. jdk1.8 当链表长度 <6 时 调整成链表
    5. jdk1.8 以前,链表时头插入,之后为尾插入

6.2.2 Hashtable

  • JDK1.0版本,线程安全,运行效率慢;不允许nul1作为key或是value。

6.2.3 Properties:

  • Hashtable的子类,要求key和value都是String。通常用于配置文件的读取。

6.2.4 TreeMap

  • 实现了SortedMap接口(是map的子接口),可以对key自动排序

7. Collections工具类

  • 概念:集合工具类,定义了除了存取以外的集合常用方法

  • 直接二分查找int i = Collections.binarySearch(list, x); 成功返回索引

  • 其他方法 : copy复制、reverse反转、shuffle打乱

  • 补充

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    // list转成数组
    Integer[] arr = list.toArray(new Integer[10]);
    sout(arr.length);
    sout(Array.toString(arr));

    // 数组转成集合
    // 此时为受限集合,不能 添加和删除!
    String[] name = {"张三","李四","王五"};
    List<String> list2 = Arrays.asList(names);

    // 把基本类型数组转为集合时,需要修改为包装类
    Integer[] nums = {100, 200, 300, 400, 500};
    List<Integer> list3 = Arrays.asList(nums);

四、IO框架

1. 流

  • 概念:内存与存储设备之间传输数据的通道

1.1 流的分类

1.1.1 方向分类

  • 输入流:将<存储设备>中的内容读到<内存>中
  • 输出流:将<内存>中的内容写到<存储设备>中

1.1.2 单位分类

  • 字节流:以字节为单位,可以读写所有数据
  • 字符流:以字符为单位,只能读写文本数据

1.1.3 功能分类

  • 节点流:具有实际传输数据的读写功能
  • 过滤流:在节点流的基础之上增强功能

2. 字节流

2.1 字节流的父类(抽象类)

1
2
3
4
5
6
7
8
9
//InputStream 字节输入流
public int read(){}
public int read(byte[] b){}
public int read(byte[] b, int off, int len){}

// OutputStream 字节输出流
public void write(int n){}
public void write(byte[] b){}
public void write(byte[] b, int off, int len){}

2.2 文件字节流

2.2.1 文件输入流(FileInputStream)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
psvm(String[] args) throws Exception{
// 1 创建FileInputStream 并指定文件路径
FileInputStream fis = new FileInputStream("d:\\abc.txt");
// 2 读取文件
// fis.read();
// 2.1单字节读取
int data = 0;
while((data = fis.read()) != -1){
sout((char)data);
}
// 2.2 一次读取多个字节
byte[] buf = new byte[3]; // 大小为3的缓存区
int count = fis.read(buf); // 一次读3个
sout(new String(buf));
sout(count);
int count2 = fis.read(buf); // 再读3个
sout(new String(buf));
sout(count2);

// 上述优化后
int count = 0;
while((count = fis.read(buf)) != -1){
sout(new String(buf, 0, count));
}

// 3 关闭
fis.close();
}

2.2.2 文件输出流(FileOutputStream)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public static void main(String[] args) throws Exception{
// 创建 FileOutputStream 对象
FileOutputStream fos=new FileOutputStream("hello/src/bayyy/IO/hello2.txt", true); // arg2: append 表示是否追加
// 写入文件
fos.write(97);
fos.write('b');
String str="hello world";
fos.write(str.getBytes());
// 关闭流
fos.close();


/* ------ 测试读取 -------- */
FileInputStream fis=new FileInputStream("hello/src/bayyy/IO/hello2.txt");
byte[] bytes = new byte[1024];
int dataLen=0;
while ((dataLen=fis.read(bytes)) != -1){
System.out.println(new String(bytes,0,dataLen));
}
}

2.3 字节缓冲流

2.3.1 缓冲流:BufferedInputStream/ BufferedOutputStream

  • 提高IO效率,减少访问磁盘次数
  • 数据存储在缓冲区中,flush是将缓冲区的内容写入文件中,也可以直接close
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// 使用字节缓冲流 读取 文件
public static void main(String[] args) throws Exception {
// 创建 BufferedInputStream 对象
FileInputStream fis=new FileInputStream("hello/src/bayyy/IO/hello.txt");
BufferedInputStream bis=new BufferedInputStream(fis);
// 读取文件
// 1. 一次读取一个字节
int data=0;
// while ((data=bis.read()) != -1){
// System.out.println((char)data);
// }
// 2. 一次读取一个字节数组
byte[] buf= new byte[1024];
int dataLen=0;
while ((dataLen=bis.read(buf)) != -1){
System.out.println(new String(buf,0,dataLen));
}
// 关闭流
bis.close(); // 关闭 BufferedInputStream 流 会自动关闭 FileInputStream 流
fis.close();
}

// 使用字节缓冲流 写入 文件
public static void main(String[] args) throws Exception{
// 创建 BufferedOutputStream 对象
FileOutputStream fos=new FileOutputStream("hello/src/bayyy/IO/hello4.txt");
BufferedOutputStream bos=new BufferedOutputStream(fos);
// 写入文件
// 1. 一次写入一个字节
for (int i = 0; i < 5; i++) {
bos.write("helloWorld!\r\n".getBytes());
bos.flush(); // 刷新缓冲区, 将缓冲区的数据写入文件
}
// // 2. 一次写入一个字节数组
// byte[] buf="helloWorld!\r\n".getBytes();
// for (int i = 0; i < 5; i++) {
// bos.write(buf);
// bos.flush();
// }
// 关闭流
// 如果上述过程未出现异常, 则会自动调用 flush() 方法
bos.close(); // 关闭 BufferedOutputStream 流 会自动关闭 FileOutputStream 流
// fos.close();

}

3. 对象流

ObjectOutputStream / ObjectInputStream

  • 增强了缓冲区功能
  • 增强了读写8种基本数据类型和字符串的功能
  • 增强了读写对象的功能
    • readObject() 从流中读取一个对象
    • writeObject(Object obj) 向流中写入一个对象

使用流传输对象的过程称为序列化、反序列化

3.1 序列化与反序列化

  • 要求

    • 序列化类必须实现 ==Serializable 接口==

    • 序列化类中的对象属性要求实现 ==Serializable 接口==

    • 需要创建常量字段seruakVersionUID

    • serialVersionUID: 序列化版本号 (用于反序列化时判断是否为同一个类)

      1
      private static final long serialVersionUID = -3166007176280153021L;

      image-20230413213128998

    • 使用==transient==修饰属性,此属性不能被序列化

    • 静态属性不能被序列化

    • 序列化多个对象,可以借助集合实现

3.1.1 序列化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 使用objectoutputStream实现序列化
public static void main(String[] args) throws Exception {
// 创建 ObjectOutputStream 对象
FileOutputStream fos=new FileOutputStream("hello/src/bayyy/IO/hello5.txt");
ObjectOutputStream oos=new ObjectOutputStream(fos);
// 序列化
// Student s1=new Student("张三", 18);
// oos.writeObject(s1); // 序列化对象类必须实现 Serializable 接口
// 序列化集合
Student s1=new Student("张三", 18);
Student s2=new Student("李四", 19);
Student s3=new Student("王五", 20);
ArrayList<Student> list=new ArrayList<>();
list.add(s1);
list.add(s2);
list.add(s3);
oos.writeObject(list);
System.out.println("序列化成功");
// 关闭流
oos.close(); // 关闭 ObjectOutputStream 流 会自动关闭 FileOutputStream 流
}

3.1.2 反序列化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 使用ObjectInputSteam实现反序列化(读取重构对象)
public static void main(String[] args) throws Exception{
// 创建 ObjectInputStream 对象
FileInputStream fis=new FileInputStream("hello/src/bayyy/IO/hello5.txt");
ObjectInputStream ois=new ObjectInputStream(fis);
// 反序列化
// Student s1=(Student)ois.readObject();
// System.out.println(s1);
// 反序列化集合
ArrayList<Student> list=(ArrayList<Student>)ois.readObject();
System.out.println(list);
// 关闭流
ois.close(); // 关闭 ObjectInputStream 流 会自动关闭 FileInputStream 流
}

3.2 编码方式

  • IS0-8859-1:收录除ASCII外,还包括西欧、希腊语、泰语、阿拉伯语、希伯来语对应的文字符号。
  • UTF-8:针对Unicode码表的可变长度字符编码
  • GB2312:简体中文
  • GBK:简体中文、扩充
  • BIG5:台湾,繁体中文

4. 字符流

4.1 字符流案例

1
2
3
4
5
6
7
8
9
10
11
12
// 传统字节流读取
psvm(String[] args){
// 1. 创建FileInputStream对象
FileInputSteam fis = new FileInputStream("路径");
// 2. 读取
int data = 0;
while((data = fis.read()) != -1){
sout((char)data);
}
// 3. 关闭
fis.close();
}

4.2 字符流的父类(抽象类)

Reader 字符输入流

  • public int read(){}
  • public int read(char[] c){}
  • public int read(char[] b, int off, int len){}

Writer 字符输出流

  • public void write(int n){}
  • public void write(String str){}
  • public void write(char[] c){}

4.3 文件字符流

4.3.1 FileReader/FileWriter

FileReader/FileWriter

1
2
3
4
5
6
7
8
9
10
11
12
13
public static void main(String[] args) throws Exception{
// 创建 FileWriter 对象
FileWriter fw=new FileWriter("hello/src/bayyy/IO/hello6.txt", true);
fw.write("好好学习!");
fw.close();
// 创建 FileReader 对象
FileReader fr=new FileReader("hello/src/bayyy/IO/hello6.txt");
int data=0;
while ((data=fr.read()) != -1){
System.out.println((char)data);
}
fr.close();
}

4.4 字符缓冲流

4.4.1 BufferedReader / BufferedWriter

BufferedReader / BufferedWriter

  • 高效读写、支持输入换行符、可一次写一行读一行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
psvm(String[] args) throws Exception{
// 创建缓冲流
FileReader fr = new FileReader("..");
BufferedReader br = new BufferedReader(fr);
// 读取
// 1. 第一种方式
char[] buf = new char[1024];
int count = 0;
while((count = br.read(buf)) != -1){
sout(new String(buf, 0, count));
}
// 2. 第二种方式 一行一行读取
String line = null;
while((line = br.readLine()) != null){
sout(line);
}

// 关闭
br.close(); // 此时会自动关闭fr
}

psvm(String[] args){
// 1. 创建BufferedWriter对象
FileWriter fw = new FileWriter("..");
BufferedWriter bw = new BufferedWriter(fw);
// 2. 写入
for(int i = 0; i < 10; i ++){
bw.write("写入的内容");
bw.newLine(); // 写入一个换行符
bw.flush();
}
// 3. 关闭
bw.close(); // 此时会自动关闭fw
}

4.5 PrintWriter

封装了print() / println() 方法 支持写入后换行

支持数据原样打印

1
2
3
4
5
6
7
8
9
10
11
psvm(String[] args){
// 1 创建打印流
PrintWriter pw = new PrintWriter("..");
// 2 打印
pw.println(12);
pw.println(true);
pw.println(3.14);
pw.println('a');
// 3 关闭
pw.close();
}

4.6 转换流

4.6.1 InputStreamReader/OutputStreamWriter

桥转换流 InputStreamReader / OutputStreamWriter

可将字节流转换为字符流

可设置字符的编码方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
psvm(String[] args) throws Exception{
// 1 创建InputStreamReader对象
FileInputStream fis = new FisInputStream("..");
InputStreamReader isr = new InputStreamReader(fis, "utf-8");
// 2 读取文件
int data = 0;
while((data = isr.read()) != -1){
sout((char)data);
}
// 3 关闭
isr.close();
}

psvm(String[] args) throws Exception{
// 1 创建OutputStreamReader对象
FileOutputStream fos = new FisOutputStream("..");
OutputStreamWRITER osw = new OutputStreamReader(fos, "utf-8");
// 2 写入
for(int i = 0; i < 10; i ++){
osw.write("写入内容");
osw.flush();
}
// 3 关闭
osw.close();
}

5. File类

  • 代表物理盘符中的一个文件或者文件夹

5.1 示例

  • 方法
    • createNewFile()//创建一个新文件
    • mkdir()//创建一个新目录
    • delete()//删除文件或空目录
    • exists()//判断File对象所对象所代表的对象是否存在
    • getAbsolutePath()//获取文件的绝对路径
    • getName()//取得名字
    • getParent()//获取文件/目录所在的目录
    • isDirectory()//是否是目录
    • isFile()//是否是文件length()//获得文件的长度
    • listFiles()//列出目录中的所有内容
    • renameTo()//修改文件名为
  • FileFilter接口
    • public interface FileFilter
      • boolean accept(File pathname)
      • 当调用File类中的listFiles()方法时,支持传入FileFilter接口接口实现类,对获取文件进行过滤,只有满足条件的文件的才可出现在listFiles()的返回值中。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
/*
File类的使用
1. 分隔符
2. 文件操作
3. 文件夹操作
*/
public class Demo{
psvm(String[] args){
separator();
}
// 1. 分隔符
public static void separator(){
sout("路径分隔符" + File.pathSeparator);
sout("名称分隔符" + File.separator);
}
// 2. 文件操作
public static void fileOpen(){
// 1. 创建文件
if(!file.exists()){ // 是否存在
File file = new File("...");
boolean b = file.creatNewFile();
}

// 2. 删除文件
// 2.1 直接删除
file.delete(); // 成功true
// 2.2 使用jvm退出时删除
file.deleteOnExit();

// 3. 获取文件信息
sout("获取绝对路径" + file.getAbsolutePaht());
sout("获取路径" + file.getPath());
sout("获取文件名称" + file.getName());
sout("获取夫目录" + file.getParent());
sout("获取文件长度" + file.length());
sout("文件创建时间" + new Date(file.lashModified()).toLocalString());

// 4. 判断
sout("是否可写" + file.canWrite());
sout("是否是文件" + file.isFile());
sout("是否隐藏" + file.isHidden());
}


// 文件夹操作
public static void directoryOpe() throws Exception{
// 1. 创建文件夹
File dir = new File("...");
sout(dir.toString());
if(!dir.exists()){
//dir.mkdir(); // 只能创建单级目录
dir.mkdirs(); // 创建多级目录
}

// 2. 删除文件夹
// 2.1 直接删除
dir.delete(); // 只能删除最底层空目录
// 2.2 使用jvm删除
dir.deleteOnExit();

// 3. 获取文件夹信息
sout("获取绝对路径" + dir.getAbsolutePaht());
sout("获取路径" + dir.getPath());
sout("获取文件名称" + dir.getName());
sout("获取夫目录" + dir.getParent());
sout("获取文件长度" + dir.length());
sout("文件夹创建时间" + new Date(dir.lashModified()).toLocalString());

// 4. 判断
sout("是否是文件夹" + dir.isFile());
sout("是否隐藏" + dir.isHidden());

// 5. 遍历文件夹
File dir2 = new File("...");
String[] files = dir2.list();
for(String string : files){
sout(string);
}

// FileFilter接口的使用

File[] files2 = dir2.listFiles(new FileFilter(){

@Override
public boolean accept(File pathname){
if(pathname.getName().endsWith(".jpg")){
return true;
}
return false;
}
});
for(File file : files2){
sout(file.getName());
}

}
}

5.2 递归遍历文件夹

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
psvm(String[] args){
listDir(new File("d:\\myfiles"));
}
public static void listDir(File dir){
File[] files = dir.listFiles();
sout(dir.getAbsolutePath());
if(files != null && files.length > 0){
for(File file : files){
if(file.isDirectory()){
listDir(file); // 递归
}else {
sout(file.getAbsolutePath());
}
}
}
}

5.3 递归删除文件夹

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static void deleteDir(File dir){
File[] files = dir.listFiles();
if(files != null && files.length > 0){
for(File file : files){
if(file.idDirectory()){
deleteDir(file); // 递归
}else{
// 删除文件
sout(file.getAbsolutePath() + "删除" + file.delete());
}
}
sout(files.getAbsolutePath() + "删除" + file.delete());
}
}

6. Properties(集合的补充)

  • Properties:属性集合

    • 特点

      1. 存储属性名和属性值

      2. 属性名和属性值都是字符串类型

      3. 没有泛型

      4. 和流有关

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        propertie.setProperty("username", "zhangsan");
        properties.setProperty("age", "20");
        System.out.println(properties.toString());
        //3遍历
        //3.1----keySet----
        //3.2-----entrySet----
        //3.3-----stringPropertyNames()---
        Set<String> pronames=properties.stringPropertyNames();
        for (String pro : pronames) {
        System.out.println(pro+"====="+properties.getProperty(pro));
  • 遍历

    • keySet
    • entrySet
    • stringPropertyNames
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    //和流有关的方法
    //----------list方法---------
    Printwriter pw=new Printwriter("d:\\print.txt");
    properties.list(pw);
    pw.close();
    //---------2 store方法-----------
    Fileoutputstream fos=new Fileoutputstream("d:\\store.properties");
    properties.store(fos,"注释");
    fos.close();
    //---------3 load方法-----------
    Properties properties2=new Properties();
    FileInputStream fis=new FileInputSteam("d:\\store.properties");
    properties2.load(fis);
    fis.close();
    System.out.println(properties.toString());

五、线程

1. 基本概念

  • 进程(Process):正在运行的程序,是系统进行资源分配的基本单位(通过PID进行区分)
  • 线程(Thread):又称轻量级进程(Light Weight Process)。

    • 进程中的一条执行路径,也是CPU的基本调度单位。
    • 一个进程由一个或多个线程组成,彼此间完成不同的工作,
    • 同时执行,称为多线程。
  • 区别:

    1. 进程是操作系统资源分配的基本单位,而线程是CPU的基本调度单位
    2. 一个程序运行后至少有一个进程。
    3. 一个进程可以包含多个线程,但是至少需要有一个线程,否则这个进程是没有意义。
    4. 进程间不能共享数据段地址,但同进程的线程之间可以。
  • 线程的组成:

    • CPU时间片:操作系统(OS)会为每个线程分配执行时间。
    • 运行数据:
      • 堆空间:存储线程需使用的对象,多个线程可以共享堆中的对象。
      • 栈空间:存储线程需使用的局部变量,每个线程都拥有独立的栈。
    • 线程的逻辑代码
  • 线程的特点:

    • 线程抢占式执行
      • 效率高
      • 可防止单一线程长时间独占CPU
    • 在单核CPU中,宏观上同时执行,微观上顺序执行。
  • 创建线程

    • 继承Thread类,重写run方法
    • 实现Runnable接口
    • 实现Callable接口

2. 线程创建

2.1 继承Thread类

1
2
3
4
// 创建线程对象
MyThread mt=new MyThread();
// 启动线程
mt.start();
  • 获取线程ID和线程名称

    • 获取线程ID和线程名称

      1. 在Thread的子类中调用this.getId()或this.getName()

      2. 使用Thread.currentThread().getId()和Thread.currentThread().getName()

        1
        2
        //            System.out.println("线程id: "+this.getId()+"线程name: "+this.getName()+" 子线程..........."+i);
        System.out.println("线程id: "+Thread.currentThread().getId()+"线程name: "+ Thread.currentThread().getName()+" 子线程..........."+i);
    • 修改线程名称

      1. 调用线程对象的setName()方法

      2. 使用线程子类的构造方法赋值

        1
        2
        3
        4
        5
        6
        public MyThread() {
        }

        public MyThread(String name) {
        super(name); // 调用Thred的构造方法
        }

2.2 实现Runnable接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// main.java
// 1. 创建Runnable接口的实现类对象, 仅表示线程要执行的任务
MyRunnable mt=new MyRunnable();
// 2. 创建Thread类的对象, 用于执行线程, 并将Runnable接口的实现类对象作为参数传递给Thread类的构造方法
Thread t=new Thread(mt);
// 3. 调用Thread类的start()方法, 启动线程
t.start();
// 4. 主线程
for (int i = 0; i < 100; i++) {
System.out.println("主线程....."+i);
}

// MyRunnable.java
public class MyRunnable implements Runnable{
@Override
public void run() {
// Coding
}
}

/* 匿名内部类方法 */
// 匿名内部类创建方法
Runnable runnable=new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("线程id: "+Thread.currentThread().getId()+"线程name: "+ Thread.currentThread().getName()+" 子线程..........."+i);
}
}
};
Thread t2=new Thread(runnable, "线程名称");
t2.start();

2.3 常见方法

  • 休眠

    • public static void sleep(long millis); (抛出异常,如果父类无异常处理,则需要try/catch处理(子类不能抛出父类范围更大的异常))
  • 放弃

    • public static void yield();
  • 加入

    • public final void join();
    • 允许其他线程加入到当前线程中,并阻塞当前线程,直到加入线程执行完毕;
  • 设置线程优先级

    • 优先级
      • 线程对象.setPriority(),
      • 线程优先级为1-10,默认为5,优先级越高,表示获取CPU机会越多。
    • 守护线程
      • 线程对象.setDaemon(true);设置为守护线程
      • 线程有两类:用户线程(前台线程)、守护线程(后台线程)
      • 如果程序中所有前台线程都执行完毕了,后台线程会自动结束。
      • 垃圾回收器线程属于守护线程

3. 线程安全问题

3.1 存在问题

  • 当多线程并发访问临界资源时,如果破坏原子操作,可能会造成数据不一致。
  • 临界资源:共享资源(同一对象),一次仅允许一个线程使用,才可保证其正确性。

3.2 同步方法

3.2.1 同步代码块

1
2
3
synchronized(临界资源对象){	// 对临界资源对象加锁
// 代码(原子操作)
}
  • 每个对象都有一个互斥锁标记,用来分配给线程的。
  • 只有拥有对象互斥锁标记的线程,才能进入对该对象加锁的同步代码块。
  • 线程退出同步代码块时,会释放相应的互斥锁标记。

3.2.2 同步方法

1
2
3
synchronized 返回值类型 方法名称(形参列表) {	// 对当前对象(this)加锁 	// 如果是静态方法,锁是 类名.class
// 代码(原子操作)
}

3.2.3 同步规则

  • 注意:
    • 只有在调用包含同步代码块的方法,或者同步方法时,才需要对象的锁标记。
    • 如调用不包含同步代码块的方法,或普通方法时,则不需要锁标记,可直接调用。
  • 已知JDK中线程安全的类:
    • StringBuffer
    • Vector
    • Hashtable
    • 以上类中的公开方法,均为synchonized修饰的同步方法。

3.3 线程通信

  • 等待:

    • public final void wait()
    • public final void wait(long timeout)
    • 必须在对obj加锁的同步代码块中。在一个线程中,调用obj.wait()时,此线程会释放其拥有的所有锁标记。同时此线程阻塞在o的等待队列中。释放锁,进入等待队列。
  • 通知:

    • public final void notify()

    • public final void notifyA11()

4. 高级多线程

4.1 线程池

  • 问题:
    • 线程是宝贵的内存资源、单个线程约占1MB空间,过多分配易造成内存溢出。
    • 频繁的创建及销毁线程会增加虚拟机回收频率、资源开销,造成程序性能下降。
  • 线程池:
    • 线程容器,可设定线程分配的数量上限。
    • 将预先创建的线程对象存入池中,并重用线程池中的线程对象。
    • 避免频繁的创建和销毁。
  • 将任务提交给线程池,由线程池分配线程、运行任务,并在当前任务结束后复用线程

4.2 创建线程池

  • 常用的线程池接口和类(所在包java.util.concurrent):
    • Executor: 线程池的顶级接口
    • ExecutorService: 线程池接口,可通过submit(Runnable task)提交任务代码
      • ThreadPoolExecutor: 线程池的实现类
        • ScheduledExecutorService: 接口, schedule()
    • Executors 工厂类: 通过此类可以获得一个线程池
      • (1) newFixedThreadPool(): 创建固定大小的线程池
      • (2) newCachedThreadPool(): 创建一个可缓存的线程池
      • (3) newSingleThreadExecutor():创建单个线程的线程池
      • (4) newScheduledThreadPool(): 创建固定大小的线程池, 支持定时及周期性任务执行
    • 通过 newFixedThreadPool(int nThreads)获取固定数量的线程池。参数:指定线程
      池中线程的数量。
    • 通过newCachedThreadPool()获得动态数量的线程池,如不够则创建新的,没有上限
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public static void main(String[] args) {
// 1 创建固定线程个数的线程池
ExecutorService es=Executors.newFixedThreadPool(4);
// 2 提交任务
Runnable runnable=new Runnable() {
private int ticket=100;
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (ticket>0) {
System.out.println(Thread.currentThread().getName()+"卖票: "+ticket--);
}
}
}
};
for (int i = 0; i < 4; i++) {
es.submit(runnable);
}
es.shutdown(); // 关闭线程池, 会等待线程池中的任务执行完毕后再关闭
}

5. Callable接口

1
2
3
public interface Callable<V> {
public V call() throws Exception;
}
  • JDK5加入,与Runnable接口类似,实现之后代表一个线程任务。
  • Callable具有泛型返回值、可以声明异常。

5.1 Callable创建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// 1 创建Callable接口的实现类对象
Callable<Integer> callable=new Callable<Integer>() {
@Override
public Integer call() throws Exception {
System.out.println(Thread.currentThread().getName()+"开始执行");
Integer sum=0;
for (int i = 1; i <= 100; i++) {
sum += i;
}
return sum;
}
};
// 2 把Callable接口的实现类对象作为参数传递给FutureTask的构造方法
FutureTask<Integer> futureTask=new FutureTask<>(callable);
// 3 把FutureTask的对象作为参数传递给Thread类的构造方法, 创建Thread对象, 并调用start()方法
Thread thread=new Thread(futureTask);
thread.start();
sout(futureTask.get());

/* 配合线程池使用 */
ExecutorService es2=Executors.newFixedThreadPool(1);
Future<Integer> future=es2.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
System.out.println(Thread.currentThread().getName()+"开始执行");
Integer sum=0;
for (int i = 1; i <= 100; i++) {
sum += i;
}
return sum;
}
});
try {
System.out.println("主线程执行完毕"+future.get());
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException(e);
}

5.2 Future接口

  • Future: 表示将要完成任务的结果
  • 表示Executor Service.submit()所返回的状态结果,就是call()的返回值
    • 方法:Vget()以阻塞形式等待Future中的异步处理结果(call()的返回值)

5.3 Lock接口

  • JDK5加入,与synchronized比较,显示定义,结构更灵活。
  • 提供更多实用性方法,功能更强大、性能更优越。
  • 常用方法:
    • void lock() //获取锁,如锁被占用,则等待。
    • boolean tryLock() //尝试获取锁(成功返回true。失败返回false,不阻塞)
    • void unlock() //释放锁

5.3.1 重入锁

  • ReentrantLock:Lock接口的实现类,yusynchronized一样具有互斥锁的功能。
1
2
3
4
5
6
7
8
9
10
11
locker.lock();
try {
str[count++]=value;
try{
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
} finally {
locker.unlock();
}

5.3.2 读写锁

ReentrantReadWriteLock:

  • 一种支持一写多读的同步锁,读写分离,可分别分配读锁、写锁。
  • 支持多次分配读锁,使多个读操作可以并发执行。

互斥规则:

  • 写-写:互斥,阻塞。
  • 读-写:互斥,读限塞写、写阻塞读。
  • 读一读:不互斥、不阻塞。
  • 在读操作远远高于写操作的环境中,可在保障线程安全的情况下,提高运行效率。
1
2
3
private ReentrantReadWriteLock rrl=new ReentrantReadWriteLock();
private ReentrantReadWriteLock.WriteLock writeLock=rrl.writeLock();
private ReentrantReadWriteLock.ReadLock readLock=rrl.readLock();

6. 线程安全的集合

6.1 类

  • Collection体系集合中,除Vector以外的线程安全集合

image-20230420231229799

  • Map安全集合体系

image-20230420231423571

6.2 实现线程安全集合的方法

6.2.1 Collections中的工具方法

  • Collections工具类中提供了多个可以获得线程安全集合的方法

    • public static \ Collection\ synchronizedCollection(Collection\ c)
    • public static \ List\ synchronizedList (List\ list)
    • public static \ Set\ synchronizedSet (Set\ s)
    • public static Map synchronizedMap(Map m)
    • public static \ SortedSet\ synchronizedSortedSet (SortedSet\ s)
    • public static SortedMap synchronizedSortedMap(SortedMap m)
  • JDK1.2提供,接口统一、维护性高,但性能没有提升,均以synchonized实现。

1
2
ArrayList<String> arrayList=new ArrayList<String>();
List<String> synList=Collections.synchronizedList(arrayList);

6.2.2 CopyOnWriteArrayList

  • 线程安全的ArrayList,加强版的读写分离。
  • 写有锁,读无锁,读写之间不阻塞,优于读写锁。
  • 写入时,先copy一个容器副本、再添加新元素,最后替换引用
  • 使用方式与ArrayList无异。
1
List<String> synList=new CopyOnWriteArrayList<();

6.2.3 CopyOnWriteArraySet

  • 线程安全的Set,底层使用==CopyOnWriteArrayList==实现。
  • 唯一不同在于,使用addIfAbsent()添加元素,会遍历数组,如存在元素,则不添加(扔掉副本)。
1
CopyOnWriteArraySet<String> set=new CopyOnWriteArraySet<String>();

6.2.4 Queue接口(队列)

  • Collection的子接口,表示队列FIFO(First In First Out)先进先出
  • 常用方法:
    • 抛出异常:
      • boolean add(E) //顺序添加一个元素(到达上限后,再添加则会抛出异常)
      • Eremove() //获得第一个元素并移除(如果队列没有元素时,则抛异常)
      • E element() //获得第一个元素但不移除(如果队列没有元素时,则抛异常)
    • 返回特殊值:推荐使用
      • boolean offer(Ee) //顺序添加一个元素(到达上限后,再添加则会返回false)
      • E poll() //获得第一个元素并移除(如果队列没有元素时,则返回nu11)
      • E peek() //获得第一个元素但不移除(如果队列没有元素时,则返回nu11)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 1. 创建队列
// 实现类:LinkedList
Queue<String> queue=new LinkedList<String>();
// 2. 入队
queue.offer("hello");
queue.offer("world");
queue.offer("java");
queue.offer("bayyy");
// 3. 出队
System.out.println("元素个数:"+queue.size());
int size=queue.size();
for (int i = 0; i < size; i++) {
System.out.println(queue.peek());
System.out.println(queue.poll());
}
System.out.println("元素个数:"+queue.size());

6.2.5 ConcurrentLinkedQueue

  • 线程安全、可高效读写的队列,高并发下性能最好的队列。
  • 无锁、CAS(Compare And Switch)比较交换算法,修改的方法包含三个核心参数(V,E,N)
  • V:要更新的变量、E:预期值、N:新值。

  • 只有当V==E时,V=N;否则表示已被更新过,则取消当前操作。

6.2.6 BlockingQueue(阻塞队列)

  • Queue的子接口,阻塞的队列,增加了两个线程状态为无限期等待的方法。
  • 方法:

    • void put(E e)//将指定元素插入此队列中,如果没有可用空间,则等待。
    • E take()//获取并移除此队列头部元素,如果没有可用元素,则等待。
  • 实现类:

    • ArrayBlockingQueue
      • 数组结构实现,有界队列(需要设定上限)
    • LinkedBlockingQueue
      • 链表结构实现,有界队列(默认上限Integer.MAX_VALUE)
1
2
3
4
// 1. 创建队列
// 实现类:ArrayBlockingQueue、LinkedBlockingQueue、PriorityBlockingQueue
BlockingQueue<String> queue1=new ArrayBlockingQueue<String>(3);
BlockingQueue<String> queue2=new LinkedBlockingQueue<String>();

6.2.7 ConcurrentHashMap

  • 初始容量默认为16段(Segment),使用分段锁设计。
  • 不对整个Map加锁,而是为每个Segment加锁。

  • 当多个对象存入同一个Segment时,才需要互斥。

  • 最理想状态为16个对象分别存入16个Segment,并行数量16。
  • 使用方式与HashMap无异。
1
2
3
4
5
6
7
8
9
10
11
// 1. 创建集合对象
// 实现类:ConcurrentHashMap
ConcurrentHashMap<String, String> map=new ConcurrentHashMap<String, String>();
// 2. 添加元素
map.put("hello", "world");
map.put("java", "bayyy");
map.put("hello", "bayyy"); // key相同,value覆盖
// 3. 遍历集合
for(Map.Entry<String, String> entity:map.entrySet()){
System.out.println(entity.getKey()+"="+entity.getValue());
}

六、JUC并发编程

1. 线程回顾(JUC)

JUC: java.util.concurrent

1.1 背景

在Java5.0添加,目的就是为了更好的支持高并发任务。让开发者进行多线程编程时减少竞争条件和死锁的问题!

  • JUC: java.util.concurrent
  • java无法直接开启线程,需要调用native方法,即调用C++代码
  • 并发(多线程操作同一个资源,交替执行)

    • CPU一核, 模拟出来多条线程,天下武功,唯快不破,快速交替
    • 并发编程的本质:充分利用CPU资源
  • 并行(多个人一起行走, 同时进行)

    • CPU多核,多个线程同时进行 ; 使用线程池操作
  • 线程的六个状态

    • ```java
      public enum State {
         // 新生
          NEW,
          // 运行
          RUNNABLE,
          // 阻塞
          BLOCKED,
          // 等待
          WAITING,
          // 超时等待
          TIMED_WAITING,
          // 终止
          TERMINATED;
      }
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54

      ### 1.2 wait/sleep的区别:

      1. 来自不同的类: wait来自object类, sleep来自线程类
      2. 关于锁的释放:wait会释放锁, sleep不会释放锁
      3. 使用的范围不同: wait必须在同步代码块中, sleep可以在任何地方睡眠
      4. 捕获异常:wait不需要捕获异常,sleep必须捕获异常

      ### 1.3 JUC结构

      1. tools(工具类):又叫信号量三组工具类,包含有
      1. CountDownLatch(闭锁) 是一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待
      2. CyclicBarrier(栅栏) 之所以叫barrier,是因为是一个同步辅助类,允许一组线程互相等待,直到到达某个公共屏障点 ,并且在释放等待线程后可以重用。
      3. Semaphore(信号量) 是一个计数信号量,它的本质是一个“共享锁“。信号量维护了一个信号量许可集。线程可以通过调用 acquire()来获取信号量的许可;当信号量中有可用的许可时,线程能获取该许可;否则线程必须等待,直到有可用的许可为止。 线程可以通过release()来释放它所持有的信号量许可。

      2. executor(执行者):是Java里面线程池的顶级接口,但它只是一个执行线程的工具,真正的线程池接口是ExecutorService,里面包含的类有:
      1. ScheduledExecutorService 解决那些需要任务重复执行的问题
      2. ScheduledThreadPoolExecutor 周期性任务调度的类实现

      3. atomic(原子性包):是JDK提供的一组原子操作类,
      - 包含有AtomicBoolean、AtomicInteger、AtomicIntegerArray等原子变量类,他们的实现原理大多是持有它们各自的对应的类型变量value,而且被volatile关键字修饰了。这样来保证每次一个线程要使用它都会拿到最新的值。

      4. locks(锁包):是JDK提供的锁机制,相比synchronized关键字来进行同步锁,功能更加强大,它为锁提供了一个框架,该框架允许更灵活地使用锁包含的实现类有:
      1. ReentrantLock 它是独占锁,是指只能被独自占领,即同一个时间点只能被一个线程锁获取到的锁。
      2. ReentrantReadWriteLock 它包括子类ReadLock和WriteLock。ReadLock是共享锁,而WriteLock是独占锁。
      3. LockSupport 它具备阻塞线程和解除阻塞线程的功能,并且不会引发死锁。

      5. collections(集合类):主要是提供线程安全的集合, 比如:
      1. ArrayList对应的高并发类是CopyOnWriteArrayList,
      2. HashSet对应的高并发类是 CopyOnWriteArraySet,
      3. HashMap对应的高并发类是ConcurrentHashMap等等

      ## 2. Lock锁

      - synchronized和lock锁的区别
      - synchronized内置的java关键字,Lock是一个java类
      - synchronized无法判断获取锁的状态, Lock可以判断是否获取到了锁
      - synchronized会自动释放锁,Lock必须要手动释放锁!如果不是释放锁,会产生死锁
      - synchronized 线程1(获得锁,阻塞),线程2(等待); Lock锁就不一定会等待下去
      - synchronized 可重入锁,不可以中断的,非公平的; Lock锁,可重入的,可以判断锁,非公平(可自己设置);
      - synchronized 适合锁少量的代码同步问题,Lock 适合锁大量的同步代码

      ## 3. 生产者消费者问题

      > - 线程也可以唤醒,而不会被通知,中断或超时,即所谓的虚份唤醒。
      >
      > - 应该使线程被唤醒的条件来防范,并且如果条件不满足则继续等待。换句话说,等待应该总是出现在循环中,i.e.
      >
      > ```java
      > synchronized (obj){
      > while (<condition does not hold>)
      > obj.wait(timeout)
      > ...//Perform action appropriate to condition
      > }

3.1 Condition

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 文档示例
class BoundedBuffer {
final Lock lock new Reentrantlock();
final Condition notFull =lock.newCondition();
final Condition notEmpty lock.newCondition();

final object[]items new object[100];
int putptr,takeptr,count;

public void put (Object x) throws InterruptedException {
Lock.lock();
try {
while (count =items.length) notFull.await();
items[putptr];
if (++putptr =items.length) putptr = 0;
!!count;
notEmpty.signal();
} finally {lock.unlock();}
}
public Object take() throws InterruptedException {
lock.lock();
try {
while (count =0) notEmpty.await();
Object x items[takeptr];
if (++takeptr == items.length) takeptr =0;
--count;
notFull.signal();
return x;
}finally { Lock.unlock(); }
}
}

4. Callable

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 1 创建Callable接口的实现类对象
Callable<Integer> callable=new Callable<Integer>() {
@Override
public Integer call() throws Exception {
System.out.println(Thread.currentThread().getName()+"开始执行");
Integer sum=0;
for (int i = 1; i <= 100; i++) {
sum += i;
}
return sum;
}
};
// 2 把Callable接口的实现类对象作为参数传递给FutureTask的构造方法
FutureTask<Integer> futureTask=new FutureTask<>(callable);
// 3 把FutureTask的对象作为参数传递给Thread类的构造方法, 创建Thread对象, 并调用start()方法
Thread thread=new Thread(futureTask);
thread.start();
sout(futureTask.get());

5. 常用的辅助类

5.1 CountDownLatch

允许一个或多个线程等待直到在其他线程中执行的一组操作完成的同步辅助(减法计数器)。

  • 方法:
    • countDown() 计数-1
    • await() 等待计数归零
1
2
3
4
5
6
7
8
9
CountDownLatch countDownLatch = new CountDownLatch(6);
for (int i = 0; i < 6; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " Go out");
countDownLatch.countDown(); // 数量-1
}, String.valueOf(i)).start();
}
countDownLatch.await(); // 等待计数器归零,然后再向下执行
System.out.println("Close Door");

5.2 CyclicBarrier

允许一组线程全部等待彼此达到共同屏障点的同步辅助(加法计数器)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> {
System.out.println("****召唤神龙");
});
for (int i = 1; i <= 7; i++) {
final int tempInt = i;
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "\t 收集到第:" + tempInt + "颗龙珠");
try {
cyclicBarrier.await();
} catch (Exception e) {
e.printStackTrace();
}
}, String.valueOf(i)).start();
}

5.3 Semaphore

一个计数信号量。在概念上,信号量维持一组许可证。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Semaphore semaphore = new Semaphore(6);
for (int i = 1; i <= 10; i++) {
new Thread(() -> {
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + "\t 抢到车位");
Thread.sleep(3000);
System.out.println(Thread.currentThread().getName() + "\t 离开车位");
} catch (Exception e) {
e.printStackTrace();
} finally {
semaphore.release();
}
}, String.valueOf(i)).start();
}

5.4 BlockingQueue

  • 四组API
方式 抛出异常 有返回值(不抛出异常) 阻塞+等待 超时等待
添加 add offer() put() offer(,,)
移除 remove poll() take() poll(,)
判断队列首 element peek - -

5.5 SynchronousQueue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
SynchronousQueue<String> synchronousQueue = new SynchronousQueue<String>();
new Thread(() -> {
try {
System.out.println(Thread.currentThread().getName() + "\t put 1");
synchronousQueue.put("1");
System.out.println(Thread.currentThread().getName() + "\t put 2");
synchronousQueue.put("2");
System.out.println(Thread.currentThread().getName() + "\t put 3");
synchronousQueue.put("3");
} catch (Exception e) {
e.printStackTrace();
}
}, "T1").start();
new Thread(() -> {
try {
Thread.sleep(3000);
System.out.println(Thread.currentThread().getName() + "\t" + synchronousQueue.take());
Thread.sleep(3000);
System.out.println(Thread.currentThread().getName() + "\t" + synchronousQueue.take());
Thread.sleep(3000);
System.out.println(Thread.currentThread().getName() + "\t" + synchronousQueue.take());
} catch (Exception e) {
e.printStackTrace();
}
}, "T2").start();

6. 线程池

  • 三大方法、七大参数、四种拒绝策略

池化技术

6.1 三大方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/* newSingleThreadExecutor、ewFixedThreadPool(5)、newCachedThreadPool() */
// ExecutorService es = Executors.newSingleThreadExecutor();
// ExecutorService es = Executors.newFixedThreadPool(5);
ExecutorService es = Executors.newCachedThreadPool();

try {
for (int i = 0; i < 100; i++) {
es.execute(() -> {
System.out.println(Thread.currentThread().getName() + " OK");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
es.shutdown();
}

6.2 七大参数

本质:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public static ExecutorService new____ThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}

// ThreadPoolExecutor
public ThreadPoolExecutor(int corePoolSize, // 核心线程池大小
int maximumPoolSize, // 最大核心线程池大小
long keepAliveTime, // 超时释放时间
TimeUnit unit, // 超时单位
BlockingQueue<Runnable> workQueue, // 阻塞队列
ThreadFactory threadFactory, // 线程工厂,创建线程所用,一般不修改
RejectedExecutionHandler handler // 拒绝策略
) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}

6.1.1 创建原生线程池

根据《Java开发手册(嵩山版)》-(七)并发处理中:

image-20230506222403752

使用ThreadPoolExecutor创建线程池

1
2
3
4
5
6
7
8
9
10
ExecutorService es = new ThreadPoolExecutor(
/* 进入1-5都只会使用核心线程池,6-8会进入阻塞队列,大于等于即会报错 */
2, // 核心线程数
5, // 最大线程数
10, // 空闲线程存活时间,空闲线程超过这个时间就会被销毁,直到线程数=核心线程数
java.util.concurrent.TimeUnit.SECONDS, // 时间单位
new LinkedBlockingQueue<>(3), // 阻塞队列
Executors.defaultThreadFactory(), // 线程工厂
new ThreadPoolExecutor.AbortPolicy() // 拒绝策略,此时超出即报错
);

6.3 四种拒绝策略

  • AbortPolicy:超出即报错
  • CallerRunsPolicy:由启动线程的线程进行 如果main调用线程,超出后则由main执行
  • DiscardPolicy:丢掉任务,不抛出异常
  • DiscardOldestPolicy:尝试和最早的任务竞争,如果竞争失败则竞争,不抛出异常

6.4 线程池数量设置

  • CPU密集型:设置系统核数,保证效率最高
    • System.out.println(Runtime.getRuntime().availableProcessors()); // 查看CPU核数
  • IO密集型:判断十分耗费IO资源的线程数,一般设置为此值的2倍

7. ForkJoin

Java的开发,高并发越来越完善

  • Java 1 支持thread,synchronized。
  • Java 5 引入了 thread pools, blocking queues, concurrent collections,locks, condition queues。
  • Java 7 加入了fork-join库。
  • Java 8 加入了 parallel streams。

JDK1.7,并行执行任务 => 大数据量 => 分而治之

  • 特点:工作窃取

    • 基于双端队列,先执行完的线程执行未执行完线程的尾端任务
    • 被窃取任务线程永远从双端队列的头部拿任务执行,而窃取任务的线程永远从双端队列的尾部拿任务执行
  • 步骤

    • 分割原问题;
    • 求解子问题;
    • 合并子问题的解为原问题的解。
  • 典型应用
    • 二分搜索
    • 大整数乘法
    • Strassen矩阵乘法
    • 棋盘覆盖
    • 合并排序
    • 快速排序
    • 线性时间选择
    • 汉诺塔
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
package io.binghe.concurrency.example.aqs;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.Future;
import java.util.concurrent.RecursiveTask;
@Slf4j
public class ForkJoinTaskExample extends RecursiveTask<Integer> {
public static final int threshold = 2;
private int start;
private int end;
public ForkJoinTaskExample(int start, int end) {
this.start = start;
this.end = end;
}
@Override
protected Integer compute() {
int sum = 0;
//如果任务足够小就计算任务
boolean canCompute = (end - start) <= threshold;
if (canCompute) {
for (int i = start; i <= end; i++) {
sum += i;
}
} else {
// 如果任务大于阈值,就分裂成两个子任务计算
int middle = (start + end) / 2;
ForkJoinTaskExample leftTask = new ForkJoinTaskExample(start, middle);
ForkJoinTaskExample rightTask = new ForkJoinTaskExample(middle + 1, end);
// 执行子任务
leftTask.fork();
rightTask.fork();
// 等待任务执行结束合并其结果
int leftResult = leftTask.join();
int rightResult = rightTask.join();
// 合并子任务
sum = leftResult + rightResult;
}
return sum;
}
public static void main(String[] args) {
ForkJoinPool forkjoinPool = new ForkJoinPool();
//生成一个计算任务,计算1+2+3+4
ForkJoinTaskExample task = new ForkJoinTaskExample(1, 100);
//执行一个任务
Future<Integer> result = forkjoinPool.submit(task);
try {
log.info("result:{}", result.get());
} catch (Exception e) {
log.error("exception", e);
}
}
}

8. 异步回调

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 没有返回值的runAsync 异步调用
CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(() -> {
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName() + " runAsync=>Void");
});

completableFuture.get();

// 有返回值的SupplyAsync 异步回调
CompletableFuture<Integer> cf = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName() + " supplyAsync=>Integer");
int i = 10 / 0;
return 1024;
});

System.out.println(cf.whenComplete((t, u) -> {
System.out.println("t=>" + t); // 正常的返回结果
System.out.println("u=>" + u); // 错误信息
}).exceptionally((e) -> {
System.out.println(e.getMessage());
return 233;
}).get());
/** fout:
ForkJoinPool.commonPool-worker-1 supplyAsync=>Integer
t=>null
u=>java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero
java.lang.ArithmeticException: / by zero
233
*/

9. JMM

请你谈谈你对 Volatile 的理解

  • Volatile 是 Java 虚拟机提供轻量级的同步机制
  • 特点

    1. 保证可见性(可见)

    2. 不保证原子性(原子)

    3. 禁止指令重排(有序)

什么是JMM

  • JMM(java memory model): Java内存模型,不存在的东西,概念!约定!

    • 作用:缓存一致性协议,用于定义数据读写的规则。
    • JMM定义了线程工作内存和主内存之间的抽象关系:线程之间的私有变量存储在主内存中, 每个线程都有一个私有的本地变量。
    • 关于JMM的一些同步的约定:
      1. 线程解锁前,必须把共享变量==立刻==更新主存。
      2. 线程加锁前,必须读取主存中的最新值==更新==工作内存中!
      3. 加锁和解锁是同一把锁
  • 线程:工作内存主内存

    • 工作内存/主内存八种内存交互操作

    • 八种内存交互操作

      • lock(锁定),作用于主内存中的变量,把变量标识为线程独占的状态。
      • read(读取),作用于主内存的变量,把变量的值从主内存传输到线程的工作内存中,以便下一步的load操作使用。
      • load(加载),作用于工作内存的变量,把read操作主存的变量放入到工作内存的变量副本中。
      • use(使用),作用于工作内存的变量,把工作内存中的变量传输到执行引擎,每当虚拟机遇到一个需要使用到变量的值的字节码指令时将会执行这个操作。
      • assign(赋值),作用于工作内存的变量,它把一个从执行引擎中接受到的值赋值给工作内存的变量副本中,每当虚拟机遇到一个给变量赋值的字节码指令时将会执行这个操作。
      • store(存储),作用于工作内存的变量,它把一个从工作内存中一个变量的值传送到主内存中,以便后续的write使用。
      • write(写入):作用于主内存中的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中。
      • unlock(解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。
    • 对交互操作制定的规则
      • lock(锁定),作用于主内存中的变量,把变量标识为线程独占的状态。
      • read(读取),作用于主内存的变量,把变量的值从主内存传输到线程的工作内存中,以便下一步的load操作使用。
      • load(加载),作用于工作内存的变量,把read操作主存的变量放入到工作内存的变量副本中。
      • use(使用),作用于工作内存的变量,把工作内存中的变量传输到执行引擎,每当虚拟机遇到一个需要使用到变量的值的字节码指令时将会执行这个操作。
      • assign(赋值),作用于工作内存的变量,它把一个从执行引擎中接受到的值赋值给工作内存的变量副本中,每当虚拟机遇到一个给变量赋值的字节码指令时将会执行这个操作。
      • store(存储),作用于工作内存的变量,它把一个从工作内存中一个变量的值传送到主内存中,以便后续的write使用。
      • write(写入):作用于主内存中的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中。
      • unlock(解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。

10. Volatile

10.1 保证可见性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private volatile static int num = 0;
// private static int num = 0;

public static void main(String[] args) {
new Thread(()->{
while (num == 0) {

}
},"t1").start();

Thread.sleep(2);
num = 1;
System.out.println("num => "+num);
}

10.2不保证原子性

原子性 : 不可分割
线程A在执行任务的时候,不能被打扰的,也不能被分割。要么同时成功,要么同时失败。

  • 增加lock、synchronized关键字

  • 使用原子类

    • Unsafe类,底层直接和操作系统挂钩(内存中直接修改值)

    • AtomicInteger、AtomicLong…

10.3 避免指令重排

指令重排:源代码–>编译器优化的重排–> 指令级并行的重排–> 内存系统重排—> 执行。处理器在进行指令重排的时候,考虑:数据之间的依赖性!

1
2
3
4
5
6
7
int x = 1; // 1
int y = 2; // 2
x = x + 5; // 3
y = x * x; // 4

// 我们所期望的:1234 但是可能执行的时候回变成 2134 1324
// 不可能是 4123!
  • volatile避免指令重排的含义

    • 当程序执行到volatile变量的读操作或者写操作时,在其前面的操作的更改肯定全部已经进行,且结果已经对后面的操作可见,在其后面的操作肯定还没有进行。
    • 在进行指令优化时,不能将在对volatile变量访问的语句放在其后面执行,也不能把volatile变量后面的语句放到其前面执行。
  • 内存屏障

    • 分类
      • LoadLoad 屏障:对于这样的语句Load1,LoadLoad,Load2。在Load2及后续读取操作要读取的数据被访问前,保证Load1要读取的数据被读取完毕。
      • StoreStore屏障:对于这样的语句Store1, StoreStore, Store2,在Store2及后续写入操作执行前,保证Store1的写入操作对其它处理器可见。
      • LoadStore 屏障:对于这样的语句Load1, LoadStore,Store2,在Store2及后续写入操作被刷出前,保证Load1要读取的数据被读取完毕。
      • StoreLoad 屏障:对于这样的语句Store1, StoreLoad,Load2,在Load2及后续所有读取操作执行前,保证Store1的写入对所有处理器可见。
    • volatile读操作
      • 在每个volatile读操作后插入LoadLoad屏障,在读操作后插入LoadStore屏障。
      • img
    • volatile写操作
      • 在每个volatile写操作的前面插入一个StoreStore屏障,后面插入一个SotreLoad屏障。
      • img

11. 单例模式

  • 方式1:饿汉式(类加载时创建,天生线程安全)

    • 创建一个常量
      私有化构造器
      提供一个静态方法(获取实例)

    • ```java
      public class SingleTon {

      private static final SingleTon INSTANCE = new SingleTon();
      private SingleTon() {}
      public static SingleTon getInstance() {
          return INSTANCE;
      }
      

      }

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35

      - 方式2:懒汉式(使用时创建,线程不安全,加同步)

      - 首先创建一个常量
      构造方法改成私有的,类外部不能创建对象
      通过一个公开的方法,返回这个对象

      - ```java
      // 方法1
      public class SingleTon2 {
      private static SingleTon2 INSTANCE=null;
      private SingleTon2() {}
      public static synchronized SingleTon2 getInstance() {
      if (INSTANCE==null) {
      return new SingleTon2();
      }
      return INSTANCE;
      }
      }

      // 方法2:双重检测锁模式(必须加volatile, )
      public class SingleTon2 {
      private volatile static SingleTon2 INSTANCE=null;
      private SingleTon2() {}
      public static SingleTon2 getInstance() {
      if (INSTANCE==null) { // 提高执行效率
      synchronized (SingleTon2.class) {
      if (INSTANCE==null) { // 保证线程安全后判断是否存在
      return new SingleTon2(); // 非原子性操作
      }
      }
      }
      return INSTANCE;
      }
      }
  • 方法3:静态内部类— 懒汉式(使用时创建,线程安全)

    • ```java
      public class SingleTon3 {
      private SingleTon3() {}
      private static class SingleTon3Holder {
          static SingleTon3 INSTANCE = new SingleTon3();
      }
      public static SingleTon3 getInstance() {
          return SingleTon3Holder.INSTANCE;
      }
      
      }
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30

      - 注意在反射时均存在安全隐患(可通过Enum枚举创建,安全!)

      ## 12. 深入理解CAS

      > CAS(CompareAndSet)

      ```java
      package com.company.cas;

      import java.util.concurrent.atomic.AtomicInteger;

      // CAS compareAndSet : 比较并交换!(unsafe,自旋锁)
      public class CAS {
      public static void main(String[] args) {
      AtomicInteger atomicInteger = new AtomicInteger(1);
      // 如果我期望的值达到了,那么就更新,否则,就不更新, CAS 是CPU的并发原语!
      System.out.println(atomicInteger.compareAndSet(1, 2));
      System.out.println(atomicInteger.get());
      //会失败,因为上面成功交换了
      System.out.println(atomicInteger.compareAndSet(1, 2));
      System.out.println(atomicInteger.get());
      }
      }
      /*
      缺点:
      1、循环会耗时
      2、一次性只能保证一个共享变量的原子性
      3、ABA问题
      * */

12.1 ABA问题

修改值时,有其他线程修改后又恢复,实际操作并非原值

image-20230507193416395

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.company.cas;

import java.util.concurrent.atomic.AtomicInteger;

//ABA问题(使用原子引用来解决问题)
public class ABA {
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(2020);

//期望,更新
//捣乱的线程
System.out.println(atomicInteger.compareAndSet(2020, 2021));
System.out.println("捣乱的线程修改值为"+atomicInteger.get());
System.out.println(atomicInteger.compareAndSet(2021, 2020));
System.out.println("捣乱的线程修改值为"+atomicInteger.get());

//期望的线程
System.out.println(atomicInteger.compareAndSet(2020, 6666));
System.out.println("期望的线程的值为:"+atomicInteger.get());

}
}

13. 原子引用

  • 解决ABA问题
  • 对应的思想:原子引用
  • 数据库中,修改数据库需要提升版本号
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
package com.company.cas;

import java.sql.Time;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicStampedReference;

//原子引用(对应的思想:乐观锁)
public class AtomicReference {
//正常的业务,这里面比较的都是一个个对象
//initialStamp:时间戳
static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(1, 1);

public static void main(String[] args) {

new Thread(() -> {
System.out.println("a =>:" + atomicStampedReference.getStamp()+ "当前值为:" + atomicStampedReference.getReference());
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}

atomicStampedReference.compareAndSet(1, 2, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);

System.out.println("a =>:" + atomicStampedReference.getStamp()+ "当前值为:" + atomicStampedReference.getReference());

atomicStampedReference.compareAndSet(2, 1, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);

System.out.println("a =>:" + atomicStampedReference.getStamp()+ "当前值为:" + atomicStampedReference.getReference());

}, "a").start();


//和乐观锁的原理相同
new Thread(() -> {
int stamp = atomicStampedReference.getStamp();
System.out.println("b =>:" + stamp+ "当前值为:" + atomicStampedReference.getReference());

try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}

System.out.println(atomicStampedReference.compareAndSet(1, 6, stamp, stamp + 1));

System.out.println("b =>:" + atomicStampedReference.getStamp()+ "当前值为:" + atomicStampedReference.getReference());
}, "b").start();
}
  • AtomicStampedReference:在构建的时候需要一个类似于版本号的int类型变量stamped,每一次针对共享数据的变化都会导致该 stamped 的变化(stamped 需要应用程序自身去负责,AtomicStampedReference并不提供,一般使用时间戳作为版本号)
  • Integer 使用了对象缓存机制,默认范围是 -128 ~ 127 ,推荐使用静态工厂方法 valueOf 获取对象实例,而不是 new,因为 valueOf 使用缓存,而 new 一定会创建新的对象分配新的内存空间;比较使用 equals 而非 ==

14. 各种锁的理解

14.1 公平锁、非公平锁

  • 公平锁: 非常公平, 不能够插队,必须先来后到!
  • 非公平锁:非常不公平,可以插队 (默认都是非公平:时间短的线程可以插队时间长的线程)
1
2
3
4
5
6
public ReentrantLock ( ) {
sync=new NonfairSync();
}
public ReentrantLock(boolean fair){
sync=fair?new Fairsync():new Nonfairsync();
}

14.2 可重入锁

  • 可重入锁(递归锁):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public class Demo2 {
public static void main(String[] args) {
new Thread(() -> {
Phone phone = new Phone();
phone.sms();
}, "A").start();

new Thread(() -> {
Phone phone = new Phone();
phone.sms();
}, "B").start();
}
}

class Phone {
Lock lock = new ReentrantLock();
public void sms() {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "sms");
call();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}

public void call() {
lock.lock(); // 锁了两次,即获取到外面的锁,即可获取到里面的锁
try {
System.out.println(Thread.currentThread().getName() + "call");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
  • synchronized 版本写法一样,含义稍有区别

14.3 自旋锁

spinlock

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
public class Demo3 {
public static void main(String[] args) {
MySpinLock mySpinLock = new MySpinLock();

//线程T1,原来没有锁,现在加锁。等待5秒后,解锁
new Thread(() -> {
mySpinLock.myLock();
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
mySpinLock.myUnLock();
}
}, "T1").start();

//线程T2,原来没有锁。现在加锁,因为都是同一把锁,要上面的锁解了,才能接现在的锁
new Thread(() -> {
mySpinLock.myLock();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
mySpinLock.myUnLock();
}
}, "T2").start();

/*
T1运行时lock,T2会在while处循环等待,直到T1unlock,T2才可以退出循环
*/
}
}

//我的自旋锁
class MySpinLock{
/**
* int 0
* Thread null
*/
AtomicReference<Thread> atomicReference = new AtomicReference<>();

//加锁
public void myLock(){
Thread thread = Thread.currentThread();
System.out.println(Thread.currentThread().getName()+"==>MyLock");

//自旋锁(如果没有锁,给他加锁。如果有锁,进入方法体)
while (!atomicReference.compareAndSet(null, thread)){

}
}

//解锁
public void myUnLock(){
Thread thread = Thread.currentThread();
System.out.println(Thread.currentThread().getName()+"==>myUnlock");
atomicReference.compareAndSet(thread, null);
}
}

14.4 死锁

image-20230507202004072

  • 解决办法:
    1. jps -l
      • jps -l
    2. 使用jstack 进程号 查找死锁问题
      • image-20230507203147134

15.1 代码排查问题方法

  • 日志
  • 堆栈
    • jps -l 查看进程号
    • jstack 进程号 查找死锁问题

15. 八锁问题

  • 解决问题前,首先需要明白的是 synchronized 只会锁两样东西,一样是调用的对象,一样是Class
  1. 问题一:两个普通的锁方法,new一个对象调用,调用过程中间睡1秒,执行结果是什么

    • ```java
      public class Test {

      public static void main(String[] args) throws InterruptedException {
          Fun fun = new Fun();
          new Thread(()->{fun.funOne();},"A线程").start();
          TimeUnit.SECONDS.sleep(1);
          new Thread(()->{fun.funTwo();},"B线程").start();
      }
      

      }

      class Fun{

      public synchronized void funOne(){
          System.out.println(Thread.currentThread().getName()+":调用方法一");
      }
      public synchronized void funTwo(){
          System.out.println(Thread.currentThread().getName()+":调用方法二");
      }
      

      }

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29

      - 判断执行顺序,先判断什么被锁了,这里synchronized锁的是Fun对象new出来的fun,因为先调用了funOne睡1秒后调用的funTwo,funTwo需要等到funOne释放锁后才会执行

      - 最终执行结果是:A线程:调用方法一 B线程:调用方法二


      2. 问题二:两个普通的锁方法,new一个对象调用,调用过程中间睡1秒,且在funOne方法中睡3秒,执行结果是什么

      - ```java
      public class Test {
      public static void main(String[] args) throws InterruptedException {
      Fun fun = new Fun();
      new Thread(()->{
      try { fun.funOne(); } catch (InterruptedException e) { e.printStackTrace(); }
      },"A线程").start();
      TimeUnit.SECONDS.sleep(1);
      new Thread(()->{fun.funTwo();},"B线程").start();
      }
      }

      class Fun{
      public synchronized void funOne() throws InterruptedException {
      TimeUnit.SECONDS.sleep(3);
      System.out.println(Thread.currentThread().getName()+":调用方法一");
      }
      public synchronized void funTwo(){
      System.out.println(Thread.currentThread().getName()+":调用方法二");
      }
      }
    • 该情况其实和问题一 一样,synchronized锁的是Fun对象new出来的fun,当调用funOne时,fun被锁,而程序不受funOne内睡的影响继续执行,在调用funTwo的时候因为fun被锁导致等待,当funOne执行完后funTwo瞬间执行

    • 最终执行结果是:3 秒后同时出现 A线程:调用方法一 B线程:调用方法二

  2. 问题三:一个普通的锁方法,一个普通无锁方法,new一个对象调用,在funOne方法中睡3秒,执行结果是什么

    • ```java
      public class Test {

         public static void main(String[] args) throws InterruptedException {
             Fun fun = new Fun();
             new Thread(()->{
                 try {  fun.funOne();  } catch (InterruptedException e) {  e.printStackTrace();  }
             },"A线程").start();
             TimeUnit.SECONDS.sleep(1);
             new Thread(()->{fun.funTwo();},"B线程").start();
         }
      

      }

      class Fun{

      public synchronized void funOne() throws InterruptedException {
          TimeUnit.SECONDS.sleep(3);
          System.out.println(Thread.currentThread().getName()+":调用方法一");
      }
      public void funTwo(){
          System.out.println(Thread.currentThread().getName()+":调用方法二");
      }
      

      }

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29

      - 程序执行在funOne中锁了fun并在方法中睡3秒,外部在睡1秒后调用funTwo,因为无锁因此直接执行

      - 最终执行结果是:1 秒后出现B线程:调用方法二 再2秒后出现 A线程:调用方法一

      4. 问题四:两个普通的锁方法,new两个对象分别调用,在funOne方法中睡3秒,执行结果是什么

      - ```java
      public class Test {
      public static void main(String[] args) throws InterruptedException {
      Fun fun1 = new Fun();
      Fun fun2 = new Fun();
      new Thread(()->{
      try { fun1.funOne(); } catch (InterruptedException e) { e.printStackTrace(); }
      },"A线程").start();
      TimeUnit.SECONDS.sleep(1);
      new Thread(()->{fun2.funTwo();},"B线程").start();
      }
      }

      class Fun{
      public synchronized void funOne() throws InterruptedException {
      TimeUnit.SECONDS.sleep(3);
      System.out.println(Thread.currentThread().getName()+":调用方法一");
      }
      public synchronized void funTwo(){
      System.out.println(Thread.currentThread().getName()+":调用方法二");
      }
      }
  • 程序调用funOne时锁住fun1,外部睡1秒后调用funTwo时锁funTwo,互不影响

  • 最终执行结果是:B线程:调用方法二 A线程:调用方法一

  1. 问题五:两个静态的锁方法,new一个对象调用,在funOne方法中睡3秒,执行结果是什么

    • ```java
      public class Test {

      public static void main(String[] args) throws InterruptedException {
          Fun fun = new Fun();
          new Thread(()->{
              try {  fun.funOne();  } catch (InterruptedException e) {  e.printStackTrace();  }
          },"A线程").start();
          TimeUnit.SECONDS.sleep(1);
          new Thread(()->{fun.funTwo();},"B线程").start();
      }
      

      }

      class Fun{

      public static synchronized void funOne() throws InterruptedException {
          TimeUnit.SECONDS.sleep(3);
          System.out.println(Thread.currentThread().getName()+":调用方法一");
      }
      public static synchronized void funTwo(){
          System.out.println(Thread.currentThread().getName()+":调用方法二");
      }
      

      }

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29

      - 这里多了静态static,因此此时锁的东西改变了,funOne和funTwo都是锁的Fun这个class,程序在执行funOne时锁住Fun class,睡1秒后调用funTwo,funTwo等待funOne内睡完释放锁后再执行

      - 最终执行结果是:A线程:调用方法一 B线程:调用方法二

      6. 问题六:两个静态的锁方法,new两个对象分别调用,在funOne方法中睡3秒,执行结果是什么

      - ```java
      public class Test {
      public static void main(String[] args) throws InterruptedException {
      Fun fun1 = new Fun();
      Fun fun2 = new Fun();
      new Thread(()->{
      try { fun1.funOne(); } catch (InterruptedException e) { e.printStackTrace(); }
      },"A线程").start();
      TimeUnit.SECONDS.sleep(1);
      new Thread(()->{fun2.funTwo();},"B线程").start();
      }
      }

      class Fun{
      public static synchronized void funOne() throws InterruptedException {
      TimeUnit.SECONDS.sleep(3);
      System.out.println(Thread.currentThread().getName()+":调用方法一");
      }
      public static synchronized void funTwo(){
      System.out.println(Thread.currentThread().getName()+":调用方法二");
      }
      }
    • 要注意的是,不管现在new结果对象,调用方法后锁的都是Fun class,funTwo会被funOne阻塞

    • 最终执行结果是:A线程:调用方法一 B线程:调用方法二

  2. 问题七:一个静态的锁方法,一个普通锁方法,new一个对象调用,在funOne方法中睡3秒,执行结果是什么

    • ```java
      public class Test {

      public static void main(String[] args) throws InterruptedException {
          Fun fun = new Fun();
          new Thread(()->{
              try {  fun.funOne();  } catch (InterruptedException e) {  e.printStackTrace();  }
          },"A线程").start();
          TimeUnit.SECONDS.sleep(1);
          new Thread(()->{fun.funTwo();},"B线程").start();
      }
      

      }

      class Fun{

      public static synchronized void funOne() throws InterruptedException {
          TimeUnit.SECONDS.sleep(3);
          System.out.println(Thread.currentThread().getName()+":调用方法一");
      }
      public synchronized void funTwo(){
          System.out.println(Thread.currentThread().getName()+":调用方法二");
      }
      

      }

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29

      - 同样只要确认锁的是不是一个东西就知道会不会阻塞,调用funOne时阻塞的是Fun class,调用funTwo时阻塞的是fun

      - 最终执行结果是:B线程:调用方法二 A线程:调用方法一

      8. 问题八:一个静态的锁方法,一个普通锁方法,new两个对象分别调用,在funOne方法中睡3秒,执行结果是什么

      - ```java
      public class Test {
      public static void main(String[] args) throws InterruptedException {
      Fun fun1 = new Fun();
      Fun fun2 = new Fun();
      new Thread(()->{
      try { fun1.funOne(); } catch (InterruptedException e) { e.printStackTrace(); }
      },"A线程").start();
      TimeUnit.SECONDS.sleep(1);
      new Thread(()->{fun2.funTwo();},"B线程").start();
      }
      }

      class Fun{
      public static synchronized void funOne() throws InterruptedException {
      TimeUnit.SECONDS.sleep(3);
      System.out.println(Thread.currentThread().getName()+":调用方法一");
      }
      public synchronized void funTwo(){
      System.out.println(Thread.currentThread().getName()+":调用方法二");
      }
      }
    • 解题跟问题七一致,调用funOne时阻塞的是Fun class,调用funTwo时阻塞的是fun2

    • 最终执行结果是:B线程:调用方法二 A线程:调用方法一

七、网络编程

1. 网络相关知识

1.1 网络模型

1.1.1 OSI参考模型

  • OSI(Open System Interconnection)开放式系统互联

image-20230422192818663

  • 第七层:应用层负责文件访问和管理、可靠运输服务、远程操作服务。(HTTP、FTP、SMTP)
  • 第六层:表示层负责定义转换数据格式及加密,允许选择以二进制或ASCII格式传输。
  • 第五层:会话层负责使应用建立和维持会话,使通信在失效时继续恢复通信。(断点续传)
  • 第四层:传输层负责是否选择差错恢复协议、数据流重用、错误顺序重排。(==TCP==、UDP)
  • 第三层:网络层负责定义了能够标识所有网络节点的逻辑地址。(==IP地址==)
  • 第二层:链路层在物理层上,通过规程或协议(差错控制)来控制传输数据的正确性。(MAC)
  • 第一层:物理层为设备之间的数据通信提供传输信号和物理介质。(双绞线、光导纤维)

1.1.2 TCP/IP模型

  • 一组用于实现网络互连的通信协议,将协议分成四个层次

image-20230422193155280

  • 第四层:应用层负责传送各种最终形态的数据,是直接与用户打交道的层,典型协议是HTTP、FTP等。
  • 第三层:传输层负责传送文本数据,主要协议是TCP、UDP协议。
  • 第二层:网络层负责分配地址和传送二进制数据,主要协议是IP协议。
  • 第一层:接口层负责建立电路连接,是整个网络的物理基础,典型的协议包括以太网、ADSL等等。

1.1.3 TCP/UDP

  • TCP协议:Transmission Control Protocol 传输控制协议

    • 是一种面向连接的、可靠的、基于字节流的传输层通信协议。数据大小无限制。建立连
      接的过程需要三次握手,断开连接的过程需要四次挥手。
  • UDP协议:User Datagram Protocol 用户数据报协议

    • 是一种无连接的传输层协议,提供面向事务的简单不可靠信息传送服务,每个包的大小
      64KB。

1.1.4 IP

  • IP协议:Internet Protocol互联网协议/网际协议
    • 负责数据从一台机器发送到另一台机器。
    • 给互联网每台设备分配一个唯一标识(IP地址)。
  • IP地址分为两种:

    • IPV4:4字节32位整数,并分成4段8位的二进制数,每8位之间用圆点隔开,每8位
      整数可以转换为一个0~255的十进制整数。
      • 格式:D.D.D.D例如:255.255.255.255
    • IPV6:16字节128位整数,并分成8段十六进制数,每16位之间用圆点隔开,每16
      位整数可以转换为一个0~65535的十进制数。
      • 格式:X.X.X.X.X.X.X例如:FFFF.FFFF.FFFF.FFFF.FFFF.FFFF.FFFF.FFFF
  • IPV4的应用分类

    • A类:政府机构,1.0.0.1~126.255.255.254
    • B类:中型企业,128.0.0.1~191.255.255.254
    • C类:个人用户,192.0.0.1~223.255.255.254
    • D类:用于组播,224.0.0.1~239.255.255.254
    • E类:用于实验,240.0.0.1~255.255.255.254
    • 回环地址:127.0.0.1,指本机,一般用于测试使用。
    • 测试IP命令:ping D.D.D.D
    • 查看IP命令:ipconfig
  • Port

    • 端口号:在通信实体上进行网络通讯程序的唯一标识。
    • 端口分类:
      • 公认端口:0~1023
      • 注册端口:1024~49151
      • 动态或私有端口:49152~65535
    • 常用端口:
      • MySq1:3306
      • Oracle:1521
      • Tomcat:8080
      • SMTP:25
      • Web服务器:80
      • FTP服务器:21

2. 网络编程相关类

2.1 InetAddress类

  • 概念:表示互联网协议(IP)地址对象,封装了与该IP地址相关的所有信息,并提供获取信息的常用方法。
  • 方法:
    • public static InetAddress getLocalHost()获得本地主机地址对象
    • public static InetAddress getByName(String host)根据主机名称获得地址对象
    • public static InetAddress[]getA11ByName(String host)获得所有相关地址对象
    • public String getHostAddress()获取IP地址字符串
    • public String getHostName()获得IP地址主机名
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
/**
* 演示 InetAddress 类的使用
* 1. 创建本机IP地址对象
* 2, 创建局域网IP地址对象
* 3. 创建外网IP地址对象
*/
// 1. 创建本机IP地址对象
// 1.1 getLocalHost()方法
InetAddress address = InetAddress.getLocalHost();
System.out.println(address); // bayyy/192.168.53.5
System.out.println("ip地址: " + address.getHostAddress() + " 主机名: " + address.getHostName());

// 1.2 getByName()方法
InetAddress address2 = InetAddress.getByName("bayyy");
InetAddress address3 = InetAddress.getByName("192.168.53.5");
InetAddress address4 = InetAddress.getByName("127.0.0.1");
InetAddress address5 = InetAddress.getByName("localhost");
System.out.println("ip地址: " + address2.getHostAddress() + " 主机名: " + address2.getHostName());
System.out.println("ip地址: " + address3.getHostAddress() + " 主机名: " + address3.getHostName());
System.out.println("ip地址: " + address4.getHostAddress() + " 主机名: " + address4.getHostName());
System.out.println("ip地址: " + address5.getHostAddress() + " 主机名: " + address5.getHostName());


// 2. 创建局域网IP地址对象
// 静态方法:getByName(String host)
InetAddress ia1 = InetAddress.getByName("192.168.53.4");
// System.out.println("ip地址: " + ia1.getHostAddress() + " 主机名: " + ia1.getHostName());
if (ia1.isReachable(1000)) { // 判断是否可以连通
System.out.println("可以ping通");
} else {
System.out.println("不可以ping通");
}

// 3. 创建外网IP地址对象
InetAddress ia2 = InetAddress.getByName("www.baidu.com"); // 通过域名创建
System.out.println("ip地址: " + ia2.getHostAddress() + " 主机名: " + ia2.getHostName());
InetAddress[] ias = InetAddress.getAllByName("www.baidu.com"); // 获取所有的IP地址
for (InetAddress ia : ias) {
System.out.println("ip地址: " + ia.getHostAddress() + " 主机名: " + ia.getHostName());

}
if (ia2.isReachable(1000)) {
System.out.println("可以ping通");
} else {
System.out.println("不可以ping通");
}

2.2 基于TCP的网络编程

  • Socket编程:
    • Socket(套接字)是网络中的一个通信节点。
    • 分为客户端Socket与服务器ServerSocket。
    • 通信要求:IP地址+端口号。
  • 开发步骤
    • 服务器端:
      • 创建ServerSocket,指定端口号
      • 调用accept等待客户端接入
      • 使用输入流,接收请求数据到服务器(等待)
      • 使用输出流,发送响应数据给客户端
      • 释放资源
    • 客户端:
      • 创建Socket,指定服务器IP+端口号
      • 使用输出流,发送请求数据给服务器
      • 使用输入流,接收响应数据到客户端(等待)
      • 释放资源

3. 示例

3.1 示例1: TCP编程实现客户端发送数据给服务器端

  • Server

    • ```java
      // 1.创建ServerSocket并指定端口号
      ServerSocket listener = new ServerSocket(8899);
      // 2.调用accept()方法开始监听,等待客户端的连接(如果没有客户端连接,该方法会一直阻塞)
      System.out.println(“服务器已启动,等待客户端连接…”);
      Socket socket = listener.accept();
      // 3. 获取输入流,读取客户端信息
      InputStream is = socket.getInputStream();
      BufferedReader br = new BufferedReader(new InputStreamReader(is, “UTF-8”));
      String data = br.readLine();
      System.out.println(“我是服务器,客户端说:” + data);
      // 4. 获取输出流,响应客户端的请求[可选]
      br.close(); // 关闭流
      socket.close(); // 关闭socket
      listener.close(); // 关闭ServerSocket
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14

      - Client

      - ```java
      // 1.创建Socket对象,指明服务器端的IP和端口号
      Socket socket=new Socket("192.168.136.1", 8899);
      // 2.获取输出流,向服务器端发送请求信息
      OutputStream os=socket.getOutputStream();
      BufferedWriter bw=new BufferedWriter(new OutputStreamWriter(os, "UTF-8"));
      bw.write("用户名:admin;密码:123");
      // 3.获取输入流,读取服务器端响应的信息[可选]
      // 4.关闭相关资源
      bw.close();
      socket.close();

3.2 示例2: TCP编程实现客户顿上传文件给服务器端

  • Server

    • ```java
      // 1. 创建一个服务器ServerSocket,和系统要指定的端口号
      ServerSocket listener=new ServerSocket(9898);
      // 2. 使用accept方法获取到请求的客户端对象(浏览器)
      System.out.println(“Server: Waiting for file…”);
      Socket socket=listener.accept();
      // 3. 使用Socket对象中的方法getInputStream,获取到网络字节输入流InputStream对象
      InputStream is=socket.getInputStream();
      // 4. 边读取,边写入
      FileOutputStream fos=new FileOutputStream(“hello/src/bayyy/Internet/Demo2/fileAccept.png”);
      byte[] bytes=new byte[1024*4];
      int count=0;
      while ((count=is.read(bytes))!=-1) {
      fos.write(bytes,0,count);
      
      }
      // 5. 关闭
      fos.close();
      is.close();
      socket.close();
      listener.close();
      System.out.println(“Server: File received.”);
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20

      - Client

      - ```java
      // 1. 创建一个客户端对象Socket,构造方法绑定服务器的IP地址和端口号
      Socket socket=new Socket("192.168.136.1", 9898);
      // 2. 使用Socket对象中的方法getOutputStream,获取到网络字节输出流OutputStream对象
      OutputStream os=socket.getOutputStream();
      // 3. 使用网络字节输出流OutputStream对象中的方法write,给服务器发送数据
      FileInputStream fis=new FileInputStream("hello/src/bayyy/Internet/Demo2/fileSend.png");
      byte[] bytes=new byte[1024*4];
      int count=0;
      while ((count=fis.read(bytes))!=-1) {
      os.write(bytes,0,count);
      }
      // 4. 释放资源
      fis.close();
      os.close();
      socket.close();
      System.out.println("Client: File sent.");

3.3 示例3: TCP实现多个客户端发送数据给服务器端

  • Server

    • ```java
      // main.java
      Socket socket=new Socket(“192.168.136.1”, 9899);
      System.out.println(socket.getInetAddress()+” connected.”);
      OutputStream os=socket.getOutputStream();
      BufferedWriter bw=new BufferedWriter(new OutputStreamWriter(os, “UTF-8”));
      Scanner input=new Scanner(System.in);
      while (true) {

      String data=input.nextLine();
      bw.write(data);
      bw.newLine();
      bw.flush();
      if (data.contains("bye") || data.contains("88")) {
          break;
      }
      

      }
      bw.close();
      socket.close();
      System.out.println(socket.getInetAddress()+” disconnected.”);
      }

      // serverThread.java
      public class ServerThread extends Thread {

      private Socket socket;
      
      public ServerThread(Socket socket) {
          this.socket = socket;
      }
      
      @Override
      public void run() {
          if (socket != null) {
              BufferedReader br = null;
              try {
                  InputStream is = socket.getInputStream();
                  br = new BufferedReader(new InputStreamReader(is, "UTF-8"));
                  while (true) {
                      String data = br.readLine();
                      if (data == null) {
                          break;
                      }
                      System.out.println(socket.getInetAddress() + " says: " + data);
                      if (data.contains("bye") || data.contains("88")) {
                          break;
                      }
                  }
              } catch (IOException e) {
                  throw new RuntimeException(e);
              } finally {
                  try {
                      br.close();
                      socket.close();
                      System.out.println(socket.getInetAddress()+" disconnected.");
                  } catch (IOException e) {
                      throw new RuntimeException(e);
                  }
              }
          }
      
      }
      

      }

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21

      - Client

      - ```java
      Socket socket=new Socket("192.168.136.1", 9899);
      System.out.println(socket.getInetAddress()+" connected.");
      OutputStream os=socket.getOutputStream();
      BufferedWriter bw=new BufferedWriter(new OutputStreamWriter(os, "UTF-8"));
      Scanner input=new Scanner(System.in);
      while (true) {
      String data=input.nextLine();
      bw.write(data);
      bw.newLine();
      bw.flush();
      if (data.contains("bye") || data.contains("88")) {
      break;
      }
      }
      bw.close();
      socket.close();
      System.out.println(socket.getInetAddress()+" disconnected.");

3.4 示例4: 使用Socket实现注册登录

  • 使用Scoket编程实现服务器端注册:
    • 注册信息保存在properties文件中
    • 封装格式:
      • id = {id : ”1001”, name : “tom”, pwd: ”123” , age : 20 }
    • 注册成功后返回字符串“注册成功”。
  • 使用Scoket编程实现服务器端登录:

    • 获取properties文件中的用户信息,进行用户id与密码的校验。
    • 校验成功后返回字符串“登录成功”。
  • UserServer.java

    • ```java
      public class UserServer {

      public static void main(String[] args) {
          new RegistThread().start();
          new LoginThread().start();
      }
      

      }

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45

      - RegistThread.java

      - ```java
      /**
      * 实现注册功能
      */
      public class RegistThread extends Thread {
      @Override
      public void run() {
      try {
      // 1. 创建服务器对象
      ServerSocket listener = new ServerSocket(6666);
      // 2. 等待客户端连接
      System.out.println("注册服务器启动成功,等待客户端连接...");
      Socket socket = listener.accept();
      // 3. 获取输入流
      BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream(), "UTF-8"));
      BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(), "UTF-8"));
      // 4. 读取客户端发送的数据[{id = {id : ”1001”, name : "tom”, pwd: ”123” , age : 20}]
      String json = br.readLine();
      String[] infos = json.substring(1, json.length() - 1).split(",");// 去掉首尾的中括号
      String id = infos[0].split(":")[1].trim();
      // 5. 加载属性文件
      Properties properties = Tools.loadProperties();
      // 6. 判断id是否存在
      if (properties.containsKey(id)) {
      // 7. 如果存在,返回注册失败
      bw.write("此用户已存在");
      } else {
      // 8. 如果不存在,将数据写入属性文件
      System.out.println("写入属性文件" + json);
      Tools.saveProperties(json);
      // 9. 返回注册成功
      bw.write("注册成功");
      bw.close();
      br.close();
      socket.close();
      listener.close();
      }
      } catch (IOException e) {
      throw new RuntimeException(e);
      }
      }
      }
  • LoginThread.java

    • ```java
      /**
      • 登录线程
        */
        public class LoginThread extends Thread {
        @Override
        public void run() {
         try {
             // 1. 创建服务器对象
             ServerSocket listener = new ServerSocket(7777);
             // 2. 等待客户端连接
             System.out.println("登录服务器启动成功,等待客户端连接...");
             Socket socket = listener.accept();
             // 3. 获取输入流
             BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream(), "UTF-8"));
             BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(), "UTF-8"));
             // 4. 读取客户端发送的数据[{id = {id : ”1001”,pwd: ”123”}]
             String json = br.readLine();
             String[] infos = json.substring(1, json.length() - 1).split(",");// 去掉首尾的中括号
             String id = infos[0].split(":")[1].trim();
             // 5. 加载属性文件
             Properties properties = Tools.loadProperties();
             // 6. 判断id是否存在
             if (properties.containsKey(id)) {
                 // 7. 如果存在,判断密码是否正确
                 String pwd = infos[1].split(":")[1].trim();
                 String value = properties.getProperty(id);
                 String[] values = value.substring(1, value.length() - 1).split(",");
                 String pwdInFile = values[2].split(":")[1].trim();
                 System.out.println(pwd + " " + pwdInFile);
                 if (pwd.equals(pwdInFile)) {
                     // 8. 如果密码正确,登录成功
                     bw.write("登录成功");
                     System.out.println("登录成功");
                 } else {
                     // 9. 如果密码错误,登录失败
                     bw.write("密码错误, 登录失败");
                     System.out.println("密码错误, 登录失败");
                 }
             } else {
                 // 8. 如果不存在,登录失败
                 bw.write("用户不存在, 登录失败");
                 System.out.println("用户不存在, 登录失败");
             }
             bw.close();
             br.close();
             socket.close();
             listener.close();
         } catch (IOException e) {
             throw new RuntimeException(e);
         }
        
        }
        }
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        39
        40
        41
        42
        43
        44
        45
        46
        47
        48
        49
        50
        51
        52
        53
        54
        55
        56
        57

        - Tools.java

        - ```java
        /**
        * 工具类
        * 1. 加载属性文件
        * 2. 读取属性文件
        */
        public class Tools {
        public static Properties loadProperties() {
        // 1. 创建属性集合
        Properties properties = new Properties();
        // 2. 判断是否存在属性文件
        File file = new File("user.properties");
        if (file.exists()) {
        // 3. 加载属性文件
        FileInputStream fis = null;
        try {
        fis = new FileInputStream(file);
        properties.load(fis);
        } catch (Exception e) {
        throw new RuntimeException(e);
        } finally {
        if (fis != null)
        try {
        fis.close();
        } catch (Exception e) {
        throw new RuntimeException(e);
        }
        }
        }
        return properties;
        }

        public static void saveProperties(String json) {
        String[] infos = json.substring(1, json.length() - 1).split(",");// 去掉首尾的中括号
        String id = infos[0].split(":")[1].trim();
        // 保存
        FileOutputStream fos = null;
        try {
        fos = new FileOutputStream("user.properties", true);
        Properties properties = new Properties();
        properties.setProperty(id, json);
        properties.store(fos, "user");
        } catch (Exception e) {
        throw new RuntimeException(e);
        } finally {
        if (fos != null)
        try {
        fos.close();
        } catch (Exception e) {
        throw new RuntimeException(e);
        }
        }
        }
        }
  • UserClient.java

    • ```java
      /**

      • 客户端
        */
        public class UserClient {
        public static void main(String[] args) {

         System.out.println("-----请选择(1)注册(2)登录-----");
         Scanner input = new Scanner(System.in);
         int num = input.nextInt();
         switch (num) {
             case 1:
                 // 注册
                 regist();
                 break;
             case 2:
                 // 登录
                 login();
                 break;
             default:
                 System.out.println("输入错误");
                 break;
         }
        

        }

        private static void login() {

         try {
             // 1. 创建客户端对象
             Socket socket = new Socket("192.168.136.1", 7777);
             // 2. 获取流
             BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(), "UTF-8"));
             BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream(), "UTF-8"));
             // 3. 获取用户信息
             String json = getLoginInfo();
             // 4. 发送数据
             bw.write(json);
             bw.newLine();
             bw.flush();
             // 5. 接收服务器返回的数据
             String result = br.readLine();
             System.out.println("服务器返回的数据:" + result);
             // 6. 关闭资源
             bw.close();
             br.close();
             socket.close();
         } catch (IOException e) {
             throw new RuntimeException(e);
         }
        

        }

        private static String getLoginInfo() {

         Scanner input = new Scanner(System.in);
         System.out.println("请输入id:");
         int id = input.nextInt();
         System.out.println("请输入pwd:");
         String pwd = input.next();
         String josn = "{id:" + id + ",pwd:" + pwd + "}";
         return josn;
        

        }

        private static void regist() {

         try {
             // 1. 创建客户端对象
             Socket socket = new Socket("192.168.136.1", 6666);
             // 2. 获取流
             BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(), "UTF-8"));
             BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream(), "UTF-8"));
             // 3. 获取用户信息
             String json = getRegistInfo();
             // 4. 发送数据
             bw.write(json);
             bw.newLine();
             bw.flush();
             // 5. 接收服务器返回的数据
             String result = br.readLine();
             System.out.println("服务器返回的数据:" + result);
             // 6. 关闭资源
             bw.close();
             br.close();
             socket.close();
         } catch (IOException e) {
             throw new RuntimeException(e);
         }
        

        }

        public static String getRegistInfo() {

         Scanner input = new Scanner(System.in);
         System.out.println("请输入id:");
         int id = input.nextInt();
         System.out.println("请输入name:");
         String name = input.next();
         System.out.println("请输入pwd:");
         String pwd = input.next();
         System.out.println("请输入age:");
         int age = input.nextInt();
         String josn = "{id:" + id + ",name:" + name + ",pwd:" + pwd + ",age:" + age + "}";
         return josn;
        

        }
        }

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        39
        40
        41
        42
        43
        44
        45
        46
        47
        48
        49
        50
        51
        52
        53
        54
        55
        56
        57
        58
        59
        60
        61
        62
        63
        64
        65
        66
        67
        68
        69
        70
        71
        72
        73
        74
        75
        76

        # 八、反射(Reflection)

        ## 1. 类对象

        ### 1.1 介绍

        - 类的对象:基于某个类new出来的对象,也称为实例对象。
        - 类对象:类加载的产物,封装了一个类的所有信息(类名、父类、接口、属性、方法、构造方法)

        ![image-20230423100651625](https://s2.loli.net/2023/04/23/jJskEF4WT8HnqOR.png)

        - 显示程序运行时所加载的类的过程: `-verbose:class` (添加 VM 选项)

        ### 1.2 类加载器

        #### 1.2.1 类的加载过程

        - 加载:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构然后生成一个代表这个类的java.lang.Classi对象
        - 链接:将ava类的二进制代码合并到VM的运行状态之中的过程。
        - 验证:确保加载的类信息符合VM规范,没有安全方面的问题
        - 准备:正式为类变量(static)分配内存并设置类变量默认初始值的阶段,这些内存都将在方法区中进行分配。
        - 解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程。
        - 初始化:
        - 执行类构造器\<clinit\>(0方法的过程。类构造器\<clinit\>()方法是由编译期自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的。(类构造器是构造类信息的,不是构造该类对象的构造器)。
        - 当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化。
        - 虚拟机会保证一个类的\<clinit\>()方法在多线程环境中被正确加锁和同步。

        #### 1.2.2 类的初始化

        - 类的主动引用(一定会发生类的初始化)

        - 当虚拟机启动,先初始化main方法所在的类

        - new一个类的对象
        - 调用类的静态成员(除了final?常量)和静态方法
        - 使用java.lang.reflect包的方法对类进行反射调用
        - 当初始化一个类,如果其父类没有被初始化,则先会初始化它的父类

        - 类的被动引用(不会发生类的初始化)

        - 当访问一个静态域时,只有真正声明这个域的类才会被初始化。如:当通过子类引用父类的静态变量,不会导致子类初始化
        - 通过数组定义类引用,不会触发此类的初始化
        - 引用常量不会触发此类的初始化(常量在链接阶段就存入调用类的常量池中了)

        #### 1.2.3 类加载器的作用

        - 介绍:类加载器作用是用来把类(clss)装载进内存的。JVM规范定义了如下类型的类的加载器
        - 引导类加载器:用C++编写的,是JVM自带的类加载器,负责Java平台核心库,用来装载核心类
        库。该加载器无法直接获取
        - 扩展类加载器:负责jre/Iib/ext目录下的jar包或-D java.ext.dirs指定目录下的jar包装入工作库
        - 系统类加载器:负责java-classpath或-D java.class.path所指的目录下的类与jar包装入工作,是最常用的加载器

        ```java
        // 获取系统类的加载器
        ClassLoader classLoader = Application.class.getClassLoader();
        System.out.println(classLoader);

        // 获取系统类的加载器的父类加载器 --> 扩展类加载器
        ClassLoader classLoader1 = classLoader.getParent();
        System.out.println(classLoader1);

        // 获取系统类的加载器的父类加载器的父类加载器 --> 根加载器(C/C++)
        ClassLoader classLoader2 = classLoader1.getParent();
        System.out.println(classLoader2);

        // 测试当前类是由哪个类加载器进行加载的
        ClassLoader classLoader3 = String.class.getClassLoader();
        System.out.println(classLoader3);

        // 测试JDK内置的类是由哪个类加载器进行加载的 --> 根加载器
        ClassLoader classLoader4 = Object.class.getClassLoader();
        System.out.println(classLoader4);

        // 获取系统类加载器可以加载的路径
        System.out.println(System.getProperty("java.class.path"));

2. 获取类对象

  1. 通过类的对象,获取类对象
    • Student s=new Student();
    • Class c=s.getClass();
  2. 通过类名获取类对象
    • Class c=类名.class;
  3. 通过静态方法获取类对象==推荐==
    • Class c=Class.forName(“包名.类名”);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 1. 通过类的对象获取
Person person = new Person("Bay", 18, "China");
Class personClass = person.getClass();
System.out.println(personClass);
// 2. 通过类名.class获取
Class personClass2 = Person.class;
System.out.println(personClass2);
// 3. 通过Class.forName("全类名")获取
try {
Class personClass3 = Class.forName("com.bayyy.Reflection.Person");
System.out.println(personClass3);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}

3. 常用方法

  • public String getName()
  • public Package getPackage()
  • public Class<? super T> getSuperclass()
  • public Class<?>[] getInterfaces()
  • public Constructor<?>[] getConstructors()
  • public T newInstance ()
  • public Method[] getMethods()
  • public Field[] getFields()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
public static void main(String[] args) throws Exception {
// Person person = new Person("Bay", 18, "China");
// System.out.println(person);
// mygetClass();
// reflectOpe1();
// reflectOpe2();
// reflectOpe3();
// invokeAnyTest();
reflectOpe4();
}

/**
* 获取Class对象的三种方式.
*/
public static void mygetClass() {
// 1. 通过类的对象获取
Person person = new Person("Bay", 18, "China");
Class personClass = person.getClass();
System.out.println(personClass);
// 2. 通过类名.class获取
Class personClass2 = Person.class;
System.out.println(personClass2);
// 3. 通过Class.forName("全类名")获取
try {
Class personClass3 = Class.forName("com.bayyy.Reflection.Person");
System.out.println(personClass3);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}

/**
* 使用反射获取类的名字、包名、父类、接口等.
*/
public static void reflectOpe1() throws Exception {
// 1. 获取类的名字
String name = Person.class.getName();
System.out.println(name);
// 2. 获取包名
Package personPackage = Person.class.getPackage();
System.out.println(personPackage);
// 3. 获取父类
Class<?> superClass = Person.class.getSuperclass();
System.out.println(superClass);
// 4. 获取接口
Class<?>[] interfaces = Person.class.getInterfaces();
for (Class<?> anInterface : interfaces) {
System.out.println(anInterface);
}
}

/**
* 使用反射获取类的构造方法、创建对象
*/
public static void reflectOpe2() throws Exception {
// 1. 获取类的名字
Class<?> class1 = Class.forName("com.bayyy.Reflection.Person");
// 2. 获取构造方法
// 2.1 获取所有构造方法
Constructor<?>[] cons = class1.getConstructors();
for (Constructor<?> con : cons) {
System.out.println(con.toString());
}
// 2.2 获取指定构造方法
// 2.2.1 获取无参构造方法
// 方法1
Constructor<?> con1 = class1.getConstructor();
// 使用无参构造方法创建对象
Object obj1 = con1.newInstance();
Person person1 = (Person) obj1;
System.out.println(person1);
// 方法2
// Person lisi = (Person)class1.newInstance(); // 已弃用
Person lisi = (Person) class1.getDeclaredConstructor().newInstance();
System.out.println(lisi);

// 2.2.2 获取带参构造方法
Constructor<?> con3 = class1.getConstructor(String.class, int.class, String.class);
Person wangwu = (Person) con3.newInstance("Bay", 18, "China");
}

/**
* 使用反射获取类的方法,并调用方法.
*/
public static void reflectOpe3() throws Exception {
Class<?> class1 = Class.forName("com.bayyy.Reflection.Person");
// 1. 获取所有方法
// 1.1 getMethods()获取所有public方法,包括父类的
Method[] methods1 = class1.getMethods(); // 只能获取public方法
// for (Method method : methods1) {
// System.out.println(method);
// }
// 1.2 getDeclaredMethods()获取所有方法,不包括父类的
Method[] methods2 = class1.getDeclaredMethods();
// for (Method method : methods2) {
// System.out.println(method);
// }
// 2. 获取指定方法
// 2.1 eat
// 2.1.1 无参
Method eatMethod = class1.getMethod("eat");
// 调用方法
Person zhangsan = (Person) class1.getDeclaredConstructor().newInstance();
eatMethod.invoke(zhangsan);
System.out.println("-------------");
// 2.1.2 带参
Method eatMethod2 = class1.getMethod("eat", String.class);
// 调用方法
Constructor con1 = class1.getConstructor(String.class, int.class, String.class);
Person zhaoliu = (Person) con1.newInstance("赵六", 18, "China");
eatMethod2.invoke(zhaoliu, "rice");
System.out.println("-------------");
// 2.2 toString
Method toStringMethod = class1.getMethod("toString");
// 调用方法
Constructor<?> con = class1.getConstructor(String.class, int.class, String.class);
Person lisi = (Person) con.newInstance("李四", 18, "China");
String str = (String) toStringMethod.invoke(lisi);
System.out.println(str);
System.out.println("-------------");
// 2.3 私有方法
Method method = class1.getDeclaredMethod("privateMethod");
// 调用方法
method.setAccessible(true); // 设置访问权限
method.invoke(lisi);
System.out.println("-------------");
// 2.4 静态方法
Method staticMethod = class1.getMethod("staticMethod");
// 调用方法
staticMethod.invoke(null);
// staticMethod.invoke(lisi); // 报错
staticMethod.invoke(class1);
}

/**
* 使用反射实现一个可以调用任何对象方法的通用方法.
*/
public static Object invokeAny(Object obj, String methodName, Class<?>[] types, Object... args) throws Exception {
// 1. 获取Class对象
Class<?> class1 = obj.getClass();
// 2. 获取方法
Method method = class1.getMethod(methodName, types);
return method.invoke(obj, args);
}

public static void invokeAnyTest() {
Properties properties = new Properties();
// properties.setProperty("name","Bay");
// System.out.println(properties.toString());
try {
invokeAny(properties, "setProperty", new Class[]{String.class, String.class}, "name", "Bay");
System.out.println(properties.toString());
} catch (Exception e) {
throw new RuntimeException(e);
}
}


/**
* 使用反射获取类的属性,并调用属性.
*/
public static void reflectOpe4() throws ClassNotFoundException, NoSuchFieldException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
// 1. 获取Class对象
Class<?> class1=Class.forName("com.bayyy.Reflection.Person");
// 2. 获取属性(字段) // 只能获取public属性
Field[] fields = class1.getFields();
System.out.println("getFields()获取public属性:");
for (Field field : fields) {
System.out.println(field); // null
}
// 3. 获取属性(字段) // 可以获取所有属性
Field[] fields2 = class1.getDeclaredFields();
System.out.println("getDeclaredFields()获取所有属性:");
for (Field field: fields2) {
System.out.println(field);
}
// 4. 获取指定属性
Field nameField = class1.getDeclaredField("name");
// 5. 赋值
Person zhangsan = (Person) class1.getDeclaredConstructor().newInstance();
nameField.setAccessible(true); // 设置访问权限
nameField.set(zhangsan,"张三");
// 6. 获取值
System.out.println(nameField.get(zhangsan));
}

4. 设计模式

4.1 设计模式介绍

  • 什么是设计模式

    • 一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。简单理解:
      特定问题的固定解决方法。
  • 好处:使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代
    码可靠性、重用性。

  • 在Gof的《设计模式》书中描述了23种设计模式

4.2 工厂设计模式(Factory Pattern)

  • 工厂模式主要负责对象创建的问题。
  • 开发中有一个非常重要的原则“开闭原则”,对拓展开放、对修改关闭。
  • 可通过反射进行工厂模式的设计,完成动态的对象创建。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 过时方法
public class UsbFactory {
public static Usb createUsb(int type) {
if (type == 1) {
return new Upan();
} else if (type == 2) {
return new Mouse();
} else if (type == 3) {
return new Upan();
}
return null;
}
}

// 使用反射方法
public static Usb createUsb(String type) {
Usb usb = null;
Class<?> class1 = null;
try {
class1 = Class.forName(type);
usb = (Usb) class1.newInstance();
} catch (Exception e) {
System.out.println(e.getMessage());
}
return usb;
}

4.3 单例模式(Singleton Pattern)

  • 单例(Singleton):只允许创建一个该类的对象。

  • 方式1:饿汉式(类加载时创建,天生线程安全)

    • 创建一个常量
      私有化构造器
      提供一个静态方法(获取实例)

    • ```java
      public class SingleTon {

      private static final SingleTon INSTANCE = new SingleTon();
      private SingleTon() {}
      public static SingleTon getInstance() {
          return INSTANCE;
      }
      

      }

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35

      - 方式2:懒汉式(使用时创建,线程不安全,加同步)

      - 首先创建一个常量
      构造方法改成私有的,类外部不能创建对象
      通过一个公开的方法,返回这个对象

      - ```java
      // 方法1
      public class SingleTon2 {
      private static SingleTon2 INSTANCE=null;
      private SingleTon2() {}
      public static synchronized SingleTon2 getInstance() {
      if (INSTANCE==null) {
      return new SingleTon2();
      }
      return INSTANCE;
      }
      }

      // 方法2:双重检测锁模式
      public class SingleTon2 {
      private static SingleTon2 INSTANCE=null;
      private SingleTon2() {}
      public static SingleTon2 getInstance() {
      if (INSTANCE==null) { // 提高执行效率
      synchronized (SingleTon2.class) {
      if (INSTANCE==null) { // 保证线程安全后判断是否存在
      return new SingleTon2();
      }
      }
      }
      return INSTANCE;
      }
      }
  • 方法3:静态内部类— 懒汉式(使用时创建,线程安全)

    • ```java
      public class SingleTon3 {
      private SingleTon3() {}
      private static class SingleTon3Holder {
          static SingleTon3 INSTANCE = new SingleTon3();
      }
      public static SingleTon3 getInstance() {
          return SingleTon3Holder.INSTANCE;
      }
      
      }
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26

      ## 5. 枚举

      - 什么是枚举
      - 枚举是一个引用类型, 枚举是一个规定了取值范围的数据类型。
      - 枚举变量不能使用其他的数据,只能使用枚举中常量赋值,提高程序安全性。
      - 定义枚举使用enum关键字

      ```java
      /**
      * 性别枚举类
      * 1. 枚举类的本质是什么? 类(entends Enum)
      * 2. 枚举类的构造器默认是私有的
      * 3. 枚举中必须要包含枚举常量,也可以包含其他的属性和方法
      * 4, 枚举常量必须在前面,多个常量之间用逗号隔开,最后分号可写可不写
      */
      public enum Gender {
      Male, FEMALE;
      private String name; // 可以包含其他的属性
      private Gender() {} // 可以包含构造器
      public void show1() {} // 可以包含方法
      public static void show2() {} // 可以包含静态方法
      }
      // test
      Gender gender = Gender.Male;
      System.out.println(gender);

九、 注解(Annotation)

1. 注解简介

  • 什么是注解

    • 注解(Annotation)是代码里的特殊标记,程序可以读取注解,一般用于替代配置文件。
  • 开发人员可以通过注解告诉类如何运行。

    • 在Java技术里注解的典型应用是:可以通过反射技术去得到类里面的注解,以决定怎么
      去运行类。
  • 常见注解:@0verride(重写)、@Deprecated(过时)、@SuppressWarnings(抑制警告信息)
  • 定义注解使用@interface关键字,注解中只能包含属性。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* 创建注解类型 @interface
* 注解的本质:接口(entends Annotation)
*/
public @interface MyAnnotation {
// 注解的属性(类似接口中的方法
String name() default "bayyy";
int age() default 24;
}

// test
public class Person {
@MyAnnotation(name = "bayyy", age = 18)
public void show() {
System.out.println("Person show");
}
@MyAnnotation
public void display() {
System.out.println("Person display");
}
}

2. 注解属性类型

  • String类型

    • 只有一个属性(String value())时,可省略属性名(value)

    • ```java
      @MyAnnotation2(“bayyy”)
      public void test3() {}

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60

      - 基本数据类型

      - 一般默认值为- “”、0、-1(表示不存在,i.e. id)

      - Class类型

      - 枚举类型

      - 注解类型

      - 以上类型的一维数组

      ## 3. 元注解

      - 元注解:用来描述注解的注解。
      - @Retention:用于指定注解可以保留的域。
      - RetentionPolicy.CLASS:在源文件中有效(即源文件保留)- 注解记录在class文件中,运行Java程序时,JVM不会保留。这是默认值
      - RetentionPolicy.RUNTIME:在源文件中有效(即源文件保留)- 注解记录在class文件中,运行Java程序时,JVM会保留注,程序可以通过反射获取该注释
      - RetentionPolicy.SOURCE:在源文件中有效(即源文件保留)- 编译时直接丢弃这种策略的注释
      - @Document: 说明该注解将被包含在javadoc中
      - @Inherited: 说明子类可以继承父类中的该注解
      - @Target: 指定注解用于修饰类的哪个对象
      1. CONSTRUCTOR:用于描述构造器
      2. FIELD:用于描述域
      3. LOCAL_VARIABLE:用于描述局部变量
      4. METHOD:用于描述方法
      5. PACKAGE:用于描述包
      6. PARAMETER:用于描述参数
      7. TYPE:用于描述类、接口(包括注解类型)或enum声明


      ```java
      // PersonInfo.java
      @Retention(value= RetentionPolicy.RUNTIME)
      @Target(value={ElementType.FIELD, ElementType.METHOD})
      public @interface PersonInfo {
      String name();
      int age();5
      String sex();
      }

      // Person.java
      @PersonInfo(name = "bayyy", age = 25, sex = "male")
      public void test4(String name, int age, String sex) {
      System.out.println(name+"==="+age+"==="+sex);
      }

      // 通过反射获取注解上的属性值
      // 1. 获取类对象
      Class<?> class1 = Class.forName("com.bayyy.annotation.Person");
      // 2. 获取方法对象
      Method method = class1.getMethod("test4", String.class, int.class, String.class);
      // 3. 获取方法上的注解
      PersonInfo personInfo = method.getAnnotation(PersonInfo.class);
      // 4. 获取注解上的属性值
      String name = personInfo.name();
      int age = personInfo.age();
      String sex = personInfo.sex();
      System.out.println(name+"==="+age+"==="+sex);

4. 一些常用注解

4.1 Spring中的注解

SpringMVC注解

这些注解描述的类 Spring会创建原生对象或代理对象并交给 IOC容器 管理,这些对象称之为bean。用时直接 @Autowired 注入即可。

  • @Mapper 描述数据层 (Mapper)
  • @Service 描述业务层 (Service)
  • @Repository 标识持久层 / 数据访问层组件(Dao)
  • @Component 可以描述各种组件(当组件不好归类时)

  • @RestController 描述控制层(Controller)并返回JSON数据类型,但不会再执行配置的视图解析器,也不会返回给jsp页面,返回值就是return里的内容。

    • 该注解等同于:@Controller + @ResponseBody
  • @Controller 描述控制层 接收用户请求 执行 视图解析器
    • 返回一个ModelAndView对象,传给指定的jsp(将传过来的ModelAndView对象解析后展示在页面上),封装后,最后返回给客户端一个Html页面。
  • @ResponseBody 将Java对象转为JSON。如果用ajax接收请求,必须使用该注解。如果没有加此注解,返回的是一个页面,return里面的内容会被认做是需要跳转页面的地址。

IOC容器注解

IOC(Inversion of Control) 是控制反转,也叫依赖注入(DI)。
把复杂系统分解成相互合作的对象,这些对象类通过封装以后,内部实现对外部是透明的,从而降低了解决问题的复杂度,而且可以灵活地被重用和扩展。

简单来说: IOC容器意味着将你设计好的对象(类)交给容器控制(管理),需要的时候通过注解来注入(获取),而不是传统的在你的对象内部直接控制(new 对象)。从而降低了程序的耦合性。

  • @Bean 描述在方法上,把方法的返回值交给容器管理,不需要再手动调用该方法。

  • @Autowired 按byType自动注入 获取对应的bean对象。如果注入的类型有多个实现类,则需要注入具体实现类的名称。

  • @Resource 按byName自动注入 获取对应的bean对象

  • @Value 注入普通类型属性。

  • 如果容器中有多个相同类型的 bean,则框架将抛出 NoUniqueBeanDefinitionException, 以提示有多个满足条件的bean。
    bean会进行自动装配。程序无法正确做出判断使用哪一个时,可以使用以下注解 ↓

  • @Qualifier(“”) 在相同类型bean上命名后,可以按不同名称注入 配合@Autowired 使用。

  • @Primary 当存在多个相同类型的 bean 时,首选被@Primary注解过的bean。

Bean注解

把Bean理解可以理解为类的代理或代言人(实际上是通过反射、代理来实现的)当你使用上面SpringMVC注解时,Spring会把这些被注解的类的实例化对象转化成一个个的bean,也称 注册 成一个个的bean,并保存到IOC容器中。

Bean的使用范围注解:
  • @Scope(value=””) 默认生成的类是单例的。

  • 取值(value) 有:

    1. singleton :单例,默认,程序启动时立刻创建对象
    2. prototype :多例,需要注入时(调用方法)才创建对象。每次注入时,都会创建新对象
    3. request :request域,需要在web环境
    4. session :session域,需要在web环境。第一次访问时创建,关闭浏览器时回收。
    5. application: context域,需要在web环境
    6. globalsession 集群环境的session域,需要在web环境
Bean的生命周期注解:
  • @PostConstruct 相当于init-method
  • @PreDestroy 相当于destroy-method

Spring启动类注解(开箱即用):

只需要导入简单的jar包文件,就可以实现对应的功能,无需(少量)繁琐的配置。

  • @SpringBootAppliction 用在启动类上,主要目的是开启自动配置组合了:
    1. @SpringBootConfiguration 继承自@Configuration,标注当前类是配置类,将当前类的一个或多个以@Bean注解标记的方法的实例纳入到Spring容器中,并且实例名就是方法名。
    2. @EnableAutoConfiguration 自动化配置, 借助@Import的支持,收集和注册特定场景相关的bean定义。简单理解:借助@Import的帮助,将所有符合自动配置条件的bean定义加载到IOC容器中。
      • 该注解里面有@AutoConfigurationPackage 自动配置的包扫描 @Import调用选择器去加载pom.xml文件中的启动项
    3. @ComponentScan 定义包扫描 指定路径 哪些包中的对象注册成bean交给IOC容器管理。

请求Mapping注解

  • @RequestMapping(“/xxx”) 注解类上 通过”/xxx”来指定控制器可以处理哪些URL请求。

  • 请求方式:

    • @GetMapping 通常注解 查询方法
    • @PostMapping 通常注解 增添保存方法
    • @DeleteMapping 通常注解 删除方法
    • @PutMapping 通常注解 更新方法
    • @PatchMapping 通常注解 更新局部方法

动态赋值注解

  • @PathVariable 接收的url动态传给被注解的参数(restFull风格)
  • @RequestBody 将接收的JSON格式的数据转为Java对象参数(适用于Post请求)
  • @RequestParam(value=“接收的xxx”) 讲接收的xxx传给被注解的参数 (适用于Post,Get请求)

缓存注解

  • @EnableCaching 启动springboot工程中的内置缓存。

  • @Cacheable(value=“缓存值取名”) 把返回值进行缓存,缓存通过切面自动切入,可用用于方法或者类上。

    • | 参数 | 描述 |
      | ————- | ——————- |
      | value | 名称 |
      | key | key |
      | condition | 可判断key条件 |
  • @CacheEvict(value=“需要清空的缓存名”) 方法是一个清缓存的切入点方法,当这个方法被调用后,即会清空缓存。

    • | 参数 | 描述 |
      | ———————— | ——————————— |
      | value | 名称 |
      | key | key |
      | condition | 缓存的条件,可以为空 |
      | allEntries | 是否清空所有缓存内容 |
      | beforeInvocation | 是否在方法执行前就清空 |

4.2 AOP切面注解

AOP(Aspect Oriented Programming) 面向切面编程。
Spring中的AOP代理还是离不开Spring的IOC容器,代理的生成,管理及其依赖关系都是由IOC容器负责,Spring默认使用 JDK动态代理
AOP详解请参考官网

切入点表达式:
  • @Pointcut(“@annotation(被切入方法的地址)”) 设置切入点

1.bean(“包名.类名”) 具体某个类 按类匹配 粗粒度
2.within(“包名.类名”) 可以使用通配符 几个类一起匹配 粗粒度
3.execution(返回值类型 包名.类名.方法名(参数列表)) 细粒度
4.注解方式 @annotation(注解的类型) 细粒度

通知:
  • @before(“pointCut()”) 在目标方法前执行
  • @After(“pointCut()”) 在目标方法后执行 ,不管有没有异常
  • @AfterReturning 在目标方法正常返回值后执行
  • @AfterThrowing 在目标方法出现异常后执行
    • 上述的4大通知,都不能控制目标方法是否执行,一般只会做程序的监控。
    • 可以通过调用JoinPoint对象获取被切入方法 签名(getSignature())。
  • @Around(“pointCut()”) 在目标方法执行前后都要执行,可以控制目标方法在哪执行。
    • 可以通过调用ProceedingJoinPoint对象的proceed方法来控制需要切入的方法的行动轨迹。

4.3 常用插件注解

Lombok 插件

  • @Data 动态添加get/set/toString/equals/hashcode/构造方法 适用于pojo / VO该注解等同于:

      • @Getter + @Setter + ToString + @EqualsAndHashCode +
      • @RequiredArgsConstructor
  • @Value 把所有的变量都设成 final 修饰 和 @Data相似

  • @AllArgsConstructor 添加构造方法
  • @NoArgsConstructor 添加无参构造
  • @sfl4g 自动生成该类的 log 静态常量
  • @Accessors(chain = true) 引用链式加载方式 方便做插入操作。

Lombok插件 官方文档说明:

MybatisPlus 注解

MyBatis-Plus (opens new window)(简称 MP)是一个 MyBatis (opens new window)
的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。

  • @TableName(value=””, resultMap=””) 表名与实体类名不一致时 需要在实体类上加入注解”value=表名”,xml 中 resultMap 的 id 不一致时需要赋值。

  • @TableId(value= “”, type = IdType.AUTO) 表示主键名/属性。 @IdType的值有:

      • AUTO 数据库自增
      • INPUT 自行输入
      • ID_WORKER 分布式全局唯一ID 长整型类型
      • UUID 32位UUID字符串
      • NONE 无状态
      • ID_WORKER_STR 分布式全局唯一ID 字符串类型
  • @TableField(“…”) 注解新增属性,如果字段名与属性一致(已开启驼峰规则),则可省略,否则加入”exist=false”参数。

    • | 参数 | 描述 |
      | ————- | —————————————————————————— |
      | value | 字段值,如果字段名与属性一致(已开启驼峰规则)则可省略 |
      | update | 预处理 set 字段自定义注入 |
      | condition | 预处理 WHERE 实体条件自定义运算规则 |
      | exist | 是否为数据库表字段 |
      | fill | 字段填充 |
  • @TableLogic 表字段逻辑处理注解(逻辑删除)

Mybatis-Plus官方文档说明

Shiro 注解

Shiro 提供了相应的注解用于权限控制,如果使用这些注解就需要使用AOP 的功能来进行判断

  • @RequiresAuthentication 表示当前Subject已经通过login 进行了身份验证,即Subject. isAuthenticated()返回true。
  • @RequiresGuest 表示当前Subject没有身份验证或通过记住我登录过,即是游客身份。
  • @RequiresUser 表示当前Subject已经身份验证或者通过记住我登录的。
  • **@RequiresRoles
  • (value={“admin”, “user”}, logical= Logical.AND)**
  • 表示当前Subject需要角色 admin 和(AND) user ,value表示需要判断的角色,logical表示判断条件。
  • **@RequiresPermissions
  • (value={“user:a”, “user:b”}, logical= Logical.OR)**
  • 表示当前Subject需要权限 user:a 或(OR) user:b ,value表示需要判断的权限,logical表示判断条件。

Shiro框架官方文档说明

4.4 Javadoc注解

javadoc 是Sun公司提供的一个技术,它从程序源代码中抽取类、方法、成员等注释形成一个和源代码配套的API帮助文档。也就是说,只要在编写程序时以一套特定的标签作注释,在程序编写完成后,通过Javadoc就可以同时形成程序的开发文档了。

Java 支持三种注释方式。前两种分别是 // 和 / /,第三种被称作说明注释,它以 /\ 开始,以 */结束。

说明注释允许你在程序中嵌入关于程序的信息。你可以使用 javadoc 工具软件来生成信息,并输出到HTML文件中。

说明注释,使你更加方便的记录你的程序信息。

Javadoc官方说明:How to Write Doc Comments for the Javadoc Tool

  • Javadoc标签:

    • | 标签 | 说明 |
      | ——————- | —————————————————————————————— |
      | @author | 标识一个类的作者 @author description |
      | {@code} | 一般在Javadoc中只要涉及到类名或者方法名,都需要使用@code进行标记 {@code text} |
      | @deprecated | 指名一个过期的类或成员 @deprecated description |
      | {@docRoot} | 指明当前文档根目录的路径 Directory Path |
      | @exception | 标志一个类抛出的异常 @exception exception-name explanation |
      | {@inheritDoc} | 从直接父类继承的注释 Inherits a comment from the immediate surperclass. |
      | {@link} | 插入一个到另一个主题的链接 {@link name text} |
      | {@linkplain} | 插入一个到另一个主题的链接,但是该链接显示纯文本字体 Inserts an in-line link to another topic. |
      | @param | 说明一个方法的参数 @param parameter-name explanation |
      | @return | 说明返回值类型 @return explanation |
      | @see | 指定一个到另一个主题的链接 @see anchor |
      | @serial | 说明一个序列化属性 @serial description |
      | @serialData | 说明通过writeObject( ) 和 writeExternal( )方法写的数据 @serialData description |
      | @serialField | 说明一个ObjectStreamField组件 @serialField name type description |
      | @since | 标记当引入一个特定的变化时 @since release |
      | @throws | 和 @exception标签一样. The @throws tag has the same meaning as the @exception tag. |
      | {@value} | 显示常量的值,该常量必须是static属性。 Displays the value of a constant, which must be a static field. |
      | @version | 指定类的版本 @version info |

4.5 其他注解

Async异步/切面注解

  • @Async 注解描述的方法为一个异步切入点方法(声明该方法执行异步),启动类上需要加上@EnableAsync才能使其生效。
    • 这个方法会在切面通知方法中通过一个新的线程调用执行,由spring线程池提供。
    • 被注解的方法不要有返回值。如果需要有返回值,需要用AsyncResult对象封装。
  • @EnableAsync 可以使用多线程 描述该类支持异步

配置注解

不同的的业务文件放在不同的配置文件yml中,所以需要动态加载配置文件

  • @PropertySource(value=“classpath:/…”,ebcidubg=“UTF-8”) 可以将自定义属性文件中的属性变量值注入到当前类的使用@Value注解的成员变量中。
  • @ConfigurationProperties 可以将属性文件与一个Java类绑定,将属性文件中的变量值注入到该Java类的成员变量中。
  • @ImportResource 注解用来导入 Spring 的配置文件,如核心配置文件 “beans.xml”,从而让配置文件里面的内容生效

其他注解

  • @Select(“…”) 简单的sql语句可以用该注解直接在方法上描述。
  • @CrossOrigin 此注解描述的Controller,表示允许跨域访问。
  • @Transactional 生成AOP代理并开启事务,如果程序出现异常可回滚。
  • @ExceptionHandler(异常类型.class) 自定义异常处理类后,统一处理某一类异常,从而能够减少代码重复率和复杂度。

十、JAVA 8新特性

1. Java8概述

Java8(又称JKD1.8)是Java语言开发的一个主要版本。Oracle公
司于2014年3月18日发布Java8。

  • 支持Lambda表达式
  • 函数式接口
  • 新的Stream API
  • 新的日期API
  • 其他特性

2. Lambda表达式

  • Lambda表达式:特殊的匿名内部类,语法更简洁。

  • Lambda表达式允许把函数作为一个方法的参数(函数作为方法参数传递),将代码像数据一样传递。

  • 基本语法:

    • ```java
      <函数式接口> <变量名> = (参数1, 参数2, …)->{
      // 方法体
      
      }
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31

      -

      - Lambda引入了新的操作符:->(箭头操作符),->将表达式分成两部分

      - 左侧:(参数1,参数2…)表示参数列表
      - 右侧:{}内部是方法体

      - 注意事项

      - 形参列表的数据类型会自动推断
      - 如果形参列表为空,只需保留()
      - 如果形参只有1个,()可以省略,只需要参数的名称即可
      - 如果执行语句只有一句,且无返回值,{}可以省略,若有返回值,则若想省去{},则必须同时省略return,且执行语句也保证只有一句
      - Lambda不会生成一个单独的内部类文件

      ## 2. 函数式接口

      ==只有一个方法的接口==

      ### 2.1 介绍

      - 如果一个接口只有一个抽象方法,则该接口称之为函数式接口,函数式接口可以使用Lambda表达式,Lambda表达式会被匹配到这个抽象方法上。

      - @Functionallnterface注解检测接口是否符合函数式接口。

      - ```java
      @FunctionalInterface
      public interface Usb {
      void service();
      }

2.2 常见函数式接口

函数式接口 参数类型 返回类型 说明
Consumer\ 消费类型接口 T void void accept(T t); // 对类型为T的对象应用操作
Supplier\ 供给型接口 T T get(); // 返回类型为T的对象
Function\ 函数型接口 T R R apply(T t); // 对类型为T的对象应用操作,并返回类型为R类型的对象
Predicate\ 断言型接口 T boolean boolean test(T t); // 确定类型为T的对象是否满足条件,并返回boolean类型

2.3 方法引用

  • 方法引用是Lambda表达式的一种简写形式。如果Lambda表达式方法体中只是调用一个特定的已经存在的方法,则可以使用方法引用。
  • 常见形式
    • 对象::实例方法
    • 类::静态方法
    • 类::实例方法
    • 类::new
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/**
* 方法引用的使用
* 使用情景:当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用!
* 1. 对象::实例方法名
* 2. 类::静态方法名
* 3. 类::实例方法名
* 4. 类::new
* 注意:Lambda体中调用方法的参数列表与返回值类型,要与函数式接口中抽象方法的函数列表和返回值类型保持一致!
*/

// 1. 对象::实例方法名
Consumer<String> consumer = s -> System.out.println(s);
consumer.accept("hello");
Consumer<String> consumer2 = System.out::println;
consumer2.accept("hello");

// 2. 类::静态方法名
Comparator<Integer> com = (x, y) -> Integer.compare(x, y);
Comparator<Integer> com2 = Integer::compare;

// 3. 类::实例方法名
Function<Person, String> function = person -> person.getName();
Function<Person, String> function2 = Person::getName;

// 4. 类::new
Supplier<Person> supplier = () -> new Person();
Supplier<Person> supplier2 = Person::new;

3. Stream API

3.1 介绍

  • 流(Stream)中保存对集合或数组数据的操作。和集合类似,但集合中保存的是数据。

  • 特点:

    • Stream 自己不会存储元素。
    • Stream 不会改变源对象。相反,他们会返回一个持有结果的新Stream。
    • Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。

3.2 Stream操作

  • 使用步骤:
    • 创建
      • 新建一个流
    • 中间操作
      • 在一个或多个步骤中,将初始Stream转化到另一个Stream的中间操作
    • 终止操作
      • 使用一个终止操作来产生一个结果。该操作会强制它之前的延迟操作立即执行。在这之
      • 后,该Stream就不能使用了。

3.2.1 创建

  • 通过Collection对象的stream()或paralle1Stream()方法
  • 通过Arrays类的stream()方法
  • 通过Strean接口的of()、iterate()、generate()方法
  • 通过IntStream、LongStream、DoubleStream接口中的of、range、rangeClosed方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
/**
* Stream API 的使用
* 1. Stream API 的特点
* ① Stream 自己不会存储元素
* ② Stream 不会改变源对象。相反,他们会返回一个持有结果的新Stream
* ③ Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行
* ④ Stream API 是在 JDK 8 中引入的,是对集合对象功能的增强
* ⑥ Stream API 是一个抽象层,它使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象
* ⑦ Stream API 可以对集合数据进行各种操作。例如:filter(过滤)、map(映射)、limit(限制)、sorted(排序)、collect(收集)等
* 2. Stream API 执行流程
* ① Stream 的实例化
* ② 一系列的中间操作(过滤、映射、...)
* ③ 终止操作
* 3. Stream 的创建
* ① 通过集合(Collection)对象的 stream() 或 parallelStream() 方法创建
* ② 通过 Arrays 中的 stream() 方法获取数组流
* ③ 通过 Stream 类中的静态方法 of(), iterate(), generate() 创建
* ④ 创建 IntStream、LongStream、DoubleStream接口中的of,range,rangeClosed方法
*/
public class Demo5 {
public static void main(String[] args) {
// 1. 通过集合(Collection)对象的 stream() 或 parallelStream() 方法创建
ArrayList<String> list = new ArrayList<>();
list.add("hello");
list.add("world");
list.add("java");
Stream<String> stream = list.stream();
Stream<String> stringStream = list.parallelStream();
// 遍历
stream.forEach(System.out::println);
stringStream.forEach(System.out::println);

// 2. 通过 Arrays 中的 stream() 方法获取数组流
String[] arr=new String[]{"hello","world","java"};
Stream<String> stream1 = Arrays.stream(arr);
stream1.forEach(System.out::println);

// 3. 通过 Stream 类中的静态方法 of(), iterate(), generate() 创建
// 3.1 of()方法
Stream<Integer> stream2 = Stream.of(1, 2, 3, 4, 5);
stream2.forEach(System.out::println);
// 3.2 iterate()方法
Stream<Integer> stream3 = Stream.iterate(0, x -> x + 2).limit(10);
stream3.forEach(System.out::println);
// 3.3 generate()方法
Stream<Double> stream4 = Stream.generate(() -> new Random().nextDouble(100)).limit(2);
stream4.forEach(System.out::println);

// 4. 创建 IntStream、LongStream、DoubleStream接口中的of,range,rangeClosed方法
// 4.1 of()方法
IntStream.of(new int[]{1, 2, 3}).forEach(System.out::println);
// 4.2 range()方法
IntStream.range(1, 3).forEach(System.out::println);
// 4.3 rangeClosed()方法
IntStream.rangeClosed(1, 3).forEach(System.out::println);
}
}

3.2.2 中间操作

  • filter、limit、skip、distinct、sorted
  • map
  • parallel
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
ArrayList<Person> list = new ArrayList<>();
list.add(new Person("张三", 18));
list.add(new Person("李四", 19));
list.add(new Person("王五", 20));
list.add(new Person("赵六", 21));
list.add(new Person("田七", 22));
System.out.println("====1====");
// 1. filter(Predicate p) -- 接收 Lambda, 从流中排除某些元素
// 2. limit(n) -- 截断流,使其元素不超过给定数量
// 3. skip(n) -- 跳过元素,返回一个扔掉了前 n 个元素的流。若流中元素不足 n 个,则返回一个空流。与 limit(n) 互补
// 4. distinct() -- 筛选,通过流所生成元素的 hashCode() 和 equals() 去除重复元素
list.stream()
.filter(person -> person.getAge() > 18) // 过滤掉年龄小于18的
.limit(4) // 只取前4个
.skip(1) // 跳过第一个
.distinct() // 去重
.forEach(System.out::println); // 遍历

System.out.println("====2====");
// 5. sorted() -- 自然排序
list.stream()
.sorted((p1, p2) -> p1.getAge() - p2.getAge())
.forEach(System.out::println);
System.out.println("====3====");
// 6. map(Function f) -- 接收 Lambda,将元素转换成其他形式或提取信息。接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素
// 7. flatMap(Function f) -- 接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流
list.stream().map(Person::getName).forEach(System.out::println);

System.out.println("====4====");
// 8. parallel() -- 返回一个并行流(多线程)
list.parallelStream()
.forEach(System.out::println);

3.2.3 终止操作

  • forEach、min、max、count
  • reduce、collect
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
ArrayList<Person> list = new ArrayList<>();
list.add(new Person("张三", 18));
list.add(new Person("李四", 19));
list.add(new Person("王五", 20));
list.add(new Person("赵六", 21));

// 1. forEach(Consumer c) -- 内部迭代
list.stream()
.filter(person -> person.getAge() > 18) // 过滤掉年龄小于18的
.forEach(System.out::println);

// 2. min(Comparator c) -- 返回流中最小值
// 3. max(Comparator c) -- 返回流中最大值
// 4. count() -- 返回流中元素的总个数
System.out.println("=====min=====");
Optional<Person> min = list.stream()
.min((p1, p2) -> p1.getAge() - p2.getAge());
System.out.println(min.get());
System.out.println("=====max=====");
Optional<Person> max = list.stream()
.max((p1, p2) -> p1.getAge() - p2.getAge());
System.out.println(max.get());
System.out.println("=====count=====");
long count = list.stream()
.count();
System.out.println(count);
// 5. reduce() -- 可以将流中元素反复结合起来,得到一个值
// 6. collect() -- 将流转换为其他形式。接收一个 Collector 接口的实现,用于给 Stream 中元素做汇总的方法
System.out.println("=====reduce=====");
Optional<Integer> reduce = list.stream()
.map(Person::getAge)
.reduce(Integer::sum); // 求和
System.out.println(reduce.get());
System.out.println("=====collect=====");
list.stream()
.map(Person::getName)
.collect(Collectors.toList())
.forEach(System.out::println);

4. 新时间 API

  • 之前时间API存在问题:线程安全问题、设计混乱
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// 旧
public static void main(String[] args) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
ExecutorService pool = Executors.newFixedThreadPool(10);
Callable<Date> callable = new Callable<Date>() {
@Override
public Date call() throws Exception {
return sdf.parse("2018-12-12 12:12:12");
}
};
List<Future<Date>> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
Future<Date> future = pool.submit(callable);
list.add(future);
}
for (Future<Date> future : list) {
try {
System.out.println(future.get());
} catch (Exception e) {
e.printStackTrace();
}
}
pool.shutdown();
}

// 新
public static void main(String[] args) {
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
ExecutorService pool = Executors.newFixedThreadPool(10);
Callable<LocalTime> callable = new Callable<LocalTime>() {
@Override
public LocalTime call() throws Exception {
return LocalTime.parse("2018-12-12 12:12:12", dtf);
}
};
List<Future<LocalTime>> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
Future<LocalTime> future = pool.submit(callable);
list.add(future);
}
for (Future<LocalTime> future : list) {
try {
System.out.println(future.get());
} catch (Exception e) {
e.printStackTrace();
}
}
pool.shutdown();
}

4.1 本地化时间 API

  • LocalDate
  • LocalTime
  • LocalDateTime
1
2
3
4
5
6
7
8
9
10
11
// 1. 创建本地时间
LocalDateTime ldt = LocalDateTime.now();
// LocalDateTime ldt2=LocalDateTime.of(2018, 12, 12, 12, 12, 12);
System.out.println(ldt);
System.out.println(ldt.getYear());

// 2. 添加时间
LocalDateTime ldt2 = ldt.plusYears(2);
System.out.println(ldt2);
LocalDateTime ldt3 = ldt.minusMonths(2);
System.out.println(ldt3);

4.2 Instant: 时间戳 / ZoneId: 时区

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
        // 1. Instant
// 1.1 创建
Instant ins1 = Instant.now();
System.out.println(ins1.toString());
System.out.println(ins1.toEpochMilli());
// 1.2 添加/减少时间
Instant ins2 = ins1.plusSeconds(100);
System.out.println(ins2);
System.out.println(Duration.between(ins1, ins2).toMillis());

// 2. ZoneId
// 2.1 获取所有时区
// Set<String> set = ZoneId.getAvailableZoneIds();
// for (String string : set) {
// System.out.println(string);
// }
System.out.println(ZoneId.systemDefault());

4.3 Date、Instant、LocalDateTime的转换

1
2
3
4
5
6
7
8
9
10
11
12
// 1. Date -> Instant -> LocalDateTime
Date date = new Date();
Instant ins = date.toInstant();
System.out.println(ins);
LocalDateTime ldt = LocalDateTime.ofInstant(ins, ZoneId.systemDefault());
System.out.println(ldt);

// 2. LocalDateTime -> Instant -> Date
Instant ins2 = ldt.atZone(ZoneId.systemDefault()).toInstant();
Date date2 = Date.from(ins2);
System.out.println(date2);
System.out.println(date2);

4.4 DateTimeFormatter

1
2
3
4
5
6
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
// DateTimeFormatter dtf2 = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
// 1. 把时间格式化为字符串
System.out.println(dtf.format(LocalDateTime.now()));
// 2. 把字符串解析为时间
System.out.println(LocalDateTime.parse("2018-12-12 12:12:12", dtf));

十一、JVM探究

(17条消息) 狂神说Java-JVM入门(笔记合集)_狂神说jvm笔记pdf_fllow_wind的博客-CSDN博客

JVM探究

  • 请你谈谈对JVM的理解?java8虚拟机和之前的变化更新?
  • 什么是OOM,什么是栈溢出StackOverFlowError?怎么分析?
  • JVM的常用调优参数有哪些?
  • 内存快照如何抓取,怎么分析Dump文件?
  • 谈谈你对JVM中类加载器的认识?

1. JVM的位置

image-20230507225055535

  • 三种JVM
    • Sun公司:HotSpot(常用) 用的最多
    • BEA:JRockit
    • IBM:J9VM

2. JVM体系结构

image-20230507225537507

  • jvm调优:99%都是在方法区和堆,大部分时间调堆。

JNI(java native interface)本地方法接口

JVM框架图

3. 类加载器

作用:加载Class文件

  • 具体实例在堆里,引用变量名放栈里
  • 分类
    1. 虚拟机自带的加载器
    2. 启动类(根)加载器
    3. 扩展类加载器
    4. 应用程序(系统类)加载器

image-20230508224713276

  • 加载方式
    1. 类加载器收到类加载的请求
    2. 将这个请求向上委托给父类加载器去完成,一直向上委托,直到启动类加载器
    3. 启动加载器检查是否能够加载当前这个类,能加载就结束,使用当前的加载器,否则,抛出异常,通知子加载器进行加载
    4. 重复步骤3
  • 加载器的作用

4. 双亲委派机制

  • ==概念==:当某个类加载器需要加载某个.class文件时,它首先把这个任务委托给他的上级类加载器,递归这个操作,如果上级的类加载器没有加载,自己才会去加载这个类。

  • ==例子==:当一个Hello.class这样的文件要被加载时。不考虑我们自定义类加载器,首先会在AppClassLoader中检查是否加载过,如果有那就无需再加载了。如果没有,那么会拿到父加载器,然后调用父加载器的loadClass方法。父类中同理也会先检查自己是否已经加载过,如果没有再往上。注意这个类似递归的过程,直到到达Bootstrap classLoader之前,都是在检查是否加载过,并不会选择自己去加载。直到BootstrapClassLoader,已经没有父加载器了,这时候开始考虑自己是否能加载了,如果自己无法加载,会下沉到子加载器去加载,一直到最底层,如果没有任何加载器能加载,就会抛出ClassNotFoundException。

  • ==作用==:
    1. 防止重复加载同一个.class。通过委托去向上面问一问,加载过了,就不用再加载一遍。保证数据安全。
    2. 保证核心.class不能被篡改。通过委托方式,不会去篡改核心.class,即使篡改也不会去加载,即使加载也不会是同一个.class对象了。不同的加载器加载同一个.class也不是同一个Class对象。这样保证了Class执行安全。

5. 沙箱安全机制

5.1 简介

Java安全模型的核心就是ava沙箱(sandbox),什么是沙箱?沙箱是一个限制程序运行的环境。沙箱机制就是将Jva代码限定在虚拟机心VM)特定的运行范围中,并且严格限制代码对本地系统资源访问,通过这样的措施来保证对代码的有效隔离,防止对本地系统造成破坏。沙箱主要限制系统资源访问,那系统资源包括什么?CPU、内存、文件系统、网络。不同级别的沙箱对这些资源访问的限制也可以不一样。

所有的ava程序运行都可以指定沙箱,可以定制安全策略。

在va中将执行程序分成本地代码和远程代码两种,本地代码默认视为可信任的,而远程代码则被看作是不受信的。对于授信的本地代码,可以访问一切本地资源。而对于非授信的远程代码在早期的v实现中,安全依赖于沙箱(Sandbox)机制。如下图所示JDK1.0安全模型

image-20230508230130492

但如此严格的安全机制也给程序的功能扩展带来障碍,比如当用户希望远程代码访问本地系统的文件时候,无法实现。因此在后续的Jva1.1版本中,针对安全机制做了改进,增加了安全策略,允许用户指定代码对本地资源的访问权限。如下图所示JDK1.1安全模型

image-20230508230200216

在Jva1.2版本中,再次改进了安全机制,增加了代码签名。不论本地代码或是远程代码,都会按照用户的安全
策略设定,由类加载器加载到虚拟机中权限不同的运行空间,来实现差异化的代码执行权限控制。如下图所示JDK1.2安全模型

image-20230508230231986

当前最新的安全机制实现,则引入了域(Domain)的概念。虚拟机会把所有代码加载到不同的系统域和应用域系统域部分专门负责与关键资源进行交互,而各个应用域部分则通过系统域的部分代理来对各种需要的资源进行访问。虚拟机中不同的受保护域(Protected Domain),对应不一样的权限(Permission)。存在于不同域中的类文件就具有了当前域的全部权限,如下图所示最新的安全模型(dk1.6)

image-20230508230253462

5.2 组成沙箱的基本组件:

  • 字节码校验器(bytecode verifier):确保Java类文件.Class遵循Java语言规范。这样可以帮助Java程序实现内存保护。但并不是所有的类文件都会经过字节码校验,比如核心类。
  • 类装载器(class loader):其中类装载器在3个方面对Java沙箱起作用
    • 它防止恶意代码去干涉善意的代码;// 双亲委派机制
    • 它守护了被信任的类库边界;
    • 它将代码归入保护域,确定了代码可以进行哪些操作。//沙箱安全机制

虚拟机为不同的类加载器载入的类提供不同的命名空间,命名空间由一系列唯一的名称组成,每一个被装载的类将有一个名字,这个命名空间是由Java虚拟机为每一个类装载器维护的,它们互相之间甚至不可见。

类装载器采用的机制是双亲委派模式

  1. 从最内层JVM自带类加载器开始加载,外层恶意同名类得不到加载从而无法使用;
  2. 由于严格通过包来区分了访问域,外层恶意的类通过内置代码也无法获得权限访问到内层类,破坏代码就自然无法生效。
  • 存取控制器(access controller):存取控制器可以控制核心API对操作系统的存取权限,而这个控制的策略设定,可以由用户指定。
  • 安全管理器(security manager):是核心API和操作系统之间的主要接口。实现权限控制,比存取控制器优先级高。
  • 安全软件包(security package):java.security下的类和扩展包下的类,允许用户为自己的应用增加新的安全特性,包括:
    • 安全提供者
    • 消息摘要
    • 数字签名 keytools https(需要证书)
    • 加密
    • 鉴别

6. Native

1
private native void start0();

凡是带了native 关键字的,说明java的作用范围达不到了,得回去调用底层C语言的库!

凡是带了native 关键字的方法会进入本地方法栈,其它的是java栈

  • JNI:Java Native Interface(本地方法接口)

  • 作用

    • 扩展java的使用,融合不同的编程语言为java所用
      • 它在内存区城中专门开辟了块标记区城: Native Method Stack
  • Native Method Stack(本地方法栈)

    • 登记native 方法,在执行引擎(Execution Engine)执行的时候。通过JNI 加载本地方法库(Native Libraies)中的方法。
    • 在企业级应用中少见,与硬件有关应用:java程序驱动打印机,系统管理生产设备等,掌握即可
  • 其他调用方法:接口(Socket)

7. PC寄存器

  • 程序计数器: Program Counter Register:
  • 每个线程都有一个程序计数器,是线程私有的,就是一个指针, 指向方法区中的方法字节码 ( 用来存储指向下一条指令的地址, 也即将要执行的指令代码 ), 在执行引擎读取下一条指令,是一个非常小的内存空间,几乎可以忽略不计。

8. 方法区

  • Method Area方法区:
    • 方法区是被所有线程共享,所有字段和方法字节码,以及一些特殊方法,如构造函数,接口代码也在此定义,简单说,所有定义的方法的信息都保存在该区域,此区域属于共享区间;
  • ==静态变量、常量、类信息(构造方法、接口定义)、运行时的常量池存在方法区中,但是实例变量存在堆内存中,和方法区无关==
    • 如:static,final,Class(类模板), 常量池

9. 栈

  • 栈:数据结构

    • 程序=数据结构+算法:持续学习~
    • 程序=框架+业务逻辑:吃饭~
  • 栈:先进后出、后进先出:桶

    • 队列:先进先出 ( FIFO ):管
    • 喝多了吐就是栈,吃多了拉就是队列
    • 为什么main() 先执行,最后结束? (因为一开始mian()先压入栈)
  • 栈:栈内存,主管程序的运行,生命周期和线程同步;
    • 线程结束,栈内存也就是释放,对于栈来说,不存在垃圾回收问题
    • 一旦线程结束,栈就Over!
  • 栈存放:8大基本类型+对象引用+实例的方法
    • 栈运行原理:栈帧(局部变量表+操作数栈)每调用一个方法都有一个栈帧
    • 栈满了 main()无法结束,会抛出错误:栈溢出 StackOverflowError

10. 堆

10.1 堆的介绍

  • Heap:一个JVM只有一个堆内存,堆的大小是可以调节的。(类,方法,常量,变量~,保存所有引用类型的真实对象)

  • 三个区域:

    • 新生代(伊甸园区) Young/new
    • 老年代 old
    • 永久代 Perm
    • 堆的三个区域

    • GC垃圾回收,主要是在伊甸园区和养老区~

    • 假设内存满了,报错OOM,堆内存不够!OutOfMemoryError:Java heap space

    • 在JDK8以后,永久存储区改了个字(元空间)

10.2 三个区域详解

  • 新生区、老年区、永久区
    • 新生区(伊甸园+幸存者区*2)
      • 类诞生和成长甚至死亡的地方;
        伊甸园,所有对象都是在伊甸园区new出来的!
        幸存者区(0, 1),轻GC定期清理伊甸园,活下来的放入幸存者区,幸存者区满了之后重GC清理 伊甸园+幸存者区,活下来的放入养老区。都满了就报OOM。
        真理:经过研究,99%的对象都是临时对象!直接被清理了
    • 老年区: 新生区剩下来的,轻GC杀不死了。
    • 永久区:
      • 这个区域常驻内存,用来存放JDK自身携带的Class对象,Interface元数据,存储的是java运行时的一些环境或类信息,该区域不存在垃圾回收GC。关闭虚拟机就会释放这个内存。
      • jdk1.6之前:永久代,常量池在方法区
        jdk1.7:永久代,但是慢慢退化了(去永久代)常量池在堆中
        jdk1.8之后:无永久代,常量池在元空间
    • 1)一个启动类,加载了大量的第三方jar包。T2)omcat部署了太多的应用,大量动态生成的反射类,不断的被加载。 ==> 直到内存满,就会出现OOM。
  • 在这里插入图片描述

  • 方法区又称非堆(non-heap),本质还是堆,只是为了区分概念。

    元空间逻辑上存在,物理上并不存在。

  • ```java
    public static void main(String[] args) {

    //返回虚拟机试图使用的最大内存
    long max = Runtime.getRuntime().maxMemory(); //字节 1024*1024
    //返回jvm初始化的总内存
    long total = Runtime.getRuntime().totalMemory();
    
    System.out.println("max="+max+"字节\t"+(max/(double)1024/1024+"MB"));
    System.out.println("total="+total+"字节\t"+(total/(double)1024/1024+"MB"));
    /* 运行后:
    max=1866465280字节   1780.0MB
    total=126877696字节  121.0MB
     */
    //默认情况下,分配的总内存占电脑内存1/4 初始化1/64
    

    }

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49

    ### 10.3 OOM解决方法

    1. 尝试扩大堆内存,如果还报错,说明有死循环代码 或垃圾代码
    - Edit Configration>add VM option>输入:-Xms1024m -Xmx1024m -XX:+PrintGCDetails
    - ![在这里插入图片描述](https://s2.loli.net/2023/05/09/dkqnSE9Mcp3UiwB.png)
    2. 分析内存,看一下哪个地方有问题(专业工具)

    ### 10.4 Jprofiler

    - MAT,Jprofiler作用:

    - 分析Dump内存文件,快速定位内存泄漏;

    - 获得堆中的数据

    - 获得大的对象(大厂面试)

    - …

    - 测试:

    - ```java
    //-Xms 设置初始化内存分配大小 默认1/64
    //-Xmx 设置最大分配内存,默认1/4
    //-XX:+PrintGCDetails 打印GC垃圾回收信息
    //-XX:+HeapDumpOnOutOfMemoryError oom DUMP

    //-Xms1m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError
    public class Demo03 {

    byte[] array = new byte[1*1024*1024]; //1m

    public static void main(String[] args) {
    ArrayList<Demo03> list = new ArrayList<>();
    int count = 0;

    try {
    while (true){
    list.add(new Demo03()); //不停地把创建对象放进列表
    count = count + 1;
    }
    } catch (Exception e) {
    System.out.println("count: "+count);
    e.printStackTrace();
    }

    }
    }
    • 在这里插入图片描述

    • 在这里插入图片描述

11. GC(垃圾回收)

  • GC两种:轻GC,重GC (Full GC,全局GC)

11.1 GC算法

标记清除法,标记整理,复制算法,分代收集法。引用计数法。

  • 引用计数法:一般JVM不用,大型项目对象太多了

    • 在这里插入图片描述
  • 复制算法

    • -XX:MaxTenuringThreshold=15 设置进入老年代的存活次数条件
    • 特点:
      • 优点:没有内存的碎片,内存效率高
      • 缺点:浪费了内存空间(一个幸存区永远是空的);假设对象100%存活,复制成本很高。
    • 使用场景:对象存活度较低的时候,新生区~
    • 在这里插入图片描述
    • 在这里插入图片描述
  • 标记清除算法

    • 标记清除
      • 特点
        • 优点:不需要额外空间,优化了复制算法。
        • 缺点:两次扫描,严重浪费时间,会产生内存碎片。
      • 在这里插入图片描述
    • 标记压缩(标记整理) :再优化
      • 三部曲:标记–清除–压缩
      • 在这里插入图片描述
    • 标记清除压缩:再优化
      • 每标记清除几次就压缩一次,或者内存碎片积累到一定程度就压缩。

11.2 算法总结

  • 特点

    • 内存效率:复制算法>标记清除算法>标记压缩算法(时间复杂度)
  • 内存整齐度:复制算法=标记压缩算法>标记清除算法
  • 内存利用率:标记压缩算法=标记清除算法>复制算法
  • 没有最好的算法,只有合适的算法(GC也被称为分代收集算法)。

  • 年轻代:存活率低,用复制算法。

  • 老年代:存活率高,区域大,用标记-清除-压缩。

参考和研究:《深入理解Java虚拟机》