JavaSE进阶知识点

目录

NO.01 static与extends

1. static的基本用法

1. static介绍

 2. 内存原理

3. 被static修饰与不被static修饰的区别

4. 使用static注意事项

2. static的应用:代码块

1. 静态代码块

2. 代码块(基本不用)

3. static的应用:单例

1. 设计模式-单例

2. 饿汉单例

3. 懒汉单例

4. 继承

 1. 继承的内存原理

2. 继承的特点

3. 继承常见问题

 4. 子类访问父类的特点

 5. super()和this()

NO.2 权限修饰符、final、enum 、多态、abstract

1. 权限修饰符

2. final

3. 枚举enum 

 4. 抽象类abstract

1. abstract简单理解

 2. final 和 abstract

  3. 模板方法模式

5. 多态

1. 多态中成员访问特点

 2. 自动类型转换与手动类型转换

 1. 介绍

 2. 解决的问题

3. 注意

NO.3 interface、内部类、常用API

1. 接口interface

1. 接口特点

2. JDK8 开始接口新增的方法

1. 默认方法

2. 静态方法

3. 私有方法( JDK 9 才有)

3. 注意事项

2. 内部类

1. 静态内部类

2. 成员内部类

3. 局部内部类(无意义)

4. 匿名内部类(重点)

3. 常用API

1. Object类常用方法

2. Objects工具类

3. StringBuilder

 1. StringBuilder介绍

2. StringBuilder常用方法

NO.4 常用API、Lambda、常见算法

1. 日期与时间

1. Date

1. Date构造器

2. Date方法

2. SimpleDateFormat

1. SimpleDateFormat构造器

2. Date方法

3. Calendar

1. 创建日历方法

2. 常用方法

2. JDK新增日期类

1. LocalDateTime、LocalDate、LocalTime

1.  三者创建对象都是一样的 API

 2. LocalDate 、 LocalTime 、 LocalDateTime 获取信息的 API.

 3. LocalDateTime 的转换 API

4. 修改相关的 API

2. Instant

3. DateTimeFormatter

4. Duration/Period

1. Period

2. Duration

5. ChronoUnit 

3. 包装类

4. 正则表达式

 1. 正则表达式组成部分

 2. 正则表达式在字符串方法中的使用

 3. 例子

5. Arrays类

1. 常用API

6. 常见算法

1. 冒泡排序

2. 选择排序

3. 二分查找

7. Lambda表达式

 NO.5 集合

 1. 集合的体系特点

1. 集合类体系结构

2. Collection 集合特点

2. Collection 的常用方法

3. 集合的遍历方式

1. 迭代器

2. 增强for循环

3. lamdbda表达式

 4. 集合存储自定义类型的对象

5. 常见数据结构

1. 栈、队列

2. 数组

3. 链表

4. 二叉树、 二叉查找树

1. 二叉树

2. 二叉查找树

5. 平衡二叉树

6. 红黑树

6. List 系列集合

1. List特有方法

2. ArrayList

1. ArrayList 底层的扩容机制

2. LinkedList

7. 集合的并发修改异常问题

8. 泛型深入

1. 泛型的上下限

 NO.6 Set、Collections、Map

1. Set系列集合

1.  HashSet 集合元素无序的底层原理:哈希表

1. 哈希表:

2. 哈希值:

2. LinkedHashSet

3. TreeSet 集合概述和特点

1. 自定义排序规则

2. 可变参数

3. Collections

1. 常用API

4. Map

1. Map集合体系特点

2. Map 集合实现类特点

3. Map API 

 4. Map集合遍历

5. HashMap的特点和底层原理

6. LinkedHashMap 集合概述和特点

7. TreeMap 集合概述和特点

NO.7 Stream和异常体系

1. 不可变集合

1. 创建不可变集合

2. Stream流

1. 集合和数组获取Stream流的方式

2. Stream流常用API与终结方法

3. Stream流收集方法

4. Collectors工具类提供收集流的方法

3. 异常处理

1. 概念、体系

2. 自定义异常

1. 自定义编译时异常

2. 自定义运行时异常

NO.8 File类、递归、IO流

1. 日志技术

1. 日志概述

2.  Logback 

 1. Logback 的 三个模块

2. 使用 Logback 第三方工具步骤

 4. Logback的使用

2. File类

1. 创建对象

2. 绝对路径和相对路径

3. File 类的判断文件类型、获取文件信息功能

4. File 类创建文件的功能

 5. File 类的遍历功能

3. 递归

1.  求数问题递归

2. 其他形式递归

 4. IO流

1. 概述

2.  输入流

 1. FileInputStream常见API

2. FileReader

3. 输出流

1. FileOutPutSteam

2. FileWriter

4. 转换流

 1. 字符输入转换流: InputStreamReader

2. 字符输入转换流: OutputStreamWriter

3. .getBytes( 编码 )

5. 缓冲流

 1. 构造器

 2. 方法

6. 打印流

1. PrintStream

2. PrintWriter

3. 输出语句重定向

7. IO流释放资源方式 

1. 基本做法:手动释放资源

2. JDK7改进方案:资源用完最终自动释放

3. JDK9改进方案:资源用完最终自动释放

5. 对象序列化

1. 对象序列化

 2. 对象反序列化

6. IO框架

NO.9 多线程

1. 多线程的创建

1. 继承 Thread 类 

2. 实现 Runnable 接口

1. 标准

 2. 匿名内部类

3. JDK 5.0 新增:实现 Callable 接口

 2. Thread 常用方法、构造器

3. 线程同步

1. 同步代码块

2. 同步方法

3. Lock 锁 

4. 线程池

 1. 线程池的构造器

2. 常见面试题

3. ThreadPoolExecutor 创建线程池对象示例

4. ThreadPoolExecutor 处理 Runnable 与 Callable

1. Runnable

2. Callable

5. Executors 工具类

5. 定时器

1.  Timer 定时器

2. ScheduledExecutorService 定时器

6. 并发、并行

1. 并发

2. 并行

7. 线程的生命周期

1. 线程的六种状态

NO.10 网络编程

1. 网络通信三要素

1. IP地址

1. IP地址命令

2. IP地址操作类-InetAddress

2. 端口号

3. 协议

1. TCP协议

2. UDP协议

2. UDP 通信

1. 数据包对象与发送端和接收端对象

1. 使用DatagramPacket :数据包对象

2. 使用DatagramSocket :发送端和接收端对象

2. UDP 的三种通信方式

3. TCP 通信

1. Socket(客户端)

2. ServerSocket(服务端)

3. TCP 通信 - 同时接受多个客户端消息

 NO.11 单元测试、反射、注解、动态代理

1. 单元测试

1. Junit 使用

2. Junit 常用注解

2. 反射

1. 反射概述

2. 获取Class类的三种方式

3. 反射获取构造器对象

4. 反射获取成员变量对象

5. 反射获取方法对象

6. 反射的作用

1. 绕过编译阶段为集合添加数据

2. 通用框架的底层原理

3. 注解

1. 自定义注解

2. 元注解

3. 注解解析

4. 动态代理

 NO.12 XML 、 XML 解析、设计模式等

1. XML

1. XML 概述

2. XML 的创建、语法规则

3. XML 的约束

1. DTD约束 - 了解

2. schema约束 - 了解

2. XML解析技术

1. Dom 解析工具

2. Dom4J 解析 XML 文件

1. Dom4j 解析 XML- 得到 Document 对象

2. Dom4j 解析 XML 的元素、属性、文本

3. XML 检索技术: Xpath

1. Document 中与 Xpath 相关的 API:

2. Xpath 的四大检索方案

①绝对路径

②相对路径

③全文检索

④属性查找

3. 设计模式

1. 工厂模式

2. 装饰模式


NO.01 static与extends

1. static的基本用法

1. static介绍

什么是static呢,是一个静态关键字,用于修饰成员变量或成员方法。被static修饰的成员变量或成员方法都可以被类的所有对象共享

 2. 内存原理

用static修饰的成员变量或成员方法会随着类的加载而加载,类加载在方法区中后,被static修饰的也会加载到方法区中,因此可以直接使用 类名.方法 的方式而不需要创建对象。我们也可以通过创建对象的方式访问,因为创建一个对象时,会在堆内存中存储方法的引用,是可以引用静态的,当然也可以引用实例的,但创建对象才会使对象的实例方法加载到方法区,很显然,被static修饰的并不需要创建对象。

3. 被static修饰与不被static修饰的区别

被static修饰的,我们不需要创建对象,直接通过 类名.方法/属性 访问到。注意:1)也可以通过创建对象的方式访问到,但不推荐。2)在同类中可以省略类名

不被static修饰的,必须需要创建对象,通过 对象.方法/属性 才能访问到。

4. 使用static注意事项

1)在静态方法里,只能访问静态的成员,不能直接访问实例成员,只能通过new对象的方式才能访问。

2)实例方法里,可以访问静态的,也可以访问实例的。

3)静态方法中不可能出现this关键字

4)工具类都要使用static,因为需要他的共享(这里建议直接将工具类的构造器私有化,显得自己很牛逼)

2. static的应用:代码块

1. 静态代码块

格式:static{}

随着类的加载而加载,自动触发,只会执行一次

使用场景:做一些静态数据的初始化,已便于后续使用,比如定义扑克牌。

2. 代码块(基本不用)

格式:{}

每次创建对象时,也就是每次调用构造器时,都会执行此代码块的代码,并领先于构造器。

使用场景:初始化实力资源,但,基本不用。

3. static的应用:单例

1. 设计模式-单例

单例就是一种设计模式,就是一种:使用最优的方案解决在软件开发中遇到的问题,设计模式一般有20多中,这里介绍单例设计模式。

单例设计模式是指:此类永远只有一个实例,就是一个类只能创建一个对象。比如我们打开有些应用程序,只能打开一次,这就节省了内存空间。

2. 饿汉单例

用类获取对象时,对象会直接创建好。

//单例类
public class TestClass{

    //静态变量存储对象,与类一起加载
    public static TestClass testClass = new TestClass();

    //必须私有化无参构造器
    private TestClass(){

    }

} 

3. 懒汉单例

在需要这个对象时,才会创建一个对象。

//单例类
public class TestClass{

    //静态变量存储对象,与类一起加载
    private static TestClass testClass;

    //必须私有化无参构造器
    private TestClass(){

    }

    //必须提供方法返回单例对象
    public static TestClass getTestClass(){

        //当第二次创建对象时,需要防止再次创建一个此对象
        if(testClass == null){
            //第一次创建对象,需要创建一个对象
            testClass = new TestClass();      
        }

        return testClass;
        
    }
} 

4. 继承

 1. 继承的内存原理

当我们创建一个对象时,此时该对象继承了自己的父类,那么在创建该对象时,会在堆内存中开辟一个空间有着自己的地址,此地址有两块区域,分为子类空间和父类空间,存着对应的属性。在创建该对象时,会优先调用父类构造器,要先完成父类数据空间的初始化,随后在触发该对象自己的构造器。在代码里没有体现是因为子类的第一行代码 super() 省略了,因此,当父类没有无参构造器时,会报错。

2. 继承的特点

① 子类可以继承父类的属性和行为,但是子类不能继承父类的构造器。
② Java 是单继承模式:一个类只能继承一个直接父类。
③ Java 不支持多继承、但是支持多层继承。
④ Java 中所有的类都是 Object 类的子类。

3. 继承常见问题

1)子类是否可以继承父类的私有成员?
可以的,但不能直接访问,即使通过 类名. 也不行,需要特别方法。

2)子类是否可以继承父类的静态成员?

有争议的知识点。子类可以直接使用父类的静态成员。个人认为这是共享并非继承。

 4. 子类访问父类的特点

一句话就是:就近原则;

1)先子类局部范围找
2)然后子类成员范围找
3)然后父类成员范围找,如果父类范围还没有找到则报错

 5. super()和this()

 this :代表本类对象的引用; super :代表父类存储空间的标识。

 this()是表示 :去调用本类的其他构造器;super()是表示:去调用父类的构造器

