0%

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

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(线程);

一.Lambda表达式
1.1 函数式编程的思想
尽量简单的格式简化面对对象的复杂语法。
面对对象是以何种形式去做,而函数式编程思想强调是拿什么东西做什么事,而不是强调以何种形式去做。

1.2 冗余的Runnable代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class TestRUNNABLEDEMO {
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"RUNNABLE");
}
});

t1.start();

//为了避免创建一个新类,不得不创建匿名内部类
//为了迎合面向对象的语法,只能使用Runnable的实现类
//重写时,方法必须和接口中一模一样
//真正需要的其实就是任务代码
}
}

1.3 函数式编程Lambda的体验

1
2
3
4
5
Thread t2 =new Thread(()->{
System.out.println(Thread.currentThread().getName()+"RUNNABLE");
});

t2.start();

1.4 Lambda标准格式介绍
(参数列表) -> {方法体; return 返回值;}

详情介绍:
| | |
|———-|:———————————————————————-:|
| (参数列表) | 相当于方法的参数,如果没有参数,那么只写小括号即可(),注意小括号不能省略 |
| -> | 固定用法,代码拿着前面的参数,做什么事 |
| {} | 大括号中先写计算过程,如果有返回值return返回值;如果没有返回值,return语句可以省略 |

1.5 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
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

public class Test_ComparatorDemo {
public static void main(String[] args) {
Integer[] num = {4, 5, 61, 7, 8, 9, 34, 56, 345};
Arrays.sort(num);
System.out.println(Arrays.toString(num));

Arrays.sort(num, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1 - o2;
}
});
System.out.println(Arrays.toString(num));

public class Test_ComparatorDemo {
public static void main(String[] args) {
Integer[] num = {4, 5, 61, 7, 8, 9, 34, 56, 345};
//使用Lambda表达式改写上方冗余代码
Arrays.sort(num, (Integer o1, Integer o2) -> {
return o2 - o1;
});
System.out.println(Arrays.toString(num));
}
}

