Java IO 体系学习及常用类学习
一、前言引入
根据文件操作类的学习我们认识到,File可以操作文件,但是不能操作文件的内容,如果要操作文件的内容,可以通过字节流或者字符流。
一、前言引入
二、IO概念和概述讲解Java IO体系(部分)
三、具体内容讲解,文件操作应用小案例
四、总结
二、IO概念和概述讲解Java IO体系(部分)
2.1 基本概念阐述
- 流的概念和作用
流是一组有顺序的,有起点和终点的字节集合,是对数据传输的总称或抽象。即数据在两设备间的传输称为流,流的本质是数据传输,根据数据传输特性将流抽象为各种类,方便更直观的进行数据操作。
- IO流的分类
根据处理数据类型的不同分为:字符流和字节流
根据数据流向不同分为:输入流和输出流
- 字符流和字节流
字符流的由来: 因为数据编码的不同,而有了对字符进行高效操作的流对象。本质其实就是基于字节流读取时,去查了指定的码表。 字节流和字符流的区别:
- 读写单位不同:字节流以字节(8bit)为单位,字符流以字符为单位,根据码表映射字符,一次可能读多个字节。
- 处理对象不同:字节流能处理所有类型的数据(如图片、avi等),而字符流只能处理字符类型的数据。
结论:只要是处理纯文本数据,就优先考虑使用字符流。 除此之外都使用字节流。
- 输入流和输出流
对输入流只能进行读操作,对输出流只能进行写操作,程序中需要根据待传输数据的不同特性而使用不同的流。
2.2 常用 IO 类的概述
- 文件流:FileInputStream / FileOutputStream, FileReader / FileWriter
这四个类是专门操作文件流的,用法高度相似,区别在于前面两个是操作字节流,后面两个是操作字符流。它们都会直接操作文件流,直接与OS底层交互。因此他们也被称为节点流。
注意使用这几个流的对象之后,需要关闭流对象,因为java垃圾回收器不会主动回收。不过在Java7之后,可以在 try()括号中打开流,最后程序会自动关闭流对象,不再需要显示地close。
- 转换流:InputStreamReader / OutputStreamReader
这两个类可以将字节流转换成字符流,被称为字节流与字符流之间的桥梁。我们经常在读取键盘输入(System.in)或网络通信的时候,需要使用这两个类
- 缓冲流:BufferedReader / BufferedWriter ,BufferedInputStream / BufferedOutputStream
没有经过Buffered处理的IO, 意味着每一次读和写的请求都会由OS底层直接处理,这会导致非常低效的问题。
经过Buffered处理过的输入流将会从一个buffer内存区域读取数据,本地API只会在buffer空了之后才会被调用(可能一次调用会填充很多数据进buffer)。
另外,BufferedReader提供一个readLine()可以方便地读取一行,而FileInputStream和FileReader只能读取一个字节或者一个字符,因此BufferedReader也被称为行读取器
接下来的内容将围绕上面提到的几个重要 IO 类展开
三、具体讲解(围绕以上常用的IO类展开)
如果要进行输入、输出操作一般都会按照如下的步骤进行(以文件操作为例):
-
通过File类定义一个要操作文件的路径;
-
通过字节流或字符流的子类对象为父类对象实例化;
-
进行数据的读(输入),写(输出)操作;
-
数据流属于资源操作,资源操作必须关闭;
3.1 字节输出流:OutputStream (重点)
OutputStream类是一个专门进行字节数据输出的一个类,这个类定义如下:
public abstract class OutputStream extends Object implements Closeable, Flushable
首先我们发现OutputStream类实现了两个接口:Closeable、Flushable,这两个接口定义如下:
有意思的是JDK1.7的时候引入了一个自动关闭机制,所以Closeable有多继承了一个
AutoCloseable接口:
public interface AutoCloseable {
public void close() throws Exception;
}
但是OutputStream类是在 JDK1.0 的时候就提供了,这个类中定义close()与flush()
两个方法,所以以上的两个接口几乎 我们可以不用在意了。
在OutputStream类里面提供有三个重要的方法:
方法 | 释义 |
---|---|
public abstract void write(int b) throws IOException | 输出单个字节 |
public void write(byte[] b) throws IOException | 输出全部字节数组 |
public void write(byte[] b,int off,int len) throws IOException | 输出部分字节数组 |
根据API的学习我们知道OutputStream是属于抽象类,如果想要为抽象类进行对象的实例化操作,本次由于是文件操作,就要使用其子类FileOutputStream。
构造方法 | 释义 |
---|---|
public FileOutputStream(File file) throws FileNotFoundException | 创建或覆盖已有文件 |
public FileOutputStream(File file,boolean append) throws FileNotFoundException | 文件内容的追加 |
示例代码:文件输出
public class Test2 {
public static void main(String args[]) throws Exception {
// 1. 定义文件要输出的路径
File file = new File("E:" + File.separator + "demo"
+ File.separator + "my.txt");
// . 此时由于目录不存在,所以文件不能够输出,那么应该创建文件的目录
if (file.getParentFile().exists()) { // 文件目录不存在
file.getParentFile().mkdirs(); // 创建目录
}
// 2. 应该使用OutputStream和其子类进行对象的实例化,此时目录存在,文件还不存在
OutputStream output = new FileOutputStream(file); // 当然了 1,2步骤可以合为一步
// 3. 进行文件内容的输出
String str = "好好学习,天天向上";
byte[] data = str.getBytes(); // 将字符串变为字节数组
output.write(data); // 将内容输出
// 4. 将输出资源关闭
output.close();
}
}
以上是将整个字节数据的内容进行了输出(第三种方式),并且发现,如果此时要输出的文件不存在,那么会自动进行创建,对于输出操作整个OutputStream类里面一共定义有三个方法。
范例:采用单个字节的方式输出
for (int x = 0; x < data.length; x++) {
output.write(data[x]);
}
范例:输出部分字节的内容
output.write(data, 1, 4);
但是每一次都是内容的覆盖感觉不是很好,我们还可以进行内容的追加,只需要更换FileOutputStream()构造方法即可。
OutputStream output = new FileOutputStream(file, true);
第三种方式,就是整个字节数据的输出。 只要是程序输出内容,都可以利用OutputStream类完成。
3.2、字节输入流InputStream
如果程序需要进行数据的读取操作,可以利用InputStream类实现,此类定义如下:
public abstract class InputStream extends Object implements Closeable
- 在InputStream里面也定义有数据读取的方法:
- 读取单个字节:public abstract int read() throws IOException返回的是int类型和writer接收参数保持一致;(吃花生,吃完就是为空,怎么在数字上描述“空”,无数据可读,返回“-1”,不知道读取多少次用while)
|----------返回值:返回数据的字节内容,如果现在没有内容,返回 -1 - 将读取的数据保存在字节数组里:public int read(byte[] b) throws IOException(一池子水,文明人拿杯子,30升杯子,10升水。返回10升,100升水,返回30)
|----------返回值:返回数据的读取长度,读取到结尾返回-1; - 将读取的数据保存在部分字节数组里:public int read(byte[] b,int off,int len) throws IOException
|-----------返回值:读取的部分数据长度,如果已经读取到结尾了,返回 -1;
- 读取单个字节:public abstract int read() throws IOException返回的是int类型和writer接收参数保持一致;(吃花生,吃完就是为空,怎么在数字上描述“空”,无数据可读,返回“-1”,不知道读取多少次用while)
InputStream是一个抽象类,要读取数据要使用FileInputStream子类,而这个子类构造方法:
构造方法 | 释义 |
---|---|
public FileInputStream(File file) throws FileNotFoundException | 该文件由文件系统中的File对象file命名 |
public FileInputStream(String name) throws FileNotFoundException | 文件由文件系统中的路径名name命名 |
示例代码:向数组里面读取数据
public class Test2 {
public static void main(String args[]) throws Exception {
// 1. 定义文件要输出的路径
File file = new File("E:" + File.separator + "demo"
+ File.separator + "my.txt");
// 判断文件是否存在后才可以读取
if (file.exists()) { // 文件存在
// 2. 使用InputStream 进行读取
InputStream input = new FileInputStream(file); // 这里的 file 也可以是本地的文件名
// 3. 进行数据读取
byte[] data = new byte[1024]; // 准备一个1024的数组
int len = input.read(data); // 将内容保存到字节数组之中
// 4. 关闭输入流
input.close();
System.out.println("【" + new String(data, 0, len)+ "】");
}
}
}
- 由于一个文件有很多的字节数据,所以如果要读取肯定采取循环的方式,由于不确定循环次数,我们采用do…while与while两种方式实现
示例代码(重点):利用while循环读取
public class Test2 {
public static void main(String args[]) throws Exception {
// 1. 定义文件要输出的路径
File file = new File("E:" + File.separator + "demo"
+ File.separator + "my.txt");
// 判断文件是否存在后才可以读取
if (file.exists()) { // 文件存在
// 2. 使用InputStream 进行读取
InputStream input = new FileInputStream(file);
// 3. 进行数据读取
byte[] data = new byte[1024]; // 准备一个1024的数组
int foot = 0; // 表示字节数组的操作脚标
int temp = 0; // 表示接收每次读取的字节数组
/*
* 第一部分:(temp = input.read()),将read读取的字节数组给了temp变量,
* 第二部分:判断读取的内容是否是-1 ,如果不是将内容保存
*/
while ((temp = input.read()) != -1) {
data[foot ++] = (byte) temp;
}
// 4. 关闭输入流
input.close();
System.out.println("【" + new String(data, 0, foot)+ "】");
}
}
}
范例:拷贝文本文件
public class CopyTxtTest {
public static void main(String[] args) throws IOException {
//封装数据源
FileInputStream fis = new FileInputStream("d:\\窗里窗外.txt");
//封装目的地
FileOutputStream fos = new FileOutputStream("林青霞.txt");
//读写数据
//方式1:一次读取一个字节
// int by;
// while((by=fis.read())!=-1) {
// fos.write(by);
// }
//方式2:一次读取一个字节数组
byte[] bys = new byte[1024];
int len;
while ((len = fis.read(bys)) != -1) {
fos.write(bys, 0, len);
}
//释放资源
fos.close();
fis.close();
}
}
3.3、字符输出流:Writer
Writer类里面定义了有如下的输出方法(部分):
方法 | 释义 |
---|---|
public void write(char[] cbuf) throws IOException | 输出全部字符数组 |
public void write(String str) throws IOException | 输出字符串 |
示例代码:输出流
public class Test2 {
public static void main(String args[]) throws Exception {
// 1. 定义文件要输出的路径
File file = new File("E:" + File.separator + "demo"
+ File.separator + "my.txt");
if (!file.getParentFile().exists()) {
file.getParentFile().mkdirs();
}
// 2. 实例化了Writer 类的对象
Writer out = new FileWriter(file);
// 3. 将内容输出
String str = "好好学习,天天向上,每天起来,拥抱太阳";
out.write(str); // 输出字符串数据
// 4. 关闭输出流
out.close();
}
}
可以发现Writer作为字符输出流,可以直接进行字符串的输出,这一点就比OutputStream强了。
3.4、字符输入流:Reader
Reader是进行字符数据读取的输入流,其本身也是一个抽象类:
public abstract class Reader extends Object implements Readable, Closeable
在Reader类里提供了一系列的read()方法:
- 读取内容到字符数组:public int read(char[] cbuf) throws IOException
|-----------返回值:表示赌球的长度,如果已经读取到结尾了返回-1;
为Reader类实例化的可以使用 FileReader 子类完成。
示例代码:使用Reader读取 数据
public class Test2 {
public static void main(String args[]) throws Exception {
// 1. 定义文件要输出的路径
File file = new File("E:" + File.separator + "demo"
+ File.separator + "my.txt");
if (file.exists()) {
// 2. 为 Reader类对象实例化
Reader in = new FileReader(file);
// 3. 进行数据读取
char[] data = new char[1024];
int len = in.read(data);
// 4. 关闭输入流
in.close();
System.out.println(new String(data, 0, len));
}
}
}
与字节输入流几乎是一样的,只是数据类型由byte换为了char而已。
3.5 字节流与字符流的转换
字符虽然要需要缓冲区进行处理,但是有一个问题千万不能忽略,字符输出流有个特点就是可以直接输出字符串,所以有的时候就不得不进行字节流与字符流的操作转换。
在java.io包里面提供了两个类:InputStreamReader、OutputStreamWriter。我们来看一下这两个类的定义以及构造方法:
范例:字节流和字符流的转换
public class Test {
public static void main(String[] args) throws IOException {
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("osw.txt"),"UTF-8"); // 可以指定编码方式
//调用写数据的方法
osw.write("你好");
//释放资源
osw.close();
System.out.println("------------------------");
InputStreamReader isr = new InputStreamReader(new FileInputStream("osw.txt"),"UTF-8");
//读数据:一次读取一个字符数据
int ch;
while((ch=isr.read())!=-1) {
System.out.print((char)ch);
}
//释放资源
isr.close();
}
}
- 使用FileWriter,FileReader 简化了OutputStreamWriter,InputStreamReader的使用当然了,从继承的关系来看FileWriter,FileReader是OutputStreamWriter,InputStreamReader的子类,体现了一代更比一代强。
3.6 字节缓冲流
字节流一次读写一个数组的速度比一次读写一个字节的速度快很多,这是加入了数组这样的缓冲区效果,java本身在设计的时候,也考虑到了这样的设计思想,所以提供了字节缓冲区流
-
字节缓冲流 :
-
BufferedOutputStream:字节缓冲输出流
-
BufferedInputStream:字节缓冲输入流
-
代码示例:字节缓冲流
public class BufferedStreamDemo {
public static void main(String[] args) throws IOException {
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("a.txt"));
// 方式1:一次读取一个字节
// int by;
// while((by =bis.read()) != -1) {
// System.out.print((char) by);
// }
// 方式2:一次读取一个字节数组
byte[] bys = new byte[1024];
int len;
while ((len = bis.read(bys)) != -1) {
System.out.print(new String(bys, 0, len));
}
bis.close();
}
}
3.7 字符缓冲流
- BufferedWriter------构造BufferedWriter(Writer out)
将文本写入字符输出流,缓冲各个字符,从而提供单个字符、数组和字符串的高效写入。
- BufferedReader-----构造BufferedReader(Reader in)
从字符输入流中读取文本,缓冲各个字符,从而实现字符、数组和行的高效读取。
示例代码:
public class Test {
// 创建字符缓冲输入流对象
BufferedReader br = new BufferedReader(new FileReader("BufferedStreamDemo.java"));
//方式1:一次读取一个字符
// int ch;
// while ((ch = br.read())!=-1) {
// System.out.print((char)ch);
// }
//方式2:一次读取一个字符数组
char[] chs = new char[1024];
int len;
while((len = br.read(chs))!=-1) {
System.out.print(new String(chs,0,len));
}
// 释放资源
br.close();
}
}
3.7.1 字符缓冲区流的特殊功能
-
BufferedWriter-----void newLine():写入一个行分隔符,这个行分隔符是由系统决定的
-
BufferedReader-----String readLine():包含该行内容的字符串,不包含任何行终止符,如果已到达流末尾,则返回 null
范例:字符缓冲区流的特殊功能复制Java文件
public class CopyJavaTest {
public static void main(String[] args) throws IOException {
//封装数据源
BufferedReader br = new BufferedReader(new FileReader("yarn-site.xml"));
//封装目的地
BufferedWriter bw = new BufferedWriter(new FileWriter("kangll.txt"));
//读写数据
String line;
while((line = br.readLine()) != null) {
bw.write(line);
bw.newLine();
bw.flush();
}
//释放资源
bw.close();
br.close();
}
}
复制结果
3.8 文件应用小案例
要求:把集合中的学生对象数据存储到文本文件,把ArrayList集合中的学生数据存储到文本文件,每一个学生数据作为文件中的一行数据
代码示例:
public class ArrayListToFileTest {
public static void main(String[] args) throws IOException {
// 创建集合对象
ArrayList<Student> array = new ArrayList<Student>();
// 创建学生对象,自己可以自行创建
list.add(new Student("it001", "科比", 45, "纽约"));
list.add(new Student("it002", "詹姆斯", 35, "洛杉矶"));
list.add(new Student("it003", "伦纳德", 27, "洛杉矶"));
// 把学生对象添加到集合中
array.add(s1);
array.add(s2);
array.add(s3);
// 创建字符缓冲输出流对象
BufferedWriter bw = new BufferedWriter(new FileWriter("students.txt"));
// 遍历集合,得到每一个学生对象,然后把该对象的数据拼接成一个指定格式的字符串写到文本文件
for (Student s : array) {
StringBuilder sb = new StringBuilder();
sb.append(s.getSid()).append(",").append(s.getName()).append(",").append(s.getAge()).append(",")
.append(s.getCity());
bw.write(sb.toString());
bw.newLine();
bw.flush();
}
//释放资源
bw.close();
}
}
反过来 ,把文本文件中的学生对象数据读取到集合
需求:从文本文件中读取学生数据到ArrayList集合中,并遍历集合,每一行数据作为一个学生元素。
代码示例 :
public class FileToArrayListTest {
public static void main(String[] args) throws IOException {
// 创建字符缓冲输入流对象
BufferedReader br = new BufferedReader(new FileReader("students.txt"));
// 创建集合对象
ArrayList<Student> array = new ArrayList<Student>();
// 读取数据,每一次读取一行数据,把该行数据想办法封装成学生对象,并把学生对象存储到集合中
String line;
while ((line = br.readLine()) != null) {
String[] strArray = line.split(",");
Student s = new Student();
s.setSid(strArray[0]);
s.setName(strArray[1]);
s.setAge(Integer.parseInt(strArray[2]));
s.setCity(strArray[3]);
array.add(s);
}
// 释放资源
br.close();
// 遍历集合
for (Student s : array) {
System.out.println(s.getSid() + "---" + s.getName() + "---" + s.getAge() + "---" + s.getCity());
}
}
}
3.9 PrintWriter 使用(IO类的使用,Socket应用)
使用TCP的 Socket编程实现 客户端和 服务器的通信,客户端发送 用户名和密码 服务器 验证是否正确,并且返回验证信息。
客户端代码实现
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
public class Client {
public static void main(String[] args) throws IOException {
// 创建客户端 Socket 对象
Socket s = new Socket("172.20.10.3",2345);
// 从控制台获取 用户名和密码
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
System.out.println("请输入用户名:");
String username = br.readLine();
System.out.println("请输入密码:");
String password = br.readLine();
// 获取输出流对象
PrintWriter out = new PrintWriter(s.getOutputStream(),true);
// 写出数据
out.println(username);
out.println(password);
// 获取输入流对象,将服务端的 信息返回
BufferedReader serverBr = new BufferedReader(new InputStreamReader(s.getInputStream()));
// 获得服务器返回的值
String result = serverBr.readLine();
System.out.println(result);
s.close();
}
}
服务器代码实现
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.List;
public class Server {
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(2345);
while (true) {
// 监听
Socket s = ss.accept();
// 获取输入流对象
BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()));
String username = br.readLine();
// String username1 = new BufferedReader(new InputStreamReader(s.getInputStream())).readLine();
String password = br.readLine();
boolean flag = false;
List<User> users = UserDB.getUserInstance();
User user = new User(username, password);
if (users.contains(user)) {
flag = true;
}
// 获取输出流对象
PrintWriter out = new PrintWriter(s.getOutputStream(), true);
// 返回判断信息
if (flag) {
out.println("登录成功!");
} else {
out.println("登录失败!");
}
}
}
}
四、总结
总结上面几种流的应用场景:
- FileInputStream / FileOutputStream 需要逐个字节处理原始二进制流的时候使用,效率低下
- FileReader / FileWriter 需要组个字符处理的时候使用
- InputStreamReader/OutputStreamReader , 字节和字符的转换桥梁,在网络通信或者处理键盘输入的时候用
- BufferedReader/BufferedWriter ,
BufferedInputStream/BufferedOutputStream ,
缓冲流用来包装字节流后者字符流,提升IO性能,BufferedReader还可以方便地读取一行,简化编程。
参考:https://www.cnblogs.com/oubo/archive/2012/01/06/2394638.html
https://www.cnblogs.com/wsg25/p/7499227.html
https://www.cnblogs.com/fysola/p/6123947.html
相关文章
- [Java基础] java多线程关于消费者和生产者
- Java实现 LeetCode 501 二叉搜索树中的众数
- Java实现 LeetCode 41 缺失的第一个正数
- java 环境配置 maven 环境配置
- RPC学习--C#使用Thrift简介,C#客户端和Java服务端相互交互
- RPC学习----Thrift快速入门和Java简单示例
- java struts2入门学习--OGNL语言常用符号和常用标签学习
- java struts2入门学习--防止表单重复提交.OGNL语言学习
- Java -- JDBC 学习--批量处理
- Java多线程学习笔记 - 四、如何完美的中断线程
- hbase学习(一)hbase单机部署和java客户端连接单机hbase
- Error:Execution failed for task ‘:app:transformNative_libsWithStripDebugSymbolForDebug‘.> java.io.IO
- Java学习路线-27:IO操作深入与IO操作类继承体系
- Java学习路线-26:字节流与字符流OutputStream/InputStream/Writer/Reader
- Java学习路线-9:多例设计与枚举类
- 一脸懵逼学习hadoop之HDFS的java客户端编写
- JAVA学习第十九课(java程序的异常处理 (二))
- Java开发技术之成为高级java工程师必须学习的三个技术
- Java学习框架
- 【java】Java IO体系总览
- 用selenium4 webdriver + java 开发第一个自动化测试脚本