两者都只能放在第一行,因此两者不能同时出现。

 this()的使用场景:

当我们在创建对象时,调用有两个参数的 name 和 id 的构造器,而我们只想定义 id,来使用name 的默认值,我们可以在只有一个参数的 id 的构造器中使用 this(id, "张三",) ,就可以去调用构造器 A 并且使用 "张三" 这个默认值。

public class Student {
    private String name;
    private String id;
    public Student(String id){
        this(id , "张三" );
    }
    public Student(String id , String name ){
        this.name = name;
        this.id = id;
    }
}

NO.2 权限修饰符、final、enum 、多态、abstract

1. 权限修饰符

权限修饰符
private只能在同一个类中相互访问。
缺省只能在同一个包中相互访问。
protected被protected修饰的那个类继承于不同包下的一个类时,即使是不同包也能相互访问。
public都可以相互访问。

2. final

final 关键字是最终的意思,可以修饰(类、方法、变量)
修饰类:表明该类是最终类,不能被继承。
修饰方法:表明该方法是最终方法,不能被重写。
修饰变量:表示该变量第一次赋值后,不能再次被赋值,所以第一次必须赋值 。

final 修饰的变量是基本类型:那么变量存储的数据值不能发生改变。
final 修饰的变量是引用类型:那么变量存储的地址值不能发生改变,但是地址指向的对象内容是可以发生变化的。就是依然可以重新 set 。

3. 枚举enum 

枚举是为了做信息的标志和信息的分类。

它就是定义一些常量的。

特征如下:

枚举都是最终类,不可以被继承。
构造器都是私有的,枚举对外不能创建对象。
枚举类的第一行默认都是罗列枚举对象的名称的。
枚举类相当于是多例模式。

enum Test{
    BLACK, YELLOW, RED, WHITE;
}

 4. 抽象类abstract

1. abstract简单理解

抽象类可以理解成不完整的设计图,一般作为父类,让子类来继承。子类必须重写抽象方法来完成,这更像是一个规范。注意,抽象类可以没有抽象方法,甚至它可以当作一个普通类一样写属性写方法,但是得到了抽象方法,失去了创建对象的能力。有抽象方法的一定是抽象类。

 2. final 和 abstract

abstract 定义的抽象类作为模板让子类继承, final 定义的类不能被继承

抽象方法定义通用功能让子类重写, final 定义的方法子类不能重写。

两者不可能同时存在。

  3. 模板方法模式

一种设计模式:当系统中统一功能多处开发,大部分代码一致,只有一部分不同时使用。

模板方法模式实现步骤
1 、定义一个抽象类。

2 、定义 2 个方法,一个是模板方法:把相同代码放里面去,不同代码定义成抽象方法
3 、子类继承抽象类,重写抽象方法。

public abstract class A {
    abstract void test();
}

class B extends A{
    @Override
    void test() {
    }
}

5. 多态

1. 多态中成员访问特点

对于 Animal a = new Dog(); 这就是多态。多态强调的是行为,因此:
方法调用:编译(敲代码阶段)看左边,运行看右边。比如当我们执行 a.run() 方法时,会采用Dog类中的run方法,而不会采用Animal中的run方法。
变量调用:编译(敲代码阶段)看左边,运行也看左边。比如当我们打印 a.name ,会打印Animal类中的name,而不会打印Dog中的name。

 2. 自动类型转换与手动类型转换

 1. 介绍

自动类型转换就是直接将子赋值给父,满足语法规则直接自动转换。

手动类型转换就是反过来,需要手动的转换,类似于 (Dog)

 2. 解决的问题

可以转换成真正的子类类型,从而调用子类独有功能。

3. 注意

1)转换类型错误会报:ClassCastException

2)使用 instanceof 判断当前对象的真实类型,再进行强制转换

public class A {
    public static void main(String[] args) {
        A a = new C();
        if(a instanceof B){
            System.out.println("b");
        }else if(a instanceof C){
            System.out.println("c");
        }
    }
}

NO.3 interface、内部类、常用API

1. 接口interface

1. 接口特点

1)JDK8 之前接口中只能是抽象方法和常量,没有其他成分了。
2)接口不能实例化。
3)接口中的成员都是 public 修饰的,写不写都是,因为规范的目的是为了公开化。
4)接口用关键字 interface 来定义

5)接口的使用都要用实现接口的实现类,实现类可以理解成所谓的子类

6)一个类实现接口,必须重写完全部接口的全部抽象方法,否则这个类需要定义成抽象类。

2. JDK8 开始接口新增的方法

1. 默认方法

类似之前的普通实例方法,必须用 default 修饰,用实现类调用。

2. 静态方法

类似之前的静态方法,必须用 static 修饰,用接口本身名调用。

3. 私有方法( JDK 9 才有)

只能在接口内部被调用。

3. 注意事项

1、接口不能创建对象

2 、一个类实现多个接口,多个接口的规范不能冲突
3 、一个类实现多个接口,多个接口中有同样的静态方法不冲突。
4 、一个类继承了父类,同时又实现了接口,父类中和接口中有同名方法,默认用父类的。
5 、一个类实现了多个接口,多个接口中存在同名的默认方法,可以不冲突,这个类重写该方法即可。
6 、一个接口继承多个接口,是没有问题的,如果多个接口中存在规范冲突则不能多继承。

2. 内部类

内部类就是定义在一个类里面的类,里面的类可以理解成(寄生),外部类可以理解成(宿主)。

1. 静态内部类

有 static 修饰,属于外部类本身。
它的特点和使用与普通类是完全一样的,类有的成分它都有,只是位置在别人里面而已。

//定义:
public class Outer{
    // 静态成员内部类
    public static class Inner{
    }
}

//调用:
Outer.Inner in = new Outer.Inner();

2. 成员内部类

无 static 修饰,属于外部类的对象。
JDK16 之前,成员内部类中不能定义静态成员, JDK 16 开始也可以定义静态成员了。

//定义:
public class Outer {
    //成员内部类
    public class Inner {
    }
}

//调用:
Outer.Inner in = new Outer().new Inner();

 成员内部类扩展:通过 类名.this.* 访问外部类的属性名

class B{
    private String a = "a";
    class E{
        public void main(String[] args) {
            System.out.println(B.this.a);
        }
    }
}

3. 局部内部类(无意义)

局部内部类放在方法、代码块、构造器等执行体中。
局部内部类的类文件名为: 外部类 $N 内部类 .class 。

4. 匿名内部类(重点)

本质上是一个没有名字的局部内部类。
作用:方便创建子类对象,最终目的是为了简化代码编写。

匿名内部类可以作为一个对象,直接传输给方法。

public class A {
    public static void main(String[] args) {
        goSwimming(new Swimming() {
            @Override
            public void swim() {
                System.out.println("大家来游泳");
            }
        });
    }
    // 定义一个方法让所有角色进来一起比赛
    public static void goSwimming(Swimming swimming) {
        swimming.swim();
    }
}
@FunctionalInterface//表示函数式接口
interface Swimming {
    void swim();
}

3. 常用API

1. Object类常用方法

toString:默认是返回当前对象在堆内存中的地址信息 : 类的全限名 @ 内存地址

equals:默认是比较当前对象与另一个对象的地址是否相同,相同返回 true ,不同返回 false

 因为Object是祖宗类,都有这俩方法,但是返回地址是没有意义的,目的是让我们重写。

2. Objects工具类

这里介绍的是 Objects工具类 

equals:比较两个对象的,底层会先进行非空判断,从而可以避免空指针异常。再进行 equals 比较,因此会更加安全。

isNull:判断变量是否为 null , 为 null 返回 true , 反之

建议使用 Objects.equals 会更好。

3. StringBuilder

 1. StringBuilder介绍

StringBuilder 是一个可变的字符串的操作类,我们可以把它看成是一个对象容器。
使用 StringBuilder 的核心作用:操作字符串的性能比 String 要更高(如拼接、修改等)。

为什么String拼接性能低?分析底层。

String类拼接字符串时,每次拼接都会在堆内存中创建一个 StringBuilder 对象、一个String对象,通过 StringBuilder 拼接后,再使用 toString 变回 String,因此很是消耗内存。

 为什么StringBuilder拼接性能高?分析底层。

 通过方法 append 拼接时,只在堆内存中产生一个 StringBuilder 对象。

2. StringBuilder常用方法
public StringBuilder append( 任意类型 )添加数据并返回 StringBuilder 对象本身
public StringBuilder reverse()将对象的内容反转
public int length()返回对象内容长度
public String toString()通过 toString() 就可以实现把 StringBuilder 转换为字符串

NO.4 常用API、Lambda、常见算法

1. 日期与时间

1. Date

1. Date构造器
public Date()创建一个 Date 对象,代表的是系统当前此刻日期时间。
public Date(long time)把时间毫秒值转换成 Date 日期对象。
2. Date方法
public long getTime()返回从 1970 年 1 月 1 日 00:00:00 走到此刻的总的毫秒数
public void setTime(long time)设置日期对象的时间为当前时间毫秒值对应的时间

2. SimpleDateFormat

1. SimpleDateFormat构造器
public SimpleDateFormat(String pattern)创建简单日期格式化对象,并封装格式化的形式信息
2. Date方法

y:年;M:月;d:日;h:时;m:分;s:秒;

public final String format(Date d)格式化日期对象
public final String format(Object time)格式化时间毫秒值
public Date parse(String source)解析字符串时间

3. Calendar

1. 创建日历方法
public static Calendar getInstance()获取当前日历对象
2. 常用方法
public int get(int field)取日期中的某个字段信息。
public void set(int field,int value)修改日历的某个字段信息。
public void add(int field,int amount)为某个字段增加 / 减少指定的值
public final Date getTime()拿到此刻日期对象。
public long getTimeInMillis()拿到此刻时间毫秒值

2. JDK新增日期类

1. LocalDateTime、LocalDate、LocalTime

1.  三者创建对象都是一样的 API
public static Xxxx now();根据当前时间创建对象LocalTime llocalTime = LocalTime.now();
public static Xxxx of(...);指定日期 / 时间创建对象LocalDate localDate1 = LocalDate.of(2099 , 11,11);
 2. LocalDate 、 LocalTime 、 LocalDateTime 获取信息的 API.
public int geYear()获取年
public int getMonthValue()获取月份( 1-12 )
Public int getDayOfMonth()获取月中第几天
Public int getDayOfYear()获取年中第几天
Public DayOfWeek getDayOfWeek()获取星期
 3. LocalDateTime 的转换 API
public LocalDate toLocalDate()转换成一个 LocalDate 对象
public LocalTime toLocalTime()转换成一个 LocalTime 对象
4. 修改相关的 API
plusDays, plusWeeks,plusMonths,plusYears从当前 LocalDate 对象添加几天、 几周、几个月、几年
minusDays, minusWeeks, minusMonths, minusYears从当前 LocalDate 对象减去几天、 几周、几个月、几年
withDayOfMonth, withDayOfYear, withMonth, withYear将月份天数、年份天数、月份、年 份 修 改 为 指 定 的 值 并 返 回 新 的
LocalDate 对象
isBefore, isAfter比较两个 LocalDate

2. Instant

JDK8 获取时间戳特别简单,且功能更丰富。 Instant 类由一个静态的工厂方法 now() 可以返回当前时间戳。

Instant instant = Instant.now();
System.out.println(" 当前时间戳是: " + instant);

Date date = Date.from(instant);
System.out.println(" 当前时间戳是: " + date);

instant = date.toInstant();
System.out.println(instant);

3. DateTimeFormatter

LocalDateTime idt = LocalDateTime.now();
System.out.println(ldt);//2021-03-01T15:09:17.444190900

DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String ldtStr = ldt.format(dtf);
System.out.println(ldtStr);//2021-03-01 15:09:17

