学习笔记 - JavaSE基础

文章目录


版权声明:第十一章网络编程为CSDN博主「ZaynFox」的原创文章,遵循CC 4.0 BY-SA版权协议,转载附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/allenfoxxxxx/article/details/90707505

一、环境搭建

1.1 Java语言的特点

  1. 简单性
  2. 安全性
  3. 健壮性
  4. 面向对象(封装、继承、多态)
  5. 跨平台(Jvm)
  6. 动态性(反射)
  7. 多线程

1.2 jdk、jre、jvm之间的关系

jdk是Java开发工具包, 由jre+java开发命令组成。
jre是Java开发环境, 由Jvm+JavaSE类库组成, jdk自带。
jvm是Java虚拟机, 写的代码在虚拟机中编译和运行。

注意: 计算机上要有系统对应版本的jvm, java程序才能运行, 要执行好一个Java程序, 有jre就可以运行。

1.3 程序的编译和运行过程

编译阶段: 通过Javac命令将编写的代码编程生成.class字节码文件(不是纯粹的二进制文件),  编译阶段只检查语法, 不做运算。

运行阶段: 通过Java命令启动JVM虚拟机, JVM虚拟机启动类加载器ClassLoader, ClassLoader在硬盘上找到目标字节码文件并将其装载到JVM中执行, 解释成二进制数据, 操作系统通过这个数据和底层平台进行交互。

1.4 注释

单行注释   // 注释内容
多行注释   /*  注释内容  */
文档注释   通过Javadoc命令生成一套网页形式的文档
  格式: /**
        *@注解1 注解内容1
        *@注解2 注解内容2
        */

常见注解
@author 类 标明开发该类的作者
@version 类 标明该类模块的版本
@see 类、属性、方法 参考转向,也就是相关主题
@param 方法 对方法中参数的说明
@return 方法 对方法返回值的说明
@exception 方法 对方法可能抛出的异常说明

语法:
javadoc -d 文档存放路径 -author…注解内容(可省) -encoding 编码方式 -charset 字符集 文件位置.java

1.5 环境变量的配置和作用

Java8配置
     JAVA_HOME                jdk的安装目录
     Path                     %JAVA_HOME%\bin;%JAVA_HOME%\jre\bin;
     ClassPath               .%JAVA_HOME%\lib;%JAVA_HOME%\lib\tools.jar
Java11配置
     JAVA_HOME                jdk的安装目录
     Path                     %JAVA_HOME%\bin

PATH变量的作用:      指定命令搜索路径, 目的是为了在任何路径下, 都能访问到jdk中bin目录下的Java指令。
CLASSPATH变量的作用: 指定类搜索路径,类加载器会在classpath下寻找所需类
JAVA_HOME:         指定jdk的目录

1.6 核心机制 - JVM

  • JVM是一个虚拟的计算机, 具有指令集并使用不同的存储区域, 负责执行指令, 管理数据、内存、寄存器等。
  • 对于不同的平台, 有不同的虚拟机。
  • 只有某平台提供了对于的java虚拟机, java程序才可在此平台运行。

二、基础语法

2.1 变量

保留字: Java后续版本可能会使用到的关键字, goto、const、

2.1.1 变量的基本概念

定义: 内存中存储数据最基本的单元
组成: 数据类型、变量名、字面量
作用: 在内存中保存数据
注意: 变量必须先声明再使用, 同一作用域变量名不可重复(作用域: 变量的作用范围, 定义变量的一对大括号内)。

2.1.2 变量的分类

局部变量:声明在方法体当中的变量,只在当前代码体中有效不能用于类的其他方法中。
成员变量(静态、实例):作为类的属性成为类的成员变量, 成员变量在整个类中都有效, 成员变量和局部变量的变量名可以相同, 此时成员变量会被隐藏起来。

2.1.3 局部变量和成员变量的区别

  1. 声明的位置不同。

  2. 作用范围不同。

  3. 成员变量有默认值,局部变量没有默认值(必须初始化)。

  4. 销毁时间不同,实例变量随着对象创建结束后销毁,局部变量随着方法结束后销毁,静态变量在方法区中保存一份。

  5. 在JVM中的内存位置不同,实例变量存放在堆内存中(new 对象才能用),局部变量存放在栈内存中,静态变量存储在方法区当中,类加载时候初始化。

    变量的默认值
      整数型      0
      浮点型      0.0
      布尔值      false
      字符型      \u0000(unicode编码)
    

2.1.4 使用变量的注意事项

语法: 数据类型 变量名 = 字面值;

  1. 数据类型的"类型"和字面值类型要一致。
  2. 在同一个作用域中,变量名不能重复。
  3. 变量可以先声明,后面再赋值,也可以在一行上同时声明多个。
  4. 变量可以反复访问和重新赋值。

2.2 标识符、关键字、字面量

2.2.1 标识符

  1. 定义:程序员可以自己决定起名的单词都是标识符。
    可以标识: 类名、方法名、接口名、变量名、常量名 …
  2. 命名规则:
    a.不能以数字开头。
    b.不能使用关键字。
    c.只能使用数字、字母、下划线、美元符号。
    d.严格限制大小写,对长度无限制。
  3. 命名规范
    a.都用英语单词,见名知意。
    b.类名 接口名:首字母大写,后面每个单词首字母大写(大驼峰)。
    c.方法名 变量名:首字母小写,后面每个单词首字母大写(小驼峰)。
    d.常量全部大写,多个单词间用_隔开(全大)。
    e.包名全部小写(全小)。

2.2.2 关键字 字面量

关键字: 具有特殊含义的代码,如public、private、class…
字面量: 变量的值

2.3 运算符

2.3.1 算术运算符

  +  -  *  /  %   ++  --

关于i++和++i的用法
    单独使用无任何区别
    当赋值给变量或输出到控制台时
           int i = 3;
           int k = i++;  此时k是i还没有自加时的值   k为3
           int k = ++i;  此时k是i自加完成后的值     k为4

2.3.2 关系运算符

 >=  <=   ==   <  >   !=     关系运算符的结果一定是布尔值

2.3.3 逻辑运算符

   &  逻辑与      |  逻辑或       
   && 短路与     ||  短路或      ! (布尔表达式)取反       ^ 逻辑异或(两边不同则为真) 

逻辑与和短路与的区别
     与运算要求两侧都为真结果才为真
     当左侧的式子结果是false, 对于逻辑与来说, 右侧的式子无论结果如何都会执行
     对于短路与来说, 当左侧的式子为false则短路, 不会执行右侧的式子

逻辑或和短路或的区别
    或运算其有有一侧为真则为真
    对于逻辑或来说, 两侧的式子都会执行
    对于短路或来说, 左侧的式子为true则短路, 不会在执行右侧的式子

2.3.4 三目运算符

 布尔条件?为true执行的表达式:为false执行的表达式;

note: 由三目运算符写的式子可以改成if…else格式, 而if…else格式的式字不一定能改写成三目。

2.3.5 赋值运算符

+=   -=   =    /=   *=   %=

2.3.6 +号连接运算

当变量和String用+号做运算时, 效果是变量转成String并和String拼接起来。

2.4 数据类型

作用: 指导JVM在程序执行时根据该变量的类型给变量赋给一定的空间大小。

2.4.1 数据类型的分类

2.4.1.1 基本数据类型

数据类型大类字节数
byte整数型1
short整数型2
int整数型4
long整数型8
float浮点型4
double浮点型8
boolean布尔型1
char字符型2

补充: 比特(bit)和字节(byte)
计算机的底层采用交流电. 只能存储0和1
一个0或一个1存储为一个bit位, 是计算机中最小的存储单位

1 byte = 8 bit
128 byte = 1024 bit 1字节 = 1024个比特位
1mb = 1024 kb
1gb = 1024 mb
1tb = 1024 gb
在这里插入图片描述

2.4.1.2 引用数据类型

String、接口、类、对象、数组、集合…

2.4.2 数据类型的使用规则

  1. short 和 char表示的种类一样,但是char可以表示更大的正整数,因为char非负数。
  2. byte、short、char可以直接赋值,前提是不超过int的范围,这是为了方便程序员,提高开发效率。
  3. 所有的整数型字面值默认被当做int处理,要将字面值化为long类型,后面加上大写的L。
  4. 所有的浮点型字面值默认被当做double处理,要将字面值化为float,后面加上大写的F。
  5. java中的数字字面值表示方法
    - 二进制 int i = 0b100;
    - 十进制 int i = 100;
    - 八进制 int i = 0100;
    - 十六进制 int i =0x100;
  6. 做财务类的软件,不要用double,用BigDecimal();方法。
  7. char可以输出一个汉字。

2.4.3 数据类型转换

  容量比较
      byte<char、short<int<long<float<double
         1. 从小容量变成大容量,叫做自由类型提升。
         2. 从大容量变成小容量,叫做强制类型转换。(损失精度, 截掉了一部分比特位   慎用)
          例如:  long x = 100;   //   int   ---->    long    自由类型转换
                    int y = (int)x     //   long  ----->  int   强制类型转换
         3. byte、short、char、混合运算,默认会化成int统一计算。
         4. 多种基本数据类型混合运算,默认化成容量最大的。
         5. 当基本数据类型和String类型使用"+"运算符连接起来的时候,会自动转化成String类型
         6. 浮点数据总是比整数值数据大。

2.5 流程控制

2.5.1 if语句

原理:当布尔表达式为true,执行if语句,为false,则执行else,如果有else if存在,if为false时按顺序依次执行else if,实在没有才会执行else语句。
四种结构:
     if...else...
     if...else if...
     if...else if...else...if
     if...else if...else
注意事项
     1. 有一条if语句执行,则后面的就不再执行。
     2. else分支可以保证有语句执行。
     3. if是可以嵌套的,但是要保证代码严格缩进。

2.5.2 switch语句

 原理: switch后面的小括号里面的参数和case进行匹配,直到所有的case都没有匹配再在执行default。
 语法格式
      switch(String或者int类型(short、byte、char也可以)的变量、枚举常量){   
          case ?: xxxx
                  break;
          case ?2: xxxx
                  break;
          default:xxxx
      }
  注意事项
    1.  case结束要有break,没有break就会发生"case穿透现象",例如case 1是输出10 , case 2是输出20 , case 1没有加break,最终的结果就是先输出10,然后输出20。
    2.  break执行或者default执行,都会让switch语句结束。
    3.  swtich(byte short char)也可以,因为这3种可以转化成int。
    4.  case可以合并写,例如:  case 1:case 2:case 3    xxxx   break;

2.5.3 for循环

 目的:实现代码复用,简化代码。
      语法:for(初始表达式;布尔表达式;更新表达式){
              循环体;
       }
 原理: 先进行初始表达式,然后执行循环体,再执行更新表达式,如果布尔表达式成立,则继续循环,不然就跳出循环。
 死循环for(;;)

2.5.4 while和do while循环

 语法:while(布尔表达式){循环体}
        do{循环体}while(布尔表达式);
 原理:while先进行布尔判断,true就循环,循环完再判断,直到为false或者在某个阶段使用break ..... 结束循环。
  note:    1.while是先判断再循环,do while是先循环再判断。               
           2.while可能一次都不执行,do while必定会执行一次。

2.5.5 控制循环的关键字

1.break  用来终止循环,可以单独作为语句使用,遵循就近原则,特殊使用:break for1;
2.continue 用来跳过循环,直接到下一次,特殊使用: continue for1;
3.return 直接终止方法,执行到这句语句方法结束。

2.6 方法

2.6.1 方法的基本概念和语法格式

方法:  类或对象的行为, 一段具有特定功能的代码片段, 目的是简化、复用代码。
基本格式: [修饰符列表] 返回值类型(数据类型1 变量1,数据类型2 变量2.....) 方法名 {
      方法体
      return 返回值;
}
注: 方法中不能再定义方法, 方法必须定义在类中

2.6.2 方法的返回值、参数

  • 当方法的返回值类型是void时, 不需要return 返回值。
  • return执行, 所在的方法执行结束。
  • 形参是局部变量,
  • Java里方法的参数传递方式只有一种:值传递。 即将实际参数值的副本传入方法内,而参数本身不受影响。
    形参是基本数据类型:将实参基本数据类型变量的“数据值”传递给形参
    形参是引用数据类型:将实参引用数据类型变量的“地址值”传递给形参
    在JDK5前 采用数组来传多个同一类型值 (String[] args)
    JDK5后 采用可变形参来传多个同类型值 (String …books)

2.6.3 方法的调用方式和调用流程

  • 静态方法调用

       类名.方法名(形参)
    
  • 实例方法调用

       new 对象实例().方法名(形参)
    
  • 调用方式

  1. 单独调用 直接调用
  2. 打印调用 在控制台中使用
  3. 赋值调用 赋值给常/变量
  • 调用流程
    方法(实参) -> 实参复制一份给对应方法的形参 -> 方法执行并返回结果给调用的方法

2.6.3 方法重载

  • 概念: 在同一个类中, 允许存在一个以上的同名方法, 只要他们的参数个数或参数类型不同即可。
  • 条件: 与返回值类型无关, 相同方法名,形参列表不同(变量的数量、类型、顺序)
  • 作用: 实现代码复用, 提高代码复用性

2.6.4 方法递归

概念: 一个方法体内调用它自身。
注意: 递归一定要向已知的方向递归, 否则这种递归就变成了无穷递归, 类似死循环, 易发生栈溢出。

三、面向对象

3.1 类和对象

3.1.1 什么是面向对象?

 面向对象就是把构成问题的事物分解成一个个对象,建立对象不是为了实现一个步骤,而是为了描述某个事物在解决问题中的行为
 类是面向对象中的一个很重要的概念,因为类是很多个具有相同属性和行为特征的对象所抽象出来的,对象是类的一个实例。
 类具有三个特性:封装、继承和多态
   - 封装:核心思想就是“隐藏细节”、“数据安全”,让对象的属性/方法不能被随意访问, 只提供符合开发者意愿的公有方法来访问这些数据和逻辑,保证了数据的安全和程序的稳定。
   - 继承:子类可以继承父类的属性和方法,并对其进行拓展。
   - 多态:同一种类型的对象执行同一个方法时可以表现出不同的行为特征。通过继承的上下转型、接口的回调以及方法的重写和重载可以实现多态。

3.1.2 类和对象的概念

 类是一组具有属性和行为的集合,也是一类事物的模版,使用类的属性和行为来模拟这类事物。
 对象是类的实例, 类是对象的模板

3.1.3 OOA OOD OOP

OOA  面向对象方法
OOD  面向对象设计
OOP  面向对象编程