public class Test_ComparatorDemo {
public static void main(String[] args) {
Dog[] dog = new Dog[4];
dog[0] = new Dog(1, "a", 2);
dog[1] = new Dog(2, "b", 2);
dog[2] = new Dog(3, "c", 3);
dog[3] = new Dog(4, "d", 4);
Arrays.sort(dog, new Comparator<Dog>() {
@Override
public int compare(Dog o1, Dog o2) {
return o1.age - o2.age;
}
});

for (Dog d : dog) {
System.out.println(d);
}

System.out.println();
}


public class Test_ComparatorDemo {
public static void main(String[] args) {
Dog[] dog = new Dog[4];
dog[0] = new Dog(1, "a", 2);
dog[1] = new Dog(2, "b", 2);
dog[2] = new Dog(3, "c", 3);
dog[3] = new Dog(4, "d", 4);

Arrays.sort(dog,(Dog o1,Dog o2)->{return o2.age-o1.age;});
for (Dog d : dog) {
System.out.println(d);
}
}
}

1.6 Lambda的省略格式
a.参数类型可以省略
b.如果参数只有一个,那么小括号可以省略
c.如果{}中的代码可以写成一句代码,那么”{}”,”return”关键字和”;”可以同时省略(不能只省略一部分)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
new Thread(()->{System.out.println(Thread.currentThread().getName()+"RUNNABLE");}).start();

new Thread(()->System.out.println(Thread.currentThread().getName()+"RUNNABLE")).start();

Arrays.sort(num, (Integer o1, Integer o2) -> {return o2 - o1;});

Arrays.sort(num, (o1, o2) -> o2 - o1);


Arrays.sort(dog, (Dog o1, Dog o2) -> {
return o2.age - o1.age;
});

Arrays.sort(dog, (o1, o2) -> o2.age - o1.age);

1.7 强烈注意:Lambda的使用前提
a.Lambda只能用于替换有且仅有一个抽象方法的接口的匿名内部类对象,这种接口称为函数式接口
b.Lambda具有上下午推断的功能,所以才会出现Lambda的省略格式

二.Stream流
2.1 引入:传统的集合操作

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 TestDemo {
public static void main(String[] args) {
List<String > list = new ArrayList<String >();
Collections.addAll(list,"zaa","wdt","xcs","zd","lf");

ArrayList<String > z = new ArrayList<String>();
for (String name:list){
if(name.startsWith("z")){
z.add(name);
}
}

ArrayList<String > Three = new ArrayList<String>();
for (String name:list){
if(name.length()==3){
Three.add(name);
}
}

System.out.println(z);
System.out.println(Three);
}
}

2.2 循环遍历的弊端分析
Lambda注重于做什么,传统的面向对象注重于怎么做

1
2
3
4
5
for (String name:list){
if(name.startsWith("z")){
z.add(name);
}
}

为了解决面向对象的语法复杂形式,引入了一种新的技术:Stream 流式思想

2.3 Stream的优雅写法

1
list.stream().filter(s -> s.startsWith(“x”)).filter(s -> s.length() == 3).forEach(s -> System.out.println(s));
Stream流的使用
•生成流
通过数据源(集合,数组等)生成流 list.stream();
•中间操作
•终结操作
一个流只能有一个终结操作,当这个操作执行完后,无法再被操作。

2.4 流式思想的概述

2.5 三种获取流的方式
a.Collection集合获取流
Stream s = 集合对象.stream();

b.Map集合不能直接获取流,但是可以间接获取流
map.keySet().stream();获取map的键
map.values().stream();获取map的值流
map.entrySet().stream();获取map的键值对流

c.数组获取流
Stream<数组中元素的类型> A =Stream.of(数据类型…变量名);

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

public static void main(String[] args) {
//获取各种容器的流
//单列集合
ArrayList<String> list = new ArrayList<String >();
Stream<String > s1 = list.stream();
HashMap<String ,Integer> map = new HashMap<String, Integer>(16);
//键流
Stream<String> keyStream = map.keySet().stream();
//值流
Stream<Integer> valueStream = map.values().stream();
//键值对流
Stream<Map.Entry<String,Integer>> entryStream = map.entrySet().stream();

Integer[] arr = {10,20,30,40};
Stream<Integer> num = Stream.of(arr);
Stream<Integer> nums = Stream.of(11,22,33,44);
}

2.6 Stream流中的常用方法
•逐个处理:forEach

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

public class StreamDemo02 {
public static void main(String[] args) {
Stream<Integer> s1 = Stream.of(11, 22, 33, 44);
// s1.forEach(new Consumer<Integer>() {
// @Override
// public void accept(Integer integer) {
// System.out.println(integer);
// }
// });
s1.forEach(integer -> System.out.println(integer));
}
}

•统计个数:count

1
2
3
4
5
public static void main(String[] args) {
Stream<Integer> s1 = Stream.of(11, 22, 33, 44);
long count = s1.count();
System.out.println(count);
}

•过滤:filter

1
2
3
4
5
6
7
8
9
10
11
//        s1.filter(new Predicate<Integer>() {
// @Override
// public boolean test(Integer integer) {
//// if (integer>100000) {
//// return true;
//// }
// return integer>1000;
// }
// });
Stream<Integer> integerStream = s1.filter(integer -> integer > 1000);
integerStream.forEach( );

•取前几个:limit

1
2
3
Stream<Integer> limit = s1.limit(3);
limit.forEach(System.out::println);

•跳过前几个:skip

1
2
Stream<Integer> skip = s1.skip(2);
skip.forEach(System.out::println);

•映射方法:map

1
2
3
4
5
6
7
8
9

// Stream<String> str = s6.map(new Function<Integer, String>() {
// @Override
// public String apply(Integer integer) {
// return String.valueOf(integer);
// }
// });
Stream<String> str = s6.map(s -> String.valueOf(s));
str.forEach(s -> System.out.println(s));

mapToInt(ToIntFunction mapper):返回一个IntStream其中包含将给定函数应用于此流的结果

1
2
int result = list.stream().mapToInt(Integer::parseInt).sum;
System.out.println(result);

•静态方式合并流:concat

1
2
3
4
5
6
7
public static Stream<T> concat(Stream<T> s1,Stream<T> s2);

Stream<String > s7 = Stream.of("a","b");
Stream<String > s8 = Stream.of("c","d");

Stream<String > ss = Stream.concat(s7, s8);
ss.forEach(s-> System.out.println(s));

•静态方式合并流:distinct –返回该流的不同元素(根据Object.equals(Object))组成的流

1
2
3
4
5
6
public static Stream<T> concat(Stream<T> s1,Stream<T> s2);

Stream<String > s7 = Stream.of("a","b","d");
Stream<String > s8 = Stream.of("c","d","a");

Stream<String > ss = Stream.concat(s7, s8).distinct().forEach(System.out::println);

•sorted():返回由此流的元素组成的流,根据自然顺序排序

1
2
3
4
5
6
7
8
9
10
public static Stream<T> concat(Stream<T> s1,Stream<T> s2);

Stream<String > s7 = Stream.of("a","b");
Stream<String > s8 = Stream.of("c","d");

Stream<String > ss = Stream.concat(s7, s8).sorted((s1,s2)->{
int num = s1.len()-s2.length();
int num2 = num==0?s1.compareTo(s2):sum;
return sum2;
}).forEach(System.out::println);

•sorted(Comparator comparator):返回由该流的元素组成的流,根据提供的comparator进行排序

注意:
a.如果是两个以上流的合并,需要多次两两合并
有filter,limit,skip,map,concat

b.如果两个流的泛型不一致也可以合并,合并之后新流的泛型是他们的共同父类
有forEach,count

2.7 练习:集合元素的处理(Stream方式)

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

public class StreamDemo04_Lambda {
public static void main(String[] args) {
List<String> s1 = new ArrayList<String>();
List<String> s2 = new ArrayList<String>();
Collections.addAll(s1, "aa", "abc", "abcd");
Collections.addAll(s2, "ba", "cd", "ccc", "abcd");

Stream<String> a = s1.stream().filter(s -> s.length() == 3).limit(3);

Stream<String> b = s2.stream().filter(s -> s.startsWith("a")).skip(2);

Stream<String> ss = Stream.concat(a, b);

Stream<Person> personStream = ss.map(s -> new Person(s));

personStream.forEach(System.out::println);

}
}

2.8 总结:函数拼接和终结方法
函数拼接方法:
调用该方法之后,返回还是一个流对象。
由于这种方法返回的还是流对象,因此支持链式编程

终结方法:
调用该方法之后,返回值不是流或无返回值
由于终结方法返回的不是流对象,因此不支持链式编程,并且当某个流调用终结方法之后,该流就关闭了,不能继续调用其他方法

2.9 收集Stream的结果

可以把流收集到集合中:调用流的collect方法即可
可以把流收集到数组中:调用toArray()方法即可

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

public class StreamDemo03 {
public static void main(String[] args) {
Stream<Integer> s1 = Stream.of(1, 2, 3, 4, 5);
Stream<Integer> s2 = Stream.of(1, 2, 3, 4, 5);
Stream<Integer> s3 = Stream.of(1, 2, 3, 4, 5);
//收集到集合
List<Integer> collect = s1.collect(Collectors.toList());
System.out.println(collect);

//收集到Set
Set<Integer> collect1 = s2.collect(Collectors.toSet());
System.out.println(collect1);

//收集到数组
Object[] objects = s3.toArray();

for(Object obj:objects){
System.out.println(obj);
}
}
}

注意:
a.一个流只能收集一次(第二次会报错)
b.如果收集到数组,默认是Object数组;

总结:
1.Lambda
标准格式:(参数列表)->{方法体;return 返回值;}
省略格式:
参数类型可省略
只有一个参数,小括号可省略
如果{}中只有一句代码,那么{} ; return可省略
2.Stream流
a.集合或者数组获取流的方法
Stream s = 集合.stream();//单列集合
Stream s = Stream.of(数组/可变参数); //数组获取流
b.调用流的各种方式
filter limit skip map count foreach concat

一.File类
1.1 File类的作用
File类可以表示文件或文件夹,是文件和目录的抽象表示
•文件和目录是可以通过File封装成对象的
•对于File而言,其封装的并不是一个真正存在的文件,仅仅是一个路径名。将来通过具体的操作把这个路径转换为具体存在的。

1.2 File类的构造

方法名 说明
public File(String path); 通过将给定的路径名字符串转换为抽象路径名来创建新的File实例
public File(String parent,String child); 从父路径名字符串和子路径名字符串创建新的File实例
public File(File parent,String child); 从父抽象路径名和子路径名字符串创建新的File实例

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

public class FileDemo01 {
public static void main(String[] args) {
File f1 = new File("/Users/u/Desktop/12306Bypass/Logs/2019-01-03.txt");

System.out.println(f1);

File f2 = new File("/Users/u/Desktop","12306Bypass/Logs/2019-01-03.txt");
System.out.println(f2);

File parent = new File("/Users/u/Desktop");
File f3 = new File(parent,"12306Bypass/Logs/2019-01-03.txt");
System.out.println(f3);
}
}

1.3 File类的获取方法
public String getAbsolutePath();//获取该File对象的绝对路径
public String getPath();//获取该File对象构造时,传入的对象
public String getName();//获取该File对象的代表的文件或文件夹的名字
public long length();//获取该File对象的文件字节

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

public class FileDemo02 {
public static void main(String[] args) {
File f1 = new File("~/Desktop/12306Bypass/Logs/2019-01-03.txt");
//getAbsolutePath
String absolutePath = f1.getAbsolutePath();
System.out.println(absolutePath);

//getPath
String path = f1.getPath();
System.out.println(path);

//name
String name = f1.getName();
System.out.println(name);

//length
long length = f1.length();
System.out.println(length+" byte");

}
}

注意:length方法只能获取文件大小,不能获取文件夹大小

1.4 相对路径和绝对路径
绝对路径:以盘符开头的路径,一个完整的路径
例如:/Users/u/Desktop”,”12306Bypass/Logs/2019-01-03.txt

相对路径:以当前项目的根目录为起始的路径

1
2
3
4
5
6
7
8
9
10

public class FileDemo03 {
public static void main(String[] args) {
File f1 = new File("/Users/u/Desktop/12306Bypass/Logs/2019-01-03.txt");
File f2 = new File("2019-01-04.txt");

String absolutePath = f2.getAbsolutePath();
System.out.println(absolutePath);
}
}

1.5 File类的判断方法
•public boolean exists();//判断该File对象代表的文件和文件夹是否存在
•public boolean isDirectory();//判断该File对象代表的是否是文件夹
•public boolean isFile();//判断该File对象代表的是否是文件

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

public class FileDemo04 {
public static void main(String[] args) {
File file = new File("/Users/u/Desktop/12306Bypass/Logs/2019-01-03.txt");
boolean b = file.exists();
System.out.println(b);

boolean directory = file.isDirectory();
System.out.println(directory);

boolean file1 = file.isFile();
System.out.println(file1);
}

}
1.6 File类的创建删除方法
public boolean mkdrir();//创建单级文件夹,返回值表示是否成功
public boolean mkdrirs();//创建多级文件夹,返回值表示是否成功

public boolean createNewFile();//创建文件,返回值表示是否成功

public boolean delete();//删除该File对象或文件夹,返回值表示是否成功

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

public class FileDemo05 {
public static void main(String[] args) throws IOException {
File file = new File("2.txt");
boolean newFile = file.createNewFile();
System.out.println(newFile);
boolean mkdir = file.mkdir();
System.out.println(mkdir);

File file2 = new File("a/b/c/2.txt");
boolean mkdirs = file2.mkdirs();
System.out.println(mkdirs);

File file1 = new File("2.txt");
boolean delete = file1.delete();
System.out.println(delete);

File file3 = new File("a/b/c/2.txt");
boolean delete1 = file3.delete();
System.out.println(delete1);
File file4 = new File("a/b/c/2.txt");
boolean delete2 = file4.delete();
System.out.println(delete2);

}
}

注意:
a.mkdir 和 mkdirs的区别
b.delete方法要么删文件,要么删文件夹,不能删除空文件夹

1.7 File类的遍历目录方法
public String[] list();//列出当前文件夹下所有直接的文件和文件夹的名字
public File[] listFiles();//列出当前文件夹下所有直接的文件和文件夹的File对象【重要】

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

public class FileDemo06 {
public static void main(String[] args) {
File file = new File("a");
String[] list = file.list();
for (String filename : list
) {
System.out.println(filename);
}
File[] files = file.listFiles();
for (File filearr : files) {
System.out.println(filearr);
}
}
}

注意:list和listFiles只能列出直接的子文件或子文件夹

二.递归
2.1 什么是递归
递归不是Java语言独有(基本上所有语言都可以递归)
递归就是:在方法中调用该方法本身

1
2
3
4
5
6
7
8
9
10
public class Recurrence {
public static void main(String[] args) {
method();
}
public static void method() {
System.out.println("method");
//调用自己
method();
}
}

无限递归(死递归)出现这个错误 StackOverflowError 栈溢出错误

如果要使用递归,必须保证递归有出口(结束条件)

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

public class Recurrence {
public static void main(String[] args) {
method1(10);
}
//有出口递归
public static void method1(int n) {
System.out.println("method1 "+n);
//调用自己
if(n == 0){
return;//递归出口
}
method1(n-1);

}
// public static void method() {
// System.out.println("method");
// //调用自己
// method();
// }
}

注意:就算递归有出口,也要保证递归在运行到出口之前次数也不能太多(太多也会爆栈)

使用递归三大步骤:
a.先定义一个方法
b.找规律调用自己
c.让递归有出口(结束条件)

2.2 递归求和案例
测试案例:求1-n的和
开发中不要用递归来求和,只用循环

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

public class Recurrence02 {
public static void main(String[] args) {
int sum = getSum(10);
System.out.println(sum);
int Recurrence_getSum = Recurrence_getSum(100);
System.out.println(Recurrence_getSum);
}

//使用递归
public static int Recurrence_getSum(int n) {
if (n == 1) {
return 1;
}
return Recurrence_getSum(n - 1) + n;
}

//使用循环
public static int getSum(int n) {
int sum = 0;
for (int i = 1; i <= n; i++) {
sum += i;
}
return sum;
}
}

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

public class Recurrence03_getFactorial {
public static void main(String[] args) {
int sum = getFactorial(10);
System.out.println(sum);
int recurrenceGetSum = Recurrence_getFactorial(10);
System.out.println(recurrenceGetSum);
}

//使用递归
public static int Recurrence_getFactorial(int n) {
if (n == 1) {
return 1;
}
return Recurrence_getFactorial(n - 1) * n;
}

//使用循环
public static int getFactorial(int n) {
int Factorial = 1;
for (int i = 1; i <= n; i++) {
Factorial *= i;
}
return Factorial;
}
}

2.4 文件搜索案例

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

public class Recurrence04_File {
public static void main(String[] args) {
File fileDir = new File("/Users/u/Downloads/a/b");
findTxtFile(fileDir);
}

public static void findTxtFile(File file) {
File[] files = file.listFiles();

for (File filearr : files) {
if (filearr.getName().endsWith(".java") && filearr.isFile()) {
System.out.println(filearr);
}else if(filearr.isDirectory()){
findTxtFile(filearr);
}
}
}
}

三.IO流的概述
3.1 什么是IO流
I:Input输入流,数据从外部设备到程序中,”读流”
O:Outpur输出流,数据从程序写到外部设备,”写流”
流:一种抽象概念,数据传输的总称。数据在设备间的传输称为流,流的本质是数据传输

Java中流的流向都是以内存角度而言的

3.2 IO流的分类
a.根据流的方向分类
输入流:读数据(Input–>Read)
输出流:写数据(Output–>Write)

b.根据流中的数据类型分类
字节流:byte
字符流:char

以上两种分类可以综和为四大类
字节输入流,字节输出流,字符输入流,字符输出流

3.3 Java中的四大IO流(其他流都是这四个之一的子类)
字节输入流:InputStream(顶层父类,抽象类)
字节输出流:OutputStream(顶层父类,抽象类)

字符输入流:Reader(顶层父类,抽象类)
字符输出流:Writer(顶层父类,抽象类)

技巧:Java中所有的流,都会是以上四个流中某一个的子类,且具体的流的命名是非常有规范的:功能名+父类名
如:
FileWriter,向文件中写出字符为单位的数据
FileInputStream,从文件中读取以字节为单位的数据

四.字节流
4.1 万物皆对象和IO流一切皆字节
万物皆字节对象:现实生活中的任何东西,我们在Java中都可以使用一个对象表示
IO流中的一切皆字节:电脑所有的数据,最终都是由字节组成的(二进制)

4.2 字节输出流
顶层父类(抽象类)
共性方法:
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);//一次写这一个字节数组中的一部分