String ldtStr1 = dtf.format(ldt);
System.out.println(ldtStr1);//2021-03-01 15:09:17

4. Duration/Period

1. Period

在 Java8 中,我们可以使用以下类来计算日期间隔差异: java.time.Period
主要是 Period 类方法 getYears() , getMonths() 和 getDays() 来计算 , 只能精确到年月日。
用于 LocalDate 之间的比较。

LocalDate today = LocalDate.now();

System.out.println(today); // 2021-03-01

LocalDate birthDate = LocalDate.of(1995, 1, 11);

System.out.println(birthDate); // 1995-01-11

Period period = Period.between(birthDate, today);

System.out.printf(" 年龄 : %d 年 %d 月 %d 日 ", period.getYears(), period.getMonths(),period.getDays());

2. Duration

在 Java8 中,我们可以使用以下类来计算时间间隔差异: java.time.Duration
提供了使用基于时间的值测量时间量的方法。
用于 LocalDateTime 之间的比较。也可用于 Instant 之间的比较。

LocalDateTime today = LocalDateTime.now();
System.out.println(today);
LocalDateTime birthDate = LocalDateTime.of(1990,10,1,10,50,30);
System.out.println(birthDate);

Duration duration = Duration.between(birthDate, today);//第二个参数减第一个参数
System.out.println(duration.toDays());//两个时间差的天数
System.out.println(duration.toHours());//两个时间差的小时数
System.out.println(duration.toMinutes());//两个时间差的分钟数
System.out.println(duration.toMillis());//两个时间差的毫秒数
System.out.println(duration.toNanos());//两个时间差的纳秒数

5. ChronoUnit 

ChronoUnit 类可用于单个时间内测量一段时间,此工具类最全,用于比较所有时间单位。

LocalDateTime today = LocalDateTime.now();
System.out.println(today);
LocalDateTime birthDate = LocalDateTime.of(1990,10,1,10,50,30);
System.out.println(birthDate);

System.out.println(" 相差的年数: " + ChronoUnit.YEARS.between(birthDate, today));
System.out.println(" 相差的月数: " + ChronoUnit.MONTHS.between(birthDate, today));
System.out.println(" 相差的周数: " + ChronoUnit.WEEKS.between(birthDate, today));
System.out.println(" 相差的天数: " + ChronoUnit.DAYS.between(birthDate, today));
System.out.println(" 相差的时数: " + ChronoUnit.HOURS.between(birthDate, today));
System.out.println(" 相差的分数: " + ChronoUnit.MINUTES.between(birthDate, today));
System.out.println(" 相差的秒数: " + ChronoUnit.SECONDS.between(birthDate, today));
System.out.println(" 相差的毫秒数: " + ChronoUnit.MILLIS.between(birthDate, today));
System.out.println(" 相差的微秒数: " + ChronoUnit.MICROS.between(birthDate, today));
System.out.println(" 相差的纳秒数: " + ChronoUnit.NANOS.between(birthDate, today));

System.out.println(" 相差的半天数: " + ChronoUnit.HALF_DAYS.between(birthDate, today));
System.out.println(" 相差的十年数: " + ChronoUnit.DECADES.between(birthDate, today));
System.out.println(" 相差的世纪(百年)数: " + ChronoUnit.CENTURIES.between(birthDate, today));
System.out.println(" 相差的千年数: " + ChronoUnit.MILLENNIA.between(birthDate, today));
System.out.println(" 相差的纪元数: " + ChronoUnit.ERAS.between(birthDate, today));

3. 包装类

包装类就是基本类型的引用类型,他们可以互相等于转换,因为底层完成了装箱和拆箱。 

byteByte
intInteger
shortShort
longLong
charCharacter
float

Float

doubleDouble
boolean

Boolean

4. 正则表达式

字符串对象提供了匹配正则表达式的方法

public boolean matches(String refex)判断是否匹配正则表达式,匹配返回 true ,反之

 1. 正则表达式组成部分

字符类

[abc] 只能是 a, b, 或 c
[^abc] 除了 a, b, c 之外的任何字符
[a-zA-Z] a 到 z A 到 Z ,包括(范围)
[a-d[m-p]] a 到 d ,或 m 通过 p :( [a-dm-p]
联合)
[a-z&&[def]] d, e, 或 f( 交集 )
[a-z&&[^bc]] a 到 z ,除了 b 和 c :( [ad-
z] 减法)
[a-z&&[^m-p]] a 到 z ,除了 m 到 p :( [a-lq-z]

预定义的字符类 ( 默认匹配一个字符 )

. 任何字符
\d 一个数字: [0-9]
\D 非数字: [^0-9]
\s 一个空白字符: [ \t\n\x0B\f\r]
\S 非空白字符: [^\s]
\w [a-zA-Z_0-9] 英文、数字、下划线
\W [^\w] 一个非单词字符

 贪婪的量词(配合匹配多个字符)

X? X , 一次或根本不
X* X ,零次或多次
X+ X , 一次或多次
X {n} X ,正好 n 次
X {n, } X ,至少 n 次
X {n,m}X ,至少 n 但不超过 m 次

 2. 正则表达式在字符串方法中的使用

public String replaceAll(String regex,String newStr)按照正则表达式匹配的内容进行替换
public String[] split(String regex) 按照正则表达式匹配的内容进行分割字符串,返回一个字符串数组。

 3. 例子

1[3-9]\\d{9}首位为1 + 第二位为3-9之间的数 + 9位任意数字
\\w{1,}@\\w{2,10}(\\.\\w{2,10}){1,2}英文、数字、下划线的至少 1 次 + @ + 英文、数字、下划线的至少两次但不超过10次 + 任何一个字符 和 英文、数字、下划线的至少两次但不超过10次这两个至少有1次但不超过2次

5. Arrays类

1. 常用API

public static String toString( 类型 [] a)返回数组的内容(字符串形式)
public static void sort( 类型 [] a)对数组进行默认升序排序
public static <T> void sort( 类型 [] a, Comparator<? super T> c)使用比较器对象自定义排序
public static int binarySearch(int[] a, int key)二分搜索数组中的数据,存在返回索引,不存在返回 -1

 2. 自定义的排序规则Comparator

设置 Comparator 接口对应的比较器对象,来定制比较规则

如果认为左边数据 大于 右边数据 返回正整数 (o1 > o2 )
如果认为左边数据 小于 右边数据 返回负整数 (o2 - o1)
如果认为左边数据 等于 右边数据 返回 0

6. 常见算法

1. 冒泡排序

思想:

一组数据,第一个数据跟第二个数据比,如果第一个数据大于第二个数据那么他俩就交换位置,反之则不做操作;接下来,第二个数据会跟第三个数据比大小,按照刚刚一二的规则比;接下来就是第三个数据和第四个数据,依次类推到最后一个数据;此时第一轮就已经将最大的数放到了最后,我们开始第二轮,就会将第二大的数放到倒数第二的位置,直至走完所有数据的长度的轮数,那么排序就完成了。

2. 选择排序

思想:

一组数据,第一个数据跟第二个数据比,如果第一个数据大于第二个数据那么他俩就交换位置,反之则不做操作;接下来,第一个数据会跟第三个数据比大小,按照刚刚一二的规则比;接下来就是第一个数据和第四个数据,依次类推到最后一个数据;此时第一轮就已经将最小的数放到了前面,我们开始第二轮,就会将第二小的数放到第二的位置,直至走完所有数据的长度的轮数,那么排序就完成了。

3. 二分查找

二分查找效率很高,但必须是排好序的数据

 数组的二分查找的实现步骤是什么样的 ?

定义变量记录左边和右边位置。(min - max)
使用 while 循环控制查询(条件是左边位置 <= 右边位置)
循环内部获取中间元素索引(min - max)/ 2
判断当前要找的元素如果大于中间元素,左边位置 = 中间索引 +1
判断当前要找的元素如果小于中间元素,右边位置 = 中间索引 -1
判断当前要找的元素如果等于中间元素,返回当前中间元素索引。

7. Lambda表达式

Lambda 表达式就是对匿名内部类进行简化书写的,省略写法如下:

参数类型可以省略不写。
如果只有一个参数,参数类型可以省略,同时 () 也可以省略。
如果 Lambda 表达式的方法体代码只有一行代码。可以省略大括号不写 , 同时要省略分号!
如果 Lambda 表达式的方法体代码只有一行代码。可以省略大括号不写。此时,如果这行代码是
return 语句,必须省略 return 不写,同时也必须省略 ";" 不写

 NO.5 集合

 1. 集合的体系特点

1. 集合类体系结构

集合有单列集合Collection 和双列集合Map;这里说Collection

2. Collection 集合特点

List 系列集合:有序、可重复、有索引

ArrayList:有序、可重复、有索引。

LinekdList :有序、可重复、有索引。


Set 系列集合:无序、不重复、无索引。
HashSet: 无序、不重复、无索引;

LinkedHashSet: 有序、不重复、无索引。
TreeSet :按照大小默认升序排序、不重复、无索引。

2. Collection 的常用方法

Collection 是单列集合的祖宗接口,它的功能是全部单列集合都可以继承使用的。

public boolean add(E e)把给定的对象添加到当前集合中
public void clear()清空集合中所有的元素
public boolean remove(E e)把给定的对象在当前集合中删除
public boolean contains(Object obj)判断当前集合中是否包含给定的对象
public boolean isEmpty()判断当前集合是否为空
public int size()返回集合中元素的个数。
public Object[] toArray()把集合中的元素,存储到数组中

3. 集合的遍历方式

1. 迭代器

直接看代码:

Iterator<String> it = lists.iterator();
    while(it.hasNext()){
        String ele = it.next();
        System.out.println(ele);
    }

2. 增强for循环

基础知识不再描述。

3. lamdbda表达式

直接看代码:

List<String> arr = new ArrayList<>();
//使用匿名内部类
arr.forEach(new Consumer<String>() {
    @Override
    public void accept(String s) {
        System.out.println(s);
    }
});
//使用Lambda表达式
arr.forEach(s ->System.out.println(s));

 4. 集合存储自定义类型的对象

集合中存储我们自定义的对象,存储的是对象地址。我们分析下底层:

我们创建一个集合会在堆内存开辟一个空间,这个集合长度是不定的,每当我们存入一个自定义的对象,先在堆内存中开辟一个空间放入对象数据,然后集合中会存入这个对象数据的地址。

5. 常见数据结构

1. 栈、队列

栈:先进后出

队列:先进先出

2. 数组

数组是一种查询快,增删慢的模型。
查询速度快:查询数据通过地址值和索引定位,查询任意数耗时相同,每个元素在内存中连续存储。

添加效率极低:添加位置后的每个数据后移,再添加元素。
删除效率低:要将原始数据删除,同时后面每个数据前移。

3. 链表

链表中的元素是在内存中不连续存储的,每个元素节点包含数据值和下一个元素的地址。

链表分单向链表和双向链表,单向存储下一个结点地址,双向存储前一个和下一个的地址。

链表查询慢。无论查询哪个数据都要从头开始找。

链表增删速率快,删除一个元素只需要将上一个节点地址更改为下一个节点地址,修改一个元素只需要将上一个节点地址更改为新节点的地址并指向下一个节点地址。

4. 二叉树、 二叉查找树

1. 二叉树

二叉树就是存储第一个数据 A 当根节点,此时 A 存有数据、左子节点位置和右子节点位置,第二个数据会使 A 当父节点,并记录着父节点位置、数据、左子节点位置和右子节点位置,第三个数据也会像第二个数据一样不断向下保存数据。

2. 二叉查找树

对二叉树进行了优化,使得在查找时可以提高查找效率

存储第一个数据会产生 根节点 ,再来一个数据如果大于根节点往右存作为右子树位置,再来一个数据如果小于根节点往左存处于左子树位置,再来一个数据小于根节点往右存,此时此数据会和处在右子树位置的节点判断,依然是大于往右,小于往左。

5. 平衡二叉树

因二叉查找树在存储数据时,可能导致一方数据被连接很长,比如在存储一个比一个数大的数据时,会导致于右子树的每个右节点都不断挂着数据,这种结构又跟链表一样了,导致查找数据效率很低,因此需要平衡二叉树。 平衡二叉树要求:任意节点的左右两个子树的高度差不超过 1 ,任意节点的左右两个子树都是一颗平衡二叉树。

添加数据时,当一个节点的左节点和右节点高度差大于 1 时,左右节点,哪个是多的一方,就需要对那个节点进行下面四种不同情况的不同策略。

1)在一个节点 A 的左节点 B 的左节点 C 上添加数据导致不平衡(左左)。