3.1.4 类和对象的使用

 class 类{
         成员变量;
           ...
         成员方法;
             ...
  }
  
  类名(对象) 引用名 = new 对象名();
  引用名.属性;
  引用名.方法();
  ...
  
  类的访问机制  
        在一个类中的访问机制, 类中的方法可以直接访问类中的成员变量(static方法访问非static 编译无法通过)
        在不同类中的访问机制, 先要创建类的对象, 再用对象访问类中定义的成员。
  匿名对象(适用于只使用一次的对象)
      例如: new Person().showAge();

3.2 面向对象的三大特性

3.2.1 封装性

  • 使用private关键字修饰成员, 隐藏对象内部的复杂性,只对外公开简单的接口。便于外界调用,从而提高系统的可扩展性、可维护性。
  • 对外提供Get/Set方法来获取和修改(只适用实例变量)

3.2.2 继承性

3.2.2.1 继承注意事项
  1. 使用extends关键字 Class A extends B
  2. 子类继承父类, 构造方法、静态资源无法继承, 子类无法直接访问父类私有化的成员和方法, 必须自己定义或通过get/set间接访问父类的成员属性。
  3. 所有类默认继承了Object类
  4. Java不支持多继承, 可以间接继承
  5. 对父类的改动可能会影响到子类
  6. 凡是 is a关系的都可以用继承
3.2.2.2 方法重写机制

方法重写: 当父类同名方法无法完成子类业务需求, 就需要对继承过来的方法进行重写(只针对实例方法)

条件:

  1. 两个类有继承关系
  2. 相同方法名,形参列表
  3. 子类的返回值类型不能大于父类的返回值类型
  4. 重写后的方法不能比重写前抛出更多, 范围更大的异常
  5. 子类的访问权限不能小于父类的访问权限

3.2.3 多态性

前提: 两个类有继承关系, 方法重写机制、父引用指向子对象
概念: Java程序中定义的引用变量所指向的具体类型和通过该引用类型发出的方法在调用时不确定,该引用变量发出的方法到底调用哪个类的实现的方法,必须在程序运行期间才能决定,这就是多态。
实质: 父类的引用指向子类型的对象, 可以直接使用在抽象类和接口上。
Java引用变量有两个类型: 编译时类型和运行时类型, 编译时类型由该变量使用的类型决定(静态绑定),运行时类型由实际付给该变量的对象决定(动态绑定,编译看左,运行看右)

  upCasting (多态)  向上转型
        Animal a = new Cat();
  downCasting(调用子类特有, 父类没有的方法)  向下转型
        if(obj instanceOf Cat){
            Cat x = (Cat)obj
        }

作用: 降低程序耦合度, 提高代码的扩展和复用性

3.2.3.1 instanceOf

Instanceof 可在运行阶段动态判断父引用是否指向堆内存中的子类对象

3.2.3.2 虚拟方法调用

子类定义了与父类同名同参数的方法, 在多态下, 将父类的方法称为虚拟方法, 父类根据赋给它的不同子类对象,动态调用属于子类的该方法,这样的方法调用在编译期是无法确定的

3.3 权限修饰符

 public                    公开的,在任何位置都能访问到 
 private                   私有化的,只能在本类中访问
 protected               受保护的,只能在本类、子类、同包下访问
 default(缺省)          只能在本类和同包下访问

可以修饰: 成员(变量、方法)、类、接口…
类和接口只能使用default、public

3.4 构造器

构造方法的作用: 在创建对象时初始化对象的默认值

  • 注意点:
  1. 当没有写无参构造,系统会自动提供, 但是当你手动写了有参构造,系统就不会提供缺省构造器, 建议自己手动把构造器写全。
  2. 构造方法可以重载。
  3. 构造器默认的修饰符和类修饰符是一致的。
  • 语法:
    public 类名(){}
    public 类名(参1,参2){…} 注: 没有返回值, 方法名同类名, 创建对象时自动调用
  • 代码块和静态代码快
    { // 每次对象创建时执行
    Java代码
    }
    static { // 类加载时执行
    Java代码
    }

构造器重载演示
在这里插入图片描述

3.5 关键字

3.5.1 this

  • this是一个引用, 变量, 存储的是当前对象的内存地址指向自己

  • this()调用当前对象的无参数构造器, this(形参)调用有参构造器,必须出现在构造方法的第一行, 和super()之间只能存在一个

  • 目的是为了在实例方法、构造器中区分形式参数和对象的属性

注意点

  1. this在方法中使用表示当前对象的引用
  2. this在构造器中使用表示当前构造器初始化的对象
  3. 使用this访问属性和方法时,如果在本类中未找到,会从父类中查找

3.5.2 super

  • super是一个引用, 变量, 是父类内存空间的表示

  • 可以访问父类定义的属性, 调用父类中定义的成员方法, 在子类中调用父类的构造器

  • 当构造方法第一行没有this() 默认有super()

  • super()调用父类的无参数构造器,super(参1,参2)调用父类的构造器

  • 当子类和父类有重名的属性或者方法, 使用this和super进行区分

3.5.3 final

  • 表示最终的
  • final修饰的类无法被继承
  • final修饰的方法无法被重写
  • final修饰的变量只能赋值一次
  • final修饰的引用地址无法改变
  • final通常用来修饰常量
  • final和abstract冲突

3.5.4 static

  • 表示类、静态的

  • 节省堆空间, 类加载时初始化(早于对象的创建), 保存在方法区中的静态域

  • 静态方法中只能调用静态的属性和方法

3.5.5 import、package

3.5.5.1 package

package语句作为Java源文件的第一条语句,指明该文件中定义的类所在的包。(若缺省该语句,则指定为无名包)。
它的格式为:package 顶层包名.子包名 ;

  • 包对应于文件系统的目录,package语句中,用 “.” 来指明包(目录)的层次;
  • 包通常用小写单词标识。通常使用所在公司域名的倒置:com.atguigu.xxx
3.5.5.2 import

为使用定义在不同包中的Java类,需用import语句来引入指定包层次下所需要的类或全部类(.*)。import语句告诉编译器到哪里去寻找类。
语法格式:import 包名. 类名

  • import static组合的使用:调用指定类或接口下的静态的属性或方法

3.6 包装类

作用: 将8种基本数据类型转化为引用数据类型
在这里插入图片描述

3.6.1 基本数据类型和String还有包装类的转换

在这里插入图片描述

3.6.2 自动装箱/拆箱

jdk5的新特性
演示
在这里插入图片描述

  • 整数型常量池

    java为了提高程序执行效率,只要在-128到127之间的整数统统会放在方法区中的“整数型常量池”中,只要在这个范围的整数值就不会在new 包装类对象了。Integer类加载时候会初始化整数型常量池,256个对象。
    如果Integer类构造方法传的不是数字,则会出现NumberFormatException;

3.7 Object类

注意: String的toString和equals方法均被重写

3.7.1 toString方法

  • 用来输出一段字符串
  • 输出一个引用, 默认会调用toString方法
  • toString方法的默认实现是输出内存Hash地址
  • 可以把toString的返回值改成自己想要的字符内容

源代码:
在这里插入图片描述

3.7.2 equals方法

基本数据类型使用比较, 比较的是值。
引用数据类型使用
比较, 比较的是内存地址。

  • equals方法默认实现是比较对象的内存地址, 底层还是 this == obj 。
  • equals是否比较值要看自定义类是否重写了equals方法

equals重写
重写逻辑:

  1. 判断是否是空值 || 判断两个类是否一致
  2. 判断this和传入参内存地址是否相同
  3. instanceOf 转型 比较结果

演示: 比较Person类两个对象是否一致
在这里插入图片描述

3.7.3 其他方法

  • hashCode方法

    • 获取引用的内存地址, 16进制的哈希值

    • 重写equals方法最好把hashCode一起重写, 相同的对象应该具有相同的散列码

源代码
在这里插入图片描述

  • finalize方法(jdk9已经过时)
    • 在引用要销毁前执行一段代码

源代码
在这里插入图片描述

3.8 抽象类

概念: 类与类的共性抽象出来的类, 无法实例化对象
修饰: 类、方法
结构: 相比普通类可以写抽象方法, 必须提供构造器,
抽象方法: 被abstract 没有方法体的方法, 空实现。
特点

  • 包含抽象方法的类一定是抽象类
  • 子类重写父类中所有的抽象方法后才可实例化
  • 子类若没有重写完则子类也是一个抽象类
  • abstract和final冲突
  • 有匿名内部类的写法

3.9 接口

3.9.1 概念

Java接口是一系列方法的声明,是一些方法特征的集合,一个接口只有方法的特征没有方法的实现,因此这些方法可以在不同的地方被不同的类实现,而这些实现可以具有不同的行为(功能)。

3.9.2 结构

 interface 接口名
 {
     常量
     抽象方法
     静态方法
     默认方法(要用default显式声明)
 }

特点

  • 可以理解为是完全抽象的抽象类
  • 可以多继承
  • 实现类实现接口, 需要全部实现接口的抽象方法
  • 接口所有的变量的修饰符都是public static final
  • 接口中的大部分方法都是抽象方法, 自动被public abstract修饰
  • 接口可以继承接口
  • 接口内没有任何抽象方法的接口是标识接口
  • 接口中的静态方法要通过 接口.方法名()的方式调用。

接口和抽象类的异同

相同点

  1. 都不能被实例化
  2. 接口的实现类或抽象类的子类都只有实现了接口或抽象类中的方法后才能实例化。

不同点

  1. 接口只有定义,不能有方法的实现,java 1.8中可以定义default方法体,而抽象类可以有定义与实现,方法可在抽象类中实现。
  2. 实现接口的关键字为implements,继承抽象类的关键字为extends。一个类可以实现多个接口,但一个类只能继承一个抽象类。所以,使用接口可以间接地实现多重继承。
  3. 接口强调特定功能的实现,而抽象类强调所属关系
  4. 接口成员变量默认为public static final,必须赋初值,不能被修改;其所有的成员方法都是public、abstract的。抽象类中成员变量默认default,可在子类中被重新定义,也可被重新赋值;抽象方法被abstract修饰,不能被private、static、synchronized和native等修饰,必须以分号结尾,不带花括号。

3.10 内部类

3.10.1 分类

普通内部类、局部内部类、静态内部类、匿名内部类

3.10.2 内部类特点

  1. 内部类的访问权限可以声明为private或protected,可以调用外部类的结构。
    可以在内部定义属性、方法、构造器等结构。
  2. 内部类对象可以访问外部类对象中所有访问权限的字段,同时,外部类对象也可以通过内部类的对象引用来访问内部类中定义的所有访问权限的字段。
  3. 静态内部类也是作为一个外部类的静态成员而存在,创建一个类的静态内部类对象不需要依赖其外部类对象, 通过外部类.静态内部类创建静态内部类对象。
  4. static内部类无法访问外部非static的内容。
  5. 可以声明为abstract、final修饰
  6. 编译以后生成OuterClass$InnerClass.class字节码文件(也适用于局部内部类)。
  7. 非static的内部类无法声明static成员。
  8. 局部内部类可以访问外部所有访问权限字段, 但是外部无法访问局部内部类中的字段

普通内部类
在这种定义方式下,普通内部类对象依赖外部类对象而存在,即在创建一个普通内部类对象时首先需要创建其外部类对象,我们在创建下面代码中的InnerTest对象时先要创建OutClassTest01对象。
内部类对象可以访问外部类对象中所有访问权限的字段,同时,外部类对象也可以通过内部类的对象引用来访问内部类中定义的所有访问权限的字段
在这里插入图片描述
静态内部类举例
在这里插入图片描述

匿名内部类
在这里插入图片描述
可以是一个接口、抽象类、

特点

  1. 直接 new 一个接口,并实现这个接口声明的方法,在这个过程其实会创建一个匿名内部类实现这个接口,并重写接口声明的方法,然后再创建一个这个匿名内部类的对象并赋值给前面的类型的引用;

  2. new 一个已经存在的类 / 抽象类,并且选择性的实现这个类中的一个或者多个非 final 的方法,这个过程会创建一个匿名内部类对象继承对应的类 / 抽象类,并且重写对应的方法。

  3. 同样的,在匿名内部类中可以使用外部类的属性,但是外部类却不能使用匿名内部类中定义的属性,因为是匿名内部类,因此在外部类中无法获取这个类的类名,也就无法得到属性信息。

四、异常处理

4.1 异常的概念

4.1.1 基本概念

  • 在Java语言中,将程序执行中发生的不正常情况称为“异常”, 异常分为编译时异常和运行时异常, Java中提供了解决异常的机制
  • (开发过程中的语法错误和逻辑错误不是异常)

4.1.2 作用

提高程序的健壮性

4.1.3 注意点

  • 异常是以"类"的形式存在的, 所以可以实例化异常对象
  • JVM执行到有异常发生的代码时候会创建对应的异常对象抛出
  • JVM一旦发生Error,程序立马停止执行并退出JVM,错误无法处理。

4.2 异常的分类

  • Error:Java虚拟机无法解决的严重问题。如:JVM系统内部错误、资源耗尽等严重情况。比如:StackOverflowError和OOM。一般不编写针对性的代码进行处理。
  • Exception: 其它因编程错误或偶然的外在因素导致的一般性问题,可以使用针对性的代码进行处理。

4.2.1 运行时(非受检)异常

编译器不要求强制处理的异常, 一般指编程时的逻辑异常, 这类异常可以不处理

4.2.2 编译时(受检)异常

指编译器在编写代码阶段要求必须处置的异常, Java要求对编译时异常全部捕获对于这类异常, 如果不处理可能会出现意外

4.3 异常的继承体系

在这里插入图片描述

4.4 异常的处理方式

4.4.1 异常对象的生成

  • 由虚拟机自动生成:程序运行过程中,虚拟机检测到程序发生了问题,如果在当前代码中没有找到相应的处理程序,就会在后台自动创建一个对应异常类的实例对象并抛出——自动抛出
  • 由开发人员手动创建:Exception exception = new ClassCastException();——创建好的异常对象不抛出对程序没有任何影响,和创建一个普通对象一样

4.4.2 try catch finally…

   try{
        可能发生异常的代码
   }catch(异常1){
        处理异常的代码1
   }catch(异常2){
        处理异常的代码2
   } finally{
        最后一定会执行的代码
   }

4.4.3 throws与throw

throws
[修饰符列表] 返回值类型 方法名(形参) throws 异常
注: 把异常抛给调用者, 最后会抛给main, 如果main没有处理, 则报错
throw 异常对象
手动抛出异常对象

4.5 自定义异常

  1. 编写Java类继承Exception或者RuntimeException
  2. 提供构造器
    • 缺省构造器
    • (String message){super(message)}
    • (String message,Throwable t){super(message,t)}
    • (Throwable t){super(t)}
  3. 编写private static final long类型的serialVersionUID
  4. 在某些情况下通过throw手动抛出

4.6 finally和return

  • 首先一个不容易理解的事实:在 try块中即便有return,break,continue等改变执行流的语句,finally也会执行。
  • finally中的return 会覆盖 try 或者catch中的返回值。
  • finally中的return或异常会抑制(消灭)前面try或者catch块中的异常。