4.4 FileOutputStream类的使用
文件的字节输出流(向文件中写字节数据)

•构造方法
public FileOutputStream(String path);//必须传入文件的路径
public FileOutputStream(File file);//必须传入文件的File对象

public FileOutputStream(String path);//必须传入文件的路径

1
2
3
4
5
6
7
8
9
10
11

public class Demo08_FileOutputStream {
public static void main(String[] args) throws FileNotFoundException {
FileOutputStream fos = new FileOutputStream("1.txt");
/*
创建文件
判断文件是否存在,如果存在,则清空该文件,若不存在,则创建文件
让fos与1.txt绑定
*/
}
}

•写字节数据的三个方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class FileOutputStream_Demo02 {
public static void main(String[] args) throws IOException {
FileOutputStream fos = new FileOutputStream("1.txt");
fos.write(97);
fos.write(57);
fos.write(55);

byte[] bs1 ={100,101,102};
fos.write(bs1);

byte[] bytes = "HelloWorld".getBytes();
fos.write(bytes);
byte[] bs2 ={100,101,102};
fos.write(bs2,1,1);//数组,下标,个数
}
}

•如何追加续写
public FileOutputStream(String path,boolean append);//append表示是否追加
public FileOutputStream(File file,boolean append);//append表示是否追加

1
2
3
4
5
6
public class FileOutputStream_Demo03 {
public static void main(String[] args) throws IOException {
FileOutputStream fos = new FileOutputStream("1.txt",true);
fos.write(97);
}
}

•如何换行

1
2
3
4
5
6
7
8
9

public class FileOutputStream_Demo04 {
public static void main(String[] args) throws IOException {
FileOutputStream fos = new FileOutputStream("1.txt",true);
for (int i = 0; i < 10; i++) {
fos.write("java \n".getBytes());
}
}
}

•flush
public void flush();//对于字节输出流没用

•close
public void close();//关闭该流,释放资源
一个流使用完毕,及时释放流,别的程序就可以使用该资源

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class FileOutputStream_Demo04 {
public static void main(String[] args) throws IOException {
FileOutputStream fos = new FileOutputStream("1.txt", true);
for (int i = 0; i < 10; i++) {
fos.write("java \n".getBytes());
}

fos.close();

while (true) {
}
//程序不会停止,但是流已关闭,可以对文件进行增删改操作
}
}

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

4.5 FileInputStream的作用
文件的字节输入流(向文件中读字节数据)
•构造方法
public FileInputputStream(String path);//必须传入文件的路径
public FileInputStream(File file);//必须传入文件的File对象

public FileInputStream(String path);//必须传入文件的路径

1
2
3
4
5
6
7
8
9
10
11
public class FileInputStream_Demo01 {
public static void main(String[] args) throws Exception {
FileInputStream fis = new FileInputStream("1.txt");
/*
创建文件对象
判断文件是否存在,如果存在,无动作
若不存在,则直接抛出异常FileNotFoundException
让fis与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
24
public class FileInputStream_Demo02 {
public static void main(String[] args) throws Exception {
FileInputStream fis = new FileInputStream("1.txt");
int read = fis.read();
System.out.println(read);//97
System.out.println((char)read);//a

//一次读取一个字节的标准循环代码
int b = 0;
while ((b = fis.read()) != -1) {
System.out.println(b);
System.out.println((char)b);
/*
(b = fis.read()) != -1
先读 fis.read()
赋值 b=读取字节
判断 b!=-1
*/

//释放资源
fis.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
public class FileInputStream_Demo03 {
public static void main(String[] args) throws Exception {
FileInputStream fis = new FileInputStream("1.txt");
//读取字节数组
byte[] bs = new byte[20];//实际开发中,一般写1024byte即1KB,或其整数倍

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

//释放资源
fis.close();
}
}

4.6 字节流练习复制图片
•复制文件的过程
FileInputStream->一次读一个数组,写一个数组->FileOutputStream

•代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Copy_File {
public static void main(String[] args) throws Exception {
FileInputStream fis = new FileInputStream("1.txt");
FileOutputStream fos = new FileOutputStream("copy.txt");

byte[] bs = new byte[1024];
int len = 0;
while ((len = fis.read(bs)) != -1) {
//为了防止最后一次读取时,写入多余的数据
fos.write(bs,0,len);
}

//原则:先开后关
fos.close();
fis.close();
}
}

总结:
File创建方式:
public File(String path);
public File(String parent,String child);
public File(File parent,String child);

File常见方法:
public String getAbsolutePath();
public String getPath();
public String getName();
public long length();

public boolean exists();
public boolean isDirectory();
public boolean isFile();

public boolean mkdrir();
public boolean mkdrirs();
public boolean createNewFile();

public boolean delete();

public String[] list();
public File[] listFiles();

绝对路径和相对路径:
绝对路径:盘符开头
相对路径:当前目录的根目录

递归含义:
方法内部调用方法本身

递归为什么出现内存溢出:
方法不断入栈,栈满则溢出
IO流的分类和功能:
输入流,输出流,字节流(byte单位),字符流(char单位)

字节输出流FileOutputStream写出数据到文件:
public void write(int b);
public void write(byte[] bs);
public void write(byte[] bs,int startIndex,int len);

字节输入流FileInputStream读取文件数据:
public int read();
public int read(byte[] bs);

算法图解

1.2 二分查找
二分查找是一种算法,其输入是一个有序的元素列表(必须有序的原因稍后解释)。如果要 查找的元素包含在列表中,二分查找返回其位置;否则返回null。

一般而言,对于包含n个元素的列表,用二分查找最多需要log2n步,而简单查找最多需要n步。

可将一系列元素存储在一系列相邻的桶(bucket),即数组中。这些桶从0开始编号:第一个桶的位置为#0,第二个桶为#1,第三个桶为#2,以此类推。

1.3 大O表示法是一种特殊的表示法,指出了算法的速度有多快。

大O表示法指的并非以秒为单位的速度。大O表示法 让你能够比较操作数,它指出了算法运行时间的增速。

1.3.4 一些常见的大O运行时间 下面按从快到慢的顺序列出了你经常会遇到的5种大O运行时间。
 O(log n),也叫对数时间,这样的算法包括二分查找。
 O(n),也叫线性时间,这样的算法包括简单查找。
 O(n * log n),这样的算法包括第4章将介绍的快速排序——一种速度较快的排序算法。
 O(n2),这样的算法包括第2章将介绍的选择排序——一种速度较慢的排序算法。
 O(n!),这样的算法包括接下来将介绍的旅行商问题的解决方案——一种非常慢的算法。

我们获得的主要启示如下。
 算法的速度指的并非时间,而是操作数的增速。
 谈论算法的速度时,我们说的是随着输入的增加,其运行时间将以什么样的速度增加。
 算法的运行时间用大O表示法表示。
 O(log n)比O(n)快,当需要搜索的元素越多时,前者比后者快得越多。

1.4 小结
 二分查找的速度比简单查找快得多。
 O(log n)比O(n)快。需要搜索的元素越多,前者比后者就快得越多。  算法运行时间并不以秒为单位。
 算法运行时间是从其增速的角度度量的。
 算法运行时间用大O表示法表示。

需要同时读取所有元素时,链表的效率很高:读取第一个元素,根据其中的地址再读取第二个元素,以此类推。但如果你需要跳跃,链表的效率 真的很低。

需要随机地读取元素时,数组的效率很高,因为可迅速找到数组的任何元素。
在链表中,元素并非靠在一起的,你无法迅速计算出第五个元素的内存地址,必须先访问第一个元素以获取第二个元素的地址,再访问第二个元素以获取第三个元素 的地址,以此类推,直到访问第五个元素。

元素的位置称为索引。因此,不说“元素20的位置为1”,而说“元素20位于索引1处”。本书 将使用索引来表示位置。

需要在中间插入元素时,数组和链表哪个更好呢?使用链表时,插入元素很简单,只需修改 它前面的那个元素指向的地址。
而使用数组时,则必须将后面的元素都向后移。如果没有足够的空间,可能还得将整个数组复制到其他地方!因此,当需要在中间插入元素时,链表是更好的选择。

如果你要删除元素呢?链表也是更好的选择,因为只需修改前一个元素指向的地址即可。而使用数组时,删除元素后,必须将后面的元素都向前移。
不同于插入,删除元素总能成功。如果内存中没有足够的空间,插入操作可能失败,但在任何情况下都能够将元素删除。

需要指出的是,仅当能够立即访问要删除的元素时,删除操作的运行时间才为O(1)。通常我 们都记录了链表的第一个元素和最后一个元素,因此删除这些元素时运行时间为O(1)。

有两 种访问方式:随机访问和顺序访问。
顺序访问意味着从第一个元素开始逐个地读取元素。
链表只能顺序访问:要读取链表的第十个元素,得先读取前九个元素,并沿链接找到第十个元素。随机 10 访问意味着可直接跳到第十个元素。
本书经常说数组的读取速度更快,这是因为它们支持随机访问。
很多情况都要求能够随机访问,因此数组用得很多。

总结:数组查找快,增删改慢。链表查找慢,增删改快。

练习
1.1 假设你要为饭店创建一个接受顾客点菜单的应用程序。这个应用程序存储一系列点菜 单。服务员添加点菜单,而厨师取出点菜单并制作菜肴。这是一个点菜单队列:服务 员在队尾添加点菜单,厨师取出队列开头的点菜单并制作菜肴。
你使用数组还是链表来实现这个队列呢?(提示:链表擅长插入和删除,而数组擅长 随机访问。在这个应用程序中,你要执行的是哪些操作呢?)

数组,只需要查找。

1.2 我们来做一个思考实验。假设Facebook记录一系列用户名,每当有用户试图登录 Facebook时,都查找其用户名,如果找到就允许用户登录。由于经常有用户登录 Facebook,因此需要执行大量的用户名查找操作。假设Facebook使用二分查找算法, 而这种算法要求能够随机访问——立即获取中间的用户名。考虑到这一点,应使用数 组还是链表来存储用户名呢?

数组,查找快

1.3 经常有用户在Facebook注册。假设你已决定使用数组来存储用户名,在插入方面数组 有何缺点呢?具体地说,在数组中添加新用户将出现什么情况?

数组默认内存占用可能不足以插入新用户,最终创建新数组来插入新用户。

1.4 实际上,Facebook存储用户信息时使用的既不是数组也不是链表。假设Facebook使用 的是一种混合数据:链表数组。这个数组包含26个元素,每个元素都指向一个链表。
例如,该数组的第一个元素指向的链表包含所有以A打头的用户名,第二个元素指向的 链表包含所有以B打头的用户名,以此类推。
假设Adit B在Facebook注册,而你需要将其加入前述数据结构中。因此,你访问数组的 第一个元素,再访问该元素指向的链表,并将Adit B添加到这个链表末尾。现在假设你要查找Zakhir H。因此你访问第26个元素,再在它指向的链表(该链表包含所有以z打头的用户名)中查找Zakhir H。

请问,相比于数组和链表,这种混合数据结构的查找和插入速度更慢还是更快?你不必给出大O运行时间,只需指出这种新数据结构的查找和插入速度更快还是更慢。

是,快

2.3 选择排序

需要检查的元素数越来越少
随着排序的进行,每次需要检查的元素数在逐渐减少,最后一次需要检查的元素都只有一 个。既然如此,运行时间怎么还是O(n2)呢?这个问题问得好,这与大O表示法中的常数相关。 第4章将详细解释,这里只简单地说一说。
你说得没错,并非每次都需要检查n个元素。第一次需要检查n个元素,但随后检查的元素 数依次为n  1, n – 2, …, 2和1。平均每次检查的元素数为1/2 × n,因此运行时间为O(n × 1/2 × n)。 但大O表示法省略诸如1/2这样的常数(有关这方面的完整讨论,请参阅第4章),因此简单地写 作O(n × n)或O(n2)。

选择排序是一种灵巧的算法,但其速度不是很快。快速排序是一种更快的排序算法,其运行 时间为O(n log n)

2.4 小结
 计算机内存犹如一大堆抽屉。
 需要存储多个元素时,可使用数组或链表。
 数组的元素都在一起。
 链表的元素是分开的,其中每个元素都存储了下一个元素的地址。
 数组的读取速度很快。
 链表的插入和删除速度很快。
 在同一个数组中,所有元素的类型都必须相同(都为int、double等)。

3.1 递归
递归只是让解决方案更清晰,并没有性能上的优势。实际上,在有些情况下,使用循环的性能更好。

编写递归函数时,必须告诉它何时停止递归。正因为如此,每个递归函数都有两部分:基线条件(base case)和递归条件(recursive case)。递归条件指的是函数调用自己,而基线条件则指的是函数不再调用自己,从而避免形成无限循环。

一.线程池方式
1.1 线程池的思想
需要使用线程时,会临时创建一个线程,然后启动。而线程的创建,以及使用完毕后的销毁,都需要消耗性能的。
因此,
先创建好线程,在需要使用时直接使用,使用完毕后归还,等待下次继续使用,即线程池思想。

1.2 线程池的概念
线程池:其实就是一个容纳多个线程对象的容器,其中的线程可以反复使用,省去了频繁创建/销毁线程对象的操作,无需反复创建线程而消耗过多资源。

1.3 线程池的使用
线程池的顶层接口:
java.util.concuttrent.Executor

线程池的子接口:
java.util.concurrent.ExcutorService

线程池的工具类:其作用是帮助我们创建一个线程池对象(即ExcutorService的实现类对象)
java.util.concuttrent.Executors

工具类中的静态方法:创建一个线程池对象
public static ExecutorService newFixedThreadPool(int nThreads);//用于创建一个具有指定线程个数的线程池对象

如何向线程池中提交任务:
调用ExecutorService接口中规定的方法
public Future submit(Runnable task);//向线程池中提交无返回值的任务 public Future submit(Callable task);//向线程池中提交有返回值的任务,返回Future类型(封装线程执行完毕后结果的那个对象)

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
24
25
26
27
28
29
30
31
32
33
34
public class ThreadPool {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//使用多态接收
ExecutorService service = Executors.newFixedThreadPool(3);


//向线程池中提交无返回值的任务
// for (int i = 0; i < 10; i++) {
service.submit(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " Done");
}
});
// }