对 A 一个右旋。此时指向 B 的数据将指向 A 。

2)在一个节点 A 的右节点 B 的右节点 C 上添加数据导致不平衡(右右)。

对 A 一个左旋。此时指向 B 的数据将指向 A 。

3)在一个节点 A 的左节点 B 的右节点 C 上添加数据导致不平衡(左右)。

对 B 一个左旋,变成(左左),接着(左左)的右旋操作

4)在一个节点 A 的右节点 B 的左节点 C 上添加数据导致不平衡(右左)。

对 B 一个右旋,变成(右右),接着(右右)的左旋操作

6. 红黑树

红黑规则:

每一个节点或是红色的,或者是黑色的,根节点必须是黑色
如果一个节点没有子节点或者父节点,则该节点相应的指针属性值为 Nil ,这些 Nil 视为叶节点,每个叶节点 (Nil) 是黑色的;
如果某一个节点是红色,那么它的子节点必须是黑色 ( 不能出现两个红色节点相连的情况 )
对每一个节点,从该节点到其所有后代叶节点的简单路径上,均包含相同数目的黑色节点。

6. List 系列集合

1. List特有方法

void add(int index,E element)在此集合中的指定位置插入指定的元素
E remove(int index)删除指定索引处的元素,返回被删除的元素
E set(int index,E element)修改指定索引处的元素,返回被修改的元素
E get(int index)返回指定索引处的元素

2. ArrayList

ArrayList 底层是基于数组实现的:根据索引定位元素快,增删需要做元素的移位操作。

第一次创建集合并添加第一个元素的时候,在底层创建一个默认长度为 10 的数组。

1. ArrayList 底层的扩容机制

判断加一个元素后,数组大小是够超出当前数组的大小,若超出,则执行扩容代码;新数组的大小其实就是( oldCapacity + (oldCapacity >> 1)),通过位运算,原来就是原数组的长度加上原数组的长度大小的一半(位运算,右移一位相当于整体1/2);如 ArrayList 的容量为10,一次扩容后是容量为15

2. LinkedList

底层数据结构是双链表,查询慢,首尾操作的速度是极快的,所以多了很多首尾操作的特有

LinkedList特有的方法
public void addFirst(E e)

在该列表开头插入指定的元素

public void addLast(E e)将指定的元素追加到此列表的末尾
public E getFirst()返回此列表中的第一个元素
public E getLast()返回此列表中的最后一个元素
public E removeFirst()从此列表中删除并返回第一个元素
public E removeLast()从此列表中删除并返回最后一个元素

7. 集合的并发修改异常问题

当我们在遍历的过程中要进行删除操作时,很有可能出现问题,下面介绍四种遍历方式都是怎么出现异常的。

哪些遍历存在问题?

1. 迭代器遍历集合

直接用集合删除元素的时候出现异常;用迭代器自己的删除方法操作可以解决。

2. 增强 for 循环遍历集合

增强 for 循环不可能解决问题。

3. for 循环遍历集合

完全可以解决,因为根据索引删除,可以使倒着遍历解决问题

4. forEach匿名内部类的循环集合

底层就是for循环,因此不可能解决问题。

//迭代器 - 集合自己方法 - 报错
Iterator<String> iter = list.iterator();
while (iter.hasNext()){
    String temp = iter.next();
    list.remove(temp);
}
//迭代器 - 迭代器自己方法 - 解决
Iterator<String> iter = list.iterator();
while (iter.hasNext()){
    iter.next();
    iter.remove();
}
//增强for循环 - 报错
for (String s : list) {
    list.remove(s);
}
//普通for循环 - 解决
for (int i = list.size() - 1; i >= 0; i--) {
    list.remove(list.get(i));
}
//forEach循环 - 报错
list.forEach((s) -> list.remove(s));

8. 泛型深入

泛型类

public class MyArrayList<T> {}

编译阶段可以指定数据类型,类似于集合的作用。

泛型方法

public <T> void show(T t) {}

方法中可以使用泛型接收一切实际类型的参数,方法更具备通用性。

泛型接口

public interface Data<E> {}

泛型接口可以让实现类选择当前功能需要操作的数据类型

1. 泛型的上下限

? extends Car: ? 必须是 Car 或者其子类 泛型上限
? super Car : ? 必须是 Car 或者其父类 泛型下限

 NO.6 Set、Collections、Map

1. Set系列集合

Set 集合的功能上基本上与 Collection 的 API 一致。

HashSet无序、不重复、无索引
LinkedHashSet有序、不重复、无索引
TreeSet排序、不重复、无索引

1.  HashSet 集合元素无序的底层原理:哈希表

1. 哈希表:

JDK8 之前的,底层使用数组 + 链表组成
JDK8 开始后,底层采用数组 + 链表 + 红黑树组成。

 哈希表的详细流程:

① 创建一个默认长度 16 ,默认加载因为 0.75 的数组,数组名 table
② 根据元素的哈希值跟数组的长度计算出应存入的位置
③ 判断当前位置是否为 null ,如果是 null 直接存入,如果位置不为 null ,表示
有元素, 则调用 equals 方法比较属性值,如果一样,则不存,如果不一样,则
存入数组。
④ 当数组存满到 16*0.75=12 时,就自动扩容,每次扩容原先的两倍

2. 哈希值:

是 JDK 根据对象的地址,按照某种规则算出来的 int 类型的数值。

Object的Api:public int hashCode() :返回对象的哈希值

同一个对象多次调用 hashCode() 方法返回的哈希值是相同的
默认情况下,不同对象的哈希值是不同的。

2. LinkedHashSet

底层数据结构是依然哈希表,只是每个元素又额外的多了一个双链表的机制记录存储的顺序。

3. TreeSet 集合概述和特点

可排序:按照元素的大小默认升序(有小到大)排序。
TreeSet 集合底层是基于红黑树的数据结构实现排序的,增删改查性能都较好。

TreeSet 集合是一定要排序的,可以将元素按照指定的规则进行排序。

1. 自定义排序规则

方式一
让自定义的类(如学生类)实现 Comparable 接口重写里面的 compareTo 方法来定制比较规则。
方式二
TreeSet 集合有参数构造器,可以设置 Comparator 接口对应的比较器对象,来定制比较规则。

两种方式中,关于返回值的规则:
如果认为第一个元素大于第二个元素返回正整数即可。
如果认为第一个元素小于第二个元素返回负整数即可。
如果认为第一个元素等于第二个元素返回 0 即可,此时 Treeset 集合只会保留一个元素,认为两者重复。
注意:如果 TreeSet 集合存储的对象有实现比较规则,集合也自带比较器,默认使用集合自带的比较器排序。

2. 可变参数

可变参数用在形参中可以接收多个数据。
可变参数的格式:数据类型 ... 参数名称

3. Collections

java.utils.Collections: 是集合工具类
作用: Collections 并不属于集合,是用来操作集合的工具类

1. 常用API

注意:使用范围:只能对于 List 集合的排序。

public static <T> boolean addAll(Collection<? super T> c, T... elements)给集合对象批量添加元素
public static void shuffle(List<?> list)打乱 List 集合元素的顺序
public static <T> void sort(List<T> list)将集合中元素按照默认规则排序
public static <T> void sort(List<T> list , Comparator<? super T> c)将集合中元素按照指定规则排序

4. Map

1. Map集合体系特点

Map集合的特点都是由键决定的。

Map 集合的键是无序 , 不重复的,无索引的,值不做要求(可以重复)。
Map 集合后面重复的键对应的值会覆盖前面重复键的值。
Map 集合的键值对都可以为 null 。

2. Map 集合实现类特点

HashMap: 元素按照键是无序,不重复,无索引,值不做要求。(与 Map 体系一致)
LinkedHashMap: 元素按照键是有序,不重复,无索引,值不做要求。
TreeMap :元素按照建是排序,不重复,无索引的,值不做要求。

3. Map API 

V put(K key, V value)添加元素
V remove(Object key)根据键删除键值对元素
void clear()移除所有的键值对元素
boolean containsKey(Object key)判断集合是否包含指定的键
boolean containsValue(Object value)判断集合是否包含指定的值
boolean isEmpty()判断集合是否为空
int size()集合的长度,也就是集合中键值对的个数

 4. Map集合遍历

方式一:键找值的方式遍历:先获取 Map 集合全部的键,再根据遍历键找值。

Set<K> keySet()获取所有键的集合
V get(Object key)根据键获取值

方式二:键值对的方式遍历,把“键值对“看成一个整体,难度较大。

Set<Map.Entry<K,V>> entrySet()获取所有键值对对象的集合
K getKey()获得键
V getValue()获取值

方式三: JDK 1.8 开始之后的新技术: Lambda 表达式。

default void forEach(BiConsumer<? super K, ? super V> action)结合 lambda 遍历 Map 集合

5. HashMap的特点和底层原理

由键决定:无序、不重复、无索引。 HashMap 底层是哈希表结构的。
依赖 hashCode 方法和 equals 方法保证键的唯一。
如果键要存储的是自定义对象,需要重写 hashCode 和 equals 方法。
基于哈希表。增删改查的性能都较好。

6. LinkedHashMap 集合概述和特点

由键决定:有序、不重复、无索引。
这里的有序指的是保证存储和取出的元素顺序一致
原理:底层数据结构是依然哈希表,只是每个键值对元素又额外的多了一个双链表的机制记录存储的顺序

7. TreeMap 集合概述和特点

由键决定特性:不重复、无索引、可排序
可排序:按照键数据的大小默认升序(有小到大)排序。只能对键排序。
注意: TreeMap 集合是一定要排序的,可以默认排序,也可以将键按照指定的规则进行排序
TreeMap 跟 TreeSet 一样底层原理是一样的

NO.7 Stream和异常体系

1. 不可变集合

1. 创建不可变集合

此集合不能被修改、添加、删除

static <E> List<E> of(E..elements)创建一个具有指定元素的List集合对象
static <E> Set<E> of(E..elements)创建一个具有指定元素的Set集合对象

static <K, V> Map<K, V> of(E..elements)

创建一个具有指定元素的Map集合对象

2. Stream流

目的是用于简化集合和数组操作的API

1. 集合和数组获取Stream流的方式

集合获取

default Stream<E> stream()获取当前集合对象的Stream流

数组获取

public static <T> Stream<T> stream(T[] array)获取当前数组的Stream流
public static <T> Stream<T> of(T...values)获取当前数组/可变数据的Stream流

2. Stream流常用API与终结方法

Stream<T> filter(Predicate<? super T> predicate)对流中数据进行过滤
Stream<T> limit(long maxSize)获取前几个元素
Stream<T> skip(long n)跳过前几个元素
Stream<T> distinct()去除流中重复数据,依赖hashCode和equals方法
static <T> Stream<T> concat(Stream a, Stream b)合并a流和b流
void forEach(Comsumer action)对流中每个元素进行遍历(终结方法)
long count()返回流中的元素数(终结方法)

3. Stream流收集方法

集合/数组才是开发的目的,用Stream流只是为了方便处理这些数据,因此我们需要一些手段通过Stream流再返回一个集合/数组,这就需要用到Stream流的收集方法。

R collect(Collector collector)开始收集Stream流,需要指定收集器

4. Collectors工具类提供收集流的方法

