0%

一.概述
1.1 什么是数据结构
把数据元素按照一定关系组织起来的集合,用来组织和存储数据

1.2 数据结构分类
逻辑结构分类:
按照数据与数据之间关系分类,是抽象意义上的数据结构

分类 含义
集合结构 数据元素同属同一个集合,但是不存在任何关系
线性结构 数据元素与数据结构之间存在一对一关系(链表)
树形结构 数据元素之间存在一对多关系(树结构)
图结构 数据元素之间存在多对多关系
物理结构:
逻辑结构在计算机中真正的表示方式,也可以叫做存储结构

分类 含义 优缺点
顺序结构 把数据元素放到地址连续存储单元中,数据间的逻辑关系和物理关系一致(数组) 优点:数据单元相互间有索引

缺点:插入效率低
链式结构 内存单元可以是连续的,也可以是不连续的,数据元素间不能反映逻辑关系,因此需要一个指针存放元素地址 优点:无索引,插入效率高

缺点:查找效率低
1.3 什么是算法
在规定时间内,按照一定的条件,从已存在的数据进行计算,获得期望的结果

一个优秀的算法追求以如下两方面考虑:

1.花费最少时间完成需求
2.占用最少内存空间完成需求
二.算法分析
2.1 算法时间复杂度分析
事前分析法:
•算法采用的策略和方案
•编译产生的代码质量
•问题的输入规模(输入量的规模)
•机器执行指令的速度

2.2 算法空间复杂度分析

一.枚举
1.1 不使用枚举存在的问题

1
2
3
4
5
6
7
8
9
10
public class TestPerson {
public static void main(String[] args) {
Person a = new Person("A","male");
System.out.println(a); Person person = new Person("B", "female");
System.out.println(person);

//此时由于性别为String类型,赋值任意字符串均可
//但是性别也是应该有要求的
//这就是不使用枚举可能出现的问题
}}

1.2 枚举的作用与应用场景
什么是枚举:是一种特殊的类,把所有可能的情况一一列举出来

什么时候使用枚举:当某个数据的值是固定有限的,就可以使用枚举把其值一一列举出来

1.3 枚举的基本语法
定义枚举的格式
public enum 枚举名{
//枚举项
枚举项1,枚举项2,枚举项3;
}

枚举的入门案例

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

public class Person {
private String name;
//任何值都可以,数据不正常
//private String gender;

private GenderEnum gender;

public Person() {
}

public Person(String name, GenderEnum gender) {
this.name = name;
this.gender = gender;
}

@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", gender='" + gender + '\'' +
'}';
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public GenderEnum getGender() {
return gender;
}

public void setGender(GenderEnum gender) {
this.gender = gender;
}}

public class TestPerson {
public static void main(String[] args) {
Person a = new Person("A",GenderEnum.Male);
System.out.println(a);
Person person = new Person("B", GenderEnum.Female);
System.out.println(person);
}}

枚举的本质
枚举的本质其实就是一个类,本质其实是当前类的一个对象

1
2
3
4
5
6
7
8
9
10
11
public enum GenderEnum {
Male,Female,Trans;
//既然枚举是一个类,那么就可以添加喜欢的成员变量和成员方法,甚至构造方法
}
//本质==>
public final class GenderEnum extends java.lang.Enum<GenderEnum>{
public static final GenderEnum Male = new GenderEnum();
public static final GenderEnum Female = new GenderEnum();
public static final GenderEnum Trans = new GenderEnum();
//构造方法是私有化的
private GenderEnum(){}}

枚举本质是一个类,那么就可以在枚举中添加各种成员变量,成员方法,构造方法等
成员变量和成员方法和普通类没有区别
构造方法:
a.必须是私有的
b.使用时 枚举项(参数)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public enum Sex {
Male(10),Female(20),Yao(30);
//既然枚举是一个类,那么我们就可以添加我们喜欢的成员变量和成员方法和构造方法
private int age;
public void showAge(){
System.out.println(age);
}

private Sex(){}

private Sex(int age){
this.age = age;
}
}

枚举的应用场景

1
2
3
4
5
6
7
8
9
10
11
12
枚举表示性别:
public enum Sex {
MAIL, FEMAIL;
}
枚举表示方向:
public enum Orientation {
UP, RIGHT, DOWN, LEFT;
}
枚举表示季度:
public enum Season {
SPRING, SUMMER, AUTUMN, WINTER;
}

二.JDK8的其他新特性
2.1 方法引用
方法引用介绍
所谓方法引用,就是把已经存在的方法,直接拿过来使用。
当我们可以一个函数式接口赋值时,赋值该接口的实现类对象,也可以赋值该接口的匿名内部类对象,也可以赋值符合接口的Lambda表达式,也可以使用方法引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class Dog {
public static void bark(){
System.out.println("Thread Dog Bark");
}
public void bark1(){
System.out.println("Thread Dog Bark_1");
}
}
public class Method_References_Demo {
public static void main(String[] args) {
//1.创建一个线程,使用实现的方法
//a.使用接口的实现类对象,给Runnable接口类型赋值
new Thread(new MyRunnable()).start();

//b.使用接口的匿名内部类对象,给Runnable赋值
new Thread(new MyRunnable() {
@Override
public void run() {
System.out.println("Thread run...");
}
}).start();

//C.使用lambda直接给Runnable接口类型赋值
new Thread(() -> System.out.println("Thread run..."));

//把已经存在的方法直接引用过来
new Thread(Dog::bark).start();//通过类名引用静态方法
Dog dd = new Dog();
new Thread(dd::bark1).start();//通对象名引用成员方法

}}
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("Thread run...");
}}

方法引用基本使用格式

表头 表头
通过类名引用其中的静态方法 类名::静态方法
通过对象引用其中的普通方法 对象名::普通方法名
通过类名引用其构造方法 类名::new Person o = new Person();
通过数组引用其构造方法 数据类型[]::new int[] arr = new int[10];
基于System.out.println这个方法引用的代码演示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class SystemOutPrintlnDemo {
public static void main(String[] args) {
List names = new ArrayList();
Collections.addAll(names,"a","b","c");

names.stream().forEach(new Consumer() {
@Override
public void accept(Object o) {
System.out.println(o);
}
});

names.forEach(new Consumer() {
@Override
public void accept(Object o) {
System.out.println(o);
}
});
names.forEach(o-> System.out.println(o));

names.forEach(System.out::println);
}}

2.2 Base64
什么是Base64
一种常见的编码方案

Base64内嵌类和方法

Decoder和Encoder
UrlDecoder和UrlEncoder
MimeDecoder和MimeDecoder

String encodeToString(byte[] bs); //编码
byte[] decode(String str);//解码
代码演示

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

public class Base64Demo {
public static void main(String[] args) {
//基于普通字符串的编码解码
String encodeToString = Base64.getEncoder().encodeToString("HelloWorld".getBytes());
System.out.println(encodeToString);

byte[] bytes = Base64.getDecoder().decode(encodeToString);
System.out.println(new String(bytes));

//基于URL的编码解码
String encodeToString_Url = Base64.getEncoder().encodeToString("www.Oracle.com/index.html".getBytes());
System.out.println(encodeToString_Url);

byte[] Url_bytes = Base64.getDecoder().decode(encodeToString_Url);
System.out.println(new String(Url_bytes));

//MIME类型的编码器和解码器
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 10; i++) {
UUID uuid = UUID.randomUUID();
sb.append(uuid.toString());
}
String content = sb.toString();
String MIMEEncode = Base64.getMimeEncoder().encodeToString(content.getBytes());
System.out.println(MIMEEncode);

byte[] MIMEDecode = Base64.getMimeDecoder().decode(MIMEEncode);
System.out.println(MIMEDecode);

String MIMEEncode2 = Base64.getMimeEncoder(8, "-".getBytes()).encodeToString(content.getBytes());
System.out.println(MIMEEncode2);

byte[] MIMEDecode2 = Base64.getMimeDecoder().decode(MIMEEncode);
System.out.println(MIMEDecode2);
}}

三.正则表达式
3.1 正则表达式的概念及演示
什么是正则:正则是一个字符串,但是里面的内容表示某种规则(规则是有具体语法含义)

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

public class Non_RegularExpression {
public static void main(String[] args) {
System.out.println("Input Number");
String scanner = new Scanner(System.in).next();

if (scanner.length() < 5 || scanner.length() > 15) {
System.out.println("Illegal Number");
return;
}

for (int i = 0; i < scanner.length(); i++) {
char ch = scanner.charAt(i);
if (ch < '0' || ch > '9') {
System.out.println("Illegal Number");
return;
}
}

if (scanner.charAt(0) == '0') {
System.out.println("Illegal Number");
return;
}

System.out.println("Legal");
}}
public class TestRegularExpression {
public static void main(String[] args) {
System.out.println("Input Number");
String scanner = new Scanner(System.in).next();
boolean matches = scanner.matches("[1-9]\\d{4,14}");
System.out.println(matches);
}}

3.2 正则表达式 - 字符类
表头 表头
[abc] 代表a或者b,或者c字符中的一个。
[^abc] 代表除a,b,c以外的任何字符。
[a-z] 代表a-z的所有小写字符中的一个。
[A-Z] 代表A-Z的所有大写字符中的一个。
[0-9] 代表0-9之间的某一个数字字符。
[a-zA-Z0-9] 代表a-z或者A-Z或者0-9之间的任意一个字符。
[a-dm-p] a 到 d 或 m 到 p之间的任意一个字符。

1
2
3
4
5
6
7
8
9
10
11
12
13
public class TestDemo02 {
public static void main(String[] args) {
String str = "aad";
//1.验证str是否以h开头,以d结尾,中间是a,e,i,o,u中某个字符
System.out.println("1." + str.matches("h[aeiou]d"));
//2.验证str是否以h开头,以d结尾,中间不是a,e,i,o,u中的某个字符
System.out.println("2." + str.matches("h[^aeiou]d"));
//3.验证str是否a-z的任何一个小写字符开头,后跟ad
System.out.println("3." + str.matches("[a-z]ad"));
//4.验证str是否以a-d或者m-p之间某个字符开头,后跟ad
System.out.println("4." + str.matches("[a-dm-p]ad"));
}
}

3.3 正则表达式 - 逻辑运算符
表头 表头
&& 与
| 非

1
2
3
4
5
6
7
8
9
10
11
12
13
/**

* 正则表达式逻辑运算符类
*/
public class TestDemo03 {
public static void main(String[] args) {
String str = "bad";
//1.要求字符串是否是除a、e、i、o、u外的其它小写字符开头,后跟ad
System.out.println("1." + str.matches("[a-z&&[^aeiou]]ad"));
//2.要求字符串是aeiou中的某个字符开头,后跟ad
System.out.println("2." + str.matches("[a|e|i|o|u]ad"));
}
}

3.4 正则表达式 - 预定义字符
表头 表头
. 匹配任何字符。
\d 任何数字[0-9]的简写;
\D 任何非数字[^0-9]的简写;
\s 空白字符:[ \t\n\x0B\f\r] 的简写
\S 非空白字符:[^\s] 的简写
\w 单词字符:[a-zA-Z_0-9]的简写
\w 非单词字符:[^\w]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**

* 正则表达式-预定义字符
*/
public class TestDemo04 {
public static void main(String[] args) {
String str = "had.";
//1.验证str是否3位数
System.out.println("1." + str.matches("\\d\\d\\d"));
//2.验证手机号:1开头,第二位:3/5/8,剩下9位都是0-9的数字
System.out.println("2." + str.matches("1[3|5|8]\\d\\d\\d\\d\\d\\d\\d\\d\\d"));
//3.验证字符串是否以h开头,以d结尾,中间是任何字符 str = "had";//要验证的字符串
System.out.println("3." + str.matches("h.d"));
//4.验证str是否是:had.
System.out.println("4." + str.matches("had\\."));
}
}

3.5 正则表达式 - 数量词
表头 表头
X? 0次或1次
X* 0次到多次
X+ 1次或多次
X{n} 恰好n次
X{n,} 至少n次
X{n,m} n到m次(n和m都是包含的)

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

public class TestDemo05 {
public static void main(String[] args) {
String str = "234";
//1.验证str是否是三位数字
System.out.println("1."+str.matches("\\d{3}"));
//2.验证str是否是多位数字
System.out.println("2."+str.matches("\\d{2,}"));
//3.验证str是否是手机号:
System.out.println("3."+str.matches("1[3|6|8]\\d{9}"));
//4.验证小数:必须出现小数点,但是只能出现1次
System.out.println("4."+str.matches("\\d+\\.\\d+"));
//5.验证小数:小数点可以不出现,也可以出现1次
System.out.println("5."+str.matches("\\d+\\.?\\d+"));
//6.验证小数:要求匹配:3、3.、3.14、+3.14、-3.
System.out.println("6."+str.matches("[+-]\\d+\\d?\\d*"));
//7.验证qq号码:
// 1).5--15位;
// 2).全部是数字;
// 3).第一位不是0
System.out.println("7."+str.matches("[1-9]\\d{4,14}"));}
}

3.6 正则表达式 - 分组括号()

1
2
3
4
5
6
public class TestDemo06 {
public static void main(String[] args) {
String str = "DG8FV-B9TKY-FRT9J-99899-XPQ4G";
//验证这个序列号:分为5组,每组之间使用-隔开,每组由5位A-Z或者0-9的字符组成
System.out.println(str.matches("([A-Z0-9]{5}-){4}[A-Z0-9]{5}"));}
}

3.7 String的split方法
public String[] split(String regex)//用regex正则表达式的符号作为”分隔符”来切割字符串。

1
2
3
4
5
6
7
8
9
10
11

public class TestDemo07 {
public static void main(String[] args) {
String str = "18 4 567 99 56";
String[] split = str.split(" +");

for (String num:split
) {
System.out.println(num);
}
}}

3.8 String类的replaceAll方法
public String replaceAll(String regex,String newStr)//将当前字符串的旧串替换为新串,其他旧串可以使用正则匹配

1
2
3
4
5
6
7
8
9
10
11
public class TestDemo08 {
public static void main(String[] args) {
//将下面字符串中的"数字"替换为"*"
String str = "jfdk432jfdk2jk24354j47jk5l31324";
System.out.println(str.replaceAll("\\d", "*"));
//将所有相邻数字只替换成一个*
System.out.println(str.replaceAll("\\d+", "*"));

String newstr = "jjjjjjjjjfddddddk43222222222jfddddddddddkkkkkkkkkk2jk24354j47jk5l31324";
System.out.println(newstr.replaceAll( "(.)\\1+" , "$1"));
}}

总结:
定义枚举

定义:
public enum 枚举名{
//枚举项
枚举项1,枚举项2,枚举项3;
}

枚举名 变量名 = 枚举名.枚举项1;

方法的引用
集合对象.foreach(System.out::println)

Base64对基本数据,URL和MIME类型进行编码
public String encodeToString(byte[] bs); //编码
public byte[] decode(String str);//解码

正则表达式作用:”匹配”字符串

正则表达式对字符类
[abc] [^abc] [a-z] [A-Z] [0-9] [a-zA-Z0-9] [a-dm-p]

正则表达式的逻辑运算符类
&& |

正则表达式的预定义字符类
. \d \D \s \S \w \W

正则表达式的分组
(…){2}

String的split方法中使用正则表达式

String的replaceAll方法中使用正则表达式

一.单例设计模式
1.1 单例设计模式介绍
a.在正常情况下,一个类可以创建多个对象
b.单例设计模式的作用介绍让某个类只能有一个对象

1.2 单例设计模式实现步骤
a.构造方法私有化(不让别人直接new对象)
b.在该类内部产生一个唯一的实例化对象,并且使用private static修饰(让别人无法直接访问)
c.提供一个public static的静态方法,该方法返回此静态对象(只能通过调用该方法调用对象)

1.3 单例设计模式的类型

懒汉式:
不立即创建本类的对象,当别人调用静态方法获取本类对象时,才创建

饿汉式:
先创建对象,当别人调用静态方法时,直接返回对象即可

1.4 饿汉单例设计模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//单例模式中的饿汉式
public class Dog {
//1.私有化构造
private Dog() {
}
//2.自定义一个对象
private static final Dog dd = new Dog();

//3提供一个静态方法,获取dd对象
public static Dog getInstance() {
return dd;
}
}

public class SingleInstanceDemo {
public static void main(String[] args) {
//获取Dog对象
for (int i = 0; i < 5; i++) {
Dog dog = Dog.getInstance();
System.out.println(dog);
}
}
}

输出:
com.test.Demo01_SingleInstance.Dog@61bbe9ba
com.test.Demo01_SingleInstance.Dog@61bbe9ba
com.test.Demo01_SingleInstance.Dog@61bbe9ba
com.test.Demo01_SingleInstance.Dog@61bbe9ba
com.test.Demo01_SingleInstance.Dog@61bbe9ba

1.5 懒汉单例设计模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//单例模式中的懒汉式
public class Cat {
//1.私有化构造
private Cat(){
}
//2.自定义一个对象
private static Cat cc=null;

//3提供一个静态方法,多线程
//多线程情况下需要使用synchronized保证该方法的原子性
//如果不加,可能会出现如下:
//线程1进来,被线程2抢走
//线程2进来,不能保证原子性
public synchronized static Cat getInstance(){
if(cc == null){
cc=new Cat();
}
return cc;
}}