4.7 异常常见问题

在catch捕获异常时,为什么不考虑使用Throwable类型,而只是使用Exception来进行接收?

  • Throwable表示的范围要比Exception大。实际上程序使用Throwable来进行处理,没有任何语法问题,但是却会存在逻辑问题。因为此时出现的(或者说用户能够处理的)只有Exception类型,而如果使用Throwable接收,还会表示可以处理Error的错误,而用户是处理不了Error错误的,所以在开发中用户可以处理的异常都要求以Exception类为主。

Error与Exception的区别

  • Error(错误)是系统中的错误,程序员是不能改变的和处理的,是在程序编译时出现的错误,只能通过修改程序才能修正。一般是指与虚拟机相关的问题,如系统崩溃,虚拟机错误,内存空间不足,方法调用栈溢等。对于这类错误的导致的应用程序中断,仅靠程序本身无法恢复和和预防,遇到这样的错误,建议让程序终止。
  • Exception(异常)表示程序可以处理的异常,可以捕获且可能恢复。遇到这类异常,应该尽可能处理异常,使程序恢复运行,而不应该随意终止异常。

异常是一起处理好还是分开处理好?

根据实际的开发要求是否严格来决定。在实际的项目开发项目工作中,所有的异常是统一使用Exception处理还是分开处理,完全根据开发者的项目开发标准来决定。如果项目开发环境严谨,基本上要求针对每一种异常分别进行处理,并且要详细记录下异常产生的时间以及产生的位置,这样可以方便程序维护人员进行代码的维护。再次注意:处理多个异常时,捕获范围小的异常要放在捕获范围大的异常之前处理。

throw和throws的区别

throw和throws都是在异常处理中使用的关键字,区别如下:

throw:指的是在方法中人为抛出一个异常对象(这个异常对象可能是自己实例化或者抛出已存在的), 是一种行为。
throws:在方法的声明上使用,表示此方法在调用时如果发生声明异常就会将异常上抛给调用者去处理, throws表示的是一种倾向, 因为不一定会发生异常。

运行时异常和编译时异常的区别

  1. 定义不同,运行时异常都是RuntimeException类及其子类异常,如NullPointerException、IndexOutOfBoundsException等。一般异常是RuntimeException以外的异常,类型上都属于Exception类及其子类。
  2. 处理方法不同,运行时异常是不检查异常,程序中可以选择捕获处理,也可以不处理。对于一般异常,JAVA编译器强制要求用户必需对出现的这些异常进行catch并处理,否则程序就不能编译通过。
  3. 发生原因不同,运行时异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生。面对这种异常不管我们是否愿意,只能自己去写一大堆catch块去处理可能的异常。

五、数组

5.1 数组的概念

  • 数组是一种容器、引用数据类型,下标从0开始,可以存基本数据类型也可以存引用数据类型,
  • 数组只能存贮单一类型的数据。
  • 创建后长度不可变,所有的数组对象都有length属性。
  • 数组内存地址是下标为0对应的地址,数组的内存地址是连续的。
  • 数组存储在堆内存中一块连续的空间(大小状态相同)。

5.2 数组的优缺点

优点:检索效率很高
为什么?

  • 数组0号下标的内存地址已知,并且数组的内存地址连续、每块空间大小和类型都相同,查找的时候会根据下标通过数学表达式算出该下标内存地址直接定位,所以数组的检索效率是最高的。

缺点:

  1. 由于为了保证数组的内存地址连续,所以在数组上随机增加/删除元素时效率较低,因为随机增删会涉及到元素向前或者向后位移的操作。
  2. 数组不能存储极大数据,因为很难找到空间极大并且连续的空间。
  3. 对于数组最后一个元素的增删,对效率来说是没有影响的。

5.3 数组的初始化

第一种: 静态初始化
int[] array = {值1,值2,…} //省略写法
int[] array = new int[]{值1,值2,…} //完整写法
note:完整写法可以拆成2步,在某些情况下会用到

第二种: 动态初始化
int[] array = new int[length] //指定数组长度
note:第一种方法可以边声明边赋值,不限制长度,第二种限制了长度,但可以在后面赋值。

5.4 遍历数组

常用的方式: for计数循环、forEach循环、
在这里插入图片描述
补充:main方法中的String[] args数组
谁在调用main方法? - JVM
默认长度是多少? - 创建了数组对象,但是长度是0。
作用是什么? - 用来接收用户输入的参数。

5.5 数组扩容

数组扩容的思想: 新建一个大数组,将小数组内容拷贝进去,数组扩容效率低,在以后的开发中应该预计数据量,减少扩容的次数来提高效率。
System.arraycopy(源数组,源下标,新数组,新下标,拷贝长度);
Arrays.copyOf(原数组,新长度) 该方法返回新数组

5.6 数组工具类Arrays

Arrays.equals(数组1,数组2) // 比较两个数组是否相等
Arrays.toString(数组) // 输出数组信息
Arrays.fill(数组,填充值) // 给数组中插入填充值
Arrays.binarySearch(查找的数组,查找值)
Arrays.sort(数组) // 排序

5.7 数组常用算法

5.7.1 基本算法

5.7.1.1 最值、平均值、和

最值
在这里插入图片描述
平均值、和
在这里插入图片描述

5.7.1.2 数组反转、复制

复制
在这里插入图片描述
反转
在这里插入图片描述

5.7.2 排序与查询

5.7.2.1 冒泡排序

在这里插入图片描述

5.7.2.2 线性查询和二分法查询

二分法查询示例
在这里插入图片描述
线性查询

在这里插入图片描述

5.7.3 数组去重

第一种方式: 利用集合去重
在这里插入图片描述
在这里插入图片描述

第二种方式: 利用多层循环
在这里插入图片描述

5.8 多维数组

5.8.1 对象数组

5.8.2 二维数组

5.8.2.1 二维数组的初始化

静态初始化
int[][] arr = new int[3][3]{{1,2,3},{4,5,6},{7,8,9}}
动态初始化
int[][] arr = new int[3][3]

5.8.2.2 二维数组的遍历
 for(int i=0;i<array.length;i++){
        for(int j=0;j<array[i].length;j++){
             遍历内容
     }
 }

六、常用API

6.1 String、StringBuffer、StringBuilder

6.1.1 关于String

   - 是一种**引用数据类型**,使用**双引号括起来**的都是**String对象实例。**
   - String对象的字符内容底层存储在一个被final修饰的char[] value数组中。这说明String是不可变的字符数组/字符序列。
   - 实现了Serializable接口,这表示String是可以**被序列化**的,    实现Comparable接口,这表示String是**可比较**的。
   - String类的toString方法和equals方法均被重写。
   - 在字符串常量池(不会存储相同的字符串)中存贮。
   - 不可变性的体现
        (1) 当对字符串重新赋值时,需要重写指定内存区域赋值, 不能在原有内存空间基础上修改
        (2) 当进行拼接操作时, 需要重写指定内存区域赋值, 不能在原有内存空间基础上修改
        (3) 当替换操作时, 如上

6.1.2 结论

  • 常量与常量拼接结果在常量池中

  • 只要其中有一个是变量,结果一定在堆中

  • 拼接的结果使用intern方法一定在常量池中。

6.1.3 String的构造器

构造器作用
String()空对象
String(String x)在堆中创建了一个对象, 指向字符常量池
String(char[] c)把字符数组拼接起来
String(char[] c,index,count)拼接字符数组, 指定起始下标和长度
String(byte[] b)把byte数组编码成字符串
String(byte[] b,index,count)把byte数组编码成字符串,指定起始下标和长度

6.1.4 String的常用方法

方法名作用
charAt(int index)返回指定下标的字符
compareTo(String anotherString)比较字符串大小, 返回int
starts/endWith(String suffer)是否以这个字符串开头
equals/equalsIgnoreCase(String anotherString)比较字符串内容
getBytes()获取字符串对象编码后的结果
indexOf/lastIndexOf(String x)根据内容返回第一次/最后一次出现的下标
isEmpty()判断是否是空串
length()字符串长度
replace(old,new)替换字符串
split(String x)切割字符串
subString(int begin,int end)截取字符串
toCharArray()将字符串化为字符数组
toLowerCase/toUpperCase()全小写/全大写
trim()去除前后空白
valueof() 静态方法(print底层)将非字符串转成字符串
concat(String a)拼接
contains(char a)比较字符串

6.1.5 字符串常量池

字符串常量池的设计思想

  • 分配字符串, 和其他对象一样, 会消耗高昂的时间与空间代价, 作为最基本的引用数据类型, 大量频繁的创建字符串,
    会极大程度的影响程序的性能

  • JVM为了提高性能和减少内存的开销, 在实例字符串常量时做了一些优化
    - 为字符串开辟一个字符串常量池, 类似于缓冲区
    - 创建字符串常量时, 首先会检查该字符串常量是否存在池中
    - 存在这个字符串, 返回引用实例, 不存在,实例化该字符串并放入池中

  • 实现的基础
    - 字符串不可变, 可以不用担心数据冲突进行共享
    - 常量池中的这些常量不会被gc回收

图示
请添加图片描述
请添加图片描述

6.1.6 StringBuffer和StringBuilder