public static Collector toList()把元素收集到List中
public static Collector toSet()把元素收集到Set中
public static Collector toMap(Function keyMapper, Function valueMapper)把元素收集到Map中

3. 异常处理

1. 概念、体系

异常总类 Throwable 下有 Error(系统级别异常) 和 Exception(异常类,程序可以处理的异常);我们都是讨论 Exception,它分为 RuntimeException(运行时异常) 和 除RuntimeException 之外所有的异常(编译时异常);当系统捕获到运行时异样会直接抛给JVM虚拟机,JVM会直接结束程序。

2. 自定义异常

1. 自定义编译时异常

自定义编译时异常提醒更加强烈,因为他需要在编译阶段必须完成异常抛出。

1)定义一个类继承于 Exception;

2)重写它的构造器

3)在出现异常的地方用 throws new 自定义对象抛出

2. 自定义运行时异常

自定义运行时异常提醒没有这么强烈,他会在运行时出现异常时才抛出。

1)定义一个类继承于 rRuntimeException;

2)重写它的构造器

3)在出现异常的地方用 throws new 自定义对象抛出

NO.8 File类、递归、IO流

1. 日志技术

1. 日志概述

希望系统能记住某些数据是被谁操作的,比如被谁删除了?
想分析用户浏览系统的具体情况,以便挖掘用户的具体喜好?
当系统在开发或者上线后出现了 bug ,崩溃了,该通过什么去分析、定位bug ?

用来记录程序运行过程中的信息,并可以进行永久存储。好比生活中的日记,可以记录你生活的点点滴滴。

2.  Logback 

Java提供了日志接口 Commons Logging ,但是因为对 Commons Logging 接口不满意,有人就搞了 SLF4J 。因为对 Log4j 的性能不满意,有人就搞了 Logback , Logback 是基于 slf4j 的日志规范实现的框架。

 1. Logback 的 三个模块

slf4j-api :日志接口
logback-core :基础模块
logback-classic :功能模块,它完整实现了 slf4j API

2. 使用 Logback 第三方工具步骤

①:导入 Logback 框架到项目中去。在项目下新建文件夹 lib ,导入 Logback 的 jar 包到该文件夹下
② :将存放 jar 文件的 lib 文件夹添加到项目依赖库中去(Libraries)。
③ :将 Logback 的核心配置文件 logback.xml 直接拷贝到 src 目录下(必须是 src 下)。
④ :创建 Logback 框架提供的 Logger 日志对象,后续使用其方法记录系统的日志信息。

3. Logback 的 配置文件详解

对 Logback 日志框架的控制,都是通过核心配置文件 logback.xml 来实现的

1)输出位置设置、格式设置

通常都是 在 logback.xml 中的 <appender> 标签下设置输出位置

<!--输出位置到控制台-->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
       ...
</appender>
<!--输出位置到文件-->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
       ...
</appender>

3)日志级别设置

ALL 和 OFF 表示打开和关闭全部日志信息

日志级别还有: TRACE < DEBUG < INFO < WARN < ERROR ; 默认级别是 DEBUG 
作用:当在 logback.xml 文件中设置了某种日志级别后,系统将只输出当前级别,以及高于当前级别的日志。

具体在 标签的 level 属性中设置指定系统的日志
<root level="INFO">
   ...
</root>
 4. Logback的使用
//一般都要定义为 final 
public static final Logger LOGGER = LoggerFactory.getLogger("本类.class");

main方法{

    LOGGER.info("info级别的信息");

}

2. File类

1. 创建对象

public File(String pathname)根据文件路径创建文件对象
public File(String parent, String child)根据父路径名字符串和子路径名字符串创建文件对象
public File(File parent, String child)根据父路径对应文件对象和子路径名字符串创建文件对象

2. 绝对路径和相对路径

绝对路径:从盘符开始

File file1 = new File(“D:\\itheima\\a.txt”);
相对路径:不带盘符,默认直接到当前工程下的目录寻找文件。
File file3 = new File(“ 模块名 \\a.txt”);

3. File 类的判断文件类型、获取文件信息功能

public boolean isDirectory()判断此路径名表示的 File 是否为文件夹
public boolean isFile()判断此路径名表示的 File 是否为文件
public boolean exists()判断此路径名表示的 File 是否存在
public long length()返回文件的大小(字节数量)
public String getAbsolutePath()返回文件的绝对路径
public String getPath()返回定义文件时使用的路径
public String getName()返回文件的名称,带后缀
public long lastModified()返回文件的最后修改时间(时间毫秒值)

4. File 类创建文件的功能

public boolean createNewFile()创建一个新的空的文件
public boolean mkdir()只能创建一级文件夹
public boolean mkdirs()可以创建多级文件夹
public boolean delete()删除由此抽象路径名表示的文件或空文件夹

 5. File 类的遍历功能

 public String[] list()获取当前目录下所有的 " 一级文件名称 " 到一个字符串数组中去返回。
public File[] listFiles()获取当前目录下所有的 " 一级文件对象 " 到一个文件对象数组中去返回(重
点)(常用)

3. 递归

方法自己调用自己,这里演示两个

1.  求数问题递归

计算 1-n 的阶乘

public class testdmo4 {
    public static void main(String[] args) {
        System.out.println(jiecheng(5));
    }
    // fn = fn-1 * n
    public static int jiecheng(int n){
        if(n == 1){
            return 1;
        }
        return jiecheng(n - 1) * n;
    }
}

2. 其他形式递归

在D盘下寻找包含指定名称的文件

public class testdemo3 {
    public static void main(String[] args) {
        searchFile(new File("D:/"), "log");
    }
    public static void searchFile(File file, String name ){
        if(file != null && file.isDirectory()){//判断是否为目录
            File[] files = file.listFiles();//找到路径下所有文件
            if(files != null && files.length > 0){//如果它不是一个空文件夹
                for (File file1 : files) {//可以对文件夹进行遍历
                    if(file1.isFile()){//文件
                        if(file1.getName().contains(name)){
                            System.out.println("找到了:" + file1.getPath());
                        }
                    }else {//文件夹
                        searchFile(file1 , name);
                    }
                }
            }
       }else {
            System.out.println("不可以找");
        }
    }
}

 4. IO流

1. 概述

IO 流的作用就是读写文件数据的。

字节输入流 InputStream (读字节数据的)
字节输出流 OutoutStream (写字节数据出去的)
字符输入流 Reader (读字符数据的)

字符输出流 Writer (写字符数据出去的)

flush()刷新流,还可以继续写数据
close()关闭流,释放资源,但是在关闭之前会先刷新流。一旦关闭,就不能再写数据

2.  输入流

实现类有:

FileInputStream 与 FileReader

 1. FileInputStream常见API
public int read()每次读取一个字节返回,如果字节已经没有可读的返回 -1
public int read(byte[] buffer)每次使用字节数组来读取数据,返回读取的字节个数,如果没有可读返回 -1
2. FileReader
构造器
public FileReader(File file)创建字符输入流管道与源文件对象接通
public FileReader(String pathname)创建字符输入流管道与源文件路径接通
API
public int read()每次读取一个字符返回,如果字符已经没有可读的返回 -1
public int read(char[] buffer)每次读取一个字符数组,返回读取的字符个数,如果字符已经没有可读的返回 -1

3. 输出流

1. FileOutPutSteam
构造器
public FileOutputStream(File file)创建字节输出流管道与源文件对象接通
public FileOutputStream(File file ,boolean append)创建字节输出流管道与源文件对象接通,可追加数据
public FileOutputStream(String filepath)创建字节输出流管道与源文件路径接通
public FileOutputStream(String filepath, boolean append)创建字节输出流管道与源文件路径接通,可追加数据
API
public void write(int a)
 
写一个字节出去
public void write(byte[] buffer)写一个字节数组出去
public void write(byte[] buffer , int pos , int len)写一个字节数组的一部分出去。
2. FileWriter
构造器
public FileWriter(File file)创建字符输出流管道与源文件对象接通
public FileWriter(File file, boolean append)创建字符输出流管道与源文件对象接通,可追加数据
public FileWriter(String filepath)创建字符输出流管道与源文件路径接通
public FileWriter(String filepath, boolean append)创建字符输出流管道与源文件路径接通,可追加数据
API
void write(int c)写一个字符
void write(char[] cbuf)写入一个字符数组
void write(char[] cbuf, int off, int len)写入字符数组的一部分
void write(String str)写一个字符串
void write(String str, int off, int len)写一个字符串的一部分

4. 转换流

文件编码和读取的编码必须一致才不会乱码。

 1. 字符输入转换流: InputStreamReader
构造器
public InputStreamReader(InputStream is)可以把原始的字节流按照代码默认编码转换成字符输入流。几乎不用,与默认的 FileReader 一样。
public InputStreamReader(InputStream is , String charset)可以把原始的字节流按照指定编码转换成字符输入流,这样字符流中的字符就不乱码了 ( 重点 )
2. 字符输入转换流: OutputStreamWriter
构造器
public OutputStreamWriter(OutputStream os)可以把原始的字节输出流按照代码默认编码转换成字符输出流。几乎不用。
public OutputStreamWriter(OutputStream os , String charset)可以把原始的字节输出流按照指定编码转换成字符输出流 ( 重点 )

3. .getBytes( 编码 )

可以把字符以指定编码获取字节后再使用字节输出流写出去:“ 我爱你中国” .getBytes( 编码 )
也可以使用字符输出转换流实现。

5. 缓冲流

字节缓冲输入流: BufferedInputStream
字节缓冲输出流: BufferedOutputStream
字符缓冲输入流: BufferedReader
字符缓冲输出流: BufferedWriter

 1. 构造器
public BufferedInputStream(InputStream is)可以把低级的字节输入流包装成一个高级的缓冲字节输入流管道,从而提高字节输入流读数据的性能
public BufferedOutputStream(OutputStream os)可以把低级的字节输出流包装成一个高级的缓冲字节输出流,从而提高写数据的性能
public BufferedReader(Reader r)可以把低级的字符输入流包装成一个高级的缓冲字符输入流管道,从而提高字符输入流读数据的性能
public BufferedWriter(Writer w)可以把低级的字符输出流包装成一个高级的缓冲字符输出流管道,从而提高字符输出流写数据的性能
 2. 方法

BufferedReader新增功能

public String readLine()读取一行数据返回,如果读取没有完毕,无行可读返回 null

BufferedWriter新增功能

public void newLine()换行操作

6. 打印流

打印流可以实现方便、高效的打印数据到文件中去。打印流一般是指: PrintStream , PrintWriter 两个类。可以实现打印什么数据就是什么数据,例如打印整数 97 写出去就是 97 ,打印 boolean 的 true ,写出去就是 true 。

可以实现打印什么数据就是什么数据,例如打印整数 97 写出去就是 97 ,打印 boolean 的 true ,写出去就是 true 。
构造器 说明
 

1. PrintStream
构造器
public PrintStream(OutputStream os)打印流直接通向字节输出流管道
public PrintStream(File f)打印流直接通向文件对象
public PrintStream(String filepath)打印流直接通向文件路径
API
public void print(Xxx xx)打印任意类型的数据出去

2. PrintWriter
构造器
public PrintWriter(OutputStream os)打印流直接通向字节输出流管道
public PrintWriter (Writer w)打印流直接通向字符输出流管道
public PrintWriter (File f)打印流直接通向文件对象
public PrintWriter (String filepath)打印流直接通向文件路径
API
public void print(Xxx xx)打印任意类型的数据出去
3. 输出语句重定向

属于打印流的一种应用,可以把输出语句的打印位置改到文件。

PrintStream ps = new PrintStream(" 文件地址 ")
System.setOut(ps);

7. IO流释放资源方式 