public class TestCat {
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
Cat cc = Cat.getInstance();
System.out.println(cc);
}
}
}

输出:
com.test.Demo02_Single_Instance_Sloth.Cat@61bbe9ba
com.test.Demo02_Single_Instance_Sloth.Cat@61bbe9ba
com.test.Demo02_Single_Instance_Sloth.Cat@61bbe9ba
com.test.Demo02_Single_Instance_Sloth.Cat@61bbe9ba
com.test.Demo02_Single_Instance_Sloth.Cat@61bbe9ba
二. 多例设计模式
2.1 多例设计模式多介绍
多例设计模式保证我们的类具有指定个数的对象

2.2 多例设计模式多实现步骤
a.私有化构造(不让别人直接new对象)
b.在该类内部产生一个private static修饰的集合(让别人无法直接访问),用于保存自己创建的对象
c.使用静态代码块向集合中添加指定个数的对象
d.提供一个public static的静态方法,该方法返回此静态对象(只能通过调用该方法调用对象)

2.3 多例设计模式多代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
//要求pig类使用多例设计模式
public class Pig {
//1.定义一个变量,指定集合中对象的个数,常量建议大写
private static final int COUNT = 4;
//2.私有化构造
private Pig(){
}

//3.创建一个集合
private static ArrayList<Pig> list = new ArrayList<Pig>();

//4.使用静态代码块
static {
//添加指定个数的对象
for (int i = 0; i < COUNT; i++) {
list.add(new Pig());
}
}

//5.提供一个静态方法
public static Pig getInstance(){
return list.get(new Random().nextInt(COUNT));
}
}

public class TestPig {
public static void main(String[] args) {
//1.获取Pig对象
for (int i = 0; i < 10; i++) {
Pig p1 = Pig.getInstance();
System.out.println(p1);
}
}
}

输出:
com.test.Demo03_MultiInstance.Pig@511d50c0
com.test.Demo03_MultiInstance.Pig@511d50c0
com.test.Demo03_MultiInstance.Pig@511d50c0
com.test.Demo03_MultiInstance.Pig@511d50c0
com.test.Demo03_MultiInstance.Pig@511d50c0

com.test.Demo03_MultiInstance.Pig@5e2de80c
com.test.Demo03_MultiInstance.Pig@5e2de80c

com.test.Demo03_MultiInstance.Pig@610455d6
com.test.Demo03_MultiInstance.Pig@610455d6

com.test.Demo03_MultiInstance.Pig@60e53b93
三.动态代理
3.1 动态代理的介绍

什么是代理
被代理者没有能力或不愿意完成某件事,寻求一个可以完成此事的对象,这个对象就是代理

生活中的代理

代码中的代理案例
a.用户登录到我们的系统后,我们的系统会为其产生一个ArrayList集合对象,内部存储了一些用户信息
b.对象需要被传给后面的很多其它对象,但要求其它对象不能对这个ArrayList对象执行添加、删除、修改操作,只能 get()获取元素
c.分析:无法控制用户如何操作集合,可以给这个集合找一个代理,判断他的操作,如果是增删改相关,直接抛出异常。如果是获取相关,找到真正的被代理集合,获取数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
// 要求代理对象和被代理有一样的方法
// 代理对象中应该保存被代理对象
/* 代理对象中的方法:
增删改,直接抛出异常
获取,调用被代理对象的方法
*/

package com.test.Demo04_Proxy;

import java.util.*;
import java.util.function.UnaryOperator;

//此类就是普通ArrayList的代理

//1.让代理对象实现被代理对象一样的接口,目的是让代理对象和被代理对象具有一样的方法
public class ArrayListProxy implements List<String> {
//2.要在代理对象中保存一个被代理对象
private ArrayList<String> list;
public ArrayListProxy(ArrayList<String> list) {
this.list = list;
}

//3.下面的一堆方法中,如果是不修改集合数据的方法,就调用List的方法
//如果是修改集合的方法,直接抛出异常

@Override
public int size() {
return list.size();
}

@Override
public boolean isEmpty() {
return list.isEmpty();
}

@Override
public boolean contains(Object o) {
return list.contains(o);
}

@Override
public void replaceAll(UnaryOperator<String> operator) {
throw new UnsupportedOperationException("不允许调用replaceAll方法");
}

@Override
public Iterator<String> iterator() {
throw new UnsupportedOperationException("不允许调用iterator方法");
}

@Override
public Object[] toArray() {
return list.toArray();
}

@Override
public <T> T[] toArray(T[] a) {
return list.toArray(a);
}

@Override
public boolean add(String s) {
throw new UnsupportedOperationException("不允许调用add方法");

}

@Override
public boolean remove(Object o) {
throw new UnsupportedOperationException("不允许调用remove方法");

}

@Override
public boolean containsAll(Collection<?> c) {
return false;
}

@Override
public boolean addAll(Collection<? extends String> c) {
throw new UnsupportedOperationException("不允许调用addAll方法");

}

@Override
public boolean addAll(int index, Collection<? extends String> c) {
throw new UnsupportedOperationException("不允许调用addAll方法");

}

@Override
public boolean removeAll(Collection<?> c) {
throw new UnsupportedOperationException("不允许调用removeAll方法");

}

@Override
public boolean retainAll(Collection<?> c) {
throw new UnsupportedOperationException("不允许调用retainAll方法");
}

@Override
public void clear() {
throw new UnsupportedOperationException("不允许调用clear方法");
}

@Override
public String get(int index) {
return list.get(index);
}

@Override
public String set(int index, String element) {
throw new UnsupportedOperationException("不允许调用set方法");
}

@Override
public void add(int index, String element) {
throw new UnsupportedOperationException("不允许调用add方法");
}

@Override
public String remove(int index) {
throw new UnsupportedOperationException("不允许调用remove方法");
}

@Override
public int indexOf(Object o) {
return list.indexOf(o);
}

@Override
public int lastIndexOf(Object o) {
return list.lastIndexOf(o);
}

@Override
public ListIterator<String> listIterator() {
throw new UnsupportedOperationException("不允许调用listIterator方法");
}

@Override
public ListIterator<String> listIterator(int index) {
throw new UnsupportedOperationException("不允许调用listIterator方法");
}

@Override
public List<String> subList(int fromIndex, int toIndex) {
return list.subList(fromIndex, toIndex);
}
@Override
public void sort(Comparator<? super String> c) {
throw new UnsupportedOperationException("不允许调用sort(Comparator<? super String>方法");
}

@Override
public Spliterator<String> spliterator() {
throw new UnsupportedOperationException("不允许调用spliterator方法");
}}

public class TestProxy {
public static void main(String[] args) {
ArrayList<String> arr = new ArrayList<>();
Collections.addAll(arr, "a", "18", "sz", "10000");
//把集合arr先交给代理对象
List<String> list = getArrayListProxy(arr);

//操作
list.add("");
}

public static List<String > getArrayListProxy(ArrayList<String > arr){
//创建一个ArrayList代理
ArrayListProxy arrayListProxy = new ArrayListProxy(arr);

return arrayListProxy;
}}

3.2 动态代理概述
动态代理和静态代理区别在于释放已经写好,上面案例就是静态代理,代理类ArrayListProxy已经编译时期定义好了
而动态代理,它的代理在运行时期通过代码动态生成。

动态代理作用:拦截对真实对象(被代理对象)方法对直接访问,增强/减弱 真实对象(被代理对象)方法对功能进行 增强/减弱

3.3 案例引出

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

public class Dynamic_Proxy {
public static void main(String[] args) {
ArrayList<String> arr = new ArrayList<String>();
Collections.addAll(arr, "a", "18", "sz", "10000");

//使用arr这个集合,生成一个它的代理
List<String> list = Collections.unmodifiableList(arr);
/*
unmodifiableList该方法返回的是arr的代理对象,类似于我们自己的ArraylistProxy对象
但是unmodifiableList生产的代理对象,是通过动态方式在运行时期创建出来
*/

//调用方法
try {
list.remove(1);
list.set(1, "a");
list.add("a");
}catch (Exception e){
e.printStackTrace();
}finally {
list.get(2);
}
}}

3.4 重点类方法
•java.lang.reflect.Proxy类:
这是 Java 动态代理机制的主类,它提供了一个静态方法来为一组接口的实现类动态地生 成代理类及其对象。

public static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h))

参数 作用
ClassLoader loader 类加载器,一般使用和被代理对象一样的类加载器
Class[] interfaces 被代理对象实现的所有接口的字节码数组文件
InvocationHandler h 处理类对象,用于拦截我们调用的所有方法,判断到底是否返回被代理对象的真实方法
•InvocationHandler接口的invoke方法及参数详解

InvocationHandler处理类,实际上它是一个接口
//invoke方法就是用于拦截我们调用的真实方法
public Object invoke(Object proxy, Method method, Object[] args);

参数 作用
Object proxy 代理对象
Method method 被拦截的方法
Object[] args 被拦截的方法参数,如果没有就是Null
3.5 动态代理综合案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class Dynamic_Proxy_InAction {
public static void main(String[] args) {
ArrayList<String> arr = new ArrayList<String>();
Collections.addAll(arr, "a", "18", "sz", "10000");

List<String> list = unmodifiableList(arr);

System.out.println(list.get(1));
// System.out.println(list.add("1"));方法不允许
System.out.println(list.indexOf("18"));
}

public static List<String> unmodifiableList(List<String> arr) {
// Proxy.newProxyInstance(arr.getClass().getClassLoader(),arr.getClass().getInterfaces(),);
List<String> list =
(List<String>) Proxy.newProxyInstance(Dynamic_Proxy_InAction.class.getClassLoader(),
arr.getClass().getInterfaces(),
new InvocationHandler() {
@Override
//该方法就是用来拦截真实对象的方法
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// System.out.println(method.getName());
// return null;
//获取方法名
String name = method.getName();
//假设需要拦截ADD,SET,REMOVE的方法
if (name.startsWith("add") || name.startsWith("set") || name.startsWith("remove")) {
throw new UnsupportedOperationException("方法不允许");
}
Object result = method.invoke(arr, args);
return result;
}
});
return list;
}
}

3.6 动态代理的优缺点总结

总结
优点 a.可以为任意接口实现类对象做动态代理(机制是由Java的反射技术生成的)
b.极大提高了开发效率
c.不改变被代理对象的代码
缺点 java的Proxy创建动态代理必须有接口,没有接口不行
可以通过调用第三方CGL iB对无接口的的对象进行动态代理

四.Lombok
4.1 下载Lombok
https://projectlombok.org/downloads/lombok.jar

4.2 安装Lombok
项目下建立lib文件夹,将lombok.jar拖入,并在IDE中查找Lombok插件安装并重启

4.3 Lombok常用注解

@Getter和@Setter
•作用:生成成员变量的get和set方法。
•写在成员变量上,指对当前成员变量有效。
•写在类上,对所有成员变量有效。
•注意:静态成员变量无效。

@ToString:
•作用:生成toString()方法。
•该注解只能写在类上。

@NoArgsConstructor和@AllArgsConstructor
•@NoArgsConstructor:无参数构造方法。
•@AllArgsConstructor:满参数构造方法。
•注解只能写在类上。

@EqualsAndHashCode
•作用:生成hashCode()和equals()方法。
•注解只能写在类上。

@Data
•作用: 生成setter/getter、equals、canEqual、hashCode、toString方法,如为final属性,则不会为 该属性生成setter方法。
•注解只能写在类上。

五.工厂设计模式
5.1 工厂模式概述
之前创建对象,都是new对象
而工厂模式,将创建对象的过程交给工厂执行,只需要从工厂中获取对象即可。

5.2 工厂模式作用
解决类与类之间的耦合问题

5.3 工厂模式实现步骤
a.提供一个所有类的父类/接口,例:提供一个Car接口
b.各种实现类都需要实现该接口,重写该接口中的方法
c.提供一个返回不同实现类对象的工厂

5.4 工厂模式实现代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
public interface Car {
void run();
}

public class Benz_Car implements Car{
@Override
public void run(){
System.out.println("Benz Run");
}
}

public class BMW_Car implements Car{
@Override
public void run(){
System.out.println("BMW RUN");
}
}

public class VW_Car implements Car{
@Override
public void run(){
System.out.println("volk swagen run");
}
}
//汽车工厂
public class Car_Factory {
//定义方法,用于产汽车
public static Car getACar(int id) {
//返回一个真正的汽车对象
if (id == 1) {
return new VW_Car();
} else if (id == 2) {
return new Benz_Car();
}else if(id == 3){
return new BMW_Car();
}else {
throw new NoSuchIdCarException();
}
}}

public class TestCar {
public static void main(String[] args) {
// Benz_Car bz = new Benz_Car();
// bz.run();
//
// BMW_Car bmw = new BMW_Car();
// bmw.run();
//
// VW_Car vw = new VW_Car();
// vw.run();
Car aCar = Car_Factory.getACar(1);
aCar.run();
}
}

补充:可以使用配置文件,来替代具体代码中的ID值

//Car.properties
id = 3

1
2
3
4
5
6
7
8
9
10
11
12
public class TestCar {
public static void main(String[] args) {
//从配置文件读取id
Properties ps = new Properties();
ps.load(new FileInputStream("Day23/Car.properties"));

String id = ps.getProperty("id");
int ID = Integer.parseInt(id);

Car aCar = Car_Factory.getACar(ID);
aCar.run();
}}

总结:
单例设计模式的好处
可以让一个类只有一个对象:
饿汉式:直接在类内部定义本类对象并创建对象
懒汉式:在类内部定义本类对象,在getInstance方法中判断再创建对象

多例模式好处:
可以让一个类只有指定个数的对象
在类内部创建一个集合,用来保存多个本类的对象
一般来说都使用静态代码块向集合中添加固定个数的对象

动态代理模式作用:
在程序运行期间,为一个被代理对象动态创建一个代理对象
当调用代理对象的方法时,都会被拦截,拦截之后可以进行判断

使用Proxy的方法生成代理对象
Proxy.newProxyInstance(ClassLoader class,Class[ interfaces,InvocationHandler handler])

InvocationHandler接口
//invoke方法就是用于拦截的
//Method被调用的方法
//Object[] 被调用方法时的参数
public Object invoke(Object proxy, Method method, Object[] args);

在此方法中会自动判断,
如果满足条件,则正常调用:method.invoke(被代理对象,args)
如果不满足条件,则不调用,可以返回异常,也可以什么都不做

使用工厂模式编写程序

一.XML概述
1.1 XML初识

XML介绍以及版本
XML是可扩展标记语言(Extensible Markup Language)
语言:XML也是一种语言
标记:是指标签(Tag)/元素(Element),<标签名>/<标签名>
可扩展:标签可以随意定义

XML版本:
XML 1.0(使用)
XML 1.1(不使用)

XML与HTML的主要差异
a.XML主要是用于传输和保存数据(目前更倾向于保存,传输用Json)
b.HTML是用来显示数据

XML入门小案例
•需求
编写XML文档,保存人员信息,Person人员,SID编号,AGE年龄,Name姓名,Gender性别
•编写

1
2
3
4
5
6


20>
JSW
Male

•运行
使用浏览器打开XML即可

XML的作用
a.XML可以存储数据(也可以传输数据,但目前已用Json传输)
b.XML也可以作为配置文件(给框架使用的)

1.2 XML的语法学习

XML的组成元素
•文档声明
什么是文档声明:表面这就是一个XML文件
文档声明必须写在XML文件的0行0列(左上角)
固定格式:
1

•元素element
格式:
1
2
3
4
5

Tag Body
>

自闭合标签,不写任何内容使用此标签
命名要求:
a.区分大小写
b.不能使用空格、冒号等特殊字符
c.不建议 以XML,xml,Xml开头

注意:一个格式良好的XML文档,有且仅有一个根标签

•属性attribute
a.属性是元素的一部分,必须写在元素的开始标签
b.格式:属性名 = “属性值”,其中属性值必须使用单引号或双引号
c.一个元素可以有0-N个属性,但是不能有同名属性,也一样不能使用特殊字符

•注释

1

•转义字符

字符 预定义的转义字符 说明
< < 小于

> 大于
“ " 双引号
‘ ' 单引号
& & 和号
•字符区
当出现大量需要转义的字符时,不建议逐一转义,会增加复杂度且降低可读性
使用CDATA区中写所有字符,会自动转义

1
2
3

二.XML约束
概述:就是编写XML文件的标签的规则

2.1 DTD约束

什么是DTD约束
文档类型定义约束,规定我们在编写XML时具体的标签,子标签,属性等

DTD约束体验
a.bookshelf.dtd 这就是DTD约束文件
b.创建一个book.xml
c.在XML中引入dtd约束
d.根据IDE提示,编写出符合DTD约束的XML文件

1
2
3
4
5





1
2
3
4
5
6
7
8
9
10
11
12
13
14

<书架>
<书>
<书名>JavaWeb开发教程</书名>
<作者>张孝祥</作者>
<售价>100.00元</售价>
</书>
<书>
<书名>三国演义</书名>
<作者>罗贯中</作者>
<售价>100.00元</售价>

</书>

</书架>
注意:开发中基本不会写DTD文件,而是由框架提供,根据DTD编写符合要求的XML文件

DTD约束语法
•DTD的引入
a.内部DTD(把DTD直接写在XML中,这种方式只能对当前XML有效)
1
2
3
4


