IO流学习
流的分类
- 操作数据单位:字节流,字符流
- 数据的流向:输入流,输出流
- 流的角色:节点流,处理流
流的体系结构
抽象基类 | 节点流或者叫文件流 | 缓冲流(处理流的一种) |
---|---|---|
InputStream | FileInputStream | BufferedInputStream |
outputStream | FileOutPutStream | BufferedOutPutStream |
Reader | FileReader | BufferedReader |
Writer | FileWriter | BufferedWriter |
1.getPath():
返回的是定义时的路径,可能是相对路径,也可能是绝对路径,这个取决于定义时用的是相对路径还是绝对路径。如果定义时用的是绝对路径,那么使用getPath()返回的结果跟用getAbsolutePath()返回的结果一样
2.getAbsolutePath():
返回的是定义时的路径对应的相对路径,但不会处理“.”和“…”的情况
3.getCanonicalPath():
返回的是规范化的绝对路径,相当于将getAbsolutePath()中的“.”和“…”解析成对应的正确的路径
读取文件的内容输出到控制台
@Test
public void testFileReader() {
//总结
// Read()的理解:返回读入的一个字符,如果达到文件末尾,返回-1
// 异常的处理:为了保证流资源一定可以执行关闭操作,需要使用try-catch-fian
// 读入的文件一定要存在,否则就会报异常 java.io.FileNotFoundException: File\hello.txt (系统找不到指定的路径。)
FileReader fileReader = null;
try {
// 实例化File类的对象,指明要操作的文件
File file = new File("E:\\xuexi\\juc\\src\\main\\java\\com\\tanghong\\juc\\File\\hello.txt");
//提供具体的流
fileReader = new FileReader(file);
//数据的读入
int read = fileReader.read();
while (read !=-1){
System.out.print((char)read);
read = fileReader.read();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (fileReader!=null){
// 一定要关闭
fileReader.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
使用 char[] cbuf = new char[5]; 读入数据
@Test
public void testFileReader1() {
FileReader fileReader = null;
try {
// 实例化File类的对象,指明要操作的文件
File file = new File("E:\\xuexi\\juc\\src\\main\\java\\com\\tanghong\\juc\\File\\hello.txt");
//提供具体的流
fileReader = new FileReader(file);
char[] cbuf = new char[5];
int len;
while ((len = fileReader.read(cbuf)) !=-1){
//取出的方法1
// for (int i = 0; i < len; i++) {
// System.out.print(cbuf[i]);
// }
// 取出的方法2
String s = new String(cbuf,0,len);
System.out.print(s);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fileReader !=null){
try {
fileReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 从内存中写出数据到硬盘的文件里
*/
@Test
public void testFileWriter() throws IOException {
/**
* 文件输入流
* 假如要输入的的文件没有会帮你自动创建,不会报错
*
* new FileWriter 有两个构造方法
* FileWriter fileWriter = new FileWriter(file);
* FileWriter fileWriter = new FileWriter(file,false);
*
* new FileWriter(file)和FileWriter fileWriter = new FileWriter(file,false); 是一样的意思
* 上面代表的都是对修改文件的覆盖
* new FileWriter(file,true);
* 意思就是在后面追加内容
*
*/
// 1. 提供File类的对象,指明写出到的文件地址
File file = new File("E:\\xuexi\\juc\\src\\main\\java\\com\\tanghong\\juc\\File\\hello1.txt");
// 2. 提供具体的流
FileWriter fileWriter = new FileWriter(file,true);
// 3. 写出的操作
fileWriter.write("唐鸿真的牛逼\n");
fileWriter.write("我觉得很好");
// 4. 关闭流
fileWriter.close();
}
结论 :
对于文本文件(.txt, .java , . c , .cpp) 使用字符流处理
对于非文本文件(.jpg, .mp3 , .mp4 , .avi, .doc , .ppt …),使用字节流
/**
* 对图片进行复制操作
*
*/
@Test
public void testInpuStream() {
FileInputStream InputStream = null; // 输出流
FileOutputStream OutputStream = null; // 输入流
try {
// 选择文件
File file = new File("E:\\xuexi\\juc\\src\\main\\java\\com\\tanghong\\juc\\File\\223734-1528036654a425.jpg");
File file1 = new File("E:\\xuexi\\juc\\src\\main\\java\\com\\tanghong\\juc\\File\\2.jpg");
//选择流
InputStream = new FileInputStream(file);
OutputStream = new FileOutputStream(file1);
// 执行
byte[] ba = new byte[5];
int len;
while ((len = InputStream.read(ba)) !=-1){
OutputStream.write(ba,0,len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 关闭流
try {
if (InputStream!=null){
InputStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (InputStream!=null){
OutputStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
实际开发中都是用Buffered 缓冲流
//两个输入流
bis1 = new BufferedInputStream(new FileInputStream(f1));
bis2 = new BufferedInputStream(new FileInputStream(f2));
//缓冲字节输出流(true表示可以在流的后面追加数据,而不是覆盖!!)
bos = new BufferedOutputStream(new FileOutputStream(f,true));
多个服务端和多个客户端的socket通信
如果服务端需要处理很多个客户端的消息通信请求应该如何处理呢,此时我们就需要在服务端引入线程了,也就是说客户端每发起一个请求,服务端就创建一个新的线程来处理客户端的请求,这样就是实现了一个客户端一个线程
/**
* 目标:实现服务端可以同时接收多个客户端的Socket通信需求
* 思路: 是服务端每接收一个客户端socket请求对象之后都交给一个独立的线程来处理客户端的数据交互需求
*/
public class Server {
public static void main(String[] args) {
try {
// 1. 注册端口
ServerSocket ss = new ServerSocket(9999);
// 2. 定义一个死循环,负责不断的接收客户端的socket
while (true){
Socket accept = ss.accept();
//3. 创建一个独立的线程来处理与这个客户端的socket通信
new ServerThreadRead(accept).start();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
public class ServerThreadRead extends Thread{
private Socket socket;
public ServerThreadRead(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
// 从socket 对象中得到一个字节输入流
InputStream is = socket.getInputStream();
// 使用缓冲字符输入包装字节输入流
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(is));
String st;
while ((st = bufferedReader.readLine()) !=null){
System.out.println(st);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
public class Client {
public static void main(String[] args) {
try {
Socket socket = new Socket("127.0.0.1", 9999);
PrintStream printStream = new PrintStream(socket.getOutputStream());
Scanner scanner = new Scanner(System.in);
while (true){
System.out.println("打印");
String s = scanner.nextLine();
printStream.println(s);
printStream.flush();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
小结
每个Socket接收到,都会创建一个线程,线程的竞争,切换上下文影响性能;
每个线程都会占用栈空间和CPU资源
并不会每个socket都进行IO操作,无意义的线程处理
哭护短的并发访问增加时,服务端将呈现1:1的线程开销,访问量越大,系统将发生线程栈溢出,线程创建失败,最终导致进程宕机,从而不能对外提供服务
Java BIO
-
java BIO 就是传统的 java io 编程,其相关的类和接口在java.io
-
BIO(blocking I/O) : 同步阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务端就需要启动一个线程进行处理,
如果这个连接不做任何事情会造成不必要的线程开销,可以通过线程池机制改善
单行发送和单行接收
/**
* 客户端
*/
public class Client {
public static void main(String[] args) throws IOException {
// 1.创建socket 对象请求服务端的链接
Socket socket = new Socket("127.0.0.1", 9999);
// 从socket对象中获取一个字节输入流
OutputStream outputStream = socket.getOutputStream();
// 把 字节输出流包装成一个打印流
PrintStream printStream = new PrintStream(outputStream);
printStream.println("唐鸿你好啊 hello");
printStream.flush();
}
}
/**
* 服务端
*/
public class Server {
public static void main(String[] args) {
BufferedReader br = null;
try {
// 定义一个ServerSocket对象进行服务端的端口注册
ServerSocket ss = new ServerSocket(9999);
// 监听客户端的Socket链接请求
Socket accept = ss.accept();
// 从socket管道中得到一个字节输入流对象
InputStream is = accept.getInputStream();
// 把字节输入流包装成一个缓冲字符输入流
br = new BufferedReader(new InputStreamReader(is));
String msg;
while ((msg = br.readLine()) !=null){
System.out.println("服务端接收到"+ msg);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (br !=null){
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
小结:
在以上通信中,服务端会一直等待客户端的消息,如果客户端没有进行消息的发送,服务端将一直进入阻塞状态。
同事服务端是按照行获取消息的,这意味着客户端也必须按照行进行消息的发送,否则服务端将进行等待消息的阻塞状态
多发和多收机制
/**
* 服务端
*/
public class Server {
public static void main(String[] args) {
BufferedReader br = null;
try {
System.out.println("服务端已开启");
// 定义一个ServerSocket对象进行服务端的端口注册
ServerSocket ss = new ServerSocket(9999);
// 监听客户端的Socket链接请求
Socket accept = ss.accept();
// 从socket管道中得到一个字节输入流对象
InputStream is = accept.getInputStream();
// 把字节输入流包装成一个缓冲字符输入流
br = new BufferedReader(new InputStreamReader(is));
String msg;
while ((msg = br.readLine()) !=null){
System.out.println(msg);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (br !=null){
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
**
* 客户端
*/
public class Client {
public static void main(String[] args) throws IOException {
// 1.创建socket 对象请求服务端的链接
Socket socket = new Socket("127.0.0.1", 9999);
// 从socket对象中获取一个字节输入流
OutputStream outputStream = socket.getOutputStream();
// 把 字节输出流包装成一个打印流
PrintStream printStream = new PrintStream(outputStream);
Scanner sc = new Scanner(System.in);
while (true){
System.out.println("请说");
String s = sc.nextLine();
printStream.println(s);
printStream.flush();
}
}
}
java NIO
NIO和BIO比较
- BIO以流的方式处理数据,而NIO以块的方式处理数据,块I/O的效率比流I/0高很多
- BIO是阻塞的,NIO则是非阻塞的
- BIO基于字节流和字符流进行操作,而NIO基于Channel(通道)和Buffer(缓冲区)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中,或者从缓冲区写入到通道中,Selector(选择器)用于监听多个通道的事件(比如:连接请求,数据到达等) 因此使用单个线程就可以监听多个客户端通道
NIO | BIO |
---|---|
面向缓冲区(buffer) | 面向流(stream) |
非阻塞(Non Blocking IO) | 阻塞IO(Blocking IO) |
选择器(Selectors) |
NIO 三大核心原理
NIO有三大核心部分:Channel(通道),Buffer(缓冲区),Selector(选择器)
Buffer缓冲区
缓冲区本质是一块可以写入数据,然后可以从中读取数据的内存,这块内存被包装成NIO Buffer对象,并提供了一组方法,用来方便的访问该块内存,相比较直接对数组的操作,Buffer API更加容易操作和管理
Channel(通道)
java NIO的通道类似流,但又有些不同:即可以从通道中读取数据,又可以写数据到通道。但流的(input或Output)读写通畅是单向的。通道可以非阻塞读取和写入通道,通道可以支持读取或写入缓冲区,也支持异步的读取写
Selector 选择器
Selector是一个javaNIO 组件,可以能够检查一个或多个NIO通道,并确定哪些通道已经准备好进行读取或写入。这样,一个单独的线程可以管理多个Channel,从而管理多个网络连接,提供效率