1. 基本做法:手动释放资源
try{
    可能出现异常的代码 ;
}catch( 异常类名 变量名 ){
    异常的处理代码 ;
}finally{
    执行所有资源释放操作 ;
}
2. JDK7改进方案:资源用完最终自动释放
try( 定义流对象 ){
    可能出现异常的代码 ;
}catch( 异常类名 变量名 ){
    异常的处理代码 ;
}
3. JDK9改进方案:资源用完最终自动释放
定义输入流对象 ;
定义输出流对象 ;
try( 输入流对象;输出流对象 ){
    可能出现异常的代码 ;
}catch( 异常类名 变量名 ){
    异常的处理代码 ;
}

5. 对象序列化

1. 对象序列化

对象必须实现序列化接口(implements Serializable)

作用:以内存为基准,把内存中的对象存储到磁盘文件中去,称为对象序列化。

使用到的流是对象字节输出流: ObjectOutputStream

 ObjectOutputStream构造器

public ObjectOutputStream(OutputStream out)把低级字节输出流包装成高级的对象字节输出流

 ObjectOutputStream序列化方法

public final void writeObject(Object obj)把对象写出去到对象序列化流的文件中去

 2. 对象反序列化

对象必须实现序列化接口(implements Serializable)
作用:以内存为基准,把存储到磁盘文件中去的对象数据恢复成内存中的对象,称为对象反序列化

使用到的流是对象字节输入流: ObjectInputStream

 ObjectInputStream构造器

public ObjectInputStream(InputStream out)把低级字节输如流包装成高级的对象字节输入流

 ObjectInputStream序列化方法

public Object readObject()把存储到磁盘文件中去的对象数据恢复成内存中的对象返回

6. IO框架

commons-io 概述
commons-io 是 apache 开源基金组织提供的一组有关 IO 操作的类库,可以提高 IO 功能开发的效率。
commons-io 工具包提供了很多有关 io 操作的类。有两个主要的类 FileUtils, IOUtils

 FileUtils 主要有如下方法 :

String readFileToString(File file, String encoding)读取文件中的数据 , 返回字符串
void copyFile(File srcFile, File destFile)复制文件。
void copyDirectoryToDirectory(File srcDir, File destDir)复制文件夹。

NO.9 多线程

1. 多线程的创建

1. 继承 Thread 类 

public class testdemo1{
    public static void main(String[] args) {
        ThreadTest threadTest = new ThreadTest();
        Thread thread = new Thread(threadTest);
        thread.start();
        System.out.println("主线程启动");
    }
}
class ThreadTest extends Thread{
    @Override
    public void run() {
        System.out.println("子线程启动。。。");
    }
}

2. 实现 Runnable 接口

1. 标准
public class testdemo1{
    public static void main(String[] args) {
        ThreadTest threadTest = new ThreadTest();
        Thread thread = new Thread(threadTest);
        thread.start();
        System.out.println("主线程启动");
    }
}
class ThreadTest implements Runnable{
    @Override
    public void run() {
        System.out.println("子线程启动。。。");
    }
}
 2. 匿名内部类
public class testdemo1{
    public static void main(String[] args) {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("子线程启动");
            }
        });
        thread.start();
        System.out.println("主线程启动");
    }
}

3. JDK 5.0 新增:实现 Callable 接口

这种方式的优点是:可以得到线程执行的结果

public class testdemo1{
    public static void main(String[] args) throws Exception {
        Callable<String> threadTest = new ThreadTest();
        FutureTask<String> futureTask = new FutureTask<>(threadTest);//定义任务
        Thread thread = new Thread(futureTask);//把线程任务对象交给 Thread 处理。
        thread.start();//必须先跑起来,futureTask才能get到
        System.out.println("主线程启动");
        //获取子线程结果
        String result = futureTask.get();
        System.out.println(result);
    }
}
class ThreadTest implements Callable<String> {//定义返回结果类型
    @Override
    public String call()  {
        System.out.println("子线程启动");
        return "子线程的结果";
    }
}

 2. Thread 常用方法、构造器

构造器
public Thread(String name)可以为当前线程指定名称
public Thread(Runnable target)把 Runnable 对象交给线程对象
public Thread(Runnable target, String name)把 Runnable 对象交给线程对象,并指定线程名称
API
String getName()获取当前线程的名称,默认线程名称是 Thread- 索引
void setName(String name)设置线程名称
public static Thread currentThread()返回对当前正在执行的线程对象的引用
public static void sleep(long time)让线程休眠指定的时间,单位为毫秒。
public void run()线程任务方法
public void start()线程启动方法

3. 线程同步

实现线程同步:

加锁:让多个线程实现先后依次访问共享资源,这样就解决了安全问题

1. 同步代码块

对于实例方法建议使用 this 作为锁对象。
对于静态方法建议使用字节码(类名 .class )对象作为锁对象

synchronized (同步锁对象){
    同步共享的内容
}

2. 同步方法

public synchronized void test(String string){
     操作共享代码
}

3. Lock 锁 

public class testdemo2 {
    public static void main(String[] args){
        Lock lock = new ReentrantLock();
        lock.lock();//上锁
        //共享的代码
        lock.unlock();//释放
    }
}

4. 线程池

线程池就是一个可以复用线程的技术。

 1. 线程池的构造器

public ThreadPoolExecutor(int corePoolSize,
        int maximumPoolSize,
        long keepAliveTime,
        TimeUnit unit,
        BlockingQueue<Runnable> workQueue,
        ThreadFactory threadFactory,
        RejectedExecutionHandler handler)
参数一:指定线程池的线程数量(核心线程): corePoolSize(不能小于 0)
参数二:指定线程池可支持的最大线程数: maximumPoolSize (最大数量 >= 核心线程数量)
参数三:指定临时线程的最大存活时间: keepAliveTime(不能小于 0)
参数四:指定存活时间的单位 ( 秒、分、时、天 ) : unit(时间单位)
参数五:指定任务队列: workQueue(不能为 null)
参数六:指定用哪个线程工厂创建线程: threadFactory(不能为 null)
参数七:指定线程忙,任务满的时候,新任务来了怎么办: handler(不能为 null)

2. 常见面试题

临时线程什么时候创建啊?
答:新任务提交时发现核心线程都在忙,任务队列也满了,并且还可以创建临时线程,此时才会创建临时线程。
什么时候会开始拒绝任务?
答:核心线程和临时线程都在忙,任务队列也满了,新的任务过来的时候才会开始任务拒绝。

3. ThreadPoolExecutor 创建线程池对象示例

//核心线程3个,最大线程数量5,临时线程存活8秒,可以有6个任务等待...
ExecutorService pools = new ThreadPoolExecutor(3, 5
        , 8 , TimeUnit.SECONDS, new ArrayBlockingQueue<>(6),
        Executors.defaultThreadFactory() ,
        new ThreadPoolExecutor.AbortPolicy());

ExecutorService 的常用方法

void execute(Runnable command)执行任务 / 命令,没有返回值,一般用来执行 Runnable 任务
Future<T> submit(Callable<T> task)执行任务,返回未来任务对象获取线程结果,一般拿来执行 Callable 任务
void shutdown()等任务执行完毕后关闭线程池
List<Runnable> shutdownNow()立刻关闭,停止正在执行的任务,并返回队列中未执行的任务

4. ThreadPoolExecutor 处理 Runnable 与 Callable

1. Runnable
public class testdemo2 {
    public static void main(String[] args){
        ExecutorService pools = new ThreadPoolExecutor(3, 5
                , 8 , TimeUnit.SECONDS, new ArrayBlockingQueue<>(2),
                Executors.defaultThreadFactory() ,
                new ThreadPoolExecutor.AbortPolicy());
        pools.submit(new threadTest01());
    }
}
class threadTest01 implements Runnable{
    @Override
    public void run() {
        System.out.println("子线程...");
    }
}
2. Callable
public class testdemo2 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService pools = new ThreadPoolExecutor(3, 5
                , 8 , TimeUnit.SECONDS, new ArrayBlockingQueue<>(2),
                Executors.defaultThreadFactory() ,
                new ThreadPoolExecutor.AbortPolicy());
        threadTest01 threadTest01 = new threadTest01();
        FutureTask<String> futureTask = new FutureTask<>(threadTest01);
        pools.submit(futureTask);
        String s = futureTask.get();
        System.out.println(s);
        System.out.println("主线程启动");
    }
}
class threadTest01 implements Callable<String>{
    @Override
    public String call() throws Exception {
        System.out.println("子线程启动");
        return "子线程返回结果";
    }
}

5. Executors 工具类

1. Executors 得到线程池对象的常用方法

public static ExecutorService newCachedThreadPool()

线程数量随着任务增加而增加,如果线程任务执行完毕且空闲了一段时间则会被回收掉。
public static ExecutorService newFixedThreadPool(int nThreads)

创建固定线程数量的线程池,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程替代它。
public static ExecutorService newSingleThreadExecutor ()

创建只有一个线程的线程池对象,如果该线程出现异常而结束,那么线程池会补充一个新线程。
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)

创建一个线程池,可以实现在给定的延迟后运行任务,或者定期执行任务。

2. Executors 使用可能存在的陷阱

public static ExecutorService newFixedThreadPool(int nThreads)

public static ExecutorService newSingleThreadExecutor()

允许请求的任务队列长度是 Integer.MAX_VALUE ,可能出现 OOM错误。


public static ExecutorService newCachedThreadPool()
public static ScheduledExecutorService newScheduledThreadPool (int corePoolSize)

创建的线程数量最大上限是 Integer.MAX_VALUE ,线程数可能会随着任务 1:1 增长,也可能出现 OOM 错误( java.lang.OutOfMemoryError )

5. 定时器

定时器是一种控制任务演示调用,或者周期调用的技术

1.  Timer 定时器

1 、Timer 是单线程,处理多个任务按照顺序执行,存在延时与设置定时器的时间有出入。
2 、可能因为其中的某个任务的异常使 Timer 线程死掉,从而影响后续任务执行。

构造器与方法

public Timer()创建 Timer 定时器对象
public void schedule(TimerTask task, long delay, long period)开启一个定时器,按照计划处理 TimerTask 任务
    public static void main(String[] args) {
        Timer timer = new Timer();
        TimerTask timerTask = new TimerTask() {
            @Override
            public void run() {
                System.out.println("我会每1秒执行一次");
            }
        };
        timer.schedule(timerTask, 0, 1000);
    }

2. ScheduledExecutorService 定时器

基于线程池,某个任务的执行情况不会影响其他定时任务的执行。

 方法

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)

得到线程池对象

public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period,TimeUnit unit)
周期调度方法

6. 并发、并行

1. 并发

CPU 同时处理线程的数量有限。CPU 会轮询为系统的每个线程服务,由于 CPU 切换的速度很快,给我们的感觉这些线程在同时执行,这就是并发。

2. 并行

在同一个时刻上,同时有多个线程在被 CPU 处理并执行。

7. 线程的生命周期

1. 线程的六种状态

1)NEW( 新建 ) :线程刚被创建,但是并未启动。
2)Runnable( 可运行 ) :线程已经调用了 start() 等待 CPU 调度。
3)Blocked( 锁阻塞 ) :线程在执行的时候未竞争到锁对象,则该线程进入 Blocked 状态。
4)Waiting( 无限等待 ) :一个线程进入 Waiting 状态,另一个线程调用 notify 或者 notifyAll 方法才能够唤醒
5)Timed Waiting( 计时等待 ):同 waiting 状态,有几个方法有超时参数,调用他们将进入 Timed Waiting 状态。带有超时参数的常用方法有 Thread.sleep 、 Object.wait 。
6)Teminated( 被终止 ) :因为 run 方法正常退出而死亡,或者因为没有捕获的异常终止了 run 方法而死亡。

NO.10 网络编程

1. 网络通信三要素

IP 地址:设备在网络中的地址,是唯一的标识。
端口:应用程序在设备中唯一的标识。
协议 : 数据在网络中传输的规则,常见的协议有 UDP 协议和 TCP 协议。

1. IP地址