<根元素>
</根元素>
b.外部DTD – 本地DTD
1
2
3

<根元素>
</根元素>
c.外部DTD – 公共DTD
1
2
3
4




•DTD中的数量词

数量词符号 含义
* 表示元素可以出现0到多个
+ 表示元素可以出现至少1个
? 表示元素可以是0或1个
, 表示元素需要按照顺序显示
| 表示元素需要选择其中的某一个
•其他语法

1
2
3
4
5
<!ATTLIST 标签名称
属性名称1 属性类型1 属性说明1
属性名称2 属性类型2 属性说明2

属性类型:

属性类型: 含义
CDATA 代表属性是文本字符串, eg:
ID 代码该属性值唯一,不能以数字开头, eg:
ENUMERATED 代表属性值在指定范围内进行枚举 Eg:
属性说明:

属性说明 含义
#REQUIRED 代表属性是必须有的
#IMPLIED 代表属性可有可无
#FIXED 代表属性为固定值,实现方式:book_info CDATA #FIXED “固定值”
1
2
3
4
5
6
7

id ID #REQUIRED
编号 CDATA #IMPLIED
出版社 (清华|北大|传智播客) “传智播客”
type CDATA #FIXED “IT”

2.2 Schema约束

什么是Schema约束
也是用于约束XML文件的,是DTD的代替者,添加了 数据类型 的约束
本身也是XML,后缀名是.xsd

Schema的约束体验
a.bookshelf.xsd 就是Schema的约束文档
b.创建自己的XML文件,把Schema中需要复制的内容复制进去,其实Schema中复制过来是根标签的开始标签,需要补充完整标签

1
2
3
4

bookshelf.xsd

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!–
<书架 xmlns=”http://www.Oracle.cn
xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance“ xsi:schemaLocation=”http://www.Oracle.cn bookshelf.xsd”

–>
<xs:schema xmlns:xs=”http://www.w3.org/2001/XMLSchema
targetNamespace=”http://www.Oracle.cn
elementFormDefault=”qualified”>
<xs:element name=’书架’ >
xs:complexType
<xs:sequence maxOccurs=’unbounded’ >
<xs:element name=’书’ > xs:complexType
xs:sequence
<xs:element name=’书名’ type=’xs:string’ />
<xs:element name=’作者’ type=’xs:string’ />
<xs:element name=’售价’ type=’xs:double’ />







c.根据提示完成自己的XML

1
2
3
4
5
6
7
8
9

<书架
xmlns=”http://www.oracle.cn“ xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance“ xsi:schemaLocation=”http://www.oracle.cn bookshelf.xsd” >
<书>
<书名>JavaScript网页开发</书名>
<作者>张孝祥</作者>
<售价>1</售价>
</书>
</书架>
Scheme语法和命名空间
作用就是处理元素和属性的名称冲突问题,与Java中的包类似,不同命名空间可能有相同的标签

1
2
3
4
5
xmlns=”http://www.oracle.cn

xmlns:aa=”http://java.sun.com

引用了两个命名空间,
如果直接写<书> 表示http://www.oracle.cn 命名空间下的书
如果写aa:书 表示http://java.sun.com 命名空间下的书

2.3 XML约束的学习要求:
重点是根据框架提供的DTD/Scheme约束,编写出符合要求的XML文档

三.XML解析
3.1 什么是XML解析
通过代码将XML文件中保存的数据读取出来

3.2 解析方式和解析器和解析开发包
a.解析方式(思想)
•DOM解析:把整个XML加载到内存,进行解析,解析后产生一个Document对象
优点:元素之前结构得以保留,文档结构完整,可以通过Document对象对标签进行增删改查操作
缺点:如果XML文件过大,可能导致内存溢出

•SAX解析:扫描XML文档,扫描一行解析一行,解析完毕后释放该行资源
优点:速度快,可以处理大文件
缺点:只能读,逐行读取后释放资源,解析操作繁琐

•PULL解析:是安卓系统内置的XML解析方式,类似SAX

b.解析器:根据解析方式,写出对应的解析代码(代码需要考虑解析过程中的每个标签,非常繁琐)

c.解析开发包:对解析器繁琐API的封装,提供简单方便的API

常见的解析开发包:
•JAXP:sun公司提供支持DOM和SAX开发包
•Dom4j:比较简单的的解析开发包(常用)
•JDom:与Dom4j类似
•Jsoup:功能强大DOM方式的XML解析开发包,尤其对HTML解析更加方便

3.3 Dom4j的基本使用

DOM的解析原理
将整个XML稳定加载到内存,进行解析,解析之后生成一个Document对象(道理的DOM树)

DOM树的结构模型

image

DOM4j的jar包和常用API
在模块下创建一个lib文件夹(必须叫lib),然后把dom4j-1.6.1.jar包放入lib中(Day22/lib/dom4j-1.6.1.jar)
将lib下的jar包添加到模块中:
a.选中右键 –> add as library
b.点击File –> Project Structure –> 先在Library添加jar包 –> 然后在Moudle加入即可
SAXReader对象

核心类 作用
new SAXReader() 构造器
Document read(File file) 加载执行xml文档
Document对象

方法 作用
Element getRootElement() 获得根元素
Element对象

方法 作用
List elements([String ele] ) 获得指定名称的所有子元素。可以不指定名称
Element element([String ele]) 获得指定名称第一个子元素。可以不指定名称
String getName() 获得当前元素的元素名
String attributeValue(String attrName) 获得指定属性名的属性值
String elementText(Sting ele) 获得指定名称子元素的文本值
String getText() 获得当前元素的文本内容
DOM4j的代码演示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class DOM4JDemo {
public static void main(String[] args) throws DocumentException {
//1.创建核心对象
SAXReader reader = new SAXReader();
//2.读取xml文件
Document document = reader.read(new File(“books.xml”));
//3.获取根标签
Element rootElement = document.getRootElement();
System.out.println(“根标签是:”+rootElement.getName());
//4.获取book标签
List bookElements = rootElement.elements();
//5.遍历集合
for (Element bookElement : bookElements) {
//6.获取标签名
String bookElementName = bookElement.getName();
System.out.println(“子标签:”+bookElementName);
//7.获取id属性值
String idValue = bookElement.attributeValue(“id”);
System.out.println(“属性id:”+idValue);
//8.继续获取子标签
List elements = bookElement.elements();
//9.遍历
for (Element element : elements) {
//10.获取标签名
System.out.println(element.getName());
//11.获取文本
System.out.println(element.getText());
}
}
}
}
3.4 Dom4j结合XPath解析XML

什么是XPath
XML的路径表达式,可以快速从N层标签中选出需要的标签

Xpath使用步骤
在模块下创建一个lib文件夹(必须叫lib),然后把jaxen-1.1.2.jar包放入lib中(Day22/lib/jaxen-1.1.2.jar)
将lib下的jar包添加到模块中:
a.选中右键 –> add as library
b.点击File –> Project Structure –> 先在Library添加jar包 –> 然后在Moudle加入即可

和XPath相关的API介绍:
List

