这是我参与更文挑战的第16天,活动详情查看:更文挑战
Java IO流
- IO是Input/Output的缩写,I/O技术是非常实用的技术,用于处理设备之间的数据传输。如读/写文件,网络通讯等。
- Java程序中,对于数据的输入输出操作以“流(stream)”的方式进行。
- Java.IO包下提供了各种“流”类和接口,用以获取不同种类的数据,并通过标准的方法输入或输出数据。
流的分类
操作数据单位:字节流、字符流
- 对于文本文件(.txt,.java,.c,.cpp),使用字符流处理
- 对于非文本文件(.jpg,.mp3,.mp4,.avi,.doc,.ppt,…),使用字节流处理
数据的流向:输入流、输出流
- 输入input 读取外部数据(磁盘、光盘等存储设备的数据)到程序(内存)中。
- 输出output 将程序(内存)数据输出到磁盘、光盘等存储设备中。
流的角色:节点流、处理流
节点流:直接从数据源或目的地读写数据。
处理流:不直接连接到数据源或目的地,而是“连接”在已存在的流(节点流或处理流)之上,通过对数据的处理为程序提供更为强大的读x写功能。
常用的几个IO流结构
抽象基类 | 节点流(或文件流) | 缓冲流(处理流的一种) |
---|---|---|
InputStream | FileInputStream (read(byte[] buffer)) | BufferedInputStream (read(byte[] buffer)) |
OutputSteam | FileOutputStream (write(byte[] buffer,0,len) | BufferedOutputStream (write(byte[] buffer,0,len) / flush() |
Reader | FileReader (read(char[] cbuf)) | BufferedReader (read(char[] cbuf) / readLine()) |
Writer | FileWriter (write(char[] cbuf,0,len) | BufferedWriter (write(char[] cbuf,0,len) / flush() |
对抽象基类的说明
抽象基类 | 字节流 | 字符流 |
---|---|---|
输入流 | InputSteam | Reader |
输出流 | OutputSteam | Writer |
- 说明Java的lO流共涉及40多个类,实际上非常规则,都是从如下4个抽象基类派生的。
- 这四个类派生出来的子类名称都是以其父类名作为子类名后缀。
InputSteam&Reader
InputStream和Reader是所有输入流的基类。
-
InputStream
(典型实现:FileInputStream
) -
Reader
(典型实现:FileReader
)
程序中打开的文件IO资源不属于内存里的资源,垃圾回收机制无法回收该资源,所以应该显式关闭文件IO资源。
FileInputStream
从文件系统中的某个文件中获得输入字节。FileInputStream用于读取非文本数据之类的原始字节流。要读取字符流,需要使用 FileReader
。
InputSteam:
-
int read()
从输入流中读取数据的下一个字节。返回0到255范围内的int字节值。如果因为已经到达流末尾而没有可用的字节,则返回值-1。
-
int read(byte[] b)
从此输入流中将最多b.length个字节的数据读入一个byte数组中。如果因为已经到达流末尾而没有可用的字节,则返回值-1.否则以整数形式返回实际读取的字节数。
-
int read(byte[] b,int off,int len)
将输入流中最多len个数据字节读入byte数组。尝试读取len个字节,但读取的字节也可能小于该值。以整数形式返回实际读取的字节数。如果因为流位于文件末尾而没有可用的字节,则返回值-1。
-
public void close throws IOException
关闭此输入流并释放与该流关联的所有系统资源。
Reader:
-
int read()
读取单个字符。作为整数读取的字符,范围在0到65535之间(0x00-0xffff)(2个字节的 Unicode码),如果已到达流的末尾,则返回-1。
-
int read(char[] cbuf)
将字符读入数组。如果已到达流的末尾,则返回-1。否则返回本次读取的字符数。
-
int read(char[] cbuf,int off,int len)
将字符读入数组的某一部分。存到数组cbuf中,从off处开始存储,最多读len个字符。如果已到达流的末尾,则返回-1。否则返回本次读取的字符数。
-
public void close throws IOException
关闭此输入流并释放与该流关联的所有系统资源
OutputSteam&Writer
Writer直接以字符作为操作单位,所以可以用字符串来替换字符数组,即以String对象作为参数。
FileOutputStream
从文件系统中的某个文件中获得输出字节。FileOutputstream
用于写出非文本数据之类的原始字节流。要写出字符流,需要使用 FileWriter
OutputStream:
-
void write(int b)
将指定的字节写入此输出流。 write的常规协定是:向输出流写入一个字节。要写入的字节是参数b的八个低位。b的24个高位将被忽略。即写入0~255范围的
-
void write(byte[] b)
将b.length个字节从指定的byte数组写入此输出流。write(b)的常规协定是:应该与调用wite(b,0,b.length)的效果完全相同。
-
void write(byte[] b,int off,int len)
将指定byte数组中从偏移量off开始的len个字节写入此输出流。
-
public void flush()throws IOException
刷新此输出流并强制写出所有缓冲的输出字节,调用此方法指示应将这些字节立即写入它们预期的目标。
-
public void close throws IOException
关闭此输岀流并释放与该流关联的所有系统资源。
Writer:
-
void write(int c)
写入单个字符。要写入的字符包含在给定整数值的16个低位中,16高位被忽略。即写入0到65535之间的 Unicode码。
-
void write(char[] cbuf)
写入字符数组
-
void write(char[] cbuf,int off,int len)
写入字符数组的某一部分。从off开始,写入len个字符
-
void write(String str)
写入字符串。
-
void write(String str,int off,int len)
写入字符串的某一部分。
-
void flush()
刷新该流的缓冲,则立即将它们写入预期目标。
-
public void close throws IOException
关闭此输出流并释放与该流关联的所有系统资源
输入、输出标准化过程
输入过程
① 创建File类的对象,指明读取的数据的来源。(要求此文件一定要存在)
② 创建相应的输入流,将File类的对象作为参数,传入流的构造器中
③ 具体的读入过程:创建相应的byte[] 或 char[]。
④ 关闭流资源
说明:程序中出现的异常需要使用try-catch-finally处理。
输出过程
① 创建File类的对象,指明写出的数据的位置。(不要求此文件一定要存在)
② 创建相应的输出流,将File类的对象作为参数,传入流的构造器中
③ 具体的写出过程:write(char[]/byte[] buffer,0,len)
④ 关闭流资源
说明:程序中出现的异常需要使用try-catch-finally处理。
节点流(文件流)
文件的输入 FileReader
从文件中读取到内存(程序)中
步骤:
- 建立一个流对象,将已存在的一个文件加载进流
FileReader fr = new FileReader(new File("Test. txt"))
; - 创建一个临时存放数据的数组
char[] ch = new char[1024]
; - 调用流对象的读取方法将流中的数据读入到数组中。
fr.read(ch)
; - 关闭资源。
fr.close()
;
public void testFileReader() {
FileReader fr = null;
try {
//1.File类的实例化
File file = new File("hello.txt");
//2.FileReader流的实例化
fr = new FileReader(file);
//3.读入的操作
//read(char[] cbuf):返回每次读入cbuf数组中的字符的个数。如果达到文件末尾,返回-1
char[] cbuf = new char[5];
int len;
while((len = fr.read(cbuf)) != -1){
String str = new String(cbuf,0,len);
System.out.print(str);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if(fr != null){
//4.资源的关闭
try {
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
复制代码
注意
- read()的理解:返回读入的一个字符。如果达到文件末尾,返回-1
- 异常的处理:为了保证流资源一定可以执行关闭操作。需要使用try-catch-finally处理
- 读入的文件一定要存在,否则就会报FileNotFoundException。
文件的输出 FileWriter
从内存(程序)到硬盘文件中
步骤:
- 创建流对象,建立数据存放文件
FileWriter fw = new FileWriter(new File("Test.txt"))
- 调用流对象的写入方法,将数据写入流
fw.write("HelloWord")
- 关闭流资源,并将流中的数据清空到文件中。
fw.close()
public void testFileWriter() {
FileWriter fw = null;
try {
//1.提供File类的对象,指明写出到的文件
File file = new File("hello1.txt");
//2.提供FileWriter的对象,用于数据的写出
fw = new FileWriter(file,false);
//3.写出的操作
fw.write("I have a dream!\n");
fw.write("you need to have a dream!");
} catch (IOException e) {
e.printStackTrace();
} finally {
//4.流资源的关闭
if(fw != null){
try {
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
复制代码
FileInputSteam
FileOutputSteam
与字符流操作几乎一样。
案例:实现图片文件复制操作
import java.io.*;
/**
* @author 乐心湖
* @date 2020/6/13 15:45
**/
public class Test {
public static void main(String[] args) {
FileInputStream fileInputStream = null;
FileOutputStream fileOutputStream = null;
try {
fileInputStream = new FileInputStream(new File("test2.jpg"));
fileOutputStream = new FileOutputStream(new File("F:\\VM","test2.png"));
//复制图片的过程
byte[] bytes = new byte[1024];
while (fileInputStream.read(bytes)!=-1){
fileOutputStream.write(bytes);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fileInputStream != null) {
try {
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fileOutputStream != null) {
try {
fileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
复制代码
注意
- 定义路径时,可以用“/”或“\”。
- 输出操作,对应的File可以不存在的。并不会报异常。
- File对应的硬盘中的文件如果不存在,在输出的过程中,会自动创建此文件。
- File对应的硬盘中的文件如果存在:
- 如果流使用的构造器是:FileWriter(file,false) / FileWriter(file) 对原有文件的覆盖。
- 如果流使用的构造器是:FileWriter(file,true) 不会对原有文件覆盖,而是在原有文件基础上追加内容。
- 读取文件时,必须保证文件存在,否则会报异常。
- 对于文本文件(.txt,.java,.c,.cpp),使用字符流处理
- 对于非文本文件(.jpg,.mp3,.mp4,.avi,.doc,.ppt,…),使用字节流处理
缓冲流(主要)
BufferedInputStream
BufferedOutputStream
BufferedReader
BufferedWriter
作用:提高流的读取和写入的速度
提高读写速度的原因:内部提供了一个缓冲区。默认情况下是8kb
当读取数据时,数据按块读入缓冲区,后面的读操作则直接访问缓冲区。
当使用BufferedInputStream
读取字节文件时,BufferedInputStream
会一次性从文件中读取8192个(8Kb),存在缓冲区中,直到缓冲区装满了,才重新从文件中读取下一个8192个字节数组。
向流中写入字节时,不会直接写到文件,先写到缓冲区中直到缓冲区写满,BufferedOutputStream
才会把缓冲区中的数据一次性写到文件里。
使用flush()
可以强制将缓冲区的内容全部写入输出流。
关闭流的顺序和打开流的顺序相反。只要关闭最外层流即可,关闭最外层流也会相应关闭内层节点流。
flush()
方法的使用:手动将buffer中内容写入文件。
如果是带缓冲区的流对象的close()方法,不但会关闭流,还会在关闭流之前刷新缓冲区,关闭后不能再写出。
使用BufferInputStream
和BufferOutputStream
实现非文本文件的复制
import java.io.*;
/**
* @author 乐心湖
* @date 2020/6/13 15:45
**/
public class Test {
public static void main(String[] args) {
BufferedInputStream bufferedInputStream = null;
BufferedOutputStream bufferedOutputStream = null;
try {
//创造节点流
FileInputStream fileInputStream = new FileInputStream(new File("test2.jpg"));
FileOutputStream fileOutputStream = new FileOutputStream(new File("F:\\VM", "test8.png"));
//创造缓冲流
bufferedInputStream = new BufferedInputStream(fileInputStream);
bufferedOutputStream = new BufferedOutputStream(fileOutputStream);
//开始复制
byte[] bytes = new byte[1024];
while ((len = bufferedInputStream.read(bytes)) != -1) {
bufferedOutputStream.write(bytes,0,len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (bufferedInputStream != null) {
try {
bufferedInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (bufferedOutputStream != null) {
try {
bufferedOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
复制代码
使用BufferedReader
和BufferedWriter
实现文本文件的复制
import java.io.*;
/**
* @author 乐心湖
* @date 2020/6/13 15:45
**/
public class Test {
public static void main(String[] args) {
BufferedReader bufferedReader = null;
BufferedWriter bufferedWriter = null;
try {
//创造节点流
FileReader fileReader = new FileReader(new File("test2.txt"));
FileWriter fileWriter = new FileWriter(new File("F:\\VM", "test2.txt"));
//创造缓冲流
bufferedReader = new BufferedReader(fileReader);
bufferedWriter = new BufferedWriter(fileWriter);
//开始复制
String data;
while ((data = bufferedReader.readLine()) != null) {
bufferedWriter.write(data);
bufferedWriter.newLine();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (bufferedReader != null) {
try {
bufferedReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (bufferedWriter != null) {
try {
bufferedWriter.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
复制代码
转换流
转换流提供了在字节流和字符流之间的转换
Java API提供了两个转换流:
InputstreamReader
:将Inputstream
转换为Reader
OutputStreamWriter
:将Writer
转换为OutputStream
字节流中的数据都是字符时,转成字符流操作更高效。
很多时候我们使用转换流来处理文件乱码问题。实现编码和解码的功能。
InputStreamReader
InputStreamReader
将一个字节的输入流转换为字符的输入流。
解码:字节、字节数组 —> 字符数组、字符串
构造器:
- public InputStreamReader(InputStream in)
- public InputStreamReader(Inputstream in,String charsetName)//可以指定编码集
OutputStreamWriter
OutputStreamWriter将一个字符的输出流转换为字节的输出流。
编码:字符数组、字符串 —> 字节、字节数组
构造器:
- public OutputStreamWriter(OutputStream out)
- public OutputStreamWriter(Outputstream out,String charsetName)//可以指定编码集
说明:文件编码的方式(比如:GBK),决定了解析时使用的字符集(也只能是GBK)。
/**
综合使用InputStreamReader和OutputStreamWriter
*/
@Test
public void test1() {
InputStreamReader isr = null;
OutputStreamWriter osw = null;
try {
//1.造文件、造流
File file1 = new File("dbcp.txt");
File file2 = new File("dbcp_gbk.txt");
FileInputStream fis = new FileInputStream(file1);
FileOutputStream fos = new FileOutputStream(file2);
isr = new InputStreamReader(fis, "utf-8");
osw = new OutputStreamWriter(fos, "gbk");
//2.读写过程
char[] chars = new char[20];
int len;
while ((len = isr.read(chars)) != -1){
osw.write(chars,0,len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//3.关流
if (isr != null){
try {
isr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (osw != null){
try {
osw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
复制代码
编码集
常见的编码表
- ASCII:美国标准信息交换码。用一个字节的7位可以表示。
- ISO8859-1:拉丁码表。欧洲码表用一个字节的8位表示。
- GB2312:中国的中文编码表。最多两个字节编码所有字符
- GBK:中国的中文编码表升级,融合了更多的中文文字符号。最多两个字节编码
- Unicode:国际标准码,融合了目前人类使用的所字符。为每个字符分配唯一的字符码。所有的文字都用两个字节来表示。
- UTF-8:变长的编码方式,可用1-4个字节来表示一个字符。
- 面向传输的众多UTF(UCS Transfer Format)标准出现了,顾名思义,UTF-8就是每次8个位传输数据,而UTF-16就是每次16个位。这是为传输而设计的编码,并使编码无国界,这样就可以显示全世界上所有文化的字符了。
- Unicode只是定义了一个庞大的、全球通用的字符集,并为每个字符规定了唯确定的编号,具体存储成什么样的字节流,取决于字符编码方案。推荐的Unicode编码是UTF-8和UTF-16。
编码应用
- 编码:字符串–>字节数组
- 解码:字节数组–>字符串
- 转换流的编码应用
- 可以将字符按指定编码格式存储
- 可以对文本数据按指定编码格式来解读
- 指定编码表的动作由构造器完成
使用要求:
客户端/浏览器端 <—-> 后台(java,GO,Python,Node.js,php) <—-> 数据库
要求前前后后使用的字符集都要统一:UTF-8 。
标准输入&输出流
System.in
:标准的输入流,默认从键盘输入
System.out
:标准的输出流,默认从控制台输出
主要方法
System类的setIn(InputStream is) 方式重新指定输入的流
System类的setOut(PrintStream ps)方式重新指定输出的流。
练习
从键盘输入字符串,要求将读取到的整行字符串转成大写输出。然后继续进行输入操作,
直至当输入“e”或者“exit”时,退出程序。
设计思路
方法一:使用Scanner实现,调用next()
返回一个字符串
方法二:使用System.in实现。System.in
—> 转换流 —> BufferedReader的readLine()
import java.io.*;
import java.util.Scanner;
/**
* @author 乐心湖
* @date 2020/6/13 15:45
**/
public class Test {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
while(true){
String data = sc.nextLine();
if ("e".equalsIgnoreCase(data) || "exit".equalsIgnoreCase(data)){
System.out.println("程序结束");
break;
}
System.out.println(data.toUpperCase());
}
}
}
复制代码
或者
public static void main(String[] args) {
BufferedReader br = null;
try {
InputStreamReader isr = new InputStreamReader(System.in);
br = new BufferedReader(isr);
while (true) {
System.out.println("请输入字符串:");
String data = br.readLine();
if ("e".equalsIgnoreCase(data) || "exit".equalsIgnoreCase(data)) {
System.out.println("程序结束");
break;
}
String upperCase = data.toUpperCase();
System.out.println(upperCase);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (br != null) {
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
复制代码
打印流
PrintStream
和 PrintWriter
说明:
- 提供了一系列重载的print()和println()方法,用于多种数据类型的输出
- System.out返回的是PrintStream的实例
对象流
ObjectInputStream
和 ObjectOutputStream
ObjectOutputStream
内存中的对象—>存储中的文件、通过网络传输出去:序列化过程ObjectInputStream
存储中的文件、通过网络接收过来 —>内存中的对象:反序列化过程
对象的序列化
对象序列化机制允许把内存中的Java对象转换成平台无关的二进制流,从而允许把这种二进制流持久地保存在磁盘上,或通过网络将这种二进制流传输到另一个网络节点。当其它程序获取了这种二进制流,就可以恢复成原来的Java对象。
序列化的好处在于可将任何实现了Serializable
接口的对象转化为字节数据,使其在保存和传输时可被还原。
序列化是RMI(Remote Method Invoke-远程方法调用)过程的参数和返回值都必须实现的机制,RMI是JavaEE的基础。因此序列化机制是JavaEE平台的基础。
如果需要让某个对象支持序列化机制,则必须让对象所属的类及其属性是可序列化的,为了让某个类是可序列化的,该类必须实现如下两个接口之一。否则,会抛出NotserializableEXception
异常
凡是实现Serializable
接口的类都有一个表示序列化版本标识符的静态变量:
-
private static final long serialVersionUID
-
serialVersionUID
用来表明类的不同版本间的兼容性。简言之,其目的是以序列化对象进行版本控制,有关各版本反序列化时是否兼容 -
如果类没有显示定义这个静态常量,它的值是Java运行时环境根据类的内部细节自动生成的。若类的实例变量做了修改,serialVersionUID可能发生变化。故建议显式声明。
-
简单来说,Java的序列化机制是通过在运行时判断类的
serialversionUID
来验证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的serialversionUID
与本地相应实体类的serialversionUID
进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常。(InvalidCastException
)
实现序列化的对象所属的类需要满足
- 需要实现接口:Serializable(标识接口)
- 当前类提供一个全局常量:serialVersionUID(序列版本号)
- 除了当前Person类需要实现Serializable接口之外,还必须保证其内部所属性也必须是可序列化的。(默认情况下,基本数据类型可序列化)
补充:ObjectOutputStream
和ObjectInputStream
不能序列化static
和transient
修饰的成员变量
序列化代码实现
序列化:将对象写入磁盘或进行网络传输
要求被序列化对象必须实现序列化
@Test
public void testObjectOutputStream(){
ObjectOutputStream oos = null;
try {
//1.创建对象,创建流
oos = new ObjectOutputStream(new FileOutputStream("object.dat"));
//2.操作流
oos.writeObject(new String("我爱北京天安门"));
oos.flush();//刷新操作
oos.writeObject(new Person("王铭",23));
oos.flush();
oos.writeObject(new Person("张学良",23,1001,new Account(5000)));
oos.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
if(oos != null){
//3.关闭流
try {
oos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
复制代码
反序列化代码实现
反序列化:将磁盘的对象数据源读出
@Test
public void testObjectInputStream(){
ObjectInputStream ois = null;
try {
ois = new ObjectInputStream(new FileInputStream("object.dat"));
Object obj = ois.readObject();
String str = (String) obj;
Person p = (Person) ois.readObject();
Person p1 = (Person) ois.readObject();
System.out.println(str);
System.out.println(p);
System.out.println(p1);
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
if(ois != null){
try {
ois.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
复制代码