StringBuffer的构造器

  • StringBuffer()

  • StringBuffer(String s)

  • StringBuffer(int size)

    • StringBuffer代表可变的字符序列, 作为参数传递时, 方法内部可以改变值, 很多方法和String相同, 必须使用构造器的写法创建对象。

    • 底层是没有final修饰的长度为16的char数组,线程安全

    • 扩容量按照原容量的2倍+2

    • 构造器可以制定长度,以避免大量append造成的空间浪费

    • 常用方法
      append() delete(int s,int e) replace(int s,int e,String str() insert(int offset,xxx) reverse() setChat(int i,char c)

    • StringBuilder
      相比StringBuffer, 是线程不安全的, 效率最快

  • String、StringBuffer、StringBuilder三者的区别
    String 不可变的字符序列
    StringBuffer 可变的字符序列、效率低、线程安全
    StringBuilder 可变的字符序列、效率高、线程不安全

note: 作为方法参数传递, 方法内部String的值不会发生变化(不带返回值的情况), StringBuffer和StringBuilder会改变其值(带不带返回值都会改变)。

6.2 日期API

6.2.1 Util.Date类

  • System.currentTimeMillis() 记录从1970年1月1日0点0分0秒到现在的总毫秒数,可以用来统计时长。

构造器

  • Date()
  • Date(long date)

常用方法:getTime() toString()

6.2.2 SimpleDateFormat类

作用:用来格式化日期格式
请添加图片描述

格式化:
    SimpleDateFormat()  使用默认的模式和语言环境
    SimpleDateFromat(String pattern);  使用指定格式的模式
     - "yyyy-MM-dd hh:mm:ss"
    String format(Date date)  格式化Date对象
    解析: parse方法  这个方法会有一个异常需要抛或者捕捉
    Date e = sdf.parse(s1);

例:
在这里插入图片描述

  • 如何实现SQL.Date 和 Date的转化?

        Date a = new java.sql.Date(xxxx);
        java.sql.Date x = (java.sql.Date)a;
    

6.2.3 Util.Calendar(日历)类

Calendar是一个抽象基类,主用用于完成日期字段之间相互操作的功能。
获取Calendar实例的方法

  • 使用Calendar.getInstance()方法。
  • 调用它的子类GregorianCalendar的构造器。

一个Calendar的实例是系统时间的抽象表示,通过get(int field)方法来取得想
要的时间信息。比如YEAR、MONTH、DAY_OF_WEEK、HOUR_OF_DAY 、
MINUTE、SECOND

  • public void set(int field,int value)
  • public void add(int field,int amount)
  • public final Date getTime()
  • public final void setTime(Date date)

注意: 获取月份时,一月是0,二月是1,以此类推,12月是11
获取星期时:周日是1,周二是2 , 。。。。周六是7

6.2.4 JDK8后的新日期API

LocalDate、LocalTime、LocalDateTime 类是其中较重要的几个类,它们的实例
是不可变的对象,分别表示使用 ISO-8601日历系统的日期、时间、日期和时间。
它们提供了简单的本地日期或时间,并不包含当前的时间信息,也不包含与时区
相关的信息。

  • LocalDate代表IOS格式(yyyy-MM-dd)的日期,可以存储 生日、纪念日等日期
  • LocalTime表示一个时间,而不是日期。
  • LocalDateTime是用来表示日期和时间的,这是一个最常用的类之一。

注:ISO-8601日历系统是国际标准化组织制定的现代公民的日期和时间的表示
法,也就是公历。

LocalDate演示
请添加图片描述

LocalTime演示
请添加图片描述

LocalDateTime演示(可以通过LocalDate和LocalTime构造,可以调用with、plus、minus方法来修改LocalDateTime对象,但是修改后原对象的值不变,会返回一个新的对象)
请添加图片描述
请添加图片描述
请添加图片描述

格式化:DateTimeFormatter类,它的使用也非常简单,调用静态方法ofPattern生成指定匹配格式的实例;调用实例的format方法可以将TemporalAccessor对象转换成指定格式的日期时间字符串,实际上这个TemporalAccessor是1个接口,前面介绍的LocalTime/LocalDate/LocalDateTime都间接实现了这个接口;调用实例的parse方法可以将时间日期字符串转换为TemporalAccessor对象,进而得到对应的LocalTime/LocalDate/LocalDateTime对象

演示:

日期/时间差
计算日期/时间差:Duration、Period类,调用它们的静态方法between,传入2个日期或时间对象即可得到日期/时间差值
Duration:用于计算2个LocalTime对象的时间差
请添加图片描述

Period:用于计算2个LocalDate对象的日期差
请添加图片描述

6.3 Java比较器

6.3.1 自然排序 Comparable

实现Comparable接口的对象列表(和数组)可以通过 Collections.sort 或
Arrays.sort进行自动排序。实现此接口的对象可以用作有序映射中的键或有
序集合中的元素,无需指定比较器。

  1. Comparable接口强行对实现它的每个类的对象进行整体排序。

  2. 必须重写compareTo(Object obj)方法

    - this > obj    返回正整数       
    - this < obj    返回负整数
    - this == obj   返回零
    
  3. 自然排序的一致性
    当且仅当 e1.compareTo(e2) == 0 与e1.equals(e2) 具有相同的 boolean 值时,类 C 的自然排序才叫做与 equals一致。

演示

6.3.2 定制排序 Comparator

  • 当元素的类型没有实现java.lang.Comparable接口而又不方便修改代码,
    或者实现了java.lang.Comparable接口的排序规则不适合当前的操作,那 么可以考虑使用 Comparator的对象来排序,强行对多个对象进行整体排 序的比较。
  • 重写compare(Object o1,Object o2)方法,比较o1和o2的大小:如果方法返
    回正整数,则表示o1大于o2;如果返回0,表示相等;返回负整数,表示 o1小于o2。
  • 可以将 Comparator 传递给 sort 方法(如 Collections.sort 或 Arrays.sort),
    从而允许在排序顺序上实现精确控制, 还可以使用 Comparator 来控制某些数据结构(如有序
    set或有序映射)的顺序,或者为那些没有自然顺序的对象 collection 提供排序

演示

6.4 SystemAPI

  • System.gc 建议启动垃圾回收器

  • System.exit(0) 关闭JVM

  • System.out.print(); 控制台输出

  • System.currentMillis(); 获取自1970年1月1日0时0分到系统当前时间的总毫秒数

6.5 数学API

java.lang.Math提供了一系列静态方法用于科学计算。其方法的参数和返回
值类型一般为double型。
abs 绝对值
acos,asin,atan,cos,sin,tan 三角函数
sqrt 平方根
pow(double a,doble b) a的b次幂
log 自然对数
exp e为底指数
max(double a,double b)
min(double a,double b)
random() 返回0.0到1.0的随机数
long round(double a) double型数据a转换为long型(四舍五入)
toDegrees(double angrad) 弧度—>角度
toRadians(double angdeg) 角度—>弧度

6.6 BigInteger和BigDecimal类

6.6.1 BigInteger类

java.math包的BigInteger可以表示不可变的任意精度的整数。BigInteger 提供
所有 Java 的基本整数操作符的对应物,并提供 java.lang.Math 的所有相关方法。
另外,BigInteger 还提供以下运算:模算术、GCD 计算、质数测试、素数生成、
位操作以及一些其他操作。

  • 构造器

    BigInteger(String val):根据字符串构建BigInteger对象
    
  • 常用方法

    public BigInteger abs():返回此 BigInteger 的绝对值的 BigInteger。
    BigInteger add(BigInteger val) :返回其值为 (this + val) 的 BigInteger
    BigInteger subtract(BigInteger val) :返回其值为 (this - val) 的 BigInteger
    BigInteger multiply(BigInteger val) :返回其值为 (this * val) 的 BigInteger
    BigInteger divide(BigInteger val) :返回其值为 (this / val) 的 BigInteger。整数
    

相除只保留整数部分。
BigInteger remainder(BigInteger val) :返回其值为 (this % val) 的 BigInteger。
BigInteger[] divideAndRemainder(BigInteger val):返回包含 (this / val) 后跟
(this % val) 的两个 BigInteger 的数组。
BigInteger pow(int exponent) :返回其值为 (thisexponent) 的 BigInteger。

6.6.2 BigDecimal类

一般的Float类和Double类可以用来做科学计算或工程计算,但在商业计算中,
要求数字精度比较高,故用到java.math.BigDecimal类。
BigDecimal类支持不可变的、任意精度的有符号十进制定点数。

  • 构造器

     public BigDecimal(double val)
     public BigDecimal(String val) 
    
  • 常用方法

     public BigDecimal add(BigDecimal augend)
     public BigDecimal subtract(BigDecimal subtrahend)
     public BigDecimal multiply(BigDecimal multiplicand)
     public BigDecimal divide(BigDecimal divisor, int scale, int roundingMode)
    

6.7 枚举类Enum

枚举类的使用 enum

  • 当需要定义一"组"常量的时候使用,使用"enum"关键字声明
  • 对象的情况有限,能够列全。
    例如: 季节 春夏秋冬
    线程的状态 新建、就绪、运行、阻塞、死亡、
    员工状态 在岗 下岗 休假 出差…

自定义枚举类 父类是java.lang.enum类

  1. 私有化类的构造器,保证不能在类的外部创建其对象

  2. 在类的内部创建枚举类的实例。声明为:public static final

  3. 对象如果有实例变量,应该声明为private final,并在构造器中初始化

    JDK5之前
    class 枚举名{
    1.私有化实例变量
    2.私有化构造器
    3.私有化对象实例
    4.其他诉求(get/set、toString)
    }
    enum 枚举名{
    1.提供当前枚举类的常量
    2.声明私有化对象构造器
    3.其他诉求: toString、get/set?
    }
    2.enum的常用方法
    values() 返回枚举类对象数组,方便遍历枚举值。
    valueOf(String str) 返回枚举对象中名称为str参数的枚举对象(没有抛异常)
    toString() 返回当前枚举对象常量的名称
    3.enum枚举类实现接口
    (1) enum实现接口需要重写抽象方法。
    (2) 可以在对象中使用“匿名内部类”的形式重写抽象方法。 例: SPRING(“春天”,“春暖花开”){
    public void show(){
    sout(“春天真舒服!”);
    }
    };

七、多线程

7.1 线程与进程

7.1.1 线程与进程的定义

程序是为了完成特定功能的一组计算机指令的集合。
进程(资源分配的单位)是一个应用程序、是一个动态的过程。
线程(调度的单位)是应用程序执行场景/执行单元。
一个进程可以启动多个线程。
   note:1.每个线程的独立栈和程序计数器不共享,堆和方法区是共享的。
        2.java至少有两个线程并发,gc垃圾回收和主线程(异常处理线程)。
        3.进程A和进程B内存不共享。

7.1.2 多线程并发和并行

多线程并行:多个CPU同时执行多个任务,比如:多个人同时做不同的事
多线程并发:一个CPU(采用时间片)同时执行多个任务,比如秒杀平台,多个人做同件事

作用:提高程序执行效率,改善代码结构,提高用户体验。

7.1.3 判断是否是多线程

一条线程即为一条执行路径,即当能用一条路径画出来时即为一个线程

7.1.4 线程分类

java中的线程分为两类:1.守护线程(如垃圾回收线程,异常处理线程),2.用户线程(如主线程)

若JVM中都是守护线程,当前JVM将退出。

7.2 Thread类

7.2.1 构造器

  • Thread():创建新的Thread对象
  • Thread(String threadname):创建线程并指定线程实例名
  • Thread(Runnable target):指定创建线程的目标对象,它实现了Runnable接口中的run方法
  • Thread(Runnable target, String name):创建新的Thread对象

7.2.2 常用方法

API作用
(static)currentThread()返回当前线程
getName/setName()获得/修改线程名
start()/run()开启线程,调用run/线程代码
sleep(long 毫秒)休眠线程
(static)yield()线程让步
stop()过时方法,强制结束线程,不建议使用
isAlive()判断线程是否还活着
join()在线程a中调用线程b的join方法时,a就会阻塞,等待b结束

sleep注意点
- 让当前线程放弃抢夺时间片,休眠时间结束后重新排队抢夺。
- 抛出InterrupedException异常。
yield注意点
- 暂定当前线程,把抢夺时间片的机会给相同等级或者更高等级的线程。
- 如果队列中没有同等优先级的线程,此方法无效

7.3 实现线程的方式

7.3.1 继承Thread类

  1. 编写类,继承java.lang.Thread(线程体) 重写run方法,run方法中就是分支线程要执行的代码。
  2. 创建子类分支线程对象,调用start方法
  3. 可以使用匿名内部类的写法
    note: start方法的作用是:启动一个分支线程,在JVM中开辟一个新的栈,只要空间开出来,start方法就结束了,线程就启动完成,会自动调用run方法并且run方法在分支栈的栈底部。(run和main此时是平级的)

演示

7.3.2 实现Runnable接口

  1. 编写类,实现java.lang.Runnable接口,这个类不是线程类, 一个可以运行的类。
  2. 重写run方法
  3. 创建线程对象Thread,构造器中的参数是你写的实现类,调用start方法执行
  4. 可以使用匿名内部类的写法

演示

这两种方式的区别
开发中优先选择:使用Runnable接口的方式
为什么?

  1. Java不支持多继承,如何我写的这个类除了Thread类还有别的类要继承,就冲突了。而使用接口的方式可以完美解决这个问题。

  2. 实现的方式更加适合来处理多个线程中有共享数据的情况

    联系: public class Thread implements Runnable
    可以看到Thread类实现了Runnable接口。
    相同点:都要重写run方法和编写分支线程逻辑。

7.3.3 线程池创建线程(jdk5新增)

背景:经常创建和销毁,使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
思路:提前创建好多个线程放到线程池中,使用时调用,不用时放回线程池中,可以避免频繁的销毁线程。
好处:提高响应、降低资源消耗、便于管理线程。

corePoolSize:核心池的大小
maximumPoolSize:最大线程数
keepAliveTime:线程没有任务时最多保持多长时间后会终止
。。。。。。

JDK 5.0 起提供了线程池相关API:ExecutorService 和 Executors
ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor

void execute(Runnable coommand):执行任务/命令,没有返回值,一般用来执行Runnable
Futuresubmit(Callable task):执行任务,有返回值,一般又来执行Callable
void shutdown():关闭连接池。

ExecutorsAPI

API作用
Executors工具类,线程池的工厂类,用于创建并返回不同类型的线程池
Executors.newCachedThreadPool()创建一个可根据需要创建新线程的线程池
Executors.newFixedThreadPool(n)创建一个可重用固定线程数的线程池
Executors.newSingleThreadExecutor()创建一个只有一个线程的线程池
Executors.newScheduledThreadPool(n)创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。

使用步骤:

  1. 需要创建实现runnable或者callable接口方式的对象
  2. 创建executorservice线程池
  3. 将创建好的实现了runnable接口类的对象放入executorService对象的execute方法中执行。
  4. 关闭线程池

演示

7.3.4 实现callable接口(jdk5新增)

与使用runnable方式相比,callable功能更强大些
runnable重写的run方法不如callaalbe的call方法强大,call方法可以有返回值
方法可以抛出异常
支持泛型的返回值
需要借助FutureTask类,比如获取返回结果

callable实现新建线程的步骤:

  1. 创建一个实现callable的实现类
  2. 实现call方法,将此线程需要执行的操作声明在call()中
  3. 创建callable实现类的对象
  4. 将callable接口实现类的对象作为传递到FutureTask的构造器中,创建FutureTask的对象
  5. 将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start方法启动(通过FutureTask的对象调用方法get获取线程中的call的返回值

演示

7.4 线程的优先级和调度

方法:

  • setPriority(int newPriority): 修改线程的优先级
  • getPriority(): 获取线程的优先级

优先级常量

  • MAX_PRIORITY(10)
  • MIN_PRIORITY(1)
  • NORM_PRIORITY(5)

说明:

  1. 线程创建时继承父线程的优先级。
  2. 低优先级只是获得调度的概率低。

调度策略: 1. 时间片(线程的调度采用时间片轮转的方式) 2. 抢占式(高优先级的线程抢占CPU)

注意:

  1. 对于同优先级别的线程组成先进先出队列,使用时间片策略,对于高优先级,优先使用抢占式。
  2. 高优先级的线程要抢占低优先级的线程的cpu的执行权。但是仅是从概率上来说的,高优先级的线程更有可能被执行。并不意味着只有高优先级的线程执行完以后,低优先级的线程才执行。

7.5 线程的生命周期

新建: Thread类或其子类对象创建后
就绪: 被start(),但是还没有获得CPU时间片的状态
运行: 被start(), 获得了CPU时间片开始执行run方法中的功能的状态
阻塞: 在某种特殊情况下,被人为挂起或输入输出操作占用时,放弃了CPU时间片后进入这个状态
死亡: 线程的工作结束或者发生了强退、异常并没处理

在这里插入图片描述
sleep和wait的异同

相同点:一旦执行方法以后,都会使得当前的进程进入阻塞状态
不同点:

  1. 两个方法声明的位置不同,Thread类中声明sleep,Object类中声明wait。
  2. 调用的要求不同,sleep可以在任何需要的场景下调用,wait必须使用在同步代码块或者同步方法中
  3. 关于是否释放同步监视器,如果两个方法都使用在同步代码块或同步方法中,sleep不会释放,wait会释放

7.6 线程通信

notify()          唤醒被wait的一个线程,如果有多个线程被wait,优先唤醒优先级别高的。
notifyAll()       唤醒所有被wait的线程
wait()            让线程进入阻塞状态,释放锁进入等待池

说明:

  1. 这3个方法只能使用在同步代码块或者同步方法中
  2. 调用者必须是同步代码块或者同步方法中的同步监视器
    可能会出现的异常: IllegaLMonitorStateException
  3. 这3个方法都是Object类中的

7.7 同步线程

多个线程对同一个共享数据进行操作时,线程没来得及更新共享数据,从而导致另外线程没得到最新的数据,从而产生线程安全问题。

7.7.1 解决线程安全的方法

  1. 同步代码块
    synchronized(同步监视器){
    需要被同步的代码(需要操作共享数据的代码)
    }
    说明:
    1. 操作共享数据(多个线程操作的变量)的代码,即为需要被同步的代码。
    2. 同步监视器,俗称“锁”,任何一个类实例的对象,都能充当锁。
    3. 完成同样功能的多个线程必须要共用一把锁,且这个锁是“唯一的”。
    4. 在实现Runnable接口创建多线程的方式中,我们可以考虑this充当同步监视器。
    5. 在继承Thread类创建多线程的方式中,慎用this充当同步监视器。

why?
Runable天生共享锁,而Thread中需要用static对象或者this关键字或者当前类(window。class)来充当唯一锁

  1. 同步方法
    如果操作共享数据的代码完整的声明在一个方法中,那么这个方法就可以声明为“同步”的方法
    声明位置 public synchronized void run(){同步的代码}
    同步方法和代码块的具体实现参考代码“卖票案例”。

    对于runnable接口实现多线程,只需要将同步方法用synchronized修饰
    而对于继承自Thread方式,需要将同步方法用static和synchronized修饰,因为对象不唯一(锁不唯一)

总结:

  • 同步方法依然涉及到同步监视器,只是不需要我们显式的声明。

  • 非静态的同步方法 this 。

  • 静态的同步方法 当前类。

  1. Lock锁机制(JDK5后引入的新机制)
    通过显式定义同步锁对象来实现同步,同步锁使用LOCK对象充当
    1. 创建Lock锁对象
    2. 调用Lock()方法
    3. 调用unLock()方法

7.7.2 synchronized 和 Lock 的异同

同:两者都是用来解决线程安全问题的
异:synchronized在执行完相应的同步代码后,会自动释放同步监视器,而Lock需要手动的启动Lock(), 同时结束同步也需要手动调用unlock()方法。

7.7.3 synchronized 与 Lock的对比

  1. Lock是显式锁(需要手动开启和关闭),synchronized是隐式锁,出了作用域自动释放
  2. Lock只有代码块锁, 而synchronized有代码块锁和方法锁(同步代码块和同步方法)
  3. 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好,并具有更好的扩展性
    选择优先 : lock—>代码块—>方法

7.7.4 判断线程是否有安全问题, 并解决

1.先判断是否多线程
2.再判断是否有共享数据
3.是否并发的对共享数据进行操作
4.选择上述三种方法解决线程安全问题

7.8 死锁

不同的线程占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁,出现死锁后,不会出现异常和提示,只是所有的线程处于阻塞状态,无法继续运行。

解决:相应的算法、尽量避免嵌套同步、尽量减少同步资源的定义。

演示死锁

7.9 生产者与消费者

生产者(Priductor)将产品交给店员(Clerk),而消费者(Customer)从店员处取走产品,店员一次只能持有固定数量的产品(比如20个),如果生产者视图生产更多的产品,店员会叫生产者停一下,如果店中有空位放产品了再通知生产者继续生产:如果店中没有产品了,店员会告诉消费者等一下,如果店中有产品了再通知消费者来取走产品。

这里可能出现两个问题:
生产者比消费者快的时候,消费者会漏掉一些数据没有收到。
消费者比生产者快时,消费者会去相同的数据。

演示

八、集合与泛型

8.1 数组的缺陷

  1. 数组元素类型单一,只能存储单类型数据。
  2. 数组长度定死,虽然能扩容,但是不方便扩展,对效率有影响。
  3. 数组中提供的方法极其有限,难以进行CURD操作。
  4. 数组中的元素是有序可重复的,对于无序不重复的要求难以实现。

8.2 集合的概念

集合是一组数据的容器,例如数组,集合中保存的是多个对象引用。
(1) 集合中不能直接存储基本数据类型和java对象。
(2) 集合本身也是一个对象,也有自己的内存地址。
(3) 集合可以嵌套。
(4) 不同的集合底层对应不同的数据结构。

8.3 集合的继承体系

Collection集合
Collection: 继承了父接口Iterable的iterator方法, 这个方法可以用来获取迭代器来遍历Collection集合, 单列数据。

      - List(有序可重复,有索引,"动态"数组)
          ArrayList
          LinkedList
          Vector
      - Set(无序不重复)
          HashSet(底层实际new了HashMap)
          TreeSet(底层实际new了TreeMap)、
          LinkedHashSet(HashSet的子类)、

Map集合
Map(映射关系的键值对, 双列数据)

      HashMap(主要实现类):线程不安全,可以存Null的key和value。
        - LinkedHashMap:保证在遍历map元素时,可以按照添加的顺序实现遍历。(什么时候用? 频繁的遍历时候)
      TreeMap:保证按照添加的Key-value进行排序,实现排序遍历,底层使用(JDK8)红黑树。             
      HashTable:线程安全,不能存Null的key和value
        - Properties:常用来处理配置文件。key和value都是String

8.4 迭代Collection集合

步骤:

  1. 获取集合对象的迭代器对象Iterator
    Collection c = new Collection的子类();
  2. 通过以上获取的迭代器对象迭代集合
    Iterator it = c.lterator();
    while(it.hasNext()){
    Object obj = it.next();
    System.out.println(obj);
    }
  3. 迭代器对象iterator中的方法:
    - boolean hasNext() 如果仍有元素可以迭代,则返回true值;
    - Object Next() 返回迭代的下一个元素
    - remove() 删除元素
    note: 一直迭代最后没元素了还要迭代会有异常,NoSuchElemmentException。

注意事项:
1. 每次调用Iterator()方法都会获得一个迭代器对象,默认游标在集合第一个元素的外侧,不指向任何元素。
2. 集合的结构如果发生变化,迭代器要重新获取。

8.5 Collection常用方法

 增
     add(Object obj)/addAll(Collection c)        向集合中添加元素/集合
 删
     remove(E e)/removeAll                       删除指定元素(修改集合结构)/求集合差集(修改集合结构)
 改
     clear()                                     清空集合(修改集合结构)
     retainAll(Collection c)                     存在于集合A中但不存在于集合B中的元素移除
     toArray()                                   将集合转化成数组。
 查
     contains(Object obj)/containsAll            判断集合中是否有指定元素/判断集合B中的所有元素是否都在集合A中(A要包含B)
     isEmpty()                                   判断集合是否为空
 长度
     size()                                      获取集合中元素个数
 结论: Collection接口的实现类对象需要重写equals,以便使用remove和contains方法。

8.6 List常用方法

 增
     add(index,element)                          指定下标添加元素
     addAll(index,Collection c)                  指定下标添加集合c所有元素
 删
     remove(int index)                           删除索引值对应的元素并返回
改
     set(index, Object element)                  修改指定下标的内容并返回
     subList(int index,int toIndex)              返回两个下标之间的元素集合(注意 toIndex是个开区间)
 查
     get(int index)                              获取指定下标元素
     indexOf(Object o)                           获取指定对象第一次出现的索引,不存在返回-1
     lastIndexOf(Object o)                       获取指定对象最后一次出现的索引,不存在返回-1   

 结论: remove(index)与remove(Object obj)一定要小心

8.7 Set集合

 HashSet(底层是HashMap)  线程不安全,可以存Null值。
   - LinkedHashSet 遍历HashSet数据,可以按照添加的顺序遍历。
    note:1. LinkedHashSet 根据元素的 hashCode 值来决定元素的存储位置, 但它同时使用双向链表维护元素的次序,这使得元素看起来是以插入顺序保存的
         2. LinkedHashSet插入性能略低于 HashSet,
 TreeSet  可以按照添加对象的指定属性进行排序,查询速度比List快。
     注意: TreeSet只能存相同类数据, 所以就直接指定泛型。
           - 向TreeSet中添加数据,必须是相同类的对象。
           - 两种排序
              - 自然排序
                  实现Comparable接口,重写CompareTo(Object obj)方法。
                  重写步骤: 1.判断参数是否为空和!(参数 instance 该类)
                           2.转型,增加比较条件(两者相等 0  a>b  1  a<b  -1)
              - 定制排序
                  实现Comparator接口,重写compare(o1,o2)方法实现定制。
             注: TreeSet的构造器可以传这个引用,可以使用非匿名实现类的匿名内部类重写Compare方法

      note: 1.Set接口没有额外的方法,使用的都是List和Collection的方法。
            2.Set的无序不可重复性的理解
               - 无序: 不等于随机性,存进来的顺序不同于取出来的顺序。
               - 不可重复: 保证添加的元素按照equals判断时,不能返回true。
            3.要求:向Set集合中添加元素,元素所在类要重写equals方法和hashCode方法。
                note:为什么建议equals和hashCode同时重写,保持一致性?
                     答:以实现对象相等规则,相等的对象具有相同的散列码。

8.8 Map接口

8.8.1 Map的特点

Map中的key:无序、不可重复的,使用Set集合存储所有的key
Map中的value:无序、可重复的,使用Collection集合存储所有的value
键值对: key-value构成了一个Entry对象
Map中的Entry:无序不重复,使用Set存储所有的entry
     note:Key所在类要重写equals和hashCode方法

8.8.2 Map的常用方法

 增
     Object put(Object key,Object value)                  向map中添加键值对(所有key都是Set集合元素)
     void putAll(Map map);                                添加map中所有的键值对。
             note: put具有修改功能。 
 删
     Object remove(Object key)                            通过key删除键值对,返回value。
 改
     void clear();                                        清空map集合
 查
     Object get(Object key)                               通过key获取value
     boolean containsKey(Object key)                      判断map中是否有指定的key
     boolean containsValue(Object value)                  判断map中是否有指定的value   
     boolean isEmpty()                                    判断map集合是否为空                                  
 长度
     int size()                                           获取map集合中键值对的数量
 遍历
     Set<k> keySet()                                      获取map集合所有的key                        
     Collection<v> values()                               获取map集合中所有的value,返给一个collection
     Set<Map.Entey<K,V>> entrySet()                       将map集合转化为set集合
     //这里的Map.Entey是个静态内部类
         note: containsKey 和 containsValue底层都调了equals方法

8.9 迭代Map集合

 第一种: 获取所有Key  遍历value
    Set<Object obj> sets = map.keySet();
    for(Object obj:sets){
        obj  // key
        sets.get(obj) // value 
    }    

 第二种: 通过entrySet静态内部类分别获取Key value遍历
    Set<Map.entry<Object obj,Object obj2>> sets = map.entrySet();
    for(Map.entry<Object obj,Object obj2> map:sets){
        map.getKey 
        map.getValue
    }

8.10 哈希表数据结构

哈希表是一个数组和单向链表的结合体,能充分发挥两者的优点。
哈希表的底层是一个数组,存储Key,value 并存储下一个node的内存地址。
哈希值:Key调用HashCode方法的结果,用作数组的下标。

  • map.put(E key,V value)实现原理
    1.先把K,V封装到Node节点对象当中。
    2.底层会调用K的HashCode方法。
    3.通过哈希算法将算出来的哈希值转化成数组下标,下标位置上如果没有任何元素,就把node加进去。如果说下标对应的位置上有链表了,此时会拿着这个K和链表上每个节点的K进行equals比对,如果所有的节点比对都是false,那么这个新节点将会被添加到链表的末尾。如果其中有一个equals返回了true,那么这个节点的value会被覆盖。
  • map.get(Object key)实现原理
    1.先调用k的HashCode方法得出哈希值,通过哈希算法转化成数组下标。通过数组下标快速定位到某个位置上。
    2.如果这个位置上什么也没有,返回null值,如果这个位置上有单向链表,那么会拿着k和单向链表上的每个k进行equals比对,如果所有的比对均是false,返回null值,只要有一个节点比对成功,返回ture值,那么这个value值就是我们要找的value,get方法最终要返回到我们找到的value中。

8.11 泛型

8.11.2 泛型的概念

定义: 允许在定义类、接口时通过一个标识表示类中某个属性的类型或者是某个方法的返回值及参数类型。这个类型参数将在使用时(例如,继承或实现这个接口,用这个类型声明变量、创建对象时)确定(即传入实
际的类型参数,也称为类型实参)。让集合中存储单一元素类型的数据。

作用: 让集合中存储单一类型的数据, 避免大量的转型、提高安全性。
注意:

  1. 泛型只在编译阶段有效

  2. 泛型如果不指定,将被擦除,泛型对应的类型均按照Object处理,但不等价
    于Object。经验:泛型要使用一路都用。要不用,一路都不要

  3. jdk7特性 自动类型推断
    在这里插入图片描述

  4. 父类有泛型,子类可以选择保留泛型也可以选择指定泛型类型:
    子类不保留父类的泛型:按需实现

    • 没有类型 擦除
    • 具体类型
  5. 子类保留父类的泛型:泛型子类
    1. 全部保留
    2. 部分保留

结论:子类必须是“富二代”,子类除了指定或保留父类的泛型,还可以增加自
己的泛型

8.11.2 泛型的使用

8.11.2.1 泛型类

定义一个泛型类
在这里插入图片描述

请添加图片描述
注意:

  1. 定义的泛型类,就一定要传入泛型类型实参么?并不是这样,在使用泛型的时候如果传入泛型实参,则会根据传入的泛型实参做相应的限制,此时泛型才会起到本应起到的限制作用。如果不传入泛型类型实参的话,在泛型类中使用泛型的方法或成员变量定义的类型可以为任何的类型。
    如图所示

  2. 泛型的类型参数只能是类类型,不能是基本数据类型。

  3. 不能对确切的泛型类型使用instanceof操作。如下面的操作是非法的,编译时会出错。
    if(ex_num instanceof Generic){}

  4. 定义泛型类时可以声明多个类型
    定义泛型类时可以声明泛型数组

8.11.2.2 泛型接口

泛型接口与泛型类的定义及使用基本相同。泛型接口常被用在各种类的生产器中

8.11.2.3 泛型方法

方法,也可以被泛型化,不管此时定义在其中的类是不是泛型类。在泛型方法中可以定义泛型参数,此时,参数的类型就是传入数据的类型。

泛型方法的格式:
[访问权限] <泛型> 返回类型 方法名([泛型标识 参数名称]) 抛出的异常

8.11.2.4 泛型在继承上的体现

如果B是A的一个子类型(子类或者子接口),而G是具有泛型声明的
类或接口,GB并不是GA的子类型!

8.11.2.5 通配符

  1. 使用类型通配符:?
    比如:List<?> ,Map<?,?>
    List<?>是List、List等各种泛型List的父类。
  2. 读取List<?>的对象list中的元素时,永远是安全的,因为不管list的真实类型
    是什么,它包含的都是Object。
  3. 写入list中的元素时,不行。因为我们不知道c的元素类型,我们不能向其中
    添加对象。
    唯一的例外是null,它是所有类型的成员
  4. 另一方面,我们可以调用get()方法并使用其返回值。返回值是一个未知的
    类型,但是我们知道,它总是一个Object。

注意点
在这里插入图片描述

通配符的上下限
在这里插入图片描述

8.12 Collection的工具类

Collections中有一系列的静态方法可以对集合元素操作。
排序操作

API作用
reverse(List)反转List集合中元素的顺序
shuffLe(List)使List集合随机排序
sort(List)使元素的自然排序对指定List元素升序排列
sort(List,Comparator)按Comparator中指定方法对List集合进行排序
swap(List,int i,int j)将指定List集合中的i和j互换位置
max(Collection)集合最大值
max(Collection,Comparator)设计比较器, 集合最大值
min(Collection)集合最小值
min(Collection,Comparator)设计比较器, 集合最小值
int frequency(Collection,Object)返回指定集合中指定元素出现的次数
void copy(List dest,List src)将src中的内容复制到dest中
boolean replaceAll(List list,Object oldVal,Object newVal)使用新值替换List中的旧值。
 note: Collections中提供了多个线程同步方法,可以将集合包装为线程同步的集合,从而解决多线程并发访问集合的线程安全问题。

8.13 集合面试点

HashSet是如何保证不可重复性的?(重点)

 HashSet的底层实际上是一个数组+链表的结构,也就是哈希表,向HashSet中添加元素a, 会调用a类的HashCode()方法计算哈希值,然后这个哈希值会通过某种算法算出在底层数组的索引位置,
  - 如果在这个位置上没有元素   添加成功
  - 如果有元素b(可能是链表式) 比较a与b的Hash值
      - 如果hash值不同, 添加成功
      - 如果hash值相同, 进行a类的equals比较(链表要比较多次),直到false成功,添加成功,如果全是true,添加失败
对于以链表形式存储元素的方式
    Jdk7: 元素a在数组中,原来的元素指向a
    Jdk8: 原来的元素在数组中, a指向原来的元素

ArrayList、LinkedList、Vector有什么区别? 如何选用?

相同点:ArrayList和Vector的底层都是Object[] 数组, 第一次增加元素时初始化容量(JDK8时)为10
ArrayList和LinkedList都是非线程安全的,效率高

不同点:Vector是线程安全的,效率低。
ArrayList的扩容量是原容量的1.5倍,Vector是原容量的2倍。

如何选用?
当我们需要频繁的增删元素时, 推荐使用LinkedList。
为什么? 增删元素涉及到元素的位移,数组的内存地址连续,这个过程中会涉及到内存地址的变化,数组效率会比较低, 而双向链表LinkedList会避免这个问题。

当我们需要频繁的检索数据时, 使用ArrayList。
为什么? 数组的内存地址连续,并且数组的0号下标已知,检索数据时底层会通过一个数学表达式直接定位索引,所以数组的检索效率最高。

九、IO流

9.1 File类

9.1.1 File类的基本介绍

  1. java.io.File类:文件和文件目录的抽象表现形式,与文件本身无关。
  2. File可以新建、删除、重命名文件和文件目录,但File不能访问文件本身。
  3. 想要在java程序中表示一个真实存在的文件或目录,必须有一个File对象,但是File对象不一定有一个真实的路径。
  4. File可以作为参数传递给IO的构造器。

9.1.2 File类的构造器

   public File(String pathName)  // 直接指定
   public File(String fatherPath,sonFile)  // 现指定父路径  子路径可以是目录也可以是文件
   public File(File f,StringSonPath) // 指定父路径 再指定子路径
   & 路径分隔符(根据操作系统的不同, 动态的分配分隔符)
   File.separator  这个常量可以根据当前的操作系统提供合适的分隔符。

9.1.3 File类常用方法

   getAbsolutePath()            获取绝对路径
   getPath()                    获取路径  // 如果用的是相对路径就显示相对路径, 反之
   getName()                    获取文件名
   getParent()                  获取上级路径
   Length()                     获取字节长度
   LastModified()               获取最后一次修改的时间 毫秒
   List()                       获取当前目录(File最后必须是个目录)下所有文件名称化成数组
   listFiles()                  获取当前目录下所有文件化成文件数组
   renameTo(File dest)          重命名并移动方法
       注意: renameTo如果要返回true   File1存在  File2不存在

9.1.4 File类的判断功能

注意: 对于不存在的文件, 下面的方法都是返回false

   isDirectory()                 判断是否是文件目录
   isFile()                      判断是否是文件
   exists()                      判断是否存在
   canRead()                     判断是否可读
   canWrite()                    判断是否可写
   isHidden()                    判断是否隐藏

9.1.5 File的创建与删除功能

   createNewFiles()     创建文件.如果文件存在,则不创建,返回false
   mkdir()              创建文件目录,如果文件目录存在,不创建,如果此文件目录的上层为空,也不创建。
   mkdirs()             创建文件目录,如果上层不存在一并创建。    
   delete()             删除
     注意: Java中的delete不走回收站, 如果要删目录,需要把这个目录下的文件和子目录删除干净!

请添加图片描述

9.2 IO流的分类

  1. 什么是IO流?

       I:input  输入流
       O:output  输出流
    

    作用:通过IO流完成内存和硬盘之间的读写,实现数据传输、网络通讯等功能。

  2. 按照数据流的流向不同可分为:输入、输出流。
    按照操作数据单位不同可分为:字节、字符流。(字节流是 8 位通用字节流,字符流是16位Unicode字符流)
    按照流的角色的不同可分为:节点、处理流。

  3. 最大的4个基类
    java.io.InputStream 字节输入流
    java.io.OutputStream 字节输出流
    java.io.Reader 字符输入流
    java.io.Writer 字符输出流

    note:

      1.只要类名以Stream结尾的都是字节流,以Reader/Writer结尾的都是字符流。
      2.所有流都实现了Closeable接口,都有close方法,使用完了一定要及时关闭,避免资源的浪费。
      3.所有的输出流都实现了flashable接口,都有flush方法,用完输出流的时候,一定要记得flash刷新一下,如果没有使用flush,有可能会在后面的操作中丢失数据。
    

9.3 常见流

常用流(流不只这些)

     文件流:
        FileInputStream
        FileOutputStream
        FileReader
        FileWriter
     转换流(将字节流转换成字符流):
        InputStreamWriter
        InputStreamReader
     缓冲流(处理流的一种):
        BufferedInputStream
        BufferedOutputStream
        BufferedReader
        BufferedWriter
     数据流:
        DataInputStream
        DataOutputStream
     对象流:
        ObjectInputStream
        ObjectOutputStream
     打印流:
        PrintStream
        PrintWriter

9.4 FileInputStream与FileReader

注:

  1. 两者的代码套路基本相同,不同的是字节流使用byte数组,字符流使用char数组。
  2. 套路:造File,造流,造数组,循环,排异常,关流。
  3. 字节流可以用于任意类型的文件,而字符流只能用于普通文本(txt,c,cpp,java…)。
  4. read方法(排异常)
    read() 读完了返回-1;
    read(char[] c)
    read(byte[] b)
    read(Array,index,length)
  5. 程序中打开的文件IO资源不属于内存,所以要显式关闭IO流。
  6. Reader基类的构造器
    new Reader(InputStreamReader isr)
    new Reader(InputStreamReader isr,String encoding)

代码演示
FilInputStream
Reader

9.5 FileOutputStream与FileWriter

注:

  1. write方法(排异常)的重载基本同read,不同的是write拥有(int b)这个特殊的方法。
  2. 关流前就要刷新。
  3. readLine != null(使用了缓冲流)
  4. FileWriter(File file,boolean a) a为true追加 a为false覆盖
  5. 读取文本文件用reader, 读取别的用Fileinput
  6. Writer基类的构造器
    new Writer(OutputStreamWriter osw)
    new Writer(OutputStreamWriter osw,String encoding)

FileInput和FileOutput实现文件复制演示
FileReader和FileWriter实现文件复制演示

9.6 缓冲流Buffered

特点: 减少访问磁盘的次数提高了文件读取性能,带缓冲区的输出流,提高了文件的写入效率 实现了装饰设计模式!

BufferedInputStream
构造器:

  1. BufferInputStream(InputStream in)// 创建一个 BufferedInputStream 并保存其参数,即输入流 in,以便将来使用。创建一个内部缓冲区数组并将其存储在 buf 中,该buf的大小默认为8192。
  2. BufferedInputStream(InputStream in, int size) //创建具有指定缓冲区大小的BufferedInputStream 并保存其参数,即输入流 in,以便将来使用。创建一个长度为 size 的内部缓冲区数组并将其存储在 buf 中。

BufferedOutputStream
构造器:

  1. BufferOutputStream(OutputStream outs) // 创建一个 BufferedInputStream 并保存其参数,即输出流outs,将数据写入指定的基本输入流中。

  2. BufferedOutputStream(OutputStream outs, int size) //创建具有指定缓冲区大小的BufferedOutputStream ,即输出流outs, 将数据写入指定的基本输入流中 。

BufferedReader
BufferedWriter
与上面的基本一致。

注意:

  • 关闭流关闭外层,外层关闭内层流自动关闭。
  • 构造器传文件流引用。
  • flush会刷新缓冲区,由于底层已经有了,所以不用显式的声明。
  • 缓冲流内部提供了一个缓冲区,会把文件的数据缓冲到缓冲区中,然后从缓冲区中读/写。
    请添加图片描述
  • 为什么要用BufferedInputStream 和 BufferedOutputStream?
    优点:效率高
    缺点:内存占用多
    why?
    不带缓冲的操作,每读一个字节就要写入一个字节,由于涉及磁盘的IO操作相比内存的操作要慢很多,所以不带缓冲的流效率很低。
    带缓冲的流,可以一次读很多字节,但不向磁盘中写入,只是先放到内存里。等凑够了缓冲区大小的时候一次性写入磁盘,这种方式可以减少磁盘操作次数,速度就会提高很多。

9.7 转换流

作用:从字节流转换到字符流
InputStreamReader
OutputStreamWriter
构造器:

  • InputStreamReader(InputStreamReader);
  • InputStreamReader(InputStreamReader,StringcharsetName);
  • InputStreamReader(InputStreamReader,字符编码);
  • OutputStreamWriter(OutputStream os);
  • OutputStreamWriter(OutputStream os,String charsetName);
    请添加图片描述
    转换流演示

9.8 其他流

9.8.1 对象流

9.8.1.1 对象流的作用

存储和读取基本数据类型或对象的处理流,他的强大之处就是可以把java中的对象写入到数据源中(序列化),也可以把对象从数据源中还原回来(反序列化)。

9.8.1.2 序列化ObjectOutputStream
  • 用ObjectOutputStream保存基本数据类型或对象的机制
  • 可将任何实现Serializable接口的对象转化为字节数据,使其在保存和传输时可被还原。
  • 方法: WriteInt、WriteDouble…
9.8.1.3 反序列化ObjectInputStream
  • 用ObjectInputStream读取基本数据类型或对象的机制
  • 原理:对象序列化机制允许把内存中的java对象转换成平台无关的二进制流,从而允许这种二进制流保存在磁盘中,或者通过网络把这个流传输到另一个网络节点中,当其他程序获取这种二进制流,就可以恢复为原来的java对象。
  • 方法: ReadInt、ReadDouble…

演示将对象进行序列化和反序列化

9.8.1.4 自定义序列化和反序列化步骤和演示

实现类要满足如下的需求,方可序列化

  1. 需要实现接口 Serializable

  2. 当前类需要提供一个全局常量: serialVersionUID
    note: serialVersionUID需要显式声明.

  3. 除了当前实现类,类中所有的属性必须都是可以序列化的。

  4. ObjectOutputStream和ObjectInputStream不能序列化static和transient修饰的成员变量。

    注意: Java的序列化机制是通过在运行时判断类的serialVersionUID来验证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地相应实体类的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常。(InvalidCastException)

演示

9.8.2 打印流

PrintStream与PrintWriter
构造器

  • new PrintXXX(String name)
  • new PrintXXX(String name,String encoding)
  • new PrintXXX(File file)
  • new PrintXXX(File file,String encoding)

注意:

  • 字节打印流(PrintStream)和字符打印流(PrintWriter)。打印流提供了非常方便的打印功能,可以打印任何的数据类型,如小数、整数、字符串等
  • PrintStream 是 OutputStream 的子类
  • 在以后程序进行(非二进制数据)内容输出的时候,优先考虑使用打印流完成,如果有中文建议使用PrintWriter
  • OutputStream只适合于输出二进制数据,如果是明文的数据一定要使用打印流完成。
  • 打印流采用的是装饰设计模式
  • 对于PrintWriter来说,当启用字段刷新之后, 调用println或者printf或者format方法,便会立马刷新操作(自动刷新).
    如果没有开启自动刷新,则需要手动刷新或者当缓冲区满的时候,再自动刷新.(或直接调用close()方法,会自动刷新)

演示: 给hello.txt文件写点文本内容

9.8.3 数据流

9.8.2.1 标准输入输出流
  • java中的System类中的字段:in、out
  • 它们代表了系统标准的输入和输出设备。 默认的输入设备是键盘。输出设备是显示器。 System.in的类型是InputStream
  • System.out的类型是PrintStream OutputStream的子类是FilterOutputStream。

修改流向

  • public static void setIn(InputStream in)
  • public static void setOut(PrintStream out)

System.in演示

System.out演示

9.8.2.2 数据输入流DataInputStream

数据 输入流 允许应用程序以与机器无关方式从底层输入流中读取基本 Java 数据类型。应用程序可以使用数据输出流写入稍后由数据输入流读取的数据。 DataInputStream 对于 多线程 访问不一定是安全的。 线程安全 是可选的,它由此类方法的使用者负责。

构造器:
DataInputStream(InputStream in)

9.8.2.3 数据输出流DataOutputStream

构造器:
DataOutputStream( OutputStream outs);

注意: 使用DateOutputStream写的数据的顺序和在读时的顺序是要一致的。
代码演示

9.8.4 随机存取流RandomAccessFile

 RandomAccessFile 声明在java.io包下,但直接继承于java.lang.Object类。并且它实现了DataInput、DataOutput这两个接口,也就意味着这个类既可以读也可以写。

RandomAccessFile 类支持 “随机访问” 的方式,程序可以直接跳到文件的任意
地方来读、写文件

  • 支持只访问文件的部分内容

  • 可以向已存在的文件后追加内容

RandomAccessFile 对象包含一个记录指针,用以标示当前读写处的位置。
RandomAccessFile 类对象可以自由移动记录指针:

  • long getFilePointer():获取文件记录指针的当前位置
  • void seek(long pos):将文件记录指针定位到 pos 位置

构造器:

  • public RandomAccessFile(File file, String mode)
  • public RandomAccessFile(String name, String mode)

mode参数

  • r: 以只读方式打开
  • rw:打开以便读取和写入
  • rwd:打开以便读取和写入;同步文件内容的更新
  • rws:打开以便读取和写入;同步文件内容和元数据的更新

note: 如果模式为只读r。则不会创建文件,而是会去读取一个已经存在的文件,
如果读取的文件不存在则会出现异常。 如果模式为rw读写。如果文件不
存在则会去创建文件,如果存在则不会创建。

9.9 IO小结

流是用来处理数据的。

  • 处理数据时,一定要先明确数据源,与数据目的地
  • 数据源可以是文件,可以是键盘, 数据目的地可以是文件、显示器或者其他设备。
  • 而流只是在帮助数据进行传输,并对传输的数据进行处理,比如过滤处理、 转换处理等。

十、反射与注解

10.1 注解(Annotation)

10.1.1 什么是注解?

代码里的特殊标记, 这些标记可以在编译, 类加载, 运行时被读取, 并执行相应的处理。
注解可以像修饰符一样被使用, 可用于修饰包,类, 构造器, 方
法, 成员变量, 参数, 局部变量的声明, 这些信息被保存在 Annotation
的 “name=value” 对中。

10.1.2 自定义注解

  • 定义新的注解需要使用@interface关键字
  • 自定义注解自动继承了java.lang.annotation.Annotation接口
  • 注解使用成员方法的形式指定成员,没有方法体。
  • 值的类型: 所有的基本类型、String、Class、Enum、Annotation、以上数组等等
  • 可以指定成员的默认值 使用default关键字
  • 没有成员定义的注解称为标记
  • 使用自定义注解需要配上注解的信息处理流程
  • 自定义注解通常都会指明两个元注解(Retention、Target)
    在这里插入图片描述
    注意点
  • 如果注解只有一个参数,最好取名value,这样在使用的时候可以直接指定属性值。例如:@Test(“hello”)
  • 只能使用public和默认权限修饰符修饰参数
  • 参数默认值:注解参数必须有确定的值。要么在定义的时候给默认值;要么在使用注解的时候指定参数值。
  • 注解处理器
    如果没有读取注解的方法,那么该注解就没有任何意义。使用注解的过程中,注解处理器是必不可少的,Java利用反射机制,完成对注解的处理 。
  • 注解处理器类库

boolean AnnotationPresent(Class<?extends Annotation> annotationClass):判断该元素是否被annotationClass注解修饰
T getAnnotation(Class annotationClass):获取 该元素上annotationClass类型的注解,如果没有返回null
Annotation[] getAnnotations():返回该元素上所有的注解
T[] getAnnotationsByType(Class annotationClass):返回该元素上指定类型所有的注解
Annotation[] getDeclaredAnnotations():返回直接修饰该元素的所有注解
T[] getDeclaredAnnotationsByType(Class annotationClass):返回直接修饰该元素的所有注解

10.1.3 JDK内置的三个注解

Java目前内置了三种注解@Override、@Deprecated、@SuppressWarnnings

@Override:用于标识方法,标识该方法属于重写父类的方法
@Deprecated:用于标识方法或类,标识该类或方法已过时,建议不要使用
@SuppressWarnnings:用于有选择的关闭编译器对类、方法、成员变量、变量初始化的警告

10.1.4 元注解

10.2 反射机制

  1. 定义: 反射被视为动态语言的关键,反射机制允许程序在执行期间借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性和方法。透过对象可以查看内部的类结构。

  2. Java是一种静态语言,但Java具有一定的动态(程序执行时可以对代码进行操作)性。

  3. 反射机制提供的功能
    运行时判断任意一个对象所属的类
    运行时构造任意一个类的对象
    运行时判断任意一个类所具有的成员变量和方法
    运行时获取泛型信息
    运行时调用一个类具有的成员变量和方法(包括私有化的)
    运行时处理注解
    生成动态代理

  4. 反射相关API
    java.lang.Class
    java.lang.reflect.Method
    java.lang.reflect.Field
    java.lang.reflect.Constructor

  5. 类加载的理解
    程序通过Javac命令,会生成一个或多个字节码文件,接着通过Java命令对某个字节码文件进行解释运行,这个过程相当于把某个字节码文件加载到内存中,这个过程就称为类的加载,加载到内存中的类,我们称为运行时类, 这个运行时类就作为class的一个实例。

  6. 获取Class实例的4种方法

    1. Class c = Person.class;
    2. Person p1 = new Person();
      Class c1 = p1.getClass();
    3. 调用Class类的静态方法forName(String classPath)
      Class.forName(“chapter01.Person”)
    4. 通过获取类加载器ClassLoader
      ClassLoader cl = Person.class.getClassLoader();
      Class c3 = cl.loadClass(“chapter01.Person”)
  7. 那些类型可以获取class实例?
    外部类、成员、接口、数组、枚举、注解、基本数据类型、void
    note: 只要元素类型和维度相等就是同一个class。

  8. 使用Class实例进行一些操作
    创建运行时类对象

    1. 获取Class实例
    2. 调用newInstance()方法
      这个方法是对象的无参构造器,访问权限通常是public。
  9. 查看运行时类的内部结构(属性)
    1. getFields() 这个方法可以返回Field[]数组,获取当前运行时类及其父类中声明为public访问权限的属性。
    2. getDeclaredFields() 这个方法可以返回Field[]数组,获取当前运行时类的所有属性信息。
    3. getType() 获取变量类型
    4. getModifiers() 获取权限修饰符 (方法同步)
    5. getName() 获取变量名(方法同步)

  10. 查看运行时类的内部结构(方法)
    1.getMethods() 获取当前运行时类所有访问权限为public的方法,返回值是数组。
    2.getDeclaredMethods() 获取当前运行时类的所有方法(不包含父类),返回值是数组。
    3.getAnnotations() 获取方法声明的注解
    4.getParameterTypes() 获取形参列表
    5.getReturnType() 获取返回值类型
    6.getExceptionTypes() 获取抛出的异常
    7.getConstructors() 获取当前运行时类当中声明为public的构造器
    8.getDeclaredConstructors() 获取当前运行时类当中所有的构造器

  • 获取运行时类的父类
    getSuperClass() 获取父类
    getGenericSuperclass() 获取带泛型的父类(返回值是Type)
    getActualTypeArguments() 获取泛型数组
  • 获取运行时类实现的接口
    getInterfaces() 获取实现的接口
    getSuperClass().getInterfaces 获取父类实现的接口
  • 获取运行时类的所在包
    getPackage() 获取所在包

11.调用运行时类的指定结构(属性,方法,构造器)

  1. getField(“参数”) 获取指定的属性
    set(“参数1”,“参数2”) 修改指定的属性
    get(参数1) 获取指定对象的属性
    getDeclaredField(“参数1”) 获取指定对象的属性

  2. getDeclaredMetHod(方法名,参数列表) 获取指定的某个对象的某个方法
    invoke(obj,…args) 返回值就是obj调用方法的返回值

  3. getDeclaredConstructor(参数)

十一、网络编程

11.1 什么是网络编程?

网络编程即使用套接字来达到进程间通信,现在一般称为TCP/IP编程。

11.2 计算机网络简介

11.2.1 概念

计算机网络是指将地理位置不同的具有独立功能的多台计算机及其外部设备,通过通信线路连接起来,在网络操作系统,网络管理软件及网络通信协议的管理和协调下,实现资源共享和信息传递的计算机系统。

11.2.2 计算机网络的主要功能

  1. 资源共享
  2. 信息传递与集中处理
  3. 均衡负载与分布处理
  4. 综合信息服务

11.2.3 计算机网络三高问题

高性能、高并发、高可用

11.2.4 计算机网络分类

局域网LAN、城域网MAN、广域网WAN、互联网

11.3 网络通信协议及接口

11.3.1 网络通信协议

计算机网络中实现通信必须有一些约定,即通信协议;包括对速率,传输代码,代码结构,传输控制步骤,出错控制等制定的标准。常见的网络通信协议有:TCP/IP协议、IPX/SPX协议、NetBEUI协议等。

TCP/IP协议:传输控制协议/因特网互联协议(Transmission Control Protocol/Internet Protocal),是Internet最基本、最广泛的协议。它定义了计算机如何连入因特网,以及数据如何在它们之间传输的标准。它的内部包含一系列的用于处理数据通信的协议,并采用了4层的分层模型,每一层都呼叫它的下一层所提供的协议来完成自己的需求。

11.3.2 网络通信接口

为了使两个节点之间能进行对话,必须在他们之间建立通信工具(即接口),使彼此之间,能进行信息交换。接口包括两部分:

硬件装置:实现结点之间的信息传送
软件装置:规定双方进行通信的约定协议

11.3.3 通信协议分层思想

  1. 为什么要分层
    由于结点之间联系很复杂,在制定协议时,把复杂成份分解成一些简单的成份,再将它们复合起来。最常用的复合方式就是层次方式,及同层间可以通信,上一层可以调用下一层,而与再下一层不发生关系。各层互不影响,利于系统的开发和扩展。

  2. 通信协议的分层规定
    把用户应用程序作为最高层,把物理通信线路作为最底层,将其间的协议处理分为若干层,规定每层处理的任务,也规定每层的接口标准。

  3. 参考模型

在这里插入图片描述
上图中,TCP/IP协议中的四层分别是应用层、传输层、网络层和链路层,每层分别负责不同的通信功能。

应用层:主要负责应用程序的协议,例如HTTP协议、FTP协议等。
传输层:主要使网络程序进行通信,在进行网络通信时,可以采用TCP协议,也可以采用UDP协议。
网络层:网络层是整个TCP/IP协议的核心,它主要用于将传输的数据进行分组,将分组数据发送到目标计算机或者网络。
数据链路层:链路层是用于定义物理传输通道,通常是对某些网络连接设备的驱动协议,例如针对光纤、网线提供的驱动。
我们编写的程序位于应用层,因此我们的程序是和TCP/UDP打交道的

11.3.4 协议分层

通信的协议还是比较复杂的,java.net包中包含的类和接口,它们提供低层次的通信细节,我们可以直接使用这些类和接口,来专注于网络程序开发,而不用考虑通信的细节。
java.net包中提供了两种常见的网络协议的支持:TCP和UDP

  • TCP是可靠的连接,TCP就像打电话,需要先打通对方电话,等待对方有回应后才会跟对方继续说话,也就是一定要确认可以发信息以后才会把信息发出去。TCP上传任何东西都是可靠的,只要两台机器上建立起了连接,在本机上发送的数据就一定能传到对方的机器上。
  • UDP就好比发电报,发出去就完事了,对方有没有接收到它都不管,所以UDP是不可靠的。
  • TCP传送数据虽然可靠,但传送得比较慢;UDP传送数据不可靠,但是传送得快。

11.3.5 UDP TCP

  1. UDP
    用户数据报协议(User Datagram Protocol)。UDP是无连接通信协议,即在数据传输时,数据的发送端和接收端不建立逻辑连接。简单来说,当一台计算机向另外一台计算机发送数据时,发送端不会确认接收端是否存在,就会发出数据,同样接收端在收到数据时,也不会向发送端反馈是否收到数据。
    由于使用UDP协议消耗资源小,通信效率高,所以通常都会用于音频、视频和普通数据的传输例如视频会议都使用UDP协议,因为这种情况即使偶尔丢失一两个数据包,也不会对接收结果产生太大影响。
    但是在使用UDP协议传送数据时,由于UDP的面向无连接性,不能保证数据的完整性,因此在传输重要数据时,不建议使用UDP协议。
    特点:数据被限制在64kb以内,超出这个范围就不能发送了。
    数据报(Datagram):网络传输的基本单位

  2. TCP
    传输控制协议(Transmission Control Protocol)。TCP协议是面向连接的通信协议,即传输数据之前,在发送端和接收端建立逻辑连接,然后再传输数据,它提供了两台计算机之间可靠无差错的数据传输。
    在TCP连接中必须要明确客户端与服务器端,由客户端向服务端发出连接请求,每次连接的创建都需要经过“三次握手”。

(1)三次握手
TCP协议中,在发送数据的准备阶段,客户端与服务器之间的三次交互,以保证连接的可靠。

第一次握手,客户端发送SYN(SEQ=x)报文给服务器端,进入SYN_SEND状态。
(客户端向服务器端发出连接请求,等待服务器确认。)
第二次握手,服务器端收到SYN报文,回应一个SYN (SEQ=y)ACK(ACK=x+1)报文,进入SYN_RECV状态。(服务器端向客户端回送一个响应,通知客户端收到了连接请求。)
第三次握手,客户端收到服务器端的SYN报文,回应一个ACK(ACK=y+1)报文,进入Established状态。(客户端再次向服务器端发送确认信息,确认连接。)
三次握手完成,TCP客户端和服务器端成功地建立连接,可以开始传输数据了。整个交互过程如下图所示。
在这里插入图片描述
(2)四次挥手
其次,TCP的客户端和服务端断开连接,需要四次挥手
在这里插入图片描述

  • 客户端打算关闭连接,此时会发送一个 TCP 首部 FIN 标志位被置为 1 的报文,也即 FIN 报文,之后客户端进入
    FIN_WAIT_1 状态。
  • 服务端收到该报文后,就向客户端发送 ACK 应答报文,接着服务端进入 CLOSED_WAIT 状态。 客户端收到服务端的 ACK
    应答报文后,之后进入 FIN_WAIT_2 状态。
  • 等待服务端处理完数据后,也向客户端发送 FIN 报文,之后服务端进入 LAST_ACK 状态。
  • 客户端收到服务端的 FIN 报文后,回一个 ACK 应答报文,之后进入 TIME_WAIT 状态
  • 服务器收到了 ACK 应答报文后,就进入了 CLOSE 状态,至此服务端已经完成连接的关闭。
  • 客户端在经过 2MSL 一段时间后,自动进入 CLOSE 状态,至此客户端也完成连接的关闭。
  • 你可以看到,每个方向都需要一个 FIN 和一个 ACK,因此通常被称为四次挥手。这里一点需要注意是:主动关闭连接的,才有
    TIME_WAIT 状态。
    为什么挥手需要四次?
    关闭连接时,客户端向服务端发送 FIN 时,仅仅表示客户端不再发送数据了但是还能接收数据。服务器收到客户端的 FIN 报文时,先回一个 ACK 应答报文,而服务端可能还有数据需要处理和发送,等服务端不再发送数据时,才发送 FIN 报文给客户端来表示同意现在关闭连接。从上面过程可知,服务端通常需要等待完成数据的发送和处理,所以服务端的 ACK 和 FIN 一般都会分开发送,从而比三次握手导致多了一次。参考

通俗理解
三次握手A:我要过来了!B:我知道你要过来了!A:我现在过来!
四次挥手A:我们分手吧!B:真的分手吗?B:真的真的要分手吗?A:是的!
由此,可以可靠地进行连接和断开。

11.3.6 IP协议

11.3.6.1 概念

IP协议:网络互连协议
在这里插入图片描述
每个人的电脑都有一个独一无二的IP地址,这样互相通信时就不会传错信息了。

11.3.6.2 分类

IP地址根据版本可以分类为:IPv4和IPv6

dIPv4IPv6
地址长度IPv4协议具有32位(4字节)地址长度IPv6协议具有128位(16字节)地址长度
格式IPv4 地址的文本格式为 nnn.nnn.nnn.nnn,其中 0<=nnn<=255,而每个 n 都是十进制数。可省略前导零。IPv6 地址的文本格式为 xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx,其中每个 x 都是十六进制数可省略前导零。
数量共有43亿,30亿在北美,4亿在亚洲,2011年就已经用尽多到每一粒沙子都可以分配一个IPv6地址

在这里插入图片描述
IPv4又可以分为五类:
在这里插入图片描述

  • A类:在IP地址的四段号码中,第一段号码为网络号码,剩下的三段号码为本地计算机的号码;A类IP地址中网络的标识长度为8位,主机标识的长度为24位,A类网络地址数量较少,有126个网络,每个网络可以容纳主机数达1600多万(256的三次方-2)台。
  • B类:在IP地址的四段号码中,前两段号码为网络号码。B类IP地址中网络的标识长度为16位,主机标识的长度为16位,B类网络地址适用于中等规模的网络,有16384个网络,每个网络所能容纳的计算机数为6万多(256的二次方-2)台
  • C类:在IP地址的四段号码中,前三段号码为网络号码,剩下的一段号码为本地计算机的号码;此类地址中网络的标识长度为24位,主机标识的长度为8位,C类网络地址数量较多,有209万余个网络。适用于小规模的局域网络,每个网络最多只能包含254(256-2)台计算机
  • D类:此类IP地址在历史上被叫做多播地址(multicast
    address),即组播地址;在以太网中,多播地址命名了一组应该在这个网络中应用接收到一个分组的站点;多播地址的最高位必须是“1110”,范围从224.0.0.0到239.255.255.255
  • E类:
    此类地址也不分网络地址和主机地址,它的第1个字节的前五位固定为“11110”,为将来使用保留,地址范围从240.0.0.1到255.255.255.254
11.3.6.3 InetAddress类

说到IP地址,就要引入一个类:InetAddress
此类表示互联网协议 (IP) 地址。
在这里插入图片描述
InetAddress类无构造方法

1.常用方法摘要
byte[] getAddress()
返回此 InetAddress 对象的原始 IP 地址。
static InetAddress getByName(String host)
在给定主机名的情况下确定主机的 IP 地址。
String getHostAddress()
返回 IP 地址字符串(以文本表现形式)。
String getHostName()
获取此 IP 地址的主机名。
static InetAddress getLocalHost()
返回本地主机。
127.0.0.1:本机地址,主要用于测试。别名:Localhost

11.4 端口

  • IP地址用来标识一台计算机,但是一台计算机上可能提供多种网络应用程序,如何来区分这些不同的程序呢?这就要用到端口。端口号是用来区分一台机器上不同的应用程序的。
  • 我们使用IP地址加端口号,就可以保证数据准确无误地发送到对方计算机的指定软件上了。
  • 端口是虚拟的概念,是一个逻辑端口。
  • 当我们使用网络软件一打开,那么操作系统就会为网络软件分配一个随机的端口号,或者打开的时候向系统要指定的端口号。
  • 通过端口,可以在一个主机上运行多个网络应用程序。端口的表示是一个16位的二进制整数,2个字节,对应十进制的0~65535。
  • 端口号在计算机内部是占2个字节。一台机器上最多有65536个端口号。一个应用程序可以占用多个端口号。端口号如果被一个应用程序占用了,那么其他的应用程序就无法再使用这个端口号了。
  • 记住一点,我们编写的程序要占用端口号的话占用1024以上的端口号,1024以下的端口号不要去占用,因为系统有可能会随时征用。端口号本身又分为TCP端口和UDP端口,TCP的8888端口和UDP的8888端口是完全不同的两个端口。TCP端口和UDP端口都有65536个

11.4.1 分类

  1. 公有端口:0~1023
    HTTP:80
    HTTPS:443
    FTP:21
    Telnet:23
  2. 程序注册端口(分配给用户或者程序):1024~49151
    Tomcat:8080
    MySQL:3306
    Oracle:1521
  3. 动态、私有端口:49152~65535

11.4.2 DOS命令查看端口号

查看所有端口:netstat -ano
查看指定端口:netstat -ano|findstr “端口号”
查看指定端口的进程:tasklist|findstr “端口号”

11.4.3 InetSocketAddress类

说到端口,则要引入一个类:InetSocketAddress

此类实现 IP 套接字地址(IP 地址 + 端口号)。

1.构造方法摘要
InetSocketAddress(InetAddress addr, int port)
根据 IP 地址和端口号创建套接字地址。
InetSocketAddress(int port)
创建套接字地址,其中 IP 地址为通配符地址,端口号为指定值。
InetSocketAddress(String hostname, int port)
根据主机名和端口号创建套接字地址。

2.常用方法摘要
InetAddress getAddress()
获取 InetAddress。
String getHostName()
获取 hostname。
int getPort()
获取端口号。

11.5 TCP、UDP网络编程

网络编程也叫做Socket编程,即套接字编程。套接字指的是两台设备之间通讯的端点。
在这里插入图片描述

11.5.1 TCP网络编程

11.5.1.1 概述

TCP通信能实现两台计算机之间的数据交互,通信的两端,要严格区分为客户端(Client)与服务端(Server)。
(1)两端通信时步骤:

  1. 服务端程序,需要事先启动,等待客户端的连接。
  2. 客户端主动连接服务器端,连接成功才能通信。服务端不可以主动连接客户端。

(2)在Java中,提供了两个类用于实现TCP通信程序:
3. 客户端:java.net.Socket 类表示。创建Socket对象,向服务端发出连接请求,服务端响应请求,两者建立连接开始通信。
4. 服务端:java.net.ServerSocket 类表示。创建ServerSocket对象,相当于开启一个服务,并等待客户端的连接。

11.5.1.2 Socket类

Socket 类:该类实现客户端套接字,套接字指的是两台设备之间通讯的端点。
(1)构造方法摘要
public Socket(String host, int port) :创建套接字对象并将其连接到指定主机上的指定端口号。如果指定的host是null ,则相当于指定地址为回送地址。
回送地址(127.x.x.x) 是本机回送地址(Loopback Address),主要用于网络软件测试以及本地机进程间通信,无论什么程序,一旦使用回送地址发送数据,立即返回,不进行任何网络传输。

(2)常用方法摘要

  • public InputStream getInputStream() : 返回此套接字的输入流。

  • 如果此Scoket具有相关联的通道,则生成的InputStream 的所有操作也关联该通道。
    关闭生成的InputStream也将关闭相关的Socket。

  • public OutputStream getOutputStream() : 返回此套接字的输出流。

  • 如果此Scoket具有相关联的通道,则生成的OutputStream 的所有操作也关联该通道。
    关闭生成的OutputStream也将关闭相关的Socket。

  • public void close() :关闭此套接字。

    • 一旦一个socket被关闭,它不可再使用。 关闭此socket也将关闭相关的InputStream和OutputStream 。
  • public void shutdownOutput() : 禁用此套接字的输出流。

    • 任何先前写出的数据将被发送,随后终止输出流。

11.5.1.3 ServerSocket类

ServerSocket类:这个类实现了服务器套接字,该对象等待通过网络的请求。
(1)构造方法摘要

  • public ServerSocket(int port)
    :使用该构造方法在创建ServerSocket对象时,就可以将其绑定到一个指定的端口号上,参数port就是端口号。
    (2)常用方法摘要

  • public Socket accept()
    :侦听并接受连接,返回一个新的Socket对象,用于和客户端实现通信。该方法会一直阻塞直到建立连接。

服务器是没有IO流的,服务器可以获取到请求的客户端对象socket
使用每个客户端socket中提供的IO流和客户端进行交互
服务器使用客户端的字节输入流读取客户端发送的数据
服务器使用客户端的字节输出流给客户端回写数据

11.5.2 UDP网络编程

从技术意义上来讲,只有TCP才会分Server和Client。对于UDP来说,从严格意义上来讲,并没有所谓的Server和Client。
java.net包给我们提供了两个类DatagramSocket(此类表示用于发送和接收数据报的套接字)和DatagramPacket(该类表示数据报的数据包。 )
1.DatagramSocket
(1)构造方法摘要
protected DatagramSocket()构造数据报套接字并将其绑定到本地主机上的任何可用端口。
protected DatagramSocket(int port)构造数据报套接字并将其绑定到本地主机上的指定端口。
protected DatagramSocket(int port, InetAddress laddr)创建一个数据报套接字,绑定到指定的本地地址。

2.DatagramPacket
(1)构造方法摘要

  • DatagramPacket(byte[] buf, int offset, int length)构造一个
    DatagramPacket用于接收指定长度的数据报包到缓冲区中。
  • DatagramPacket(byte[] buf, int offset, int length, InetAddress
    address, int port)构造用于发送指定长度的数据报包到指定主机的指定端口号上。

(2)常用方法摘要

  • byte[] getData() 返回数据报包中的数据。
  • InetAddress getAddress() 返回该数据报发送或接收数据报的计算机的IP地址。
  • int getLength() 返回要发送的数据的长度或接收到的数据的长度。

如果是TCP中先启动客户端会报错
而如果是UDP中先启动发送方不会报错,但会正常退出。

11.6 URL编程

11.6.1 概念

URL(Uniform Resource Locator):统一资源定位符,它表示Internet上某一资源的地址。
通过URL我们可以访问Internet上的各种网络资源,比如最常见的www,ftp站点。浏览器通过解析给定的URL可以在网络上查找相应的文件或其他资源。

URI=URL+URN

  • URI:Uniform Resource Identifier ,统一资源标志符。
  • URL:Uniform Resource Locator,统一资源定位符。
  • URN:Uniform Resource Name,统一资源命名。

网络三大基石:HTML,HTTP,URL

11.6.2 格式

URL的基本结构由5部分组成:
<传输协议>://<主机名>:<端口号>/<文件名>#片段名?参数列表

片段名:即锚点,例如看小说,直接定位到章节
参数列表格式:参数名=参数值&参数名=参数值…
例如:
http://localhost:8080/index.jsp#a?username=Tom&password=123456

11.6.3 URL类

  • 构造方法摘要
    URL(String spec)根据 String 表示形式创建 URL 对象。
    URL(String protocol, String host, int port, String file) 根据指定协议名、主机名、端口号和文件名创建 URL 对象。
    URL(String protocol, String host, String file) 根据指定的协议名、主机名和文件名创建 URL。

  • 常用方法摘要
    String getProtocol()获取此 URL的协议名称。
    String getHost() 获取此 URL 的主机名。
    int getPort() 获取此 URL 的端口号。
    String getPath() 获取此 URL 的文件路径。
    String getFile()获取此 URL 的文件名。
    String getQuery()获取此 URL的查询部分。
    URLConnection openConnection() 返回一个URLConnection实例,表示与URL引用的远程对象的URL 。

  • URLConnection类中又有一个方法: InputStream getInputStream() 返回从此打开的连接读取的输入流。

小结:
在这里插入图片描述

十二、新特性

12.1 JDK动态代理

代理设计模式的原理

  • 使用一个代理类将对象包装起来,
    然后用该代理对象取代原始对象。任何对原始对象的调用都要通过代理。代理对象决定是否以及何时将方法调用转会原对象

  • 静态代理是指代理类和目标对象的类都是在编译阶段就确定好的, 一个代理类只能为一个接口服务,不利于程序的扩展

  • 动态代理是指客户通过代理类来调用其他对象的方法, 并且是在程序运行时根据需要动态的创建目标类的代理对象

实现静态代理的代码
请添加图片描述
请添加图片描述
请添加图片描述
动态代理相关API
在这里插入图片描述

实现动态代理的代码
请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述

12.2 Lambda表达式

  1. 举例 (o1,o2) -> Integer.compare(o1,o2);
    -> lambda操作符
    左边 lambda形参列表(其实就是接口中的抽象方法的形参列表)
    右边 lambda体(就是重写的抽象方法的方法体)

  2. lambda表达式的使用情况
    语法格式1: 无参无返回值
    () -> (体);
    语法格式2: 带参数没有返回值
    (参1) -> {体};
    语法格式3: 数据类型可以忽略, 类型推断
    (参变量) -> {体};
    语法格式4: 只有一个参数, 小括号可以省略
    参变量 -> {体};
    语法格式5: 多参数带返回值
    (参1,参2,参3…) -> {体 return 值};
    语法格式6:只有一条语句, return和大括号都能省
    (参1,参2,参3) -> xxxx

    总结:
    - 只有一个参数 类型和括号省
    - 只有一条执行语句 括号和return省

  3. lambda实质: 作为函数式接口的实例

12.3 函数(Functional)式接口

  • 一个接口中只声明了一个抽象方法, 这样的接口称为函数式接口。
    • 可以在接口上使用@Functionallnterface注解, 这样做可以检查它是否是一个函数式接口, 同时javadoc也会包含一条声明, 说明这个接口是一个函数式接口。
    • 若lambda表达式抛出一个非运行时异常, 那么该异常需要在目标接口的抽象方法上进行声明

请添加图片描述

12.4 方法引用与构造器引用

方法引用
- 当要传递给lambda体的操作, 已经有实现的方法了, 可以使用方法引用
- 方法引用可以看作是lambda表达式深层次的表达, 换句话来说, 方法引用就是lambda表达式, 也就是函数式接口的一个实例, 通过方法的名字来指向一个方法, 可以认为是lambda表达式的一个语法糖
- 要求: 实现接口的抽象方法的参数列表和返回值类型, 必须与方法引用的方法的参数列表和返回值类型保持一致
- 格式: 使用操作符 “::” 将类(对象) 与 方法名分隔开来
- 使用情况
对象 :: 非静态方法名
类 :: 静态方法名
类 :: 非静态方法名
- 代码示范
对象 :: 非静态方法名
请添加图片描述
请添加图片描述

         类   :: 静态方法名

请添加图片描述

         类   :: 非静态方法名

请添加图片描述

构造器引用
- 示范
请添加图片描述请添加图片描述

12.3 Stream流

  • 作用: 对集合进行操作. 提供了一种高效且易于使用的处理数据方式

    • 和collection集合的区别: Collection是一种静态的内存数据结构, 而stream是有关计算的

    • 特点
      1.Stream关注的是对数据的运算, 与cpu有关

      1. stream不会自己存储元素

      2. stream不会改变源对象, 返回一个持有结果的stream

      3. stream是延迟执行的, 这意味着他们会等到需要结果的时候才执行

    • 操作

      1. 创建Stream(一个数据源(集合、数组),获取一个流)
      2. 中间操作
        一个中间操作链. 对数据源的数据进行处理
      3. 终止操作(终端操作)
        一旦执行终止操作, 就执行中间操作链, 并产生结果, 之后不会被再次使用
        请添加图片描述
  • 创建Stream的几种方式
    1) 通过集合
    default Stream stream(); 返回顺序流
    default Stream parallelStream(); 返回并行流
    请添加图片描述

    2) 通过数组Arrays.stream(n)方法, 该方法有重载
    static stream(T[] array) 返回流
    请添加图片描述

    3)通过stream.of()方法
    请添加图片描述

    4)创建无限流(了解)
    - 迭代 stream iterate(final T feed,final UnaryOperator f)

    - 生成   stream<T> generate(Supplier<T> s)
    