方法 作用
List selectNodes(“表达式”) 获取符合表达式的元素集合
Element selectSingleNode(“表达式”) 获取符合表达式的唯一元素
XPath语法
获取XML文档节点元素一共有如下4种XPath语法方式:
绝对路径表达式方式 例如: /元素/子元素/子子元素…
相对路径表达式方式 例如: 子元素/子子元素.. 或者 ./子元素/子子元素..
全文搜索路径表达式方式 例如: //子元素//子子元素
谓语(条件筛选)方式 例如: //元素[@attr1=value]
演示
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class XPathDemo {
public static void main(String[] args) throws DocumentException {
//1.创建核心对象
SAXReader reader = new SAXReader();
//2.读取xml文件
Document document = reader.read(new File(“books.xml”));
//3.使用Xpath
//全文搜索路径表达式
List list = document.selectNodes(“//sale”);
for (Element element : list) {
System.out.println(element.getText());
}
//谓语表达式
Element ele = (Element) document.selectSingleNode(“//book[@id=’0001’]/sale”);
System.out.println(ele.getText());
}
}

一.反射
1.1 类加载器

类的加载
当程序要使用某个类时,如果该类还未被加载到内存中,则系统会通过类的加载,类的连接,类的初始化这三个步骤对类进行初始化。如果不出意外,JVM将会连续完成这三个步骤,使用有时也把这三个步骤统称为类加载或类初始化。
类的加载:
•就是将class文件读入内存,并为之创建一个java.lang.Class对象(字节码文件对象,在Heap区)
•任何类被使用时,系统都会为之建立一个java.lang.Class对象
类的连接:
•验证阶段:用于检验被加载的类是否有正确的内部结构,并合其他类协调一致
•准备阶段:负责为类变量分配内存,并社长默认初始化值
•解析阶段:将类的二进制数据中的符合引用替换为直接引用
类的初始化:
•在该阶段,主要就是对类变量进行初始化

类的初始化时机:
•假如类还未被加载和连接,则程序先加载并连接该类
•假如该类的父类还未被初始化,则先初始化其直接父类
•假如类中有初始化语句,则系统依次执行这些初始化语句
注意:在执行第二个步骤时,系统对直接父类的初始化步骤也遵循初始化步骤1-3

类的初始化时机
a.创建类的实例
b.调用类的静态变量,或为静态变量赋值
c.调用类的静态方法
d.初始化某个类的子类
e.直接使用java命令来运行某个主类
f.使用反射方式来强制创建某个类或接口对应的java.lang.Class对象

类加载器的作用和分类
类加载器将类加载到内存

类加载器的作用:
•负责将.class文件加载到内存中,并为之生成对应的java.lang.Class对象

JVM类加载机制
•全盘负责:当一个类加载器负责加载某个Class时,该Clas所依赖的和引用的其他Class也将由该类加载器负责载入,除非显示使用另一个类加载器来载入
•父类委托:当一个类加载器负责加载某个Class时,先让父类加载器试图加载该Class,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类
•缓存机制:保证所有加载过的Class都会被缓存,当程序需要使用某个Class对象时,类加载器先从缓存去中搜索该Class,只有当缓存区中不存在该Class对象时,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存储到缓存区

类加载器有三种:
ClassLoader:是负责加载类的对象
•启动类加载器(Bootstrap ClassLoader):用于加载系统类库\bin目录下的class,例如: rt.jar。
•扩展类加载器(Extension ClassLoader):用于加载扩展类库\jre\lib\ext目录下的class。
•应用程序类加载器(Application ClassLoader):用于加载我们自定义类的加载器。

ClassLoader:中的两种方法
•static ClassLoader getSystemClassLoader();返回用于委派的系统类加载器
•ClassLoader getParent();返回父类加载器进行委派

1
2
3
4
5
6
7
public class ClassLoaderDemo {
public static void main(String[] args) {
ClassLoader classLoader = String.class.getClassLoader();
System.out.println(classLoader);
ClassLoader classLoader1 = ClassLoaderDemo.class.getClassLoader();
System.out.println(classLoader1);
}}

双亲委派机制
当某个类加载器需要加载一个类时,它并不是直接加载,而是把加载需求交给父级类加载器,最终请求会到达启动类加载器,启动类加载器会判断是否可以加载该类,如果加载不了,在交给扩展类加载器,扩展类加载器判断是否可以加载,如果加载不了,再交给程序类加载器
双亲委派机制主要作用:就是让一个类只会被加载一次,确保一个类在内存中只有它一个。

1.2 什么是反射
是指在运行时去获取一个类的变量和方法信息。然后通过获取到的信息(字节码文件对象)来创建对象,调用方法的一种机制。由于这种方法的动态性,可以 极大增强程序的灵活性,程序不需要在编译时期将完成确定,在运行期仍能完成扩展

1.3 反射在实际开发中的应用
•开发IDE
•框架的设计与底层原理的学习

1.4 反射中万物皆对象的概念
一个类变异之后的字节码文件Class对象
Class对象中的成员变量:Field对象
Class对象中的成员方法:Method对象
Class对象中的构造方法:Constructor对象
创建对象–>newInstance对象
调用/执行–>invoke对象

反射语法与正常语法是反过来的,例如:
创建对象:
正常语法:new 构造方法(参数);
反射语法:构造方法对象.new Instance(参数);

调用方法:
正常语法:对象名.成员方法名(参数);
反射语法:成员方法对象.invoke(对象名,参数);

1.5 反射的第一步获取字节码文件对象(Class对象)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class GetClassDemo {
public static void main(String[] args) throws ClassNotFoundException {
//获取一个类Class对象的三种方式
//1.通过类的一个静态成员 class
Class clazz1 = Dog.class;
System.out.println(clazz1);
//2.通过该类的一个对象,获取该类的Clazz对象
Dog dd = new Dog();
Class clazz2 = dd.getClass();
System.out.println(clazz2);

//通过反射强制加载该类,并获取该类的Class对象
Class clazz3 = Class.forName("com.test.Demo02_GetClass.Dog");
System.out.println(clazz3);

/*以上是三种获取Dog类Class对象的方式,并不是获取三个Class对象,实际上它们获取的是同一个Class对象*/
System.out.println(clazz1 == clazz2);
System.out.println(clazz2 == clazz3);
System.out.println(clazz1 == clazz3);
}

}
1.6 Class对象中的三个常用方法
public String getName();//获取全限定类名
public String getSimple();//获取类名
public Object getInstance();//创建Class对象所代表的那个类的对象,底层实际上使用的是该类的无参构造

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

public class ClassMethodDemo {
public static void main(String[] args) throws IllegalAccessException, InstantiationException {
Class clazz = Dog.class;
//获取全限定类名
String name = clazz.getName();
System.out.println(name);

//获取类名
String simpleName = clazz.getSimpleName();
System.out.println(simpleName);

//创建Class对象所代表的那个类的对象,底层实际上使用的是Dog的无参构造
Dog dog = (Dog) clazz.newInstance();
System.out.println(dog);
}

}
输出:
com.test.Demo03_ClassMethod.Dog
Dog
Dog{name=’null’, legs=0}

1.7 通过反射获取构造方法 && 使用构造方法创建对象

反射获取构造方法
Constructor getConstructor(Class… parameterTypes); //获取单个pulic构造
Constructor getDeclaredConstructor(Class… parameterTypes); //获取单个”任意”修饰符构造
Constructor[] getConstructors(); //获得类中的所有构造方法对象,获得public修饰符构造(返回数组)
Constructor[] getDeclaredConstructors();//获得类中的所有修饰符构造方法对象(返回数组)

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


public class GetConstructorDemo {
public static void main(String[] args) throws NoSuchMethodException {
Class clazz = Dog.class;
System.out.println(clazz);

Constructor con1 = clazz.getConstructor();
System.out.println(con1);

//获取单个pulic构造
// Constructor con2 = clazz.getConstructor(String.class, int.class);
// System.out.println(con2);
//获取单个"任意"修饰符构造
Constructor deccon = clazz.getDeclaredConstructor(String.class, int.class);
System.out.println(deccon);

Constructor[] cons = clazz.getConstructors();
System.out.println(cons.length);
for (Constructor con : cons) {
System.out.println(con);
}

Constructor[] deccons = clazz.getDeclaredConstructors();
System.out.println(deccons.length);
for (Constructor con : deccons) {
System.out.println(con);
}
}
}

使用构造方法创建对象
语法:
构造方法对象.newInstance(参数);

1
2
3
4
5
6
7
8
9
10
11
12
@Test
public void test02() throws Exception {
Class clazz = Dog.class;
System.out.println(clazz);

Constructor con1 = clazz.getConstructor();
System.out.println(con1);

//使用构造创建对象
Dog dog = (Dog) con1.newInstance();
System.out.println(dog);
}

如果是私有构造怎么办
私有构造必须设置暴力权限,然后才能正常使用,否则会抛出IllegalAccessException异常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Test
public void test01() throws Exception {
Class clazz = Dog.class;
System.out.println(clazz);

Constructor con1 = clazz.getConstructor();
System.out.println(con1);

Constructor con2 = clazz.getDeclaredConstructor(String.class,int.class);
System.out.println(con2);

//使用构造创建对象
Dog dog = (Dog) con1.newInstance();
System.out.println(dog);

//私有构造,不能直接使用,因为没有权限
//设置暴力访问权限
con2.setAccessible(true);

Dog dog2 = (Dog) con2.newInstance("a", 1);
System.out.println(dog2);

}
1.8 通过反射获取成员方法 && 调用成员方法

反射获取成员方法
public Method get Method(String name,Class…args);//获取public方法
public Method get getDeclaredMethod(String name,Class…args);//获取 任意修饰 方法
public Method[] get Methods();//获取所有的public成员,包括父类继承的
public Method[] get getDeclaredMethods();//获取所有任意修饰方法,不包含父类继承的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class GetConstructorDemo {
@Test
public void test01() throws Exception {
Class clazz = Dog.class;
//获取单个public成员方法
Method eat = clazz.getMethod("eat");
System.out.println(eat);

Method eat1 = clazz.getMethod("eat", String.class, String.class);
System.out.println(eat1);

Method eat3 = clazz.getDeclaredMethod("eat", String.class);
System.out.println(eat3);

System.out.println();

//获取所有的 public 成员方法,包含父类
Method[] methods = clazz.getMethods();
System.out.println(methods.length);
for (Method method : methods) {
System.out.println(method);
}

System.out.println();

//获取所有的 任意修饰 成员方法,但是不包含父类
Method[] decmethods = clazz.getDeclaredMethods();
System.out.println(decmethods.length);
for (Method method : decmethods) {
System.out.println(method);
}
}
}

调用成员方法
语法格式:
成员方法对象.invoke(对象名,参数);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class GetConstructorDemo {
@Test
public void test01() throws Exception {
Dog dd = new Dog();
Class clazz = dd.getClass();
//获取单个public成员方法
Method eat = clazz.getMethod("eat");
System.out.println(eat);

Method eat1 = clazz.getMethod("eat", String.class, String.class);
System.out.println(eat1);

//使用成员方法
eat.invoke(dd);

eat1.invoke(dd," a"," b");
}}

如果是私有成员方法怎么调用
私有成员方法必须设置暴力权限,然后才能正常使用,否则会抛出IllegalAccessException异常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Test
public void test02() throws Exception {
Dog dd = new Dog();
Class clazz = dd.getClass();
//获取单个public成员方法
Method eat = clazz.geMethod("eat");
System.out.println(eat);

Method eat1 = clazz.getMethod("eat", String.class, String.class);
System.out.println(eat1);

Method eat2 = clazz.getDeclaredMethod("eat", String.class);
System.out.println(eat1);

//使用成员方法
eat.invoke(dd);

eat1.invoke(dd," a"," b");

//设置暴力访问权限,否则会抛出IllegalAccessException异常
eat2.setAccessible(true);
eat2.invoke(dd,"a");
}

1.9 通过反射获取成员属性

二.注解
2.1 什么是注解
•注解是JDK1.5的新特性
•注解是一种标记,是类的组成部分,可以给类携带一些额外的信息
•标记(注解)可以用在各种地方(包,类,构造方法,普通方法,成员变量,局部变量…)
•注解主要是给编译器或JVM看的,用于完成某些特定功能

2.2 注解的三个作用
a.给程序带入一些参数
b.编译检查
c.给框架使用,作为框架的配置文件

2.3 常见的注解介绍
@author:用于标识作者
@version:用于标识对象的版本号
@Override:用于标识该方法是重写的
@deprecated:用于标识过期的API
@Test:用于单元测试的注解

2.4 自定义注解
自定义类:public class 类名
自定义接口:public interface 接口
自定义枚举:public enum 枚举名
自定义注解:public @interface 注解名

格式:
public @interface 注解名{

}

2.5 给自定义注解添加属性
格式:
public @interface 注解名{
//注解内部只有属性,没有别的
数据类型 属性名();
数据类型 属性名()[default 默认值];
}

注解中并不是所有数据类型都可以的
只能是以下三大类型:
a.八大基本类型(byte,short,char,int,long,float,double,boolean)
b.引用类型(String,Class,注解类型,枚举类型)
c.以上12种具体类型的一维数组

2.6 自定义注解练习
创建时选中Annocation

1
2
3
4
5
6
public @interface Book {
String value();
double Price() default 100;

String[] atuhors();
}

2.7 使用注解时的注意事项
使用格式:
@注解名(属性名=属性值,属性名=属性值)

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

@Book(value = "A", Price = 150.0, authors ={"a","b"})

public class TestDemo {
@Book(value = "B", Price = 160.0, authors ={"c","d"})
private int age;
private String name;

public TestDemo() {
}

@Book(value = "C", Price = 170.0, authors ="e")

public TestDemo(int age, String name) {
this.age = age;
this.name = name;
}

public void show(int age) {
String name = "a";
System.out.println(age);
}}

注意:
a.使用注解时的每个属性都必须有值(有默认值可以不再赋值,没有默认值的必须赋值)
b.如果是数组,需要使用{}把值包起来,如果数组的值只有一个,那么{}可以省略

2.8 自定义注解中的特殊属性名value
a.如果注解中只有一个属性,并且名字叫做value,那么使用时可以直接写属性的值,省略属性名
b.如果注解中有value之外的其他属性,那么其他属性都有默认值,且使用注解时只给value赋值,那么也可以直接写属性的值,省略属性名

1
2
3
public @interface Book {
//特殊属性value
String value();double Price() default 100.0;}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class TestDemo {
private int age;
private String name;
public TestDemo() {
}

@Book("a")
public TestDemo(int age, String name) {
this.age = age;
this.name = name;
}

public void show(int age) {
String name = "a";
System.out.println(age);
}}

2.9 注解的注解–元注解
Java官方提供的注解,用来修饰或定义注解的注解

两个元注解
@Target 元注解
作用:用来标识注解的使用位置,如果没有标识,那么注解在各种地方都可以使用
取值:
可以使用ElementType枚举类中的值,常用如下:
TYPE,类,接口
FIELD, 成员变量
METHOD, 成员方法
PARAMETER, 方法参数
CONSTRUCTOR, 构造方法
LOCAL_VARIABLE, 局部变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//使用元注解来修饰定义的注解
@Target(ElementType.LOCAL_VARIABLE)
public @interface MyAnno {

public class TestDemo {
private int age;
private String name;
}
public TestDemo() {
}

public TestDemo(int age, String name) {
this.age = age;
this.name = name;
}

public void show(int age) {
//使用定义的注解
@MyAnno
String name = "a";
System.out.println(age);
}

@Retention 元注解
作用:用来标识注解的声明周期(有效范围)
取值:
可以使用RetentionPolicy枚举类中的值,常用如下:

方法名 说明
SOURCE 只作用在源码阶段,编译生成的字节码文件后消失
CLASS 在源码阶段、编程成字节码文件阶段存在,运行阶段不存在
RUNTIME 在源码阶段,字节码文件阶段,运行阶段都存在

1
2
3
4
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.LOCAL_VARIABLE)
public @interface MyAnno {
}

2.10 注解的解析

什么是注解解析
提供代码获取出来某个注解中的属性值

注解解析的步骤
a.获取注解所在类的Class对象
b.获取注解所在的对象(可能Field,Method,Constructor)
c.判断获取的对象中是否有该注解存在
d.如果有要的注解,取出注解即可
e.从注解中取出属性值即可

与之相关的API:
Annotation:注解类,Java中所有定义的注解的父类
AnnotatedElement:该接口定义了与注解解析相关的方法
Field,Method,Constructor,Class等类都是实现了AnnotatedElement接口

public boolean isAnnotationPresent(Class annotationClass);//判断是否存在某个注解
Annotion getAnnotation(Class annotationClass);//获取指定类型的注解
Annotation[] getAnnotations();//获取所有注解,包含父类继承的
Annotation[] getDeclaredAnnotations()//获取所有注解,只包含本类的

注解解析的代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@Retention(RetentionPolicy.RUNTIME)
public @interface Student {
int Age();String Name();

String[] bys();
}

public class TestStudent {
@Student(Age = 1, Name = "a", bys = "c")
public void show() {

}}


public class TestDemo {
public static void main(String[] args) throws Exception {
Class clazz = TestStudent.class;
Method showMethod = clazz.getMethod("show");
if (showMethod.isAnnotationPresent(Student.class)) {
Student anno = showMethod.getAnnotation(Student.class);
int age = anno.Age();
String name = anno.Name();
String[] bys = anno.bys();

System.out.println(age+" "+name+" "+bys.toString());

System.out.println("Y");
} else {
System.out.println("N");
}
}}

2.11 注解解析案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTest {
}

public class Demo {
@MyTest
public void test01(){
System.out.println("a");
}

@MyTest
public void test02(){
System.out.println("b");
}
}

public class TestMyTest {
public static void main(String[] args) throws Exception {
Class clazz = Demo.class; Method[] methods = clazz.getMethods();

for (Method method : methods) {
if (method.isAnnotationPresent(MyTest.class)) {
method.invoke(new Demo());
}else {
System.out.println("N Annotation");
}
}
}}

总结:
反射技术获取Class字节码对象
Class clazz = 类名.class;
Class clazz = 对象名.getClass();
Class clazz = Class.forName(“包名”,”类名”);

通过反射技术怄气构造方法对象,并创建对象
获取构造:
public Constructor getConstructor(参数的类型.class,…)//获取单个 public 构造
public Constructor getDeclareConstructor(参数的类型.class,…)//获取单个 任意修饰 构造
public Constructor[] getConstructors()//获取所有个 public 构造
public Constructor[] getDeclareConstructors()//获取所有 任意修饰 构造
使用构造:
构造方法对象.newInstance(实际参数);
如果是私有构造:
必须在使用之前设置暴力权限->构造方法对象.getAccessable(true);

反射获取成员方法对象,并调用方法
public Method getMethod(String name,参数的类型.class,…)//获取单个 public 方法
public Method getDeclareMethod(String name,参数的类型.class,…)//获取单个 任意修饰 方法
public Method[] getMethods()//获取所有个 public 方法,包括父类继承的
public Method[] getDeclareMethods()//获取所有 任意修饰 方法,不包含父类的

使用方法:
成员方法对象.invoke(对象名,方法的实际参数);
如果是私有方法:
必须在使用之前设置暴力权限->成员方法对象.getAccessable(true);

通过反射获取属性对象,并能够给对象的属性赋值和取值

注解的作用:
a.给程序带入参数
b.编译检查
c.给框架做配置文件

自定义注解和使用注解:
自定义注解:
public @interface 注解名{
数据类型 属性名();
数据类型 属性名() default 默认值;
数据类型[] 属性名() default {默认值1,默认值2,…};

}
数据类型限定:
a.八大基本类型(byte,short,char,int,long,float,double,boolean)
b.引用类型(String,Class,注解类型,枚举类型)
c.以上12种具体类型的一维数组

特殊属性:
value
a.如果注解中只有一个属性,并且名字叫做value,那么使用时可以直接写属性的值,省略属性名
b.如果注解中有value之外的其他属性,那么其他属性都有默认值,且使用注解时只给value赋值,那么也可以直接写属性的值,省略属性名

元注解及其作用:
@Target
@Retension

解析注解并获取注解中的数据
写代码把某个注解上的那些属性值打印出来

注解解析的步骤
a.获取注解所在类的Class对象
b.获取注解所在的对象(可能Field,Method,Constructor)
c.判断获取的对象中是否有该注解存在
d.如果有要的注解,取出注解即可
e.从注解中取出属性值即可

一.Selector(选择器,多路复用器)
1.1 多路复用的概念
多路:多个服务器分别去监听多个端口

如果不使用”多路复用”,每个服务器都需要开许多线程处理每个端口的请求,如果在高并发下,会造成系统性能下降。
如果使用”多路复用”,可以把多个服务器注册到一个Selector,只需要开启一个线程即可处理服务器(在高并发下性能较高)

1.2 选择器Selector

什么是Selector
Selector也称为选择器,也叫多路复用器,可以让多个Channel注册到Selector上,然后监听各个Channel上发生的事件

Selector创建API
创建Selector的方式:
Selector selector = Selector.open();

将要交给选择器管理的通道注册到选择器:
Channel.configureBlocking(false);//channel是一个通道,并且必须是非阻塞通道
SelectionKey key = channel.register(selector,SelectionKey.OP_ACCEPT);

参数1:该通道注册到的选择器
参数2:选择器对何种事件感兴趣(服务器通道只能写SelectionKey.OP_ACCEPT,表示有客户端连接)
返回值:SelectionKey 是一个对象,该对象中包含了注册到选择器的通道

可以监听4中不同类型的事件,使用SelectionKey常量表示:
连接就绪–常量:SelectionKey.OP_CONNECT
接收就绪–常量:SelectionKey.OP_ACCEPT(ServerSocketChannel在注册时只能使用此项)
读就绪–常量:SelectionKey.OP_READ
写就绪–常量:SelectionKey.OP_WRITE

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class SelectorDemo {
public static void main(String[] args) throws IOException {
//多个服务器,每个监听多个端口
ServerSocketChannel server1 = ServerSocketChannel.open();
server1.configureBlocking(false);
server1.bind(new InetSocketAddress(8888));

ServerSocketChannel server2 = ServerSocketChannel.open();
server2.configureBlocking(false);
server2.bind(new InetSocketAddress(9999));

//获取Selector
Selector selector = Selector.open();

//将多个Server注册到同一个Selector
server1.register(selector, SelectionKey.OP_ACCEPT);
server2.register(selector, SelectionKey.OP_ACCEPT);
}
}

1.3 Selector中的常用方法

Channel注册Selector的API
•获取所有已经成功注册到当前选择器的通道集合
public Set keys();

•表示所有已有客户端连接的通道的集合
public Set selectedKeys();

•如果目前没有客户端连接,该方法会阻塞。如果有客户端连接会返回本次连接的客户端数量
public int select();

代码演示
1.4 Selector实现多路连接(上)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
public class SocketChannelSelector_Demo {
public static void main(String[] args) {
new Thread(() -> {
try (SocketChannel socket = SocketChannel.open()) {
socket.connect(new InetSocketAddress("127.0.0.1", 8888));
} catch (IOException e) {
e.printStackTrace();
}
}).start();
new Thread(() -> {
try (SocketChannel socket = SocketChannel.open()) {
socket.connect(new InetSocketAddress("127.0.0.1", 9999));
} catch (IOException e) {
e.printStackTrace();
}
}).start();
}
}


public class ServerSelector_SelectionKey {
public static void main(String[] args) throws IOException {
//多个服务器,每个监听多个端口
ServerSocketChannel server1 = ServerSocketChannel.open();
server1.configureBlocking(false);
server1.bind(new InetSocketAddress(8888));

ServerSocketChannel server2 = ServerSocketChannel.open();
server2.configureBlocking(false);
server2.bind(new InetSocketAddress(9999));

//获取Selector
Selector selector = Selector.open();

//将多个Server注册到同一个Selector
server1.register(selector, SelectionKey.OP_ACCEPT);
server2.register(selector, SelectionKey.OP_ACCEPT);

//接收客户端连接
Set<SelectionKey> keys = selector.keys();
System.out.println(keys.size());

Set<SelectionKey> selectionKeys = selector.selectedKeys();
System.out.println(selectionKeys.size());

int selectedCount = selector.select();
System.out.println(selectedCount);
}
}

1.5 Selector实现多路连接(下)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
public class SocketChannelSelector_Demo {
public static void main(String[] args) {
new Thread(() -> {
try (SocketChannel socket = SocketChannel.open()) {
socket.connect(new InetSocketAddress("127.0.0.1", 8888));
} catch (IOException e) {
e.printStackTrace();
}
}).start();
new Thread(() -> {
try (SocketChannel socket = SocketChannel.open()) {
socket.connect(new InetSocketAddress("127.0.0.1", 9999));
} catch (IOException e) {
e.printStackTrace();
}
}).start();
}
}


public class ServerSelector_SelectionKey {
public static void main(String[] args) throws IOException, InterruptedException {
//多个服务器,每个监听多个端口
ServerSocketChannel server1 = ServerSocketChannel.open();
server1.configureBlocking(false);
server1.bind(new InetSocketAddress(8888));

ServerSocketChannel server2 = ServerSocketChannel.open();
server2.configureBlocking(false);
server2.bind(new InetSocketAddress(9999));

//获取Selector
Selector selector = Selector.open();

//将多个Server注册到同一个Selector
server1.register(selector, SelectionKey.OP_ACCEPT);
server2.register(selector, SelectionKey.OP_ACCEPT);

//接收客户端连接
Set<SelectionKey> keys = selector.keys();
System.out.println(keys.size());
while (true) {
Set<SelectionKey> selectionKeys = selector.selectedKeys();
System.out.println(selectionKeys.size());

//此方法会阻塞
int selectedCount = selector.select();
System.out.println(selectedCount);

//遍历已连接通道到集合
Iterator<SelectionKey> it = selectionKeys.iterator();
while (it.hasNext()) {
//获取当前通道
SelectionKey key = it.next();
//从SelectionKey中获取通道对象
ServerSocketChannel channel = (ServerSocketChannel) key.channel();

//查看此通道是监听哪个端口
System.out.println(channel.getLocalAddress());

//取出连接到该服务器客户端到客户端通道
SocketChannel accept = channel.accept();
System.out.println(accept);
//从连接到通道中把已经处理过到服务器通道移除
it.remove();
}
Thread.sleep(1000 * 5);
System.out.println();
}
}
}

1.6 Selecto多路信息接收测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
public class ServerSelector_SelectionKey {
public static void main(String[] args) throws IOException, InterruptedException {
//多个服务器,每个监听多个端口
ServerSocketChannel server1 = ServerSocketChannel.open();
server1.configureBlocking(false);
server1.bind(new InetSocketAddress(8888));

ServerSocketChannel server2 = ServerSocketChannel.open();
server2.configureBlocking(false);
server2.bind(new InetSocketAddress(9999));

//获取Selector
Selector selector = Selector.open();

//将多个Server注册到同一个Selector
server1.register(selector, SelectionKey.OP_ACCEPT);
server2.register(selector, SelectionKey.OP_ACCEPT);

//接收客户端连接
Set<SelectionKey> keys = selector.keys();
System.out.println(keys.size());
while (true) {
Set<SelectionKey> selectionKeys = selector.selectedKeys();
System.out.println(selectionKeys.size());

//此方法会阻塞
int selectedCount = selector.select();
System.out.println(selectedCount);

//遍历已连接通道到集合
Iterator<SelectionKey> it = selectionKeys.iterator();
while (it.hasNext()) {
//获取当前通道
SelectionKey key = it.next();
//从SelectionKey中获取通道对象
ServerSocketChannel channel = (ServerSocketChannel) key.channel();

//查看此通道是监听哪个端口
System.out.println(channel.getLocalAddress());

//取出连接到该服务器客户端到客户端通道
SocketChannel accept = channel.accept();
System.out.println(accept);

//与客户端进行交互的代码,接收客户端接收的信息
ByteBuffer inBuf = ByteBuffer.allocate(1024);
accept.read(inBuf);
inBuf.flip();
String msg = new String(inBuf.array(),0,inBuf.limit());
System.out.println(channel.getLocalAddress()+msg);

//从连接到通道中把已经处理过到服务器通道移除
it.remove();
}
Thread.sleep(1000 * 5);
System.out.println();
}
}
}

二.NIO2-AIO(异步非阻塞)
2.1 AIO概述和分类
什么是AIO:ASynchronized 异步非阻塞的IO

AIO的分类:
异步文件通道•AsynchronousFileChannel
异步客户端通道•AsynchronousSocketChannel
异步服务器通道•AsynchronousServerSocketChannel
异步UDP协议通道•AsynchronousDatagramChannel

AIO的异步:
a.建立连接时可以使用异步(调用连接时的方法,非阻塞,连接成功后会以方法的回调机制通知我们)
b.读取数据时,可以使用异步(调用read方法时,非阻塞,等数据接收到之后以方法调用的机制通知我们)

2.2 AIO异步非阻塞连接的建立

异步非阻塞的客户端通道

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
public class AIO_SocketChannel {
public static void main(String[] args) throws IOException {
//创建异步客户端通道
AsynchronousSocketChannel socketChannel = AsynchronousSocketChannel.open();

//连接服务器,采用异步非阻塞方式
//connect(服务器IP和端口号),附件(null),接口
socketChannel.connect(new InetSocketAddress("127.0.0.1",8888),null, new CompletionHandler<Void, Object>() {
@Override
public void completed(Void result, Object attachment) {
System.out.println("Successful");
}

@Override
public void failed(Throwable exc, Object attachment) {
System.out.println("Failed");
}
});

System.out.println("Continue");
while (true){

}
}
}
异步非阻塞的服务器通道


public class AIO_ServerSocketChannel {
public static void main(String[] args) throws IOException {
//创建异步服务器端通道
AsynchronousServerSocketChannel serversocketChannel = AsynchronousServerSocketChannel.open();

//绑定本地某个端口
serversocketChannel.bind(new InetSocketAddress(8888));

//接收异步客户端,采用异步非阻塞方式
//accept附件(null),接口
serversocketChannel.accept(null, new CompletionHandler<AsynchronousSocketChannel, Object>() {
@Override
public void completed(AsynchronousSocketChannel result, Object attachment) {
System.out.println("Connect Client Successful");
}

@Override
public void failed(Throwable exc, Object attachment) {
System.out.println("Connect Client Failed");
}
});

System.out.println("Server Continue");
while (true) {

}
}
}

异步非阻塞建立连接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
public class AIO_SocketChannel {
public static void main(String[] args) throws IOException {
//创建异步客户端通道
AsynchronousSocketChannel socketChannel = AsynchronousSocketChannel.open();

//连接服务器,采用异步非阻塞方式
//connect(服务器IP和端口号),附件(null),接口
socketChannel.connect(new InetSocketAddress("127.0.0.1", 8888), null, new CompletionHandler<Void, Object>() {
@Override
public void completed(Void result, Object attachment) {
System.out.println("Connect Server Successful");
}

@Override
public void failed(Throwable exc, Object attachment) {
System.out.println("Connect Server Failed");
}
});

System.out.println("Client Continue");
while (true) {

}
}
}

public class AIO_ServerSocketChannel {
public static void main(String[] args) throws IOException {
//创建异步服务器端通道
AsynchronousServerSocketChannel serversocketChannel = AsynchronousServerSocketChannel.open();

//绑定本地某个端口
serversocketChannel.bind(new InetSocketAddress(8888));

//接收异步客户端,采用异步非阻塞方式
//accept附件(null),接口
serversocketChannel.accept(null, new CompletionHandler<AsynchronousSocketChannel, Object>() {
@Override
public void completed(AsynchronousSocketChannel result, Object attachment) {
System.out.println("Connect Client Successful");
try {
System.out.println(result.getLocalAddress());
} catch (IOException e) {
e.printStackTrace();
}
}

@Override
public void failed(Throwable exc, Object attachment) {
System.out.println("Connect Client Failed");
}
});

System.out.println("Server Continue");
while (true) {

}
}
}

2.3 AIO同步读写数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
public class AIO_SocketChannel {
public static void main(String[] args) throws IOException {
//创建异步客户端通道
AsynchronousSocketChannel socketChannel = AsynchronousSocketChannel.open();

//connect(服务器IP和端口号),附件(null),接口
socketChannel.connect(new InetSocketAddress("127.0.0.1", 8888), null, new CompletionHandler<Void, Object>() {
@Override
public void completed(Void result, Object attachment) {
System.out.println("Connect Server Successful");

//客户端给服务器发送数据
ByteBuffer buffer = ByteBuffer.allocate(1024);
buffer.put("I am AIO Client".getBytes());
buffer.flip();
socketChannel.write(buffer);
try {
socketChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}

@Override
public void failed(Throwable exc, Object attachment) {
System.out.println("Connect Server Failed");
}
});

System.out.println("Client Continue");
while (true) {

}
}
}


public class AIO_ServerSocketChannel {
public static void main(String[] args) throws IOException {
//创建异步服务器端通道
AsynchronousServerSocketChannel serversocketChannel = AsynchronousServerSocketChannel.open();

//绑定本地某个端口
serversocketChannel.bind(new InetSocketAddress(8888));

//接收异步客户端,采用异步非阻塞方式
//accept附件(null),接口
serversocketChannel.accept(null, new CompletionHandler<AsynchronousSocketChannel, Object>() {
@Override
public void completed(AsynchronousSocketChannel result, Object attachment) {
System.out.println("Connect Client Successful");

ByteBuffer buffer = ByteBuffer.allocate(1024);
Future<Integer> future = result.read(buffer);
try {
byte[] array = buffer.array();
System.out.println(Arrays.toString(array));

Integer len = future.get();
System.out.println(len);
//当调用future的get方法时,数据才写入到buffer中
//所以我们不能在调用get方法之前,调用flip,否则数据将无法写入
buffer.flip();
} catch (Exception e) {
e.printStackTrace();
}
}

@Override
public void failed(Throwable exc, Object attachment) {
System.out.println("Connect Client Failed");
}
});

System.out.println("Server Continue");
while (true) {
}
}
}

2.4 AIO异步读写数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
/**
* AIO下的 异步客户端通道
*/
public class AIOSocketChannelDemo01 {
public static void main(String[] args) throws IOException, InterruptedException {
//1.创建 异步的客户端通道
AsynchronousSocketChannel socketChannel = AsynchronousSocketChannel.open();
//2.去连接服务器,采用异步非阻塞方法
//connect(服务器的connectIP和端口号,附件(null),接口);
socketChannel.connect(new InetSocketAddress("127.0.0.1", 8888), null, new CompletionHandler<Void, Object>() {
@Override
public void completed(Void result, Object attachment) {
System.out.println("连接服务器成功...");
//给服务器发送数据
ByteBuffer buffer = ByteBuffer.allocate(1000);
buffer.put("你好我是AIO客户端..".getBytes());
buffer.flip();
//异步的write(缓冲区,超时时间,时间单位,附件(null),回调接口);
socketChannel.write(buffer, 10, TimeUnit.SECONDS, null, new CompletionHandler<Integer, Object>() {
@Override
public void completed(Integer result, Object attachment) {
System.out.println("数据成功发送...");
//释放资源
try {
socketChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}

@Override
public void failed(Throwable exc, Object attachment) {
System.out.println("数据发送失败...");
}
});


}

@Override
public void failed(Throwable exc, Object attachment) {
System.out.println("连接服务器失败...");
}
});

System.out.println("程序继续执行....");
Thread.sleep(5000);
}
}


/**
* AIO下的 异步服务器端通道
*/
public class AIOServerSocketChannelDemo01 {
public static void main(String[] args) throws IOException, InterruptedException {
//1.创建一个异步的服务器通道
AsynchronousServerSocketChannel serverSocketChannel = AsynchronousServerSocketChannel.open();
//2.绑定本地某个端口
serverSocketChannel.bind(new InetSocketAddress(8888));
//3.接收异步客户端,采用异步非阻塞方式
//accpet(附件(nulll),接口);
serverSocketChannel.accept(null, new CompletionHandler<AsynchronousSocketChannel, Object>() {
@Override
public void completed(AsynchronousSocketChannel socketChannel, Object attachment) {
System.out.println("有客户端连接....");
//从客户端中读取数据
//异步的read(字节缓冲区,超时时间,时间单位,附件(null),回调接口)
ByteBuffer buffer = ByteBuffer.allocate(1000);
socketChannel.read(buffer, 10, TimeUnit.SECONDS, null, new CompletionHandler<Integer, Object>() {
@Override
public void completed(Integer result, Object attachment) {
System.out.println("数据读取完毕..");
System.out.println("接收到数据的长度:"+result);
System.out.println("接收到的数据是:" + new String(buffer.array(), 0, result));
//释放资源
try {
socketChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}

@Override
public void failed(Throwable exc, Object attachment) {
System.out.println("读取数据失败...");
}
});
}

@Override
public void failed(Throwable exc, Object attachment) {
System.out.println("客户端连接失败...");
}
});

System.out.println("程序继续执行..");
while (true) {
Thread.sleep(1000);
}

}
}

总结:
Selector作用:
Selector可以让多个服务器注册到它上,完成多路复用功能

使用Selector选择器
注册:
channel.register(selector,SelectionKey.OP_ACCEPT);
方法:
//表示所有被连接到服务器通道的集合
public Set selectedKeys();
Set keys = selector.selectedKeys();

//获取所有已经成功注册到选择器的服务器通道集合
public Set keys();
Set keys = selector.keys();

//如果目前没有客户端连接,该方法会阻塞。如果有客户端连接会返回本次连接的客户端数量
public int select();
int count = selector.select();

AIO特点:
a.支持异步非阻塞连接 connect accept
b.支持异步非阻塞到读写数据
socketChannel.write(…);
socketChannel.read(…);

一.Junit单元测试
1.什么是单元测试
I.单元,在Java中的一个单元可以指某个方法,或某个类
II.测试,写一段代码单元进行测试

Junit是专门为单元测试提供的一个第三方框架
作用:让一个普通方法独立运行(替代main方法)

2.Junit的使用步骤
•下载
junit.org (Jetbrains IDEA开发工具中自带)

•具体使用步骤
a.编写一个被测试类(业务类)
b.编写测试类
c.在测试类中使用Junit单元测试框架

•运行测试
在需要独立运行的方法上加注解 @Test

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//业务类
public class Dog {
public int getSum(int a, int b, int c) {
int sum = a + b + c;
return sum;
}
}

public class TestDog {
@Test
public void testDog01(){
Dog dd = new Dog();
int sum = dd.getSum(1, 2, 3);
System.out.println(sum);
}

@Test
public void testDog02(){
Dog dd = new Dog();
int sum = dd.getSum(11, 22, 33);
System.out.println(sum);
}
}

3.单元测试中的其他四个注解
•Junit4.x

注解 作用
@Before 表示该方法会自动在”每个”@Test方法之前执行(每次执行)
@After 表示该方法会自动在”每个”@Test方法之后执行(每次执行)
@BeforeClass 表示该方法会自动在”所有”@Test方法之前执行,必须为静态类(只执行一次)
@AfterClass 表示该方法会自动在”所有”@Test方法之后执行,必须为静态类(只执行一次)
注意:@BeforeClass和@AfterClass必须在静态方法中使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class TestDog {
@AfterClass
public static void testDog02(){
Dog dd = new Dog();
int sum = dd.getSum(1, 2, 3);
System.out.println(sum);
}

@Test
public void testDog01(){
Dog dd = new Dog();
int sum = dd.getSum(11, 22, 33);
System.out.println(sum);
}

@BeforeClass
public static void testDog03(){
Dog dd = new Dog();
int sum = dd.getSum(111, 222, 333);
System.out.println(sum);
}
}

•Junit5.x

@BeforeEach ———— 相当于@Before
@AfterEach ———— 相当于@After
@BeforeAll ———— 相当于@BeforeClass
@AfterAll ———— 相当于@AfterClass

注意:@BeforeAll和@AfterAll也必须修饰静态方法

Assert.assertEquals(“异常信息”,”得到的结果”,”预期的结果”)
作用:比较”得到的结果”和”预期的结果”
如果一样:什么都不做
如果不一样:抛出异常,并封装异常信息

二.NIO介绍
1.阻塞与非阻塞
阻塞:完成某个任务时,任务没有结束之前,当前线程无法继续向下执行
非阻塞:完成某个任务时,不需要等待任务结束,当前线程立即可以向下继续执行,后期再通过其他代码获取任务结果

2.同步与异步
同步:
同步可能是阻塞的,也可能是非阻塞的
同步阻塞:完成某个任务时,任务没有结束之前,当前线程无法继续向下执行
同步非阻塞:完成某个任务时,不需要等待任务结束,当前线程立即可以向下继续执行,后期再通过其他代码获取任务结果

异步:
异步一般来说都是非阻塞
异步非阻塞:完成某个任务时,不需要等待任务结束,当前线程立即可以向下继续执行,后期不需要写其他代码获取任务结果,任务完成后自动会通过其他机制把结果传递回来

3.BIO,NIO,AIO介绍

名称 介绍 名称
BIO(传统IO): 同步阻塞的IO Block IO
NIO: 同步阻塞与同步非阻塞,由Buffer(缓冲区),Channel(通道),Selector(迭代器)组成 New IO
NIO2(AIO): 异步非阻塞IO A Synchronized
三.NIO-Buffer类介绍
1.Buffer的介绍和种类
•什么是Buffer
Buffer称为缓冲区,本质是一个数组

•Buffer的一般操作步骤
a.写入缓冲区(把数据保存到数组中)
b.调用flip方法(切换缓冲区的写模式为读模式)
c.读缓冲区(把数组中的数据读取出来)
d.调用clear(清空缓冲区)或compact(清除缓冲区中的已读取过的数据数据)方法

•Buffer的种类

名称 介绍
ByteBuffer 字节缓冲区(字节数组)最常用
CharBuffer 字符缓冲区(字符数组)
DoubleBuffer Double缓冲区(小数数组)
FloatBuffer Float缓冲区(小数数组)
IntBuffer 整型缓冲区(整型数组)
LongBuffer 长整型缓冲区(长整型数组)
ShortBuffer 短整型缓冲区(短整型数组)
2.byteBuffer的三种创建方式
public static allocate(int capacity);//在堆区中申请一个固定大小的ByteBuffer缓冲区
public static allocateDirect(int capacity);//在系统的内存中申请一个固定大小字节的ByteBuffer缓冲区
public static wrap(byte[] arr);//把一个字节数组直接包装成ByteBuffer缓冲区

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

public class ByteBuffer01 {
public static void main(String[] args) {
//allocate,间接缓冲区
// 在JVM堆中,创建或销毁更快
ByteBuffer buffer1 = ByteBuffer.allocate(10);
//allocateDirect,直接缓冲区
// 直接在操作系统中申请,操作缓冲区角度,效率更高
ByteBuffer buffer2 = ByteBuffer.allocateDirect(10);

//wrap,间接缓冲区,与buffer1相同
byte[] bs = new byte[1024];
ByteBuffer buffer3 = ByteBuffer.wrap(bs);
}
}

3.byteBuffer的三种添加数据方式
public ByteBuffer put(byte b);//添加单个字节
public ByteBuffer put(byte b,bs);//添加字节数组
public ByteBuffer put(byte b,bs,int startIndex,int len);//添加一个字节数组中的一部分

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

public class ByteBuffer02 {
public static void main(String[] args) {
ByteBuffer buffer1 = ByteBuffer.allocate(10);
System.out.println(Arrays.toString(buffer1.array()));
//[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
buffer1.put((byte) 10);
System.out.println(Arrays.toString(buffer1.array()));
//[10, 0, 0, 0, 0, 0, 0, 0, 0, 0]
buffer1.put((byte) 20);
buffer1.put((byte) 30);
System.out.println(Arrays.toString(buffer1.array()));
//[10, 20, 30, 0, 0, 0, 0, 0, 0, 0]
byte[] bs = {40,50,60};
ByteBuffer put = buffer1.put(bs);
System.out.println(Arrays.toString(buffer1.array()));
//[10, 20, 30, 40, 50, 60, 0, 0, 0, 0]
byte[] new_bs = {70,80,90};
buffer1.put(new_bs,0,2);
System.out.println(Arrays.toString(buffer1.array()));
//[10, 20, 30, 40, 50, 60, 70, 80, 0, 0]

// byte[] break_bs = {40,50,60};
// ByteBuffer break_put = buffer1.put(break_bs);
// System.out.println(Arrays.toString(buffer1.array()));
//BufferOverflowException
}
}

4.byteBuffer的容量-capacity
什么是容量(capacity):
是指Buffer最多包含的元素个数,并且Buffer一旦创建,容量无法更改
public int capacity();//获取Buffer容量

1
2
3
4
5
6
7
public class ByteBuffer03 {
public static void main(String[] args) {
ByteBuffer buffer = ByteBuffer.allocate(10);
int capacity = buffer.capacity();
System.out.println(capacity);
}
}

5.byteBuffer的限制-limit
什么是限制:是指第一个不能操作的元素索引,取值范围(0-capacity)
限制的作用:相当于人为”修改”缓冲区的大小,实际上缓冲区大小没有改变,只是元素的个数改变了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

public class ByteBuffer04_limit {
public static void main(String[] args) {
ByteBuffer buffer = ByteBuffer.allocate(10);
System.out.println(Arrays.toString(buffer.array()));
//[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
int limit = buffer.limit();
System.out.println(limit);

buffer.limit(3);
byte[] bs = {10,20,30,40};//BufferOverflowException
buffer.put(bs);
System.out.println(Arrays.toString(buffer.array()));

}
}

6.byteBuffer的位置-position
什么是位置:将要写入/读取的元素的索引,取值范围(0-capacity/limit,有最大值position但是无法操作)

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

public class ByteBuffer05_position {
public static void main(String[] args) {
ByteBuffer buffer = ByteBuffer.allocate(10);
System.out.println(buffer.capacity());//10
System.out.println(buffer.limit());//10
System.out.println(buffer.position());//0

byte[] bs ={10,20,30,40,50};
buffer.put(bs);
System.out.println(Arrays.toString(buffer.array()));
//[10, 20, 30, 40, 50, 0, 0, 0, 0, 0]
System.out.println(buffer.position());//5

buffer.position(3);
buffer.put((byte)101);
System.out.println(Arrays.toString(buffer.array()));
//[10, 20, 30, 101, 50, 0, 0, 0, 0, 0]
System.out.println(buffer.position());//4
}
}

7.byteBuffer的标记-mark
什么是标记:给当前的position记录下来,当调用reset(重置)时,position会回到标记,取值范围(0-position)

public class ByteBuffer06_mark {
    public static void main(String[] args) {
    ByteBuffer buffer = ByteBuffer.allocate(10);

    buffer.put((byte)10);
    buffer.put((byte)20);
    buffer.put((byte)30);
    buffer.mark();
    buffer.put((byte)40);
    buffer.put((byte)50);

    System.out.println(Arrays.toString(buffer.array()));
    //[10, 20, 30, 40, 50, 0, 0, 0, 0, 0]
        buffer.reset();
    buffer.put((byte)98);
    buffer.put((byte)99);
    buffer.put((byte)100);

    System.out.println(Arrays.toString(buffer.array()));
    //[10, 20, 30, 98, 99, 100, 0, 0, 0, 0]
    }
}

8.byteBuffer的其他方法
public int remaining();//获取position与limit之间的元素数
public boolean isReadyOnly();//获取当前缓冲区是否可读
public boolean isDirect();//获取当前缓冲区是否为直接缓冲区
public boolean clear();//还原缓冲区的初始状态

将position设置0
将limit置为capacity
丢弃标记
public Buffer flip();//切换读写模式(缩小范围)

将limit设置为当前position位置
将当前position位置设置为0
丢弃标记
public Buffer rewind();//重绕缓冲区

将position置为0
limit不变
丢弃标记
四.Channel(通道)
1.Channel介绍和类
什么是Channel:Channel是一个类,new出对象,可以通过它读写数据,和IO流类似,最大的不同在于IO流有Input/Output之分,但是Channel没有输入输出之分,都是Channel

•为所有的原始类型提供(Buffer)缓存支持;
•字符集编码解决方案(Charset);
•Channel : 一个新的原始I/O抽象;
•支持锁和内存映射文件的文件访问接口;
•提供多路(non-bloking)非阻塞式的高伸缩性网路I/O。

Channel的分类:

名称 介绍
FileChannel 文件通道
DatagramChannel UDP协议通道,通过UDP协议收发数据
SocketChannel TCP协议中客户端通道,给客户端写数据
ServerSocketChannel TCP协议中服务器端通道,给服务器写数据
2.FileChannel类的基本使用

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

public class FileChannel01 {
public static void main(String[] args) throws Exception {
//创建文件对象
File srcFile = new File("Danboard.jpg");
File destFile = new File("copy.jpg");
//创建输入输出流
FileInputStream fis = new FileInputStream(srcFile);
FileOutputStream fos = new FileOutputStream(destFile);

//通道
FileChannel inChannel = fis.getChannel();
FileChannel outChannel = fos.getChannel();

//复制文件
ByteBuffer buffer = ByteBuffer.allocate(1024);
int len = 0;
while ((len=inChannel.read(buffer))!=-1){
//切换模式
buffer.flip();
//读缓冲区数据
outChannel.write(buffer);
//清空
buffer.clear();
}
outChannel.close();
inChannel.close();
fos.close();
fis.close();
}
}

3.FileChannel结合MappedByteBuffer实现高效读写

读取硬盘文件到内存–>内存复制一份文件–>副本写入到硬盘

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

public class FileChannel_RandomAccessFile {
public static void main(String[] args) throws Exception {
//创建文件
//Read Only/Read Write
RandomAccessFile srcFile = new RandomAccessFile("Danboard.jpg", "r");
RandomAccessFile destFile = new RandomAccessFile("copy.jpg", "rw");

//获取通道
FileChannel inchannel = srcFile.getChannel();
FileChannel outchannel = destFile.getChannel();

//获取文件大小
long size = inchannel.size();

//建立映射缓冲区
//map(模式,开始索引,字节数)
MappedByteBuffer inmap = inchannel.map(FileChannel.MapMode.READ_ONLY, 0, size);
MappedByteBuffer outmap = outchannel.map(FileChannel.MapMode.READ_WRITE, 0, size);

//复制
for (int i = 0; i < size; i++) {
byte b = inmap.get(i);
outmap.put(i,b);
}

inchannel.close();
outchannel.close();
}
}

只适用于复制2G以下的文件,如果是2G以上的文件,分多次复制

4.SocketChannel和ServerSocketChannel的实现连接

ServerSocketChannel的创建(阻塞方式)

1
2
3
4
5
6
7
8
9
10
11
12

//创建阻塞的服务器通道
public class ServerSocketChannel_Demo {
public static void main(String[] args) throws IOException {
//创建ServerSocketChannel
ServerSocketChannel open = ServerSocketChannel.open();
//绑定端口
open.bind(new InetSocketAddress(8888));
//后续代码
SocketChannel accept = open.accept();
}
}

ServerSocketChannel的创建(非阻塞方式)

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

public class ServerSocketChannel_Demo02_NIO {
public static void main(String[] args) throws IOException {
//创建ServerSocketChannel
ServerSocketChannel open = ServerSocketChannel.open();
//设置为同步非阻塞的服务器通道
open.configureBlocking(false);
//绑定端口
open.bind(new InetSocketAddress(8888));
//后续代码
SocketChannel accept = open.accept();
}
}

public class ServerSocketChannel_Demo02_NIO {
public static void main(String[] args) throws IOException, InterruptedException {
//创建ServerSocketChannel
ServerSocketChannel open = ServerSocketChannel.open();
//设置为同步非阻塞的服务器通道
open.configureBlocking(false);
//绑定端口
open.bind(new InetSocketAddress(8888));
while (true) {
//后续代码
SocketChannel accept = open.accept();
if (accept != null) {
System.out.println("Y");
} else {
Thread.sleep(2000);
System.out.println("N");
}
}
}
}

SocketChannel的创建

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

// 阻塞式客户端
public class SocketChannelDemo {
public static void main(String[] args) throws IOException {
//创建SocketChannel对象
SocketChannel socketChannel = SocketChannel.open();
//连接服务器
boolean connect = socketChannel.connect(new InetSocketAddress("127.0.0.1", 8888));
// 相当于socketChannel.bind(new InetSocketAddress("127.0.0.1",8888));

System.out.println(connect);
}
}

public class SocketChannelDemo02_NIO {
public static void main(String[] args) throws IOException, InterruptedException {
//创建SocketChannel对象
SocketChannel socketChannel = SocketChannel.open();
//设置为同步非阻塞的客户端通道
socketChannel.configureBlocking(false);

while (true) {
try {
//连接服务器
boolean connect = socketChannel.connect(new InetSocketAddress("127.0.0.1", 8888));
// 相当于socketChannel.bind(new InetSocketAddress("127.0.0.1",8888));

if (connect) {
System.out.println("Y");
}
} catch (Exception e) {
e.printStackTrace();
Thread.sleep(1000 * 2);
}

}
}
}

5.SocketChannel和ServerSocketChannel的实现通信

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

public class SocketChannelDemo {
public static void main(String[] args) throws IOException, InterruptedException {
SocketChannel open = SocketChannel.open();
boolean connect = open.connect(new InetSocketAddress("127.0.0.1", 8888));
if(connect){
System.out.println("Y");
ByteBuffer wrap = ByteBuffer.wrap("Client".getBytes());
open.write(wrap);
ByteBuffer buffer = ByteBuffer.allocate(1024);
int len = open.read(buffer);
buffer.flip();
System.out.println(new String(buffer.array(),0,len));
}

//释放资源
open.close();
}
}
public class ServerSocketChannelDemo {
public static void main(String[] args) throws IOException {
//创建ServerSocketChannel
ServerSocketChannel open = ServerSocketChannel.open();
ServerSocketChannel bind = open.bind(new InetSocketAddress(8888));
SocketChannel accept = bind.accept();
ByteBuffer bytebuffer = ByteBuffer.allocate(1024);
int read = accept.read(bytebuffer);

bytebuffer.flip();
String str = new String(bytebuffer.array(), 0, read);

System.out.println(str);
ByteBuffer buffer = ByteBuffer.allocate(1024);
buffer.put("Server".getBytes());
buffer.flip();
accept.write(buffer);

//释放资源
open.close();
accept.close();
}
}

总结:
Junit单元测试
a.第三方单元测试框架
b.@Test将一个普通方法可以独立运行
c.@Before,@After,@BeforeClass,@AfterClass

阻塞:任务没有执行完毕,线程无法向下继续推行
非阻塞:无论任务是否执行完毕,线程继续向下执行

同步:
同步可能是阻塞的,也可能是非阻塞的
同步阻塞:完成某个任务时,任务没有结束之前,当前线程无法继续向下执行
同步非阻塞:完成某个任务时,不需要等待任务结束,当前线程立即可以向下继续执行,后期再通过其他代码获取任务结果

异步:
异步一般来说都是非阻塞
异步非阻塞:完成某个任务时,不需要等待任务结束,当前线程立即可以向下继续执行,后期不需要写其他代码获取任务结果,任务完成后自动会通过其他机制把结果传递回来

创建和使用ByteBuffer
创建:
public static allocate(int capacity);//在堆区中申请一个固定大小的ByteBuffer缓冲区
public static allocateDirect(int capacity);//在系统的内存中申请一个固定大小字节的ByteBuffer缓冲区
public static wrap(byte[] arr);//把一个字节数组直接包装成ByteBuffer缓冲区
使用:
public ByteBuffer put(byte b);//添加单个字节
public ByteBuffer put(byte b,bs);//添加字节数组
public ByteBuffer put(byte b,bs,int startIndex,int len);//添加一个字节数组中的一部分
public int capacity();//获取Buffer容量
buffer.limit();
buffer.position();
buffer.mark();
public int remaining();//获取position与limit之间的元素数
public boolean isReadyOnly();//获取当前缓冲区是否可读
public boolean isDirect();//获取当前缓冲区是否为直接缓冲区
public boolean clear();//还原缓冲区的初始状态
public Buffer flip();//切换读写模式(缩小范围)
public Buffer rewind();//重绕缓冲区

使用MappedByteBuffer实现高效读写
使用文件对象 RandomAccessFile 不能使用普通File

使用ServerSockerChannel和SocketChannel实现连接并收发信息
ServerSockerChannel服务器端,可以是同步阻塞的也可以是非同步阻塞的
SocketChannel客户端,可以是同步阻塞的也可以是非同步阻塞的
通过configureBlocking方式可以设置阻塞或非阻塞

一.网络编程入门
概述:在网络通信协议下,实现网络互连的不同计算机上运行的程序间可以交换的数据

1.1 软件架构介绍
C/S架构:Client/Server
B/S架构:Browser/Server

1.2 网络通信协议
•什么是网络通信协议:计算机必须遵循的规则
•TCP/IP协议
TCP协议:Transmission Control Protocol,传输控制协议
IP协议:Internet Protocol,因特网协议

1.3 Java中支持的常见协议
java.net包提供两个协议
TCP传输控制协议:面向连接的协议(数据传输之前必须先建立连接,通过三次握手建立连接)
优点:保存数据完整,安全
缺点:消耗性能

UDP用户数据协议:面向无连接协议(数据传输时,不需要关心对方是否存在,只负责传输)
优点:性能较高
缺点:不能保证完整与安全性

1.4 网络编程三要素
a.网络通信协议
b.IP地址:每台连接到互联网的计算机的唯一标识,由32位二进制组成
c.端口号:一台计算机上不同软件的标识
端口号:一共0-65535个,1024是本机发出口,其他为随机高端口

1.5 计算机小知识
•IP地址的分类
IPv4:由32位二进制组成,2^32个地址
IPv6:由128位二进制组成,2^128个地址

•IP地址的相关命令
ipconfig/ifconfig:查看本机IP
ping/ping6:查看网络是否畅通

•特殊的IP地址
127.0.0.1(localhost):本机回环地址

1.6 InetAddress类的基本使用
a.InetAddress类代表IP地址
b.获取本机IP地址
InetAddress.getLocalHost();
c.获取其他机器IP地址
InetAddress.getByName(“”);
d.成员方法
String getHostName();获得主机名
String getHostAddress();获得IP地址字符串

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

public class Test_InetAddress {
public static void main(String[] args) throws UnknownHostException {
//获取本机IP地址
InetAddress localHost = InetAddress.getLocalHost();
System.out.println(localHost);
//获取其他机器IP地址
InetAddress byName = InetAddress.getByName("www.baidu.com");
System.out.println(byName);

//成员方法
String hostName = byName.getHostName();
System.out.println(hostName);
String address = byName.getHostAddress();
System.out.println(address);
}
}

二.TCP通信程序
2.1 TCP通信分为客户端和服务器
客户端:用户电脑
服务器端:服务商电脑

2.2 TCP中两个重要的类
a.Socket类,代表客户端(套接字)
b.ServerSocket类,代表服务器端(服务器套接字)

2.3 Socket类的介绍和使用
•构造方法
public Socket(String ip,int port);//服务器IP地址,服务器端口号
此构造会根据传入的参数自动连接服务器:
若连接成功,则对象正常创建
若连接失败,则直接抛出异常

1
2
3
4
5
6
public class SocketDemo {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("192.168.50.54",8080 );
System.out.println(socket);
}
}

•常用方法
public OutputStream getOutputStream();//获取连接通道中的输出流
public InputStream getInputStream();//获取连接通道中的输入流

public void shutDownOutput();//关闭连接通道中的输出流
public void shutDownInput();//关闭连接通道中的输入流

public void close();//关闭客户端对象

2.4 ServerSocket类的介绍和使用
•构造方法
public ServerSocket(int port);//指定服务器端使用的端口号

•常用的成员方法
public Socket accept();//接收连接到服务器的Socket对象,如果暂时没有客户端,该方法会阻塞
public Socket close();//关闭服务器对象

注意:服务器获取到客户端之后,也可以获得连接中的两个流,但是获取时是输入流还是输出流要相对服务器判断

2.5 简单的TCP通信实现(单向通信)
客户端给服务器发送信息,服务器不反馈。
客户端:
a.创建socket对象
b.获取输出流
c.调用输出流的write方法
d.释放资源
服务器:
a.创建ServerSocket对象
b.接收连接到的客户端对象
c.获取输入流
d.调用输入流的read方法
e.释放资源

UDP接收数据步骤:
①创建接收端的Socket对象(DatagramSocket)
DatagramSocket(int port)
②创建一个数据包,用于接收数据
DatagramPacket(byte[] buf,length)
③调用DatagramSocket对象的方法接收数据
void receive(DatagramPacket p)
④解析数据包,并把数据打印在控制台
byte[] getData()
int getLength()
⑤关闭接收端
void close()

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

//TCP 方式
public class ServerSocketDemo_TCP {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(8888);
System.out.println("Server running");
Socket accept = serverSocket.accept();
System.out.println("Client Coming");

InputStream in = accept.getInputStream();

byte[] bs = new byte[1024];
int len = in.read(bs);
System.out.println("Client Said"+new String(bs,0,len));

in.close();
serverSocket.close();
}
}
//UDP方式
public class ServerSocketDemo_UDP {
public static void main(String[] args) throws IOException {
DatagramSocket ds = new DatagramSocket(8888);
byte[] bys = new byte[1024];
DatagramPacket dp = new DatagramPacket(bys,bys,length)

ds.receive(dp);

System.out.println(new String(dp.getData(),0,dp.getlength()));

ds.close();
}
}


public class SocketDemo {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("127.0.0.1",8888 );
System.out.println("Connect Successful");
OutputStream out = socket.getOutputStream();

out.write("I am Client".getBytes());
System.out.println("Sent Successful");

out.close();
socket.close();
System.out.println("Client close");
}
}

2.6 简单的TCP通信实现(双向通信)
客户端给服务器发送信息,服务器接收后反馈。
客户端:
a.创建socket对象
b.获取输出流
c.调用输出流的write方法
===读取服务器回复的信息
d.获取输入流
e.调用输入流的read方法
f.释放资源

d.释放资源
服务器:
a.创建ServerSocket对象
b.接收连接到的客户端对象
c.获取输入流
d.调用输入流的read方法
====回信息
e.获取输出流
f.调用输出流的write方法
g.释放资源

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public class ServerSocketDemo {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(8888);
System.out.println("Server running");
Socket accept = serverSocket.accept();
System.out.println("Client Coming");

InputStream in = accept.getInputStream();

byte[] bs = new byte[1024];
int len = in.read(bs);
System.out.println("Client Said"+new String(bs,0,len));

OutputStream outputStream = accept.getOutputStream();
outputStream.write("I am Server.".getBytes());

outputStream.close();
in.close();
serverSocket.close();
}
}