1. IP地址命令
ipconfig查看本机IP地址
ping xxx.xxx.xxx.xxx查看是否能连通此IP地址
2. IP地址操作类-InetAddress

InetAddress API 如下:

public static InetAddress getLocalHost()返回本主机的地址对象
public static InetAddress getByName(String host)得到指定主机的 IP 地址对象,参数是域名或者 IP 地址
public String getHostName()获取此 IP 地址的主机名
public String getHostAddress()返回 IP 地址字符串
public boolean isReachable(int timeout)在指定毫秒内连通该 IP 地址对应的主机,连通返回 true

2. 端口号

端口号:标识正在计算机设备上运行的进程(程序),被规定为一个 16 位的二进制,范围是0~65535 。

 端口类型
1)周知端口: 0~1023 ,被预先定义的知名应用占用(如: HTTP 占用 80 , FTP 占用 21)
2)注册端口: 1024~49151 ,分配给用户进程或某些应用程序。(如: Tomcat 占 用 8080 , MySQL占用 3306 )
3)动态端口: 49152 到 65535 ,之所以称为动态端口,是因为它 一般不固定分配某种进程,而是动态分配。

3. 协议

TCP(Transmission Control Protocol) :传输控制协议
UDP(User Datagram Protocol) :用户数据报协议

1. TCP协议

TCP 协议特点
使用 TCP 协议,必须双方先建立连接,它是一种面向连接的可靠通信协议。
传输前,采用“三次握手”方式建立连接,所以是可靠的 。
在连接中可进行大数据量的传输 。
连接、发送数据都需要确认,且传输完毕后,还需释放已建立的连接,通信效率较低。

TCP 协议通信场景
对信息安全要求较高的场景,例如:文件下载、金融等数据通信。

2. UDP协议

UDP 协议特点
UDP 是一种无连接、不可靠传输的协议。
将数据源 IP 、目的地 IP 和端口封装成数据包,不需要建立连接
每个数据包的大小限制在 64KB 内
发送不管对方是否准备好,接收方收到也不确认,故是不可靠的
可以广播发送 ,发送数据结束时无需释放资源,开销小,速度快。
UDP 协议通信场景
语音通话,视频会话等。

2. UDP 通信

1. 数据包对象与发送端和接收端对象

1. 使用DatagramPacket :数据包对象
构造器
public DatagramPacket(byte[] buf, int length, InetAddress address, int port)创建发送端数据包对象;buf :要发送的内容,字节数组;length :要发送内容的字节长度;address :接收端的 IP 地址对象;port :接收端的端口号。
public DatagramPacket(byte[] buf, int length)创建接收端的数据包对象;buf :用来存储接收的内容;length :能够接收内容的长度。
API
public int getLength()获得实际接收到的字节个数
2. 使用DatagramSocket :发送端和接收端对象
构造器
public DatagramSocket()创建发送端的 Socket 对象,系统会随机分配一个端口号。
public DatagramSocket(int port)创建接收端的 Socket 对象并指定端口号
API
public void send(DatagramPacket dp)发送数据包
public void receive(DatagramPacket p)接收数据包

2. UDP 的三种通信方式

单播:单台主机与单台主机之间的通信。
广播:当前主机与所在网络中的所有主机通信。

具体操作:(使用广播地址255.255.255.255)
① 发送端发送的数据包的目的地写的是广播地址、且指定端口( 255.255.255.255 , 9999 )
② 本机所在网段的其他主机的程序只要注册对应端口就可以收到消息了。( 9999 )
组播:当前主机与选定的一组主机的通信。

具体操作:(使用组播地址224.0.0.0 ~ 239.255.255.255)

① 发送端的数据包的目的地是组播 IP ( 例如: 224.0.1.1, 端口: 9999)
② 接收端必须绑定该组播 IP(224.0.1.1) ,端口还要注册发送端的目的端口 9999 ,这样即可接收该组播消息。
③ DatagramSocket 的子类 MulticastSocket 可以在接收端绑定组播 IP 。

3. TCP 通信

在 java 中只要是使用 java.net.Socket 类实现通信,底层即是使用了 TCP 协议

1. Socket(客户端)

构造器
public Socket(String host , int port)创建发送端的 Socket 对象与服务端连接,参数为服务端程序的 ip 和端口。
API
OutputStream getOutputStream()获得字节输出流对象
InputStream getInputStream()获得字节输入流对象

2. ServerSocket(服务端)

构造器
public ServerSocket(int port)注册服务端端口
API
public Socket accept()等待接收客户端的 Socket 通信连接
连接成功返回 Socket 对象与客户端建立端到端通信

3. TCP 通信 - 同时接受多个客户端消息

主线程定义了循环负责接收客户端 Socket 管道连接
 每接收到一个 Socket  通信管道后分配一个独立的线程负责处理它。

//通过线程池技术,完成 Socket 通信
public class SocketKeHu {
    public static void main(String[] arg) {//客户端
        try {
            System.out.println("=====客户端启动=====");
            Socket socket = new Socket("192.168.204.1", 1234);
            OutputStream outputStream = socket.getOutputStream();
            PrintStream printStream = new PrintStream(outputStream);
            Scanner scanner = new Scanner(System.in);
            while (true){
                System.out.println("请说:");
                String s = scanner.nextLine();
                printStream.println(s);
                printStream.flush();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
class SocketFuWu {
    public static void main(String[] args) {//服务端
        System.out.println("=====服务端启动=====");
        try {
            ExecutorService pools = new ThreadPoolExecutor(3, 5
                    , 8 , TimeUnit.SECONDS, new ArrayBlockingQueue<>(6),
                    Executors.defaultThreadFactory() ,
                    new ThreadPoolExecutor.AbortPolicy());
            ServerSocket serverSocket = new ServerSocket(1234);
            while (true){
                Socket accept = serverSocket.accept(); //重点就是要new管道
                Runnable myThread = new ThreadFor(accept);
                pools.execute(myThread);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
class ThreadFor implements Runnable{
    private Socket socket;
    public ThreadFor(Socket socket) {
        this.socket = socket;
    }
    @Override
    public void run() {
        try {
            InputStream inputStream = socket.getInputStream();
            InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
            BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
            String msg;
            while ((msg = bufferedReader.readLine()) != null){
                System.out.println(socket.getInetAddress()+"说了:"+msg);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

 NO.11 单元测试、反射、注解、动态代理

1. 单元测试

单元测试就是针对最小的功能单元编写测试代码, Java 程序最小的功能单元是方法,因此,单元测试就是针对Java 方法的测试,进而检查方法的正确性。

1. Junit 使用

1)加入依赖包

2)在测试方法上使用 @Test 注解:标注该方法是一个测试方法
3)在测试方法中完成被测试方法的预期正确性测试。
4)选中测试方法,选择“ JUnit 运行” ,如果测试良好则是绿色;如果测试失败,则是红色

2. Junit 常用注解

开始执行的方法 : 初始化资源。
执行完之后的方法 : 释放资源。

@Test测试方法
@Before用来修饰实例方法,该方法会在每一个测试方法执行之前执行一次。
@After用来修饰实例方法,该方法会在每一个测试方法执行之后执行一次。
@BeforeClass用来静态修饰方法,该方法会在所有测试方法之前只执行一次。
@AfterClass用来静态修饰方法,该方法会在所有测试方法之后只执行一次。

2. 反射

1. 反射概述

反射的第一步都是先得到编译后的 Class 类对象,然后就可以得到 Class 的全部成分。

1)反射是指对于任何一个 Class 类,在 " 运行的时候 " 都可以直接得到这个类全部成分。
2)在运行时 , 可以直接得到这个类的构造器对象: Constructor
3)在运行时 , 可以直接得到这个类的成员变量对象: Field
4)在运行时 , 可以直接得到这个类的成员方法对象: Method
5)这种运行时动态获取类信息以及动态调用类中成分的能力称为 Java 语言的反射机制。

2. 获取Class类的三种方式

1)Class c1 = Class.forName("全类名");

2)Class c2 = 类名.class;

3)Class c3 = 对象.getClass();

3. 反射获取构造器对象

Constructor<?>[] getConstructors()返回所有构造器对象的数组(只能拿 public 的)
Constructor<?>[] getDeclaredConstructors()返回所有构造器对象的数组,存在就能拿到
Constructor<T> getConstructor(Class<?>... parameterTypes)返回单个构造器对象(只能拿 public 的)
Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)返回单个构造器对象,存在就能拿到

获取构造器对象后的使用

T newInstance(Object... initargs)根据指定的构造器创建对象
public void setAccessible(boolean flag)设置为 true, 表示取消访问检查,进行暴力反射

4. 反射获取成员变量对象

Field[] getFields()返回所有成员变量对象的数组(只能拿 public 的)
Field[] getDeclaredFields()返回所有成员变量对象的数组,存在就能拿到
Field getField(String name)返回单个成员变量对象(只能拿 public 的)
Field getDeclaredField(String name)返回单个成员变量对象,存在就能拿到

获取成员变量对象的使用

void set(Object obj, Object value) :赋值。
Object get(Object obj)获取值。

5. 反射获取方法对象

Method[] getMethods()返回所有成员方法对象的数组(只能拿 public的)
Method[] getDeclaredMethods()返回所有成员方法对象的数组,存在就能拿到
Method getMethod(String name, Class<?>... parameterTypes)返回单个成员方法对象(只能拿 public 的)
Method getDeclaredMethod(String name, Class<?>... parameterTypes)返回单个成员方法对象,存在就能拿到

获取方法对象的使用

Object invoke(Object obj, Object... args)


参数一:用 obj 对象调用该方法;

参数二:调用方法的传递的参数(如果没有就不写);

返回值:方法的返回值(如果没有就不写)
 

6. 反射的作用

1. 绕过编译阶段为集合添加数据

反射是作用在运行时的技术,此时集合的泛型将不能产生约束了,此时是可以为集合存入其他任意类型的元素的。

2. 通用框架的底层原理

反射可以做通用框架;比如:

给你任意一个对象,在不清楚对象字段的情况可以,可以把对象的字段名称和对应值存储到文件中去。此时需要用到反射。

public class file {
    public static void up(Object object){
        Class cls = object.getClass();
        Field[] declaredFields = cls.getDeclaredFields();
        for (Field field : declaredFields) {
            try {
                String fieName = field.getName(); // getName 可以拿取方法/变量/构造器 名字
                field.setAccessible(true);
                String val = field.get(object) + ""; //get 可以拿取对象中的值
                System.out.println(fieName + " ----> " + val);
                //这里不做输出流输出到文件了。
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

3. 注解

1. 自定义注解

value 属性,如果只有一个 value 属性的情况下,使用 value 属性的时候可以省略 value 名称不写 !!
但是如果有多个属性 , 且多个属性没有默认值,那么 value 名称是不能省略的。

public @interface 注解名称 {
    public 属性类型 属性名 () default 默认值 ;
}

2. 元注解

元注解:注解注解的注解。

@Target: 约束自定义注解只能在哪些地方使用,
@Retention :申明注解的生命周期

@Target 中可使用的值定义在 ElementType 枚举类中,常用值如下
1)TYPE ,类,接口
2)FIELD, 成员变量
3)METHOD, 成员方法
4)PARAMETER, 方法参数
5)CONSTRUCTOR, 构造器
6)LOCAL_VARIABLE, 局部变量

@Retention 中可使用的值定义在 RetentionPolicy 枚举类中,常用值如下
1) SOURCE : 注解只作用在源码阶段,生成的字节码文件中不存在
2) CLASS : 注解作用在源码阶段,字节码文件阶段,运行阶段不存在,默认值 .
3) RUNTIME :注解作用在源码阶段,字节码文件阶段,运行阶段(开发常用)

3. 注解解析

与注解解析相关的接口
1)Annotation: 注解的顶级接口,注解都是 Annotation 类型的对象
2)AnnotatedElement: 该接口定义了与注解解析相关的解析方法