//向线程池中提交有返回值的任务
Future<Integer> future = service.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 1; i < 101; i++) {
sum+=i;
}
return sum;
}
});
Integer result = future.get();//get方法具有阻塞功能,会等待任务执行完毕后再返回结果
System.out.println(result);

//如果想要整个进程停止,那么需要关闭线程池
service.shutdown();
}
}

二.死锁
2.1 什么是死锁
在多线程中,有多把锁,最后导致所有的线程都在等待中,造成的现象,称为死锁

2.2 产生死锁的条件
a.至少有2个线程
b.至少有2个锁对象
c.至少有synchronized嵌套

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
public static void main(String[] args) {

//2个锁对象
Object obj1 = new Object();
Object obj2 = new Object();

//2个线程
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
//必须有synchronized嵌套
synchronized (obj1) {
System.out.println("Thread 1 get obj1 and want obj2");
//方式一:sleep,不释放锁
// try {
// Thread.sleep(500);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
synchronized (obj2) {
System.out.println("Thread 1 get obj2,execute");
System.out.println(Thread.currentThread().getName() + "Done");
}
}
}
}
}).start();

new Thread(new Runnable() {
@Override
public void run() {
while (true) {
synchronized (obj2) {
System.out.println("Thread 2 get obj2 and want obj1");
// try {
// Thread.sleep(500);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
synchronized (obj1) {
System.out.println("Thread 2 get obj1,execute");
System.out.println(Thread.currentThread().getName() + "Done");
}
}
}
}
}).start();
}

注意:如果出现了死锁,无解。只能尽量避免死锁

三.线程状态
3.1 线程的六种状态(A thread state. A thread can be in one of the following states:)
•新建状态-NEW
A thread that has not yet started is in this state.
刚创建,且未调用strat方法的状态

•运行状态-RUNNABLE
A thread executing in the Java virtual machine is in this state.
处于新建状态的线程start方法之后
可运行状态(就绪)
可运行状态(运行)
注意:只有新建状态的线程才能调用start()

•受(锁)阻塞状态-BLOCKED
A thread that is blocked waiting for a monitor lock is in this state.
状态进入:
线程运行过程中,遇到同步方法,线程需要锁对象,但是锁对象已被其他线程持有
状态返回:
其他线程释放锁,当前线程抢到锁,才能从锁阻塞回到可运行状态

•限时等待状态-TIMED_WAITING
A thread that is waiting for another thread to perform an action for up to a specified waiting time is in this state.
状态进入:
线程指定到代码调用Thread.slee(毫秒值),线程就处于限时等待状态
状态返回:
sleep时间到了,返回RUNNABLE状态

•永久等待-WAITING
A thread that is waiting indefinitely for another thread to perform a particular action is in this state.
状态进入:
当前线程必须持有锁对象
调用锁对象的wait()方法
此时线程就会进入永久等待,进入之前,当前线程会自动释放锁对象
状态返回:
其他线程要持有锁(必须锁刚刚进入无限等待的线程释放的锁)
调用锁对象的notify()方法

注意:被唤醒的线程是没有锁对象的,会进入锁阻塞,直到其他线程释放锁对象,才能进入可运行状态

•TERMINATED
A thread that has exited is in this state.
当线程任务执行完毕,已退出的线程状态(消亡状态)
注意:处于消亡状态的线程,不能再调用start方法

thread

四.等待唤醒机制
4.1 等待唤醒机制(Wait和Notify)

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 TestWating {
public static void main(String[] args) throws InterruptedException {

//creat lock
Object lock = new Object();

//create thread
new Thread(new Runnable() {
@Override
public void run() {
synchronized (lock) {
//get in code block
System.out.println("Thread A get into Wating");
System.out.println("Thread A Get Lock");
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
//wake back to task
System.out.println("A Wake");
}
}
}).start();

Thread.sleep(1000);

//New Threak wake up Thread A
new Thread(new Runnable() {
@Override
public void run() {
//Same lock Object
synchronized (lock) {
System.out.println("Thread B Got Lock");
System.out.println("Thread B Wake A Up");
lock.notify();
for (int i = 0; i < 10; i++) {
System.out.println(i);
}
}
}
}).start();
}
}

注意:
a.只有线程进入了线程等待,其他线程调用了notify()才有作用,否则也可以调用刚,但是没有任何作用
b.锁对象.notify方法只能唤醒一个锁对象,具体哪一个是随机的
c.锁对象.notifyAll方法可以唤醒多个线程,谁抢到锁谁执行

4.2 生产者与消费者
生产者与消费者是一个十分经典的多线程协作模式,弄懂生产者消费者问题能对多线程编程的理解更加深刻
所谓生产者消费者问题,实际上包含了两类线程:
•一类是生产者线程用于生产数据
•一类是消费者线程用于消费数据

为了解耦生产者和消费者的关系,通常会采用共享的数据区域,将像是一个仓库
•生产者生产数据之后直接放置在共享区域中,并不需要关系消费者行为
•消费者只需要从共享数据区其获取数据,并不需要关系生产者行为

生产者 —> 共享数据区 <— 消费者

(代码演示)
需求:
生产者消费案例
需要两个线程,线程1包子铺线程,赋值生产包子。线程2吃货线程,赋值吃包子。
如果没有包子,那么吃货线程等待,包子铺线程执行,执行完后唤醒吃货线程。
如果没有包子,那么包子铺线程等待,吃货线程执行,执行完后唤醒包子铺线程。

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

ArrayList<String> arr = new ArrayList<String>();
arr.add("first");

Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (arr) {
while (true) {

if (arr.size() > 0) {
try {
System.out.println("Bread presence");
arr.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("Add Bread");
arr.add("bread");
System.out.println("Bread Done");
arr.notify();
}}

}
});

Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
while (true) {

synchronized (arr) {
if (arr.size() == 0) {
try {
System.out.println("No bread");
arr.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("Ate bread");
arr.remove(0);
System.out.println("Call Boss");
arr.notify();
}}
}
});
t1.start();
t2.start();
}
}

五.定时器
5.1 什么是定时器
可以让某个线程在某个时间做指定的任务,或某个时间以后指定时间间隔中反复做某个任务

5.2 定时器Timer的使用
•构造方法
public Timer();//构造一个定时器

•成员方法
public void schedule(TimerTask task,long delay);//在指定时间之后,执行指定任务
public void schedule(TimerTask task,long delay,long period);//在指定时间之后,开始周期性执行任务,时间间隔period毫秒
public void schedule(TimerTask task,Date time);//在指定的时间点,执行指定的任务
public void schedule(TimerTask task,Date time,long period);//在指定时间第一次执行任务,之后周期性执行任务,间隔period毫秒