public class SocketDemo {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("127.0.0.1",8888 );
System.out.println("Connect Successful");
OutputStream out = socket.getOutputStream();

out.write("I am Client".getBytes());
System.out.println("Sent Successful");

InputStream in = socket.getInputStream();

byte[] bs = new byte[1024];
int len = in.read(bs);
System.out.println("Server Said"+new String(bs,0,len));

in.close();
out.close();
socket.close();
System.out.println("Client close");
}
}

三.文件上传
3.1 分析
客户端:
读取文件FileInputStream
发送数据getOutputStream
a.创建Socket
b.获取输出流
c.创建文件的输入流
d.循环:一边读文件,一边发送
e.添加一句代码,告诉服务器文件发送完毕
f.获取输入流
g.读取服务器回复的信息
h.释放资源

服务器:
读取数据getInputStream
写入文件FileOutputStream
a.创建ServerSocket
b.获取客户端
c.获取输入流
d.创建文件输出流
e.循环:一边读数据,一边写文件
f.获取输出流
g.给客户端回信息
h.释放资源

3.2 文件上传案例实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
public class SocketDemo {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("127.0.0.1", 8888);
System.out.println("Connect Successful");
OutputStream outputStream = socket.getOutputStream();

FileInputStream fileInputStream = new FileInputStream("1.png");

byte[] bs = new byte[1024];
int len = 0;
while ((len = fileInputStream.read(bs)) != -1) {
outputStream.write(bs, 0, len);
}
socket.shutdownOutput();
System.out.println("File send successfle");

InputStream inputStream = socket.getInputStream();

len = inputStream.read(bs);
System.out.println(new String(bs,0,len));

inputStream.close();
fileInputStream.close();
outputStream.close();
socket.close();
System.out.println("Client close");
}
}