提示:所有的类成分 Class, Method , Field , Constructor ,都实现了 AnnotatedElement 接口他们都拥有解析注解的能力

方法
Annotation[] getDeclaredAnnotations()获得当前对象上使用的所有注解,返回注解数组。
T getDeclaredAnnotation(Class<T> annotationClass)根据注解类型获得对应注解对象
boolean isAnnotationPresent(Class<Annotation> annotationClass)判断当前对象是否使用了指定的注解,如果使用了则返回 true ,否则 false

技巧
1)注解在哪个成分上,我们就先拿哪个成分对象。
2)比如注解作用成员方法,则要获得该成员方法对应的 Method 对象,再来拿上面的注解
3)比如注解作用在类上,则要该类的 Class 对象,再来拿上面的注解
4)比如注解作用在成员变量上,则要获得该成员变量对应的 Field 对象,再来拿上面的注解

4. 动态代理

提出问题:

当我们编写代码时,有100个功能,在这100个功能中都需要记录开始时间与结束时间,从而测试功能的性能。难道我们要编写100个重复的代码吗。

解决问题:

动态代理可以将代码像切面编程一样,可以在功能里插入一些功能,也叫代理。

案例:性能测试,三个功能都需要完成性能测试,采用动态管理做出来。

public class TestTime {
    public static void main(String[] args) {
        UserService userService = ProxyUtils.getProxy(new UserServiceImpl());
        userService.save();
        userService.delete();
        userService.update();
    }
}

class ProxyUtils{
    //需要对这个对象进行动态代理
    public static UserService getProxy(UserService userService){
        /*     参数详解
        1. 类加载器,负责将代理类加载到内存 - 固定写法
        2. 获取被代理对象实现的所有接口 - 固定写法
        3. 真正的核心代码,需要执行的操作   */
        return (UserService)Proxy.newProxyInstance(userService
                        .getClass().getClassLoader(),
                userService.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, 
                                         Object[] args) throws Throwable {
                        /*     参数详解
                        1. 代理对象本事,一般不管
                        2. 正在被代理的方法
                        3. 被代理的方法的参数    */
                        long currentTimeMillis1 = System.currentTimeMillis();
                        //触发真正的业务功能
                        Object result = method.invoke(userService, args);
                        long currentTimeMillis2 = System.currentTimeMillis();
                        System.out.println(method.getName() + "方法用时:"+
                                (currentTimeMillis2-currentTimeMillis1) + "ms");
                        return result;
                    }
                });
    }
}

interface UserService{
    void delete();
    void save();
    void update();
}

class UserServiceImpl implements UserService{
    public void delete(){
        try {
            System.out.println("正在进行删除操作。。。需要1秒");
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    public void save() {
        try {
            System.out.println("正在进行保存操作。。。需要0.5秒");
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    public void update() {
        try {
            System.out.println("正在进行更新操作。。。需要1.5秒");
            Thread.sleep(1500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

 NO.12 XML 、 XML 解析、设计模式等

1. XML

1. XML 概述

它是是一种数据表示格式,可以用于自定义数据格式。

一是纯文本,默认使用 UTF-8 编码;二是可嵌套;
如果把 XML 内容存为文件,那么它就是一个 XML 文件。

XML 内容经常被当成消息进行网络传输,或者作为配置文件用于存储系统的信息。

2. XML 的创建、语法规则

第一行必须是这个文档说明:

<?xml version="1.0" encoding="UTF-8" ?>

version : XML 默认的版本号码、该属性是必须存在的
encoding :本 XML 文件的编码

XML其他组成

XML 文件中可以定义注释信息: <!– 注释内容 -->

XML 文件中可以存在 CDATA 区 : <![CDATA[ ... 内容... ]]>
XML 文件中可以存在以下特殊字符

&lt;< 小于
&gt;> 大于
&amp;& 和号
&apos;' 单引号
&quot;" 引号

3. XML 的约束

由于 XML 文件可以自定义标签,导致 XML 文件可以随意定义,程序在解析的时候可能出现问题。

1. DTD约束 - 了解

步骤:

① :编写 DTD 约束文档,后缀必须是 .dtd
② :在需要编写的 XML 文件中导入该 DTD 约束文档
③ :按照约束的规定编写 XML 文件的内容。
 

<!ELEMENT 书架 ( 书 +)>
<!ELEMENT 书 ( 书名 , 作者 , 售价 )>
<!ELEMENT 书名 (#PCDATA)>
<!ELEMENT 作者 (#PCDATA)>
<!ELEMENT 售价 (#PCDATA)>

注意:不能约束具体的数据类型。

2. schema约束 - 了解

步骤:

① :编写 schema 约束文档,后缀必须是 .xsd ,具体的形式到代码中观看。
② :在需要编写的 XML 文件中导入该 schema 约束文档
③ :按照约束内容编写 XML 文件的标签。

2. XML解析技术

存储数据、做配置信息、进行数据传输。
最终需要被程序进行读取,解析里面的信息。

1. Dom 解析工具

常见的解析工具:

JAXP

SUN 公司提供的一套 XML 的解析的 API
JDOM

JDOM 是一个开源项目,它基于树型结构,利用纯 JAVA 的技术对 XML 文档实现解析、生成
、序列化以及多种操作。
dom4j
是 JDOM 的升级品,用来读写 XML 文件的。具有性能优异、功能强大和极其易使用的特点,它的性能超过 sun 公司官方的 dom 技术,同时它也是一个开放源代码的软件, Hibernate也用它来读写配置文件。
jsoup

功能强大 DOM 方式的 XML 解析开发包,尤其对 HTML 解析更加方便

DOM 解析解析文档对象模型 

Document 对象:整个 xml 文档
Element 对象:标签
Attribute 对象:属性

Text 对象:文本内容
Node 对象:包含了Element、Attribute、Text

2. Dom4J 解析 XML 文件

1. Dom4j 解析 XML- 得到 Document 对象
SAXReader类
public SAXReader()创建 Dom4J 的解析器对象
Document read(String url)加载 XML 文件成为 Document 对象
Document类
Element getRootElement()获得根元素对象
2. Dom4j 解析 XML 的元素、属性、文本
List<Element> elements()得到当前元素下所有子元素
List<Element> elements(String name)得到当前元素下指定名字的子元素返回集合
Element element(String name)得到当前元素下指定名字的子元素 , 如果有很多名字相同的返回第一个
String getName()得到元素名字
String attributeValue(String name)通过属性名直接得到属性值
String elementText( 子元素名 )得到指定名称的子元素的文本
String getText()得到文本

3. XML 检索技术: Xpath

XPath 在解析 XML 文档方面提供了一独树一帜的路径思想,更加优雅,高效
XPath 使用路径表达式来定位 XML 文档中的元素节点或属性节点。

示例:    / 元素 / 子元素 / 孙元素
               // 子元素 // 孙元素

1. Document 中与 Xpath 相关的 API:
Node selectSingleNode(" 表达式 ")获取符合表达式的唯一元素
List<Node> selectNodes(" 表达式 ")获取符合表达式的元素集合
2. Xpath 的四大检索方案

①绝对路径        ②相对路径        ③全文检索        ④属性查找

①绝对路径

采用绝对路径获取从根节点开始逐层的查找 /contactList/contact/name 节点列表并打印信息

/ 根元素 / 子元素 / 孙元素从根元素开始,一级一级向下查找,不能跨级
②相对路径

先得到根节点 contactList
再采用相对路径获取下一级 contact 节点的 name 子节点并打印信息

./ 子元素 / 孙元素从当前元素开始,一级一级向下查找,不能跨级
③全文检索

直接全文搜索所有的 name 元素并打印

//contact找 contact 元素,无论元素在哪里
//contact/name找 contact ,无论在哪一级,但 name 一定是 contact 的子节点
//contact//name contact无论在哪一种, name 只要是 contact 的子孙元素都可以找到
④属性查找

在全文中搜索属性,或者带属性的元素

//@ 属性名查找属性对象,无论是哪个元素,只要有这个属性即可。
// 元素 [@ 属性名 ]查找元素对象,全文搜索指定元素名和属性名。
// 元素 //[@ 属性名 =‘ 值’ ]查找元素对象,全文搜索指定元素名和属性名,并且属性值相等。

3. 设计模式

1. 工厂模式

概述:

之前我们创建类对象时 , 都是使用 new 对象的形式创建 , 在很多业务场景下也提供了不直接 new 的方式 。
工厂模式( Factory Pattern )是 Java 中最常用的设计模式之一, 这种类型的设计模式属于创建型模式,它提供了一种获取对象的方式。

作用
工厂的方法可以封装对象的创建细节,比如:为该对象进行加工和数据注入。
可以实现类与类之间的解耦操作(核心思想)。

public class Test {
    public static void main(String[] args) {
        ShapeFactory shapeFactory = new ShapeFactory();
        //获取 Circle 的对象,并调用它的 draw 方法
        Shape shape1 = shapeFactory.getShape("CIRCLE");
        //调用 Circle 的 draw 方法
        shape1.draw();
        //获取 Rectangle 的对象,并调用它的 draw 方法
        Shape shape2 = shapeFactory.getShape("RECTANGLE");
        //调用 Rectangle 的 draw 方法
        shape2.draw();
        //获取 Square 的对象,并调用它的 draw 方法
        Shape shape3 = shapeFactory.getShape("SQUARE");
        //调用 Square 的 draw 方法
        shape3.draw();
    }
}

class ShapeFactory {
    //使用 getShape 方法获取形状类型的对象
    public Shape getShape(String shapeType){
        if(shapeType == null){
            return null;
        }
        if(shapeType.equalsIgnoreCase("CIRCLE")){
            return new Circle();
        } else if(shapeType.equalsIgnoreCase("RECTANGLE")){
            return new Rectangle();
        } else if(shapeType.equalsIgnoreCase("SQUARE")){
            return new Square();
        }
        return null;
    }
}

interface Shape {
    void draw();
}

class Rectangle implements Shape {
    @Override
    public void draw() {
        System.out.println("Inside Rectangle::draw() method.");
    }
}

class Square implements Shape {
    @Override
    public void draw() {
        System.out.println("Inside Square::draw() method.");
    }
}

class Circle implements Shape {
    @Override
    public void draw() {
        System.out.println("Inside Circle::draw() method.");
    }
}

2. 装饰模式

概述:

创建一个新类,包装原始类,从而在新类中提升原来类的功能。

作用:

装饰模式指的是在不改变原类的基础上 , 动态地扩展一个类的功能。

public class Test {
    public static void main(String[] args) {
        Shape circle = new Circle();
        ShapeDecorator redCircle = new RedShapeDecorator(new Circle());
        ShapeDecorator redRectangle = new RedShapeDecorator(new Rectangle());
        //Shape redCircle = new RedShapeDecorator(new Circle());
        //Shape redRectangle = new RedShapeDecorator(new Rectangle());
        System.out.println("Circle with normal border");
        circle.draw();
        System.out.println("\nCircle of red border");
        redCircle.draw();
        System.out.println("\nRectangle of red border");
        redRectangle.draw();
    }
}

interface Shape {
    void draw();
}

class Rectangle implements Shape {
    @Override
    public void draw() {
        System.out.println("Shape: Rectangle");
    }
}

class Circle implements Shape {
    @Override
    public void draw() {
        System.out.println("Shape: Circle");
    }
}

abstract class ShapeDecorator implements Shape {
    protected Shape decoratedShape;
    public ShapeDecorator(Shape decoratedShape){
        this.decoratedShape = decoratedShape;
    }
    public void draw(){
        decoratedShape.draw();
    }
}

class RedShapeDecorator extends ShapeDecorator {
    public RedShapeDecorator(Shape decoratedShape) {
        super(decoratedShape);
    }
    @Override
    public void draw() {
        decoratedShape.draw();
        setRedBorder(decoratedShape);
    }
    private void setRedBorder(Shape decoratedShape){
        System.out.println("Border Color: Red");
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值