•案例演示

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 TestTimer {
public static void main(String[] args) {
Timer t1 = new Timer();
Timer t2 = new Timer();
Timer t3 = new Timer();
Timer t4 = new Timer();

t1.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("Timer 1 Done");
}
},1000);

t2.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("Timer 2 Done");
}
},1000,1000);

Calendar cc = Calendar.getInstance();
cc.add(Calendar.SECOND,10);
Date date = cc.getTime();


t3.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("Timer 3 Done");
}
}, date);

t4.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("Timer 4 Done");
}
},date,1000 );

t2.cancel();
t4.cancel();
}
}

总结:
1.线程池
a.怎么创建
ExecutorService service = Executos.newFixedThreadPool(int 线程个数);
b.提交任务
service.submit(Runnalbe task);//提交无返回任务
Future future = service.submit(Callable task);//提交有返回任务
通过future.get()该方法会阻塞,直到线程执行完毕,返回结果
c.关闭线程池
service.shutDown();

2.死锁
a.多个线程
b.多把锁
c.嵌套反方向获取锁

死锁只能避免,出现即无法解决

3.线程的状态
a.NEW(新建状态)
b.RUNNABLE(可运行状态)
c.TERMIINATED(消亡状态)
d.BLOCKED(阻塞状态)
e.TIMED_WATING(限时等待状态)
f.WATING(无限等待状态)*

怎么进入WATING
I.当前线程获取锁对象
II.调用锁对象.wait()方法
III.进入WATING之前自动释放锁对象
怎么唤醒
I.其他线程持有锁对象(必须刚刚释放的同一把锁)
II.调用锁对象的.notify()方法
III.WATING线程醒来,进入BLOCKED状态,直到再次获取对象状态

4.TImer
四个方法
public void schedule(TimerTask task,long delay);//在指定时间之后,执行指定任务
public void schedule(TimerTask task,long delay,long period);//在指定时间之后,开始周期性执行任务,时间间隔period毫秒
public void schedule(TimerTask task,Date time);//在指定的时间点,执行指定的任务
public void schedule(TimerTask task,Date time,long period);//在指定时间第一次执行任务,之后周期性执行任务,间隔period毫秒

一.synchronized关键字
1.1 AtomicInteger不足之处
回顾:可以保证对”变量”操作是原子性的(有序性,可见性)

无法解决:多行代码的原子性

1.2 多行代码的原子性安全问题——卖票案例

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

public class TicketTask implements Runnable {
int count = 100;
@Override
public void run() {
while (true) {
if (count > 0) {

try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Sold" + count);
count--;
} else {
break;
}
}
}
}


public class TestDemo {
public static void main(String[] args) {
TicketTask tt = new TicketTask();
Thread t1 = new Thread(tt);
Thread t2 = new Thread(tt);
Thread t3 = new Thread(tt);

t1.start();
t2.start();
t3.start();
//线程出现安全问题
//可能出现重复票据
//还可能出现0甚至-1这种非法数据
}
}

重复数据原因:当一个线程执行完卖票后,还没来得及对票数-1,就被其他线程抢走了CPU,导致其他线程也卖出了一样的票
非法数据原因:当剩下最后一张票时,多个线程都经过了if判断,进入卖票的代码块中,依次卖出1,0,-1张票

1.3 synchronized关键字介绍
a.synchronized是什么:synchronized是Java的关键字
b.synchronized作用:可以让多句代码具有原子性(当某个线程执行多句代码的过程中不被其他线程打断)

1.4 解决方法1:同步代码块
synchronized(锁对象){
需要同步的代码(需要保持原子性的代码)
}

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 TicketTask implements Runnable {
int count = 100;
Object obj = new Object();
@Override
public void run() {
while (true) {
synchronized (obj) {
if (count > 0) {
System.out.println("Sold" + count);
count--;
} else {
break;
}
}
}
}
}


public class TestDemo {
public static void main(String[] args) {
TicketTask tt = new TicketTask();
Thread t1 = new Thread(tt);
Thread t2 = new Thread(tt);
Thread t3 = new Thread(tt);

t1.start();
t2.start();
t3.start();
}
}

1.5 解决方法2:同步方法
格式:
public synchronized void 方法名(){

}

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 TicketTask implements Runnable {
int count = 100;
@Override
public void run() {
while (true) {
sellTicket();
}
}

public synchronized void sellTicket() {//默认当前任务对象
if (count > 0) {
System.out.println("Sold" + count);
count--;
}
}
}

public class TestDemo {
public static void main(String[] args) {
TicketTask tt = new TicketTask();
Thread t1 = new Thread(tt);
Thread t2 = new Thread(tt);
Thread t3 = new Thread(tt);

t1.start();
t2.start();
t3.start();
}
}

注意:
a.同步代码块和同步方法原理差不多,到底是同步代码块的同步锁需要自己指定,而同步方法的同步锁默认使用当前对象this
b.同步代码块可以是static的,如果同步方法是static的,默认使用当前类的字节码文件(.class文件)作为锁对象。

1.6 解决方法3:Lock锁
Lock是一个接口,我们需要使用其实现类ReentrantLock
ReentrantLock的API:
public ReentrantLock();
public void lock();//加锁
public void unlock();//解锁
格式:
ReentrantLock lock = new ReentrantLock();
lock.lock();//加锁
lock.unloc();//解锁

解决代码:

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 TicketTask implements Runnable {
int count = 100;
//创建一个lock锁
Lock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
lock.lock();
if (count > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Sold" + count);
count--;
} else {
break;
}
lock.unlock();
}
}
}

public class TestDemo {
public static void main(String[] args) {
TicketTask tt = new TicketTask();
Thread t1 = new Thread(tt);
Thread t2 = new Thread(tt);
Thread t3 = new Thread(tt);

t1.start();
t2.start();
t3.start();
}
}

二.并发包
什么是并发包:这是JDK提供的,包含一个在高并发情况使用集合或工具,

2.1 CopyOnWriteArrayList
arraylist线程是不安全的(ArrayIndexOutOfBoundsException),需要CopyOnWriteArrayList

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 Mythread extends Thread{
// public static List<Integer> list = new ArrayList<>();//ArrayIndexOutOfBoundsException
public static List<Integer> list = new CopyOnWriteArrayList<>();

@Override
public void run() {
for (int i = 0; i < 10000; i++) {
list.add(i);
}
System.out.println("Done");
}
}

public class TestArrayList {
public static void main(String[] args) throws InterruptedException {
Mythread mt1 = new Mythread();
Mythread mt2 = new Mythread();
mt1.start();
mt2.start();

Thread.sleep(1000);
System.out.println(Mythread.list.size());
}
}

2.2 CopyOnWriteArraySet

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

public class TestHashSet {
public static void main(String[] args) throws InterruptedException {
MyThread mt1 = new MyThread();
mt1.start();

for (int i = 10000; i < 20000; i++) {
MyThread.set.add(i);
}

Thread.sleep(1000);
System.out.println(MyThread.set.size());
}
}

public class Mythread extends Thread {
// public static List<Integer> list = new ArrayList<>();
public static List<Integer> list = new CopyOnWriteArrayList<>();
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
list.add(i);
}
System.out.println("Done");
}
}

2.3 ConcurrentHashMap

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

public class MyThread extends Thread {
//public static HashMap<Integer,Integer> map = new HashMap<Integer, Integer>();
// public static Map<Integer, Integer> map = new Hashtable<Integer, Integer>();
public static ConcurrentHashMap<Integer, Integer> map = new ConcurrentHashMap<Integer, Integer>();
@Override
public void run() {
long start = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
map.put(i, i);
}
long end = System.currentTimeMillis();
System.out.println("Done "+(end-start));
}
}

public class TestHashMap {
public static void main(String[] args) throws InterruptedException {
// MyThread mt1 = new MyThread();
//
// mt1.start();
//
// for (int i = 10000; i < 20000; i++) {
// MyThread.map.put(i,i);
// }
for (int i = 0; i < 1000; i++) {
new MyThread().start();
}
Thread.sleep(1000*20);
System.out.println(MyThread.map.size());
}
}

小结:
HashMap线程不安全(多线程不能操作同一个HashMap)
Hashtable线程安全(多线程可以操作同一个HashMap),但是效率低
ConcurrentHashMap 线程安全的,并且效率较高

•为什么Hashtable效率低:Hashtable锁定整个哈希表,一个操作正在进行时
•为什么ConcurrentHashMap效率高:局部锁定(CAS),只锁定链表(桶)。对当前元素锁定时,它元素不锁定。

2.4 CountDownLatch
•CountDownLatch的作用:允许当前线程,等待其他线程完成某种操作之后,当前线程继续执行
•CountDownLatch的API
构造方法:
public CountDownLatch(int count);//初始化,需要传入计数器,需要等待的线程数
成员方法:
public void await() throws InterruptedException();//让当前线程等待
public void countDown()//计数器减1