public class ServerSocketDemo {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(8888);
Socket accept = serverSocket.accept();

InputStream inputStream = accept.getInputStream();

FileOutputStream fileOutputStream = new FileOutputStream(System.currentTimeMillis()+".png");

byte[] bs = new byte[1024];
int len = 0;
while ((len = inputStream.read(bs)) != -1) {
fileOutputStream.write(bs, 0, len);
}

OutputStream outputStream = accept.getOutputStream();
outputStream.write("I am Server.".getBytes());

outputStream.close();
fileOutputStream.close();
inputStream.close();
accept.close();
serverSocket.close();
}
}


public class ServerSocketMultiDemo {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(8888);
while (true) {
Socket accept = serverSocket.accept();
InputStream inputStream = accept.getInputStream();

FileOutputStream fileOutputStream = new FileOutputStream(System.currentTimeMillis() + ".png");

byte[] bs = new byte[1024];
int len = 0;
while ((len = inputStream.read(bs)) != -1) {
fileOutputStream.write(bs, 0, len);
}

OutputStream outputStream = accept.getOutputStream();
outputStream.write("I am Server.".getBytes());

outputStream.close();
fileOutputStream.close();
inputStream.close();
accept.close();
}
}
}


public class ServerSocketMultiDemo_Thread {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(8888);
while (true) {
Socket accept = serverSocket.accept();

new Thread(() -> {
try {
InputStream inputStream = accept.getInputStream();

FileOutputStream fileOutputStream = new FileOutputStream(System.currentTimeMillis() + ".png");

byte[] bs = new byte[1024];
int len = 0;
while ((len = inputStream.read(bs)) != -1) {
fileOutputStream.write(bs, 0, len);
}

OutputStream outputStream = accept.getOutputStream();
outputStream.write("I am Server.".getBytes());
outputStream.close();
fileOutputStream.close();
inputStream.close();
accept.close();
} catch (IOException e) {
e.printStackTrace();
}
}).start();

}
}
}