代码演示
在这里插入图片描述

 - 中间操作 
   1) 筛选与切片
   2) 映射
   3) 排序
   
 - 终止操作(终止后不能再次使用, 必须重新获取)

   1) 匹配与查找
      allMatch(Predicate p)          检查是否匹配所有元素
      anyMatch(Predicate p)          检查是否至少匹配一个元素
      noneMatch(Predicate p)         检查是否没有匹配的元素
      findFirst()                    返回第一个元素
      findAny()                      返回当前流的任意元素
      count
      max(Comparator c)
      min(Comparator c)
      forEach(Consumer c)            内部迭代 
   2)归约
       reduce(T iden,BinaryOperator b)  可以将流中元素反复结合起来, 得到一个值, 返回T
   reduce(BinaryOperator b) 可以将流中元素反复结合起来, 得到一个值, 返回Optional<T>
   
   备注: map和reduce的连接通常称为map-redece模式  因为谷歌用它来进行网络搜索而出名 

3)收集(了解)
collect(Collector c)
将流转换为其他形式, 接收一个Collector接口的实现, 用于给Stream中元素做汇总的方法
note: 东西比较多, 查文档

12.4 Optional类

  • Optional类是一个容器类, 可以保存类型T的值, 代表这个值存在, 或者仅仅保存null, 表示这个值不存在, 原来用null表示一个值不存在, 现在Optional可以更好的表达这个概念, 并且可以避免空指针异常。
  • javadoc描述: 这是一个可以为null的容器对象, 如果值存在, 则isPresent()方法会返回true, 调用get()方法会返回该对象
    请添加图片描述
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值