•CountDownLatch使用案例
需求:
线程1需要执行打印:A和C,线程2要执行打印B
需要结果:线程1打印A,线程2打印B,之后线程A打印C,输出A B 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
public class MyThread1 extends Thread {
private CountDownLatch latch;
public MyThread1(CountDownLatch latch) {
this.latch = latch;
}

@Override
public void run() {
System.out.println("A");
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

public class MyThread2 extends Thread {
private CountDownLatch latch;
public MyThread2(CountDownLatch latch) {
this.latch = latch;
}

@Override
public void run() {
System.out.println("B");
latch.countDown();
System.out.println("D");
}
}

public class TestCountDownLatch {
public static void main(String[] args) {
//创建CountDownLatch
CountDownLatch latch = new CountDownLatch(1);
MyThread1 mt1 = new MyThread1(latch);
MyThread2 mt2 = new MyThread2(latch);

mt1.start();
mt2.start();
}
}

2.5 CyclicBarrier
•CyclicBarrier的作用:让多个线程都到达了某种要求之后,新的任务才能执行
•CyclicBarrier的API
构造方法:
public CycliBarrier(int parties,Runnable barrierAction);
//parties【需要多少线程】,Runnable barrierAction)【所有线程都满足要求了,执行任务】

成员方法:
public int await();//当所有线程都到达了,需要调用await

•CyclicBarrier使用案例

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
public class PersonThread extends Thread {
private CyclicBarrier cb1;
public PersonThread(CyclicBarrier cb1){
this.cb1 = cb1;
}

@Override
public void run() {
try {
Thread.sleep((int) (Math.random()) * 1000);
System.out.println(Thread.currentThread().getName() + "Done");
cb1.await();
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
}

}

public class TestPersonThread {
public static void main(String[] args) {
CyclicBarrier cb1 = new CyclicBarrier(5,new Runnable(){
@Override
public void run() {
System.out.println("Done");
}
});
PersonThread pt1 = new PersonThread(cb1);
PersonThread pt2 = new PersonThread(cb1);
PersonThread pt3 = new PersonThread(cb1);
PersonThread pt4 = new PersonThread(cb1);
PersonThread pt5 = new PersonThread(cb1);

pt1.start();
pt2.start();
pt3.start();
pt4.start();
pt5.start();
}
}

2.6 Semaphore
•Semaphore的作用:用于控制并发线程的数量

•Semaphore的API:
构造方法:
public Semaphore(int permits);//参数permit表示最多有多少个线程并发执行
成员方法:
public void acquire();//获取线程许可证
public void release();//释放线程许可证

•Semaphore使用案例

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 MyThread extends Thread{
private Semaphore semaphore;
public MyThread(Semaphore semaphore){
this.semaphore= semaphore;
}
@Override
public void run() {
try {
semaphore.acquire();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" "+System.currentTimeMillis());
try {
Thread.sleep(1000*new Random().nextInt(6));
} catch (InterruptedException e) {
e.printStackTrace();
}
// System.out.println(Thread.currentThread().getName());
System.out.println(Thread.currentThread().getName()+" "+System.currentTimeMillis());
semaphore.release();
}
}

public class TestDemo {
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(3);

for (int i = 0; i < 10; i++) {
new MyThread(semaphore).start();
}
}
}

2.7 Exchanger
•Exchanger的作用:用于线程间的数据交换

•Exchanger的API:
构造方法:
public Exchanger();

成员方法:
public exchanger();//参数发送给别的线程的数据,返回值返回的数据

•Exchanger使用案例

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 class MyThread1 extends Thread {
private Exchanger<String> exchanger;
public MyThread1(Exchanger<String> exchanger) {
this.exchanger = exchanger;
}

@Override
public void run() {
System.out.println("MyThread-1 send to Thread-2");

String result = null;

try {
result = exchanger.exchange("Get Thread-1");
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread1 Get:"+result);
}
}

public class MyThread2 extends Thread {
private Exchanger<String > exchanger;
public MyThread2(Exchanger<String > exchanger){
this.exchanger=exchanger;
}

@Override
public void run() {
System.out.println("MyThread-2 send to Thread-1");

String result = null;

try {
result = exchanger.exchange("Get Thread-2");
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread2 Get:"+result);
}
}

public class TestDemo {
public static void main(String[] args) {
Exchanger<String> exchanger = new Exchanger<String>();

MyThread1 mt1 = new MyThread1(exchanger);
MyThread2 mt2 = new MyThread2(exchanger);

mt1.start();
mt2.start();
}
}

总结:
使用同步代码块解决线程安全问题
synchronized(锁对象){
需要同步的代码(需要保证原子性的代码)
}
使用同步方法解决线程安全问题
public synchronized void 方法名(){
需要同步的代码(需要保证原子性的代码)
}
使用Lock锁解决线程安全问题
Lock lock = new ReentrantLock();
lock.lock();
需要同步的代码(需要保证原子性的代码)
lock.unlock();

说明volatile关键字和synchronize关键字区别
volatile能解决有序性和可见性
原子类 能解决变量的原子性(有序性和可见性)
synchronized 能解决多句代码的原子性(有序性和可见性)

描述CopyOnWriteArrayList类作用
代替多线程的情况,线程安全的ArrayList集合

描述CopyOnWriteArraySet类作用
代替多线程的情况,线程安全的HashSet集合

描述ConcurrentHashMap类作用
代替多线程的情况,线程安全的HashMap集合,比HashTable效率更高

描述CountDownLatch类作用
可以运行一个线程等待另一个线程执行完毕后再继续执行

描述CycliBarrrier类作用
让一组线程都到达某中调节后再执行某个任务

描述Semaphore类作用
控制多线程并发的最大数量

描述Exchanger类作用
用于线程间的通信(数据交换)

一.多线程
1.1 并行和并发
并行:两个事件,在同一时刻,都在发生
并发:两个事件,在同一时间段内,都在发生

1.2 进程和线程
进程:正在内存中运行的程序,称为进程
•是系统进行资源分配和调用的独立单位
•每一个进程都有自己的诶次空间和系统资源

线程:是进程中的单个顺序控制流,进程中完成某个小功能的模块(进程中用于执行某个功能的执行单元)
•单线程:一个进程如果只有一条执行路径,则称为单线程程序
•多线程:一个进程如果有多条执行路径,则称为多线程程序

•进程和线程的区别
线程是属于某个进程的
进程:每个进程都有自己独立的内存空间(栈/堆),并且至少有一个线程
线程:每个线程都会跟所在的进程申请一块独立的栈,堆共享进程的堆(堆是共享的,栈是独立的),线程小号的资源比进程小得多

•线程调度
线程调用是指CPU在不同的进程不同的线程之间进行快速切换

线程调度的分类:
分时调度:每个线程平均拥有CPU的执行权
抢占式调度:每个线程随机分配CPU的执行权(具体的分配多少和线程的优先级有关)
Java进程中,所有的线程,采用抢占式调度

1.3 Thread类的介绍
例:IDE的main方法运行时是创建一个进程,该进程中至少有2个线程
I.main方法所在的线程,称为主线程
II.垃圾回收期线程

a.Thread类是什么
Thread是Java定义好的,代表线程的类,只要创建该类的一个对象,其实介绍创建了一个线程

b.Thread类的构造方法
public Thread();//无参构造
public Thread(String name);//带有线程名的构造
public Thread(Runnable r);//带有线程任务的构造
public Thread(Runnable r,String name);//即带有线程名字,又有线程任务的构造

c.Thread类的成员方法
public String getName();//获取线程的名字。无参构造,线程会有自己的名字,如Thread0,Thread1
public String setName(String name);//修改线程的名字
public void run();//代表线程要执行的任务,任务有关的代码需要写在此方法中
public void start();//线程只创建并不会执行,必须调用start开启后才会执行任务

public static void sleep(long millis);//让当前线程休眠X毫秒(指Thread.sleep(1000)代码写在哪个线程中,所在的线程就当前线程)
public static Thread currentThread();//获取当前线程对象(指Thread.currentThread()代码写在哪个线程中,所在的线程就当前线程)

public static void join();//等待这个线程死亡
public static void setDaemon(boolean on);//将此线程标记为守护线程,当允许的线程都是守护线程时,Java虚拟机将退出

public final int getPriority();//返回此线程的优先级
public final void setPriority(int newPriority);//更改此线程的优先级
线程优先级高,仅表示获取CPU时间片的几率高,并不表示会优先执行

1.4 创建新的线程方式一:继承方式
a. API描述:将类声明为Thread的子类。该子类重写Thread类的run方法。接下来可以分配并启动该子类的实例
b.分析创建步骤:
I.创建子类 继承 Thread
II.子类中重写run方法(在run中编写线程要执行的任务代码)

•为什么要重写run()方法:因为run()是用来封装被线程执行的代码
•run()方法和start()方法的区别:
i.run()封装线程执行的代码,直接调用,相当于普通方法的调用
ii.start()启动线程,然后由JVM调用此线程的run()方法

III.创建子类对象(实际上就是创建一个线程对象)
IV.调用线程对象的start方法(启动该线程)
案例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class MyThread extends Thread{
@Override
public void run(){
for (int i = 0; i < 50; i++) {
System.out.println(getName()+i);
}
}
}

public class ThreadDemo {
public static void main(String[] args) {
//创建子类对象(实际上就是创建一个线程对象)
MyThread mt = new MyThread();
//调用线程对象的start方法(启动该线程)
mt.start();
//主线程 不会等待子线程任务结束
for (int i = 0; i < 50; i++) {
System.out.println(Thread.currentThread().getName()+i);
}
}

}
总结:
a.可以给线程起名,也可以使用默认名
b.获取线程名称时,建议使用通用方式Thread.currentThread().getName();子线程内部,也可以直接调用getName();

1.5 创建新的线程方式二:实现方式
a.API描述:声明实现Runnable接口的类。该类然后实现run方法。然后可以分配该类的实例,在创建Thread时作为一个参数来传递并启动

b.分析创建步骤:
I.创建实现类 实现Runnable接口(实际上接口中就一个任务方法:run方法)
II.实现类重写run方法(run中编写具体的任务代码)
III.创建实现类对象(该实现类对象并不少线程对象,称为任务对象)
IV.创建Thread对象,同时传入实现类对象public Thread(Runnable r);
V.启动该线程(调用线程对象的start方法)

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
//创建实现类,实现Runnable接口
public class MyRunnable implements Runnable {
//实现类重写run方法(run中编写具体的任务代码)
@Override
public void run() {
//run方法中写任务代码
for (int i = 0; i < 50; i++) {
System.out.println(i);
}
}
}


public class TestThread {
public static void main(String[] args) {
//创建实现类对象(该实现类对象并不少线程对象,称为任务对象)
MyRunnable mr = new MyRunnable();
//创建Thread对象,同时传入实现类对象public Thread(Runnable r);
Thread tt = new Thread(mr);
//启动该线程(调用线程对象的start方法)
tt.start();
//主线程不会等待子线程执行完毕
for (int i = 0; i < 50; i++) {
System.out.println(Thread.currentThread().getName()+i);
}
}
}

1.5.1 两种线程的优劣势比较
两种创建线程的方式,实现方式比较好
I.实现方式的线程和任务是分开的,由程序员自己组合的,适合多个相同的程序代码的线程去共享同一个资源。
II.实现方式避免了java中的单继承的局限性。
III.实现方式线程和任务是接耦的,继承方式线程和任务是紧耦合的。增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和线程独立。
IV.线程池只能放入实现Runable或Callable类线程,不能直接放入继承Thread的子类。
综上所述,在开发中建议使用实现方式(并不是说实现方式不对)

1.6 匿名内部类简化线程创建方式
匿名内部类的作用:可以快速创建一个类的子类对象,或者一个接口的实现类对象

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) {
new Thread() {
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println(Thread.currentThread().getName() + i);
}
}
}.start();
new Thread (new Runnable() {
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println(Thread.currentThread().getName() + i);
}
}
}).start();

for (int i = 0; i < 50; i++) {
System.out.println(Thread.currentThread().getName() + i);
}
}

}
二.高并发和线程安全
2.1 高并发及线程安全介绍
什么是高并发:在某个时间点上,大量的用户(线程),访问同一个资源。
线程安全:在某个时间点上,大量用户(线程)访问同一个资源时,由于线程运行机制的原因,可能导致被访问的资源出现类”数据污染”

2.2 多线程的运行机制[内存方面]

1
2
3
4
5
6
7
8
9
 public class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
} }

public class Demo {
public static void main(String[] args) {
} }

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
public class MyThread extends Thread {
public static int a = 0;
@Override
public void run() {
System.out.println("线程启动,休息2秒...");
try {
Thread.sleep(1000 * 2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("将a的值改为1");
a = 1;
System.out.println("线程结束...");
}
}


public class TestSafeDemo01 {
public static void main(String[] args) {
//1.启动线程
MyThread t = new MyThread();
t.start();
//2.主线程继续
while (true) {
if (MyThread.a == 1) {
System.out.println("主线程读到了a = 1");
}
}
}
}

2.4 多线程的安全性问题:有序性
单线程会在不影响代码的结果的程度上,对代码进行重排。
如果在在”多线程”情况下,可能对一样的代码执行后得出不一样的结果。
我们要保证在多线程的情况下,不对代码进行”重排”,保证代码是有序的(不要使用重排)

2.5 多线程的安全性问题:原子性
什么是原子性:线程对一个共享变量进行++时,这个++是分成两部操作的,先取出值加1,然后给共享变量赋值,如果取出值加1后,还没有来得及赋值就被其他线程抢走CPU,此时我们称++操作不具有原子性

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 MyThread extends Thread {
public static int a = 0;
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
a++;
}
System.out.println("Done");
}
}

public class TestSaftDemo {
public static void main(String[] args) throws InterruptedException {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
t1.start();//Thread 1对a加了一万次
t2.start();//Thread 2对a加了一万次

Thread.sleep(1000);
System.out.println(MyThread.a);
}
}

三.volatile关键字
3.1 volatile是什么
volatile是一个关键字,用来修饰成员变量(静态变量),被他修改的变量,具有可见性和有序性

3.2 volatile解决可见性

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
public class MyThread extends Thread {
public volatile static int a = 0;
@Override
public void run() {
System.out.println("线程启动,休息2秒...");
try {
Thread.sleep(1000 * 2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("将a的值改为1");
a = 1;
System.out.println("线程结束...");
}
}

public class TestSafeDemo01 {
public static void main(String[] args) {
//1.启动线程
MyThread t = new MyThread();
t.start();
//2.主线程继续
while (true) {
if (MyThread.a == 1) {
System.out.println("主线程读到了a = 1");
}
}
}
}

3.3 volatile解决有序性

3.4 volatile不能解决原子性
即使加了volatile,仍然会被其他线程抢占,无法解决原子性问题

小结:volatile作用
a.解决变量的可见性,一旦变量发生改变,所有使用到该变量的线程都会取到最新值
b.解决变量的有序性,一旦变量加上volatile,那么编译器不会对变量运行的代码进行重排
c.无法解决变量操作过程中的原子性,对变量的操作哈斯可能被其他线程打断

四.原子类
4.1 原子类概述
a.什么是原子类:是对普通类型(如:int,Integer,double ,Double)的原子类封装,使其的操作成员原子操作
b.原子类的作用:对原子类的增强或减少操作,保证其原子性,保证中间不会被其他线程”打断”
c.原子类有哪些:

在java.util.concurrent.atomic包下定义了一些对“变量”操作的“原子类”:
1).java.util.concurrent.atomic.AtomicInteger:对int变量操作的“原子类”;
2).java.util.concurrent.atomic.AtomicLong:对long变量操作的“原子类”;
3).java.util.concurrent.atomic.AtomicBoolean:对boolean变量操作的“原子类”;
它们可以保证对“变量”操作的:原子性、有序性、可见性。

4.2 AtomicInteger类示例
a.AtomicInteger是什么:是对int类型变量进程操作原子类
b.AtomicInteger的构造方法:public AtomicInteger(int num);
c.AtomicInteger的成员方法:
public int getAndIncrement();//就相当于我们的 变量++
public int ncrementAndGet();//就相当于我们的 ++变量

d.AtomicInteger的改写案例

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 MyThread extends Thread {
public static int a = 0;

@Override
public void run() {
for (int i = 0; i < 10000; i++) {
a++;
}
System.out.println("Done");
}
}


public class TestSaftDemo {
public static void main(String[] args) throws InterruptedException {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
t1.start();//Thread 1对a加了一万次
t2.start();//Thread 2对a加了一万次

Thread.sleep(1000);
System.out.println(MyThread.a);
}
}

4.3 AtomicInteger类底层的工作原理-CAS机制
CAS:Compare And Swap(自旋)
我们假设两个线程交替运行的情况,看看它是怎样工作的:
初始AtomicInteger的值为0
线程A执行:var5 = this.getIntVolatile(var1,var2);获取的结果为:0
线程A被暂停
线程B执行:var5 = this.getIntVolatile(var1,var2);获取的结果为:0
线程B执行:this.compareAndSwapInt(var1,var2,var5,var5 + var4)
线程B成功将AtomicInteger中的值改为1
线程A恢复运行,执行:this.compareAndSwapInt(var1,var2,var5,var5 + var4)
此时线程A使用var1和var2从AtomicInteger中获取的值为:1,而传入的var5为0,比较失败,返回 false,继续循环。
线程A执行:var5 = this.getIntVolatile(var1,var2);获取的结果为:1
线程A执行:this.compareAndSwapInt(var1,var2,var5,var5 + var4)
此时线程A使用var1和var2从AtomicInteger中获取的值为:1,而传入的var5为1,比较成功,将其修改 为var5 + var4,也就是2,将AtomicInteger中的值改为2,结束。

•CAS也被称为乐观锁:因为大部分比较的结果为true,就直接修改了。只有少部分多线程并发的情况会 导致CAS失败,而再次循环。

4.4 AtomicIntegerArray类示例
•非原子类数组在多线程并发时会有问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class MyThread extends Thread {
public static int[] intArray = new int[1000];
@Override
public void run(){
for (int i = 0; i < intArray.length; i++) {
intArray[i]++;
}
}
}

public class TestDemo {
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 1000; i++) {
new MyThread().start();
}
Thread.sleep(1000*5);
System.out.println("sleep 5 second");
for (int i = 0; i <MyThread.intArray.length ; i++) {
System.out.println(MyThread.intArray[i]);
}
}
}

有极个别元素小于1000,因为int[]不是原子类数组,

•使用原子类数组,保证原子性,解决问题

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 MyThread extends Thread {
public static int[] intArray = new int[1000];
public static AtomicIntegerArray arr = new AtomicIntegerArray(intArray);

@Override
public void run(){
for (int i = 0; i < arr.length(); i++) {
arr.addAndGet(i, 1);
}
}
}

public class TestDemo {
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 1000; i++) {
new MyThread().start();
}
Thread.sleep(1000*5);
System.out.println("sleep 5 second");
for (int i = 0; i <MyThread.arr.length() ; i++) {
System.out.println(MyThread.arr.get(i));
}
}
}

总结:
进程和线程的区别:
进程:正在内存中运行的程序
线程:进程中完成某个功能的执行单元

并发于并行的区别:
并行:两个线程真的一起运行
并发:两个线程看起来一起运行,实际交替执行

Java多线程原理[CPU]:
线程调度

继承子类方式创建多线程:
I.子类 继承 Thread
II.子类 重写 run
III.创建 子类 对象
IV.调用 子类 对象 start

接口实现类方式实现多线程:
I.实现类 实现 Runnable
II.实现类 重写 run
III.创建 实现类 对象
IV.创建 Thread对象 同时 传入 实现类 对象
V.调用 Thread 对象 start 方法
VI.使用匿名内部类快速创建继承方式和实现方式的线程

接口实现方式好处:
I.实现方式的线程和任务是分开的,由程序员自己组合的,适合多个相同的程序代码的线程去共享同一个资源。
II.实现方式避免了java中的单继承的局限性。
III.实现方式线程和任务是接耦的,继承方式线程和任务是紧耦合的。增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和线程独立。
IV.线程池只能放入实现Runable或Callable类线程,不能直接放入继承Thread的子类。

安全问题出现的原因:
可见性 有序性 原子性

volatile关键字作用:
解决可见性与有序性,不能解决原子性

原子类AtomicInteger使用:
解决原子性
创建:
AtomicInteger i = new AtomicInteger(1);
使用:
i.getAndIncrement();//i++
i.incrementAndGet();//++i

原子类工作机制:
原子类底层使用CAS机制

一.选择排序
1.1 选择排序概述
核心思想:选中第一个元素,取出以后的元素依次和选中的元素进行比较,大的往后走,小的往前走,接着选中第二个元素同样操作,依此类推

1.2 选择排序图解