3.3 模拟BS架构服务器

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

public class ServerSocketDemo {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(8888);
while (true) {
Socket accept = serverSocket.accept();

new Thread(() -> {
try {

InputStream inputStream = accept.getInputStream();

BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
String line = bufferedReader.readLine();

String[] splits = line.split(" ");
String substring = splits[1].substring(1);
System.out.println(substring);

FileInputStream fileInputStream = new FileInputStream(substring);
OutputStream outputStream = accept.getOutputStream();

byte[] bs = new byte[1024];
int len = 0;
outputStream.write("HTTP/1.1 200 OK\r\n".getBytes());
outputStream.write("Content-Type:text/html\r\n".getBytes());
outputStream.write("\r\n".getBytes());

while ((len = fileInputStream.read(bs)) != -1) {
outputStream.write(bs, 0, len);
}

outputStream.close();
inputStream.close();
accept.close();
} catch (Exception e) {
e.printStackTrace();
}
}).start();
// serverSocket.close();
}
}
}

总结:
TCP协议特点:面向有连接(先建立连接,后建立数据)
UDP协议特点:面向无连接(只需要发送数据,不需关心对方是否存在)
TCP协议两个常用名称
Socket:客户端类
•构造方法
public Socket(String ip,int port);//服务器IP地址,服务器端口号
•常用方法
public OutputStream getOutputStream();//获取连接通道中的输出流
public InputStream getInputStream();//获取连接通道中的输入流
public void shutDownOutput();//关闭连接通道中的输出流
public void shutDownInput();//关闭连接通道中的输入流
public void close();//关闭客户端对象

ServerSocket:服务器类
•构造方法
public ServerSocket(int port);//指定服务器端使用的端口号
•常用的成员方法
public Socket accept();//接收连接到服务器的Socket对象,如果暂时没有客户端,该方法会阻塞
public Socket close();//关闭服务器对象

TCP协议单双向传输数据
TCP协议下文件上传案例
上传:客户端将文件发送至服务器,服务器保存到硬盘
下载:
a.客户端读取本地文件
b.通过流输出发送给服务器
c.服务器读取输入流数据
d.保存到服务器本地

TCP协议到BS案例:
不需要写客户端,使用浏览器访问服务器,

一.缓冲流
1.1 缓冲流的介绍
缓冲流也叫高效流,是对之前的四个基本流的增强(性能,方法一模一样),会创建缓冲区,减少硬盘IO读写,提高速度。

1.2 缓冲Buffered流的分类
缓冲字节输入流:BufferedInputStream –> 普通的字节输入流IntputStream的增强
缓冲字节输出流:BufferedOutputStream –> 普通的字节输出流OutputStream的增强
缓冲字符输入流:BufferedReader –> 对普通的字符输入流Reader的增强
缓冲字符输出流:BufferedWriter –> 对普通的字符输出Writer流的增强

1.3 字节缓冲流的介绍和使用
•字节缓冲流的构造
public BufferedInputStream(InputStream in);//缓冲流的构造需要接收普通字节流
public BufferedOutputStream(OutputStream in);//缓冲流的构造需要接收普通字节流

•字节缓冲流的高效测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public class Buffered_Demo01 {
public static void main(String[] args) throws Exception {
copy01();
copy02();
}

public static void copy01() throws IOException {
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("1.txt"));
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("2.txt"));

long start = System.currentTimeMillis();

int b = 0;
while ((b = bis.read()) != -1) {
bos.write(b);
}
long end = System.currentTimeMillis();

System.out.println(end - start);

bis.close();
bos.close();
}

public static void copy02() throws IOException {
FileOutputStream fos = new FileOutputStream("1.txt");
FileInputStream fis = new FileInputStream("2.txt");

long start = System.currentTimeMillis();

int b = 0;
while ((b = fis.read()) != -1) {
fos.write(b);
}
long end = System.currentTimeMillis();

System.out.println(end - start);

fos.close();
fis.close();
}
}

更快的方式:使用缓冲流的同时,使用一次读取一个字节数组的方式

1.4 字符缓冲流的介绍和使用
•字符缓冲流的构造
public BufferedWriter(Writer w);//缓冲流的构造需要接收普通字符流
public BufferedReader(Reader r);//缓冲流的构造需要接收普通字符流

字符缓冲流也是对普通字符类对性能增强

•字符缓冲流的2个特有方法
BufferedWriter是对普通Writer的增强
多了一个特有方法:向文件中写一个换行符(根据系统而定)
public void newLine();

1
2
3
4
5
6
7
8
9
10
11
12
13

public class Buffered_R_W {
public static void main(String[] args) throws IOException {
BufferedWriter bw = new BufferedWriter(new FileWriter("1.txt"));
for (int i = 0; i < 10; i++) {
bw.write("Java");
bw.newLine();
}

bw.close();
//先开后关,不用即关
}
}

BufferedReader是对普通Reader的增强
多了一个特有方法:一次读取一行内容
public void ReadLine();

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

public static void main(String[] args) throws IOException {
BufferedReader bf = new BufferedReader(new FileReader("1.txt"));
String s = bf.readLine();
System.out.println(s);

//一次读取一行的标准循环代码
String line = "";//用来保存每次读取的一行数据
while ((line = bf.readLine()) != null) {
System.out.println(line);
}
//(line = bf.readLine()) != null
/*
读取bf.readLine();
赋值line = 读到一行数据
判断line != null;
*/

bf.close();
}

注意:一次读取一行的标准循环不会因为有一行是(null字符串/空行)而停止,只有读取到文件的末尾,没有内容时返回null才停止

1.5 总和练习:文本排序

读取文件并排序

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

public class TestDemo {
public static void main(String[] args) throws Exception {
List<String> arr = new ArrayList<String>();
BufferedReader bf = new BufferedReader(new FileReader("6.txt"));

String line = "";
while ((line = bf.readLine()) != null) {
arr.add(line);
}
bf.close();

Collections.sort(arr, (o1, o2) -> o1.charAt(0) - o2.charAt(0));

BufferedWriter bw = new BufferedWriter(new FileWriter("new.txt"));

for (String s : arr) {
bw.write(s);
bw.newLine();
}

bw.close();
}
}

二.转换流
2.1 编码和解码
编码:把字符 —> 字节,比如’a’–>97(0110 0001)
解码:把字节 —> 字符,比如97(0110 0001)–>’a’

2.2 字符集
字符集:就是一个系统所支持的所有字符(文字、标点、数字,图形符号等)的集合

2.3 字符编码
字符编码:是指字符和二进制一一对应的一套规则,比如字符’a’对应的码值是 97

常见的字符集和字符编码

字符集 编码
ASCII 字符集 —> ASCII编码,规定ASCII字符集中所有的字符 都占一个字节
GBK 字符集 —> GBK编码,规定所有中文字符都占2个字节(这两个字节是负数)
ISO-8859-1字符集 —> 西欧国家字符集(以后使用Tomcat7以前默认就是使用ISO-8859-1)
Unicode字符集 —> UTF-8,规定所有的中文字符都占3个字节
2.4 编码引出的问题
IDEA默认使用UTF-8,Windows默认使用GBK编码,以下例子会导致读取出3个字符

1
2
3
4
5
6
7
8
9
10

public class TestDemo {
public static void main(String[] args) throws Exception {
FileReader fr = new FileReader("GBK.txt");
int ch = fr.read();
System.out.println((char)ch);

fr.close();
}
}

解决方法:
硬编码方式:
a.把文件编码改为UTF-8和IDEA一致
b.把IDEA编码改成GBK文件一致
软编码方式:转换流

2.5 使用转换流InputStreamReader解决读取中文的问题
转换输入流: InputStreamReader extends Reader

•构造方法
public InputStreamReader(InputStream in,String charsetName);//使用指定编码读文件
public InputStreamReader(InputStream in);//使用IDE默认编码读取文件

•使用InputStreamReader读取不同编码的文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class TestDemo02_InputStreamReader {
public static void main(String[] args) throws Exception {
InputStreamReader isr = new InputStreamReader(new FileInputStream("utf8.txt"), "UTF-8");
int ch = isr.read();
System.out.println((char)ch);

ch = isr.read();
System.out.println((char)ch);

ch = isr.read();
System.out.println((char)ch);

isr.close();
}
}

2.6 使用转换流OutputStreamWriter解决读取中文的问题
转换输出流:OutputStreamWriter extends Writer

•构造方法
public OutputStreamWriter(OutputStream out,String charsetName);//写文件时指定编码
public OutputStreamWriter(OutputStream out);//使用IDE默认编码

•输出指定编码的中文

1
2
3
4
5
6
7
8
9
10

public class TestDemo03_OutputStreamWriter {
public static void main(String[] args) throws Exception {
// OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("newGBK.txt"), "GBK");
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("newutf8.txt"), "UTF-8");
osw.write("你好");

osw.close();
}
}

2.7 转换流的理解(转换流是字节与字符之间的桥梁)
InputStreamReader:读取字节,解码为字符,根据编码(UTF-8或GBK)去读2个或3个字节,然后根据编码(UTF-8或GBK)解码为字符
OutputStreamWriter:写出字符,编码为字节,根据编码(UTF-8或GBK)把字符编码为2个或3个字节,然后写到对应文件中

2.8 练习:转换文件编码
将GBK文件转换为UTF-8文件
分析:
a.先把GBK文件中的内容读取出来(GBK)
b.再把数据写入到UTF-8文件中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

public class TestDemo {
public static void main(String[] args) throws Exception {
InputStreamReader isr = new InputStreamReader(new FileInputStream("newGBK.txt"), "GBK");
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("coyt_to_UTF8.txt"), "UTF-8");

int ch = 0;
while ((ch = isr.read()) != -1) {
osw.write(ch);
}

osw.close();
isr.close();
}
}

三.序列化流
3.1 什么是序列化流
对象序列化:就是将对象保存到磁盘中,或者在网络中传输对象。
这种机制就是使用一个字节序列表示一个对象,该字节序列包含:对象的类型,对象的数据和对象中存储的属性等信息。
字节序列写到文件后,相当于文件中持久保存了一个对象的信息。
反之,该字节序列还可以从文件中读取回来,重构对象,对他进行反序列化

序列化流:写出对象的流
ObjectOutputStream

反序列化流:读取对象的流
ObjectInputStream

3.2 ObjectOutputStream介绍和使用
•构造方法
public ObjectOutputStream(OutputStream out);//需要接收一个普通的字节输出流

•序列化操作的前提
想要实现序列化,必须实现java.io.Serializable,否则NotSerializableException报错

该接口中没有方法,该接口一般作为标记接口

•序列化操作(代码演示)

1
2
3
4
5
6
7
8
9
10
11
12
1
public class Dog implements java.io.Serializable{}

public class TestDemo {
public static void main(String[] args) throws Exception {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("Dog.txt"));
Dog dd = new Dog(10, "a");
oos.writeObject(dd);

oos.close();
}
}

注意:不需要查看Dog.txt文件,编码无法查看,需要反序列化读取

3.3 ObjectInputStream介绍和使用
•构造方法
public ObjectInputStream(InputStream in);//需要接收一个普通的字节输入流

•反序列化操作(代码演示)

1
2
3
4
5
6
7
8
9
10

public class TestDemo_ObjectInputStream {
public static void main(String[] args) throws Exception {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("Dog.txt"));
Dog obj = (Dog)ois.readObject();
System.out.println(obj);

ois.close();
}
}

3.4 反序列化操作的两种异常演示
a.ClassNotFoundException 类没有找到的异常
出现原因:
序列化之后,反序列化之前,删除了用来序列化的类

b.InvalidClassException 无效类异常
出现原因:
序列化之后,反序列化之前,修改了用来序列化的类

c.实际上序列化流通过serialVersionUID来识别的

3.5 练习:如果需要序列化多个对象怎么操作
注意:序列化流一个文件只适合序列化一个对象
分析:
a.把要序列化的多个对象,保存到一个集合对象
b.将这个集合作为对象,序列化到文件中
c.从文件中将整个集合反序列化
d.遍历集合把里面的对象一一打印出来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class TestDemo {
public static void main(String[] args) throws Exception {
write();
read();
}

public static void write() throws Exception {
ArrayList<Dog> dogs = new ArrayList<Dog>();
dogs.add(new Dog(1, "a", 4));
dogs.add(new Dog(2, "b", 5));
dogs.add(new Dog(3, "c", 6));

ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("Dogs.txt"));
oos.writeObject(dogs);
oos.close();
}

public static void read() throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("Dogs.txt"));

ArrayList<Dog> dogs = (ArrayList<Dog>) ois.readObject();
ois.close();
for (Dog dog : dogs) {
System.out.println(dog);
}

}
}

四.打印流
4.1 打印流的介绍
a.输出System.out.println();实际上就是调用了打印流的方法
b.打印流PrintStream类
c.打印流中重载了各种数据类型的print和println方法,打印数据时非常方便
d.打印流不会抛出IOException

4.2 PrintStream的构造和常用方法
构造方法:
public PrintStream(String Path);//直接指定路径
public PrintStream(File file);//直接指定文件
public PrintStream(OutputStream out);//先给一个输出流,绑定什么对象就打印到什么对象

成员方法:
public void print(各种类型);//不带换行的打印
public void println(各种类型);//带换行的打印

4.3 修改打印流的流向

1
2
3
4
5
6
7
8
9
10
11
public class TestPrintStream02 {
public static void main(String[] args) throws FileNotFoundException {
PrintStream ps1 = new PrintStream("p.txt");
ps1.println("Hallo");

System.setOut(ps1);

System.out.println("Java");

}
}

五.装饰设计
什么是设计模式:为了解决一系列问题,总结出来的一套方案

5.1 装饰模式的作用
装饰模式指的是在不改变原类,不实用继承的基础上,动态地扩展一个对象的功能。

5.2 装饰者设计模式的4个基本步骤
•装饰类(自己定义的新类)和被装饰类(原类)必须实现相同的接口
•在装饰类中必须传入被装饰类的引用
•在装饰类中对需要的扩展方法进行扩展
•在装饰类中对不需要的扩展方法调用被装饰类中的同名方法

5.3 代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
//相同接口
public interface SingStar {
void sing();
void dance();
}

//被装饰类
public class Singer implements SingStar{
@Override
public void sing(){
System.out.println("aaaaa");
}
@Override
public void dance(){
System.out.println("bbbbb");
}
}

//装饰类
public class Wrapper implements SingStar{
private Singer SG;
public Wrapper(Singer SG) {
this.SG = SG;
}

@Override
public void sing(){
System.out.println("ccc");
SG.sing();
}
@Override
public void dance(){
SG.dance();
}
}


//调用同名方法
public class TestDemo {
public static void main(String[] args) {
Singer sg = new Singer();
Wrapper wrapper = new Wrapper(sg);
wrapper.sing();
wrapper.dance();
}
}

六.commons-io工具包
6.1 commons-io的介绍和下载
是Apache提供的库

工具 功能描述
org.apache.commons.io 有关Streams、Readers、Writers、Files的工具类
org.apache.commons.io.input 输入流相关的实现类,包含Reader和InputStream
org.apache.commons.io.output 输出流相关的实现类,包含Writer和OutputStream
org.apache.commons.io.serialization 序列化相关的类
下载commons-io.zip
a.解压
b.模块下创建lib文件夹,将commons-io.jar复制进去
c.选择common-io.jar,选则add as library,加载

6.2 常用API介绍
•复制文件API

•复制文件夹API

1
2
3
4
5
6
7
8
9
10
11
12
public class Commons_IO_TestDemo {
public static void main(String[] args) throws IOException {
//1.IOUitls.copy,适合复制2GB以下的文件
IOUtils.copy(new FileInputStream("1.txt"),new FileOutputStream("copy_1_common.txt"));
//1.IOUitls.copy,适合复制2GB以下的文件
IOUtils.copyLarge(new FileInputStream("1.txt"),new FileOutputStream("copy_1_common_Large.txt"));
//FileUtils复制文件到文件夹
FileUtils.copyFileToDirectory(new File("1.txt"), new File("copy_1_common_FileUtil.txt"));
//FileUtils复制文件夹到文件夹
FileUtils.copyDirectoryToDirectory(new File("/Users/swzxsyh/Downloads/c9-python-getting-started"),new File("/Users/swzxsyh/Desktop"));
}
}

总结:
1.缓冲流
字节缓冲流(BufferedOutputStream和BufferedInputStream),没有特有方法,性能比普通流更高

字符缓冲流(BufferedWriter和BufferedReader),有特有方法,性能比普通流更高
BufferedWriter:
public void newLine();
BufferedReader:
public String readLine();
2.转换流
转换输出流: 可以指定编码写文件
OutputStreamWriter
public OutputStreamWriter(OutputStream out,String 指定的编码);
转换输入流: 可以指定编码读文件
InputStreamReader
public InputStreamReader(InputStream in,String 指定的编码);

转换输入流(可以指定编码读文件):
InputStreamWriter
public InputStreamReader(InputStream out,String charsetName);//写文件时指定编码

3.序列化流
序列化流: 写对象
ObjectOutputStream
public void writeObject(对象);//该对象的类必须实现java.io.Serializable接口
反序列化流: 读对象
ObjectInputStream
public Object readObject();

4.打印流
PrintStream ps = new PrintStream(String path/File file/OutputStream out);
方法:
print(各种数据类型);
println(各种数据类型);
5.装饰设计模式
步骤:
a.被装饰类和装饰类实现同一个接口
b.装饰类内部必须含有被装饰类的引用
c.在装饰类中对需要装饰的方法进行装饰
d.在装饰类中对不需要装饰的方法调用原对象的方法

6.commons-io【重点】
IOUtils 复制文件(2G以上和2G以下)
FileUtils 复制文件和复制文件夹

一.字符流
为什么要用字符流
字节流也是可以读取文本文件的,但是读取中文时可能会出现只读取其中的部分字节,因为中文不止由一个字节组成。
为了解决问题,引入了字符流,以字符为单位操作

字符输入流
顶层父类:Reader(抽象类)
共性方法:
public void close();//释放资源
public int read();//一次读一个char字符,返回字符ASCII码值,为int类型
public int read(char[] chs);//一次读一个char字符数组,返回值表示实际读取的字符个数

FileReader类的使用
文件的字符输入流(从文件中读取字符数据的)

•构造方法
public FileReader(String Path);
public FileReader(File file);

1
2
3
4
5
6
7
8
9
10
public class Test_FileReader {
public static void main(String[] args) throws Exception {
FileReader fr = new FileReader("1.txt");
/*
创建对象
判断文件是否存在,若存在,则不清空。若不存在,则返回错误FileNotFoundException
绑定fr和1.txt文件
*/
}
}

•读取一个字符

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

public class Test_FileReader02 {
public static void main(String[] args) throws Exception {
FileReader fr = new FileReader("1.txt");
//一次读一个字符
// int read = fr.read();
// System.out.println(read);
// System.out.println((char)read);
//一次读取一个字符的标准循环代码
int ch = 0;
while ((ch = fr.read()) != -1) {
System.out.println((char) ch);
}

/*(ch = fr.read()) != -1
读取fr.read();
赋值ch = 读到的字符
判断ch != -1
*/

fr.close();
}
}

•读取一个字符数组

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

public class Test_FileReader03 {
public static void main(String[] args) throws Exception {
FileReader fr = new FileReader("1.txt");

// char[] chs = new char[4];
// int len = fr.read(chs);
// System.out.println(len);
// System.out.println(new String(chs));
char[] chs = new char[1024];

int len = 0;
while ((len = fr.read(chs)) != -1) {
System.out.println(new String(chs, 0, len));
}
/*
(len = fr.read(chs)) != -1
读取fr.read();
赋值 len = 实际读取的个数
判断len != -1
*/

fr.close();
}
}

字符输出流
顶层父类:Writer(抽象类)
共性方法:
public void close();//释放资源
public int flush();//对于字符串游泳
public int write();//一次写一个char字符,返回字符ASCII码值,为int类型
public int write(char[] chs);//一次写一个char字符数组
public int write(char[] chs,int startIndex,int len);//一次写一个char字符数组的一部分

public write(String str);//直接写一个字符串
public write(String str,int startIndex,int len);//直接写一个字符串的一部分

FileWriter类的使用
文件的字符输出流(向文件中写字符数据)
•构造方法
public FileWriter(String Path);
public FileWriter(File file);

1
2
3
4
5
6
7
8
9
10
public class TestFileWriter {
public static void main(String[] args) throws IOException {
FileWriter fw = new FileWriter("2.txt");
/*
创建对象fw
判断文件是否存在,若存在则覆盖,若不存在则创建
绑定fw和2.TXT
*/
}
}

•写出字符数据的三组(五个)方法
public int write();//一次写一个char字符,返回字符ASCII码值,为int类型

public int write(char[] chs);//一次写一个char字符数组
public int write(char[] chs,int startIndex,int len);//一次写一个char字符数组的一部分

public write(String str);//直接写一个字符串
public write(String str,int startIndex,int len);//直接写一个字符串的一部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class TestFileWriter02 {
public static void main(String[] args) throws IOException {
FileWriter fw = new FileWriter("2.txt");
//写一个字符
fw.write('a');
fw.write('字');

//写一个字符数组
char[] chs ={'a','b','字','符'};
fw.write(chs);

fw.write(chs,0,2);

fw.write("一二三四1234abcd");

fw.write("一二三四1234abcd",0,3);

fw.close();
}

}
•关闭和刷新的区别
flush();只刷新缓冲区,不关闭流
close();不仅刷新缓冲区,之后还会关闭流,流不能继续使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class TestFileWriter03 {
public static void main(String[] args) throws IOException {
FileWriter fw = new FileWriter("3.txt");
fw.write("PHP");

//刷新缓冲区,将write的字符从Java缓冲区刷入3.txt
fw.flush();
fw.write("Python");
fw.flush();

fw.close();
//fw.write("CPP");
//报错java.io.IOException: Stream closed,流已关闭,无法写入
}

}
•续写和换行
如何续写:
public FileWriter(String path,boolean append);//append表示是否续写
public FileWriter(File file,boolean append);//append表示是否续写

换行:

1
2
3
4
5
6
7
8
9
10

public class TestFileWriter05_Return_Line {
public static void main(String[] args) throws IOException {
FileWriter fw = new FileWriter("3.txt");
for (int i = 0; i < 10; i++) {
fw.write("php"+"\n");
}

fw.close();
}

}
二.IO流的异常处理 throws
JDK7之前的标准IO处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//IO流异常的标准处理方式(JDK 1.7之前)
public static void mtehod01() {
FileReader fr = null;
try {
fr = new FileReader("1.txt");
int read = fr.read();
System.out.println(read);
fr.close();
} catch (IOException ioe) {
ioe.printStackTrace();
} finally {
try {
if (fr != null) {
fr.close();
}
} catch (IOException ie) {
ie.printStackTrace();
}
}

}
JDK7引入的IO处理
try-with-resource(和资源一起try)
格式:
try(创建资源的代码){

}catch(XxxException e){
e.printStackTrace();
}

1
2
3
4
5
6
7
8
9
//IO流异常的标准处理方式(JDK 1.7引入)
public static void mtehod02() {
try (FileReader fr = new FileReader("1.txt")){
int read = fr.read();
System.out.println((char)read);
} catch (IOException ioe) {
ioe.printStackTrace();
}
}

三.Properties类
Properties类的介绍
Properties继承HashTable,而HashTable继承Dictionary,实现了Map接口,实际上Properties就是一个双列接口
a.Properties就是一个双列集合(Properties extends HashTable extend Dictionary implements Map)
b.Properties的键和值已经确定为String了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Test_Properties {
public static void main(String[] args) {
//获取系统相关的键值对
Properties properties = System.getProperties();
System.out.println(properties);
}
}
构造方法
public Properties();//创建一个空的Properties对象
public static void main(String[] args){
//创建一个空的Proerties对象
Properties ps = new Properties();
System.out.println(ps);
}

基本保存数据的方法
Map接口定义的方法:
增:put(键,值)
删:remove(键)
改:put(键,值)
查:get(键)
遍历的两个方法
Set<键的类型> keys = map.keySet();
Set<Map.Entry<K,V>> entrys = map.entrySet();
Properties具有Map接口中定义的方法,但是一般使用其特有方法
public Object setProperty(String key,String value);//添加键值对,和put功能一样,也能做修改使用
public String getProperty(String key);//以键找值,和get功能一样
public Set stringPropertyNames();//查找所有属性名的集合,和keySet功能一样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Test_Properties02 {
public static void main(String[] args) {
//获取系统相关的键值对
Properties properties = new Properties();
System.out.println(properties);
//增
properties.setProperty("MI", "6888");
properties.setProperty("HW", "8888");
properties.setProperty("AP", "10888");
System.out.println(properties);
//改
properties.setProperty("MI", "3888");
System.out.println(properties);
//获取
String mi = properties.getProperty("MI");
System.out.println(mi);
//获取所有属性名(key)
Set<String> strings = properties.stringPropertyNames();
System.out.println(strings);
}
}

与流相关的方法
Properties有两个流相关方法:一个叫保存,一个叫加载
public void store(OutputStream out,String Description);//保存Properties对象中的数据
public void store(Writer write,String Description);//保存Properties对象中的数据

public void load(InputStream in);//把Properties内容加载到当前对象
public void load(Reader read);//把Properties文件内容加载到当前对象

1
2
3
4
5
6
7
8
9
10
11
12
13

public class Test_Properties04_Load {
public static void main(String[] args) throws IOException {
Properties properties = new Properties();
properties.load(new FileInputStream("5.properties"));
System.out.println(properties);

Set<String> propertyNames = properties.stringPropertyNames();
for (String propertyName : propertyNames) {
System.out.println(propertyName + "=" + properties.get(propertyName));
}
}
}

注意:一般不会使用properties文件保存中文数据,否则需要额外编码转换

四.ResourceBundle工具
ResourceBundle类的介绍
ResourceBundle类实际上是一个抽象类,它的子类PropertyResourceBundle,可以读取以.properties为后缀的文件中的内容

ResourceBundle类对象的创建
public static ResourceBundle getBundle(String baseName);用于绑定指定的.properties文件
注意:
a.xxx.properties文件必须放在类的根路径下(src目录下)
b.给定参数适,只需要给文件名的名字,不需要写文件后缀

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class ResourceBundleDemo {
public static void main(String[] args) {
ResourceBundle data = ResourceBundle.getBundle("data");
System.out.println(data);
}
}
ResourceBundle读取陪着文件操作
public String getString(String key);

public class ResourceBundleDemo {
public static void main(String[] args) {
ResourceBundle resourceBundle = ResourceBundle.getBundle("data");
System.out.println(resourceBundle);
String username = resourceBundle.getString("username");
String passwd = resourceBundle.getString("passwd");

System.out.println(username+" "+passwd);
}
}

总结:
Java四大流:
-字节输出流OutputStream:
子类:FileOutputStream
public void close();//关闭该流,释放资源
public void flush();//刷新缓冲区(主要字符流使用)
public void write(int b);//一次写一个字节,输入是int,但是只能写一个byte的大小,即最大127
public void write(byte[] bs);//一次写一个字节数组
public void write(byte[] bs,int startIndex,int len);//一次写这一个字节数组中的一部分

构造方法三件事:
创建输出流对象
若存在覆盖,若不存在创建
释放资源

-字节输入流InputStream:
子类:FileInputStream
public void close();//关闭该流,释放资源
public int read();//一次读一个字节
public int read(byte[] bs);//一次读一个字节数组,返回值表示实际读取的字节个数
public int read(byte[] bs,int startIndex,int len);//一次读一个字节数组的一部分(基本不用)

构造方法三件事:
创建输入流对象
若存在则读取,若不存在报错
释放资源

-字符输出流Writer:
子类:FileWriter
public void close();//释放资源
public void flush();//刷新缓冲区

public int write();//一次写一个char字符,返回字符ASCII码值,为int类型
public int write(char[] chs);//一次写一个char字符数组
public int write(char[] chs,int startIndex,int len);//一次写一个char字符数组的一部分
public write(String str);//直接写一个字符串
public write(String str,int startIndex,int len);//直接写一个字符串的一部分

构造方法三件事:
创建字符输出流对象
若存在覆盖,若不存在创建
释放资源

-字符输入流Reader:
public void close();//释放资源
public int read();//一次读一个char字符,返回字符ASCII码值,为int类型
public int read(char[] chs);//一次读一个char字符数组,返回值表示实际读取的字符个数

构造方法三件事:
创建字符输入流对象
若存在覆盖,若不存在创建
释放资源

打印地址和内容的有哪些
a.数组(除了char数组),都是打印地址
b.集合(Collection还是Map)都是打印内容
c.其他类的对象,打印出来的地址还是内容,要看释放重写toString

排序的工具类
对数组进行排序:Arrays.sort(array,new Comparator<数组元素类型>);//必须引用类型
对List集合排序:Collections.sort(,new Comparator<集合中元素对类型>);
对Set集合排序:
并不是所有的Set都能排序,TreeSet才能排序:TreeSet set = new TreeSet(new 比较器对象());
TreeSet排序:TreeSet set = new TreeSet(new 比较器对象());//或自己写排序算法:冒泡,选择,插入,希尔,快速,堆,归并…

Stream流和IO流没有关系

字节流可以操作任何文件(一切皆字节)
字符流只能操作文本文件(如果使用字符流操作图片、视频,那么结果是乱码)

线程池(保存线程的集合)
ExecutorService service = Executors.newFixedThreadPool(线程数);
线程池对象底层实际上有一个集合:
LinkedList ThreadList= new LinkedList();
for(int i = 0;i<线程数量;i++){
ThreadList.add(线程对象);
}
service.submit(任务对象);
//ThreadList.removeFirst(线程);执行完后执行ThreadList.addLast(线程);