1.3 选择排序代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public static void main(String[] args) {
int[] arr = {5, 4, 7, 1, 8, 2, 3, 6, 9};
//外层循环,控制选中的元素
for (int i = 0; i < arr.length - 1; i++) {
//内存循环,控制元素的比较
for (int j = i + 1; j < arr.length; j++) {
//比较arr[i] arr[j]
if (arr[i] > arr[j]) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
}
System.out.println(Arrays.toString(arr));
}

二.二分查找
2.1 普通查找和二分查找
普通查找:给定数组,从数组中找到某个元素的索引
int[] arr = {4,5,6,1,7,2,8,9};
找出7出现的索引,只能从前往后找。
二分查找:给定的数组,必须是有自然顺序的,从数组中找某个元素出现的索引
int[] arr = {1,3,5,6,8,9,10,12};
找出8出现的索引,我们依然可以从前到后遍历,但是效率低。从中间开始,根据中间值的大小,可以让查找范围缩小一般

2.2 二分查找图解

a.首先定义两个遍历
int left = 0;
int right = arr.length-1

循环{
b.获取中间索引
int middle = (left+right/2)
c.获取middle索引元素和目标值比较
if (arr[middle] > key) {
right = middle = 1;
} else if (arr[middle] < key) {
left = middle + 1;
} else {
return middle;
}
}

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

public static void main(String[] args) {
int[] arr = {10, 20, 30, 40, 50, 60, 70, 88, 90};
//查找元素
int key = 20;
//调用方法
int index = Binary_Search(arr, key);
System.out.println(index);

int key_1 = 200;
int index_1 = Binary_Search(arr, key_1);
System.out.println(index_1);
}

//二分查找
public static int Binary_Search(int[] arr, int key) {
//开始和结束索引
int min = 0;
int max = arr.length - 1;
while (min <= max) {
//获取中间索引
int middle = (min + max) / 2;
//比较中间索引的元素和key
if (arr[middle] > key) {
max = middle - 1;
} else if (arr[middle] < key) {
min = middle + 1;
} else {
return middle;
}
}
return -1;
}

三.异常
3.1 什么是异常
异常:写程序或者运行程序过程中遇到的非正常返回

3.2 异常的继承关系
所有异常和错误的根类:Throwable
|-Error 错误类
|-Exception 异常类
|–RuntimeException
|–非RuntimeException

Error:严重问题,不需要处理
Exception:称为异常类,它表示程序本身可以处理的问题
•RuntimeException:在编译期不检查,出问题后,需要修改代码解决
•非RuntimeException:编译期将必须处理,否则程序不能通过编译,更不能正常运行

3.3 异常中常用的三个方法-Throwable的成员方法

方法名 说明
public void printStackTrace(); 以深红色在控制台打印异常的详细信息(包括异常类型,异常的原因,异常的位置)【最常用】
public String getMessage(); 返回此throwable的详细消息字符串
public String toString(); 获取异常的类型和异常的描述信息
打印方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
public static void main(String[] args) {
try{}
int[] arr ={1,2,3};
System.out.println(arr[3]);
}catch(ArrayIndexOutOfBoundsException e){
System.out.println(e.getMessage());
System.out.println(e.toString());
e.printStackTrace();
// Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 3
// at com.test.Demo03_Exception01.TestException.main(TestException.java:6)
}
}

3.4 异常的分类
•编译时的异常
写好代码之后,运行代码之前出现的异常
编译时异常:Exception以及Exception的子类(RuntimeException除外)

•运行时的异常
运行代码时出现的异常
运行时异常:RuntimeException以及其子类

编译时异常和运行时异常的区别:
Java中的异常被分为两大类:编译时异常和运行时异常,也被称为受检异常和非受检异常
所有的RuntimeException类及其子类被称为运行时异常,其他的异常都是编译时异常

3.5 JVM异常产生过程 - 默认处理方案
如果程序出现问题,且未做任何处理,最终JVM会做出默认的处理

把异常名称,异常原因以及异常出现的位置等信息输出在控制台
程序停止执行
四.异常处理
Java中异常相关的五个关键字
throw
throws
try…cache
finally

4.1 抛出异常throw
a.throw是什么:throw是一个关键字
b.什么时候使用到throw:想要向上抛出异常时,使用throw关键字
c.使用格式:throw 异常对象
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

public static void main(String[] args) {
int[] arr = {1, 2, 3, 4, 5, 6};
int element = getElement(arr);
System.out.println(element + "\n");
int[] new_arr = {1, 2, 3};
int new_element = getElement(new_arr);
System.out.println(new_element);
/*
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Out of Array
at com.test.Demo03_Exception01.TestException02.getElement(TestException02.java:18)
at com.test.Demo03_Exception01.TestException02.main(TestException02.java:10)
*/
}

public static int getElement(int[] arr) {
//自己判断数组是否有3索引
if (arr.length < 4) {
throw new ArrayIndexOutOfBoundsException("Out of Array");
}
//获取数组中索引为3的元素
int number = arr[3];
return number;
}

4.2 Objects中非空判断方法
public static T requireNonNull(T obj);方法内部帮助我们判断是否为null

查看源码:

1
2
3
4
5
public static <T> T requireNonNull(T obj) {
if (obj == null)
throw new NullPointerException;
return obj;
}

4.3 遇到异常的两种处理方式
如果遇到编译时异常,可以使用以下两种方式处理
如果我们遇到运行时异常,编译时期不用处理,运行后根据异常信息修改代码即可

4.4.1 throws声明抛出异常
a.声明异常的格式
throws关键字是给方法使用的,为该方法做出声明,声明该方法内部有编译时异常,调用者需要使用该异常格式:

public void 方法名(参数列表)throws XxxException{
代码(如果代码有异常)
}

b.案例演示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static void main(String[] args) throws FileNotFoundException{
ReadFiles("1.txt");

}

//throws关键字是给方法用的,为该方法做出声明,声明该方法内部有编译时异常,调用者需要处理该异常
public static void ReadFiles(String name) throws FileNotFoundException {
//假设硬盘上有一个1.txt文件
if (("1.txt").equals(name)) {
System.out.println("Successful");
} else {
//抛出异常
throw new FileNotFoundException("No File:" + name);
}
}

4.4.2 try-cache捕获异常
a.捕获异常的格式
格式:
try {
可能出现异常的代码
} catch (XxxException e) {
//处理异常
e.printStackTrace();//直接打印(开发阶段)
save(e);//将异常保存到异常日志
}

执行流程:
程序从try里面的代码开始执行
出现异常,会自动生成一个异常类对象,该异常对象被提交给Java Runtime系统
当Java Runtime系统接收到异常对象时,会到catch中去找匹配的异常类,找到后进行异常的处理
执行完毕后,程序还可以继续往下执行

b.案例演示

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

try {
ReadFiles("1.txt");
}catch (Exception e){
System.out.println("Has Exception");
e.printStackTrace();
}
System.out.println();

try {
ReadFiles("2.txt");
}catch (Exception e){
System.out.println("Has Exception");
e.printStackTrace();
}
System.out.println("Continue");
}
//throws关键字是给方法用的,为该方法做出声明,声明该方法内部有编译时异常,调用者需要处理该异常
public static void ReadFiles(String name) throws FileNotFoundException {
//假设硬盘上有一个1.txt文件
if (("1.txt").equals(name)) {
System.out.println("Successful");
} else {
//抛出异常
throw new FileNotFoundException("No File:" + name);
}
}

c.捕获到异常之后,如何查看异常的信息
I.直接带你出来,调用e.printStackTrace();//一般开发阶段
II.可以先保存起来,比如保存到异常日志。e.save();//用户使用阶段

4.5 finally代码块
a.finally代码块的格式
finally一般不能单独使用,配合try…catch使用
try {
可能出现异常的代码
} catch (XxxException e) {
//处理异常
e.printStackTrace();//直接打印(开发阶段)
save(e);//将异常保存到异常日志
}finally{
写在finally中的代码,不论是否有异常,都会执行
}
b.finally代码块的作用
写在finally中的代码,不论是否有异常,都会执行
一般用于写释放资源,关闭连接等代码

c.案例演示

1
2
3
4
5
6
7
8
try {
ReadFiles("2.txt");
}catch (Exception e){
System.out.println("Has Exception");
e.printStackTrace();
}finally {
xxx.close();
}

7.异常的注意事项
•运行时异常被抛出可以不处理,不需要throws声明,也不需要try…catch捕获
•如果父类的方法抛出了多个异常,子类覆盖(重写)父类方法时,只能抛出相同的异常或者它的子集(即少于父类方法)
•父类方法没有抛出异常,子类在重写该方法时,必须也不能抛出异常
•当多异常分别处理时,捕获处理,前面的类不能是后面类的父类
I.每个异常单独try…catch(一般不使用)
try{
method1();
}catch(1 e1){
}
try{
method2();
}catch(2 e2){

}
II.所有异常一起try,但是分开catch(偶尔使用)
try{
method1();
method2();
}catch(1 e1){

}catch(2 e2){

}
注意事项:要求前面的异常必须是子类异常,后面的异常必须是父类异常

III.所有异常一起try,一个catch【经常使用】
try{
method1();
method2();
method3();
}catch(Exception e1){

}

•在try/catch后可以追加finally代码块,其中的代码一定会被执行,通常用于资源回收。

五.自定义异常
5.1 为什么要定义异常
a.什么叫做自定义异常:自定义一个异常类,然后在适当时创建异常对象,并抛出
b.为什么要自定义异常:JDK提供的异常类不可能描述开发中所有问题

5.2 自定义异常的步骤
a.形似:创建一个类,类名必须叫XxxException
b.神似:继承Exception或RuntimeException
c.一般来说需要提供两个构造,无参构造+带有String参数的构造

1
2
3
4
5
6
7
8
9
10
public class MyException extends RuntimeException {
//无参构造
public MyException() {}

//带有异常信息的构造
public MyException(String message) {
//保存message参数
super(message);
}
}

5.3 自定义异常的练习
模拟操作,若用户名已存在,则抛出异常
a.JDK不带该异常,所以要自动抛出
b.

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

public class TestDemo {
public static void main(String[] args) {
String name = new Scanner(System.in).nextLine();
try {
register(name);
} catch (RegisterException e) {
e.printStackTrace();
}
}
public static void register(String name) throws RegisterException {
ArrayList<String> usernames = new ArrayList<>();
Collections.addAll(usernames, "a", "b", "c");

if (usernames.contains(name)) {
throw new RegisterException("UserName Repeat");
} else {
System.out.println("Regeist Successful");
}
}
}

总结:
a.选择排序的执行过程
for (int i = 0; i < arr.length - 1; i++) {
for (int j = i + 1; j < arr.length; j++) {
if (arr[i] > arr[j]) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
}
b.二分查找
public static int Binary_Search(int[] arr, int key) {
int min = 0;
int max = arr.length - 1;
while (min <= max) {
int middle = (min + max) / 2;
if (arr[middle] > key) {
max = middle - 1;
} else if (arr[middle] < key) {
min = middle + 1;
} else {
return middle;
}
}
return -1;
}
c.能分辨程序中的异常和错误的区别
所有异常和错误的根类:Throwable
|-Error 错误类 一般是硬件引起,程序员一般无法处理
|-Exception 异常类,异常一般是由程序编写不当造成的,程序员有能力处理
|–编译时异常 Exception以及其子类(RuntimeException除外)
|–运行时异常 RuntimeException以及其子类
d.三个常见的运行时异常
ArrayIndexOutOfBoundsException
NullPointerException
ClassPathException向下转型时出现的类型转换异常
e.使用try…catch关键字处理异常
try{

}catch()XxxException e{
e.printStackTrace();
}
f.使用throws关键字处理异常
throws给方法用的,表名抛出异常,该调用的调用者要处理异常
public void 方法名(参数列表) throws Exception(){}
g.自定义异常