知识补充:1.文本文件和二进制文件在存储时的区别
2.【字符编码系列】常用的几种字符编码(GBK,UTF-8,UTF-16)
1、概述
- 输入输出流的应用场景: 比如我们在自己电脑上复制粘贴文件;再比如更改qq或微信的头像(我们将本地数据上传到网络服务器)等,这些问题的解决都需要输入输出流。
- 流:就是指一连串流动的字符,以先进先出的方式发送信息的通道。
- 下图展示了输入输出流:
- Java的输入输出流分为字节流和字符流两类。
- 输出设备:像屏幕、打印机等;输入设备:键盘、扫描仪等。
- 文件既可以作为输入设备,也可以作为输出设备,当作为输入设备时,叫读取文件,而作为输出设备时,叫写入文件。
2、File类的使用
- File即文件,文件可认为是相关记录或放在一起的数据的集合。
- 在Java中,使用
java.io.File
类对文件进行操作。 - Windows中的目录分隔符为
"\"
,Linux中的目录分隔符为"/"
。
2.1、File类的常用方法
- 1.首先在自己电脑的某个分区(这里为d盘)上,新建一个名为
File
的文件夹,在File
文件夹里分别新建名为io
和set
两个文件夹,然后在io文件夹建一个名为score.txt
的空白的文本文件。
- 2.首先来说一下创建文件对象的几种方式(具体解释可以参考官方API)。
// 方式1
File f1 = new File("d:\\File\\io\\score.txt");
// 方式2
File f1 = new File("d:\\File", "io\\score.txt");
// 方式3
File f1 = new File("d:\\File");
File f2=new File(f1,"io\\score.txt");
- 2.在eclipse中新建一个
IOProj
的Java项目,新建一个com.cxs.file
的包,在其中新建一个FIleDemo
的类,勾选主方法(自行尝试,结果略)。
public class FileDemo {
public static void main(String[] args) {
// 创建File对象
File f1 = new File("d:\\File\\io\\score.txt");
// 判断是文件还是目录
System.out.println("是否是目录:" + f1.isDirectory());
System.out.println("是否是文件:" + f1.isFile());
// 创建目录
File f2 = new File("d:\\File\\set", "hashset");
if (!f2.exists()) {
f2.mkdir();// 若是创建多级目录,则需要调用mkdirs()方法
}
// 创建文件,运行前先将新建好的文件score.txt删除
if (!f1.exists()) {
try {
f1.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
2.2、绝对路径与相对路径
- 绝对路径:从盘符开始的路径
- 相对路径:从当前路径开始的路径
- 我们可以在cmd命令窗口做以下尝试(每个人的文件及文件夹位置有所不同,仅作示例)。
- 1.删除上面的代码,重新编写以下代码。
public class FileDemo {
public static void main(String[] args) {
File f = new File("chaixingsi.txt");
try {
f.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
}
- 2.运行代码,打开项目位置,发现指定文件已被创建。若不指定绝对路径,则默认会在当前项目位置创建文件。
- 3.继续补充代码,如下图所示,结果略。
3、字节流
3.1、字节流概述
- 字节流分为字节输入流(InputStream)和字节输出流(OutputStream)。
- 字节输入流类的结构如下
- 字节输出流类的结构如下
- 这里我们不会讲解所有的输入输出流,重点会讲解一下文件输入流和文件输出流、缓冲输入流和缓冲输出流,然后在讲解到对象序列化时会讲解对象输入流和对象输出流。
3.2、FileInputStream
- 文件输入流:从文件系统中的某个文件中获得输入字节。
- 用途:用于读取诸如图像数据(二进制数据)之类的原始字节流(具体见官方API文档,若是从文档中读取字符串进行显示或写入到其他地方,则一般会采用字符流去完成)。
- FileInputStream类常用的方法有:
方法名 | 描述 |
---|---|
public int read() | 从输入流中读取一个数据字节 |
public int read(byte[] b) | 从输入流中将最多b.length个字节的数据读入一个byte数组中 |
public int read(byte[] b,int off,int len) | 从输入流中将最多len个字节的数据读入一个byte数组中,off代表偏移量 |
public int close() | 关闭此文件输入流并释放与此流有关的所有系统资源 |
注释:当read()方法的返回值为-1时,表示已经读到文件的末尾。
- 1.新建一个
FileInputDemo
类,勾选主方法,代码如下。(创建好类之后在当前项目(即IOProj所在位置处)添加一个自定义文本文件,里面用英文写一句话,名字和内容随意)
public class FileInputDemo {
public static void main(String[] args) {
try {
FileInputStream fis = new FileInputStream("chaixingsi.txt");
int n = fis.read();
while (n != -1) {
System.out.print((char) n);
n = fis.read();
}
fis.close();// 关闭输入流并释放与此输入流有关的系统资源
} catch (FileNotFoundException e) {// 此处FileNotFoundException为IOException的子类
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
- 2.运行程序,结果如下。
- 3.我们重新简化一下上面的代码,这样是不是简洁多了呢\(^o^)/。
FileInputStream fis = new FileInputStream("chaixingsi.txt");
int n = 0;
while ((n = fis.read()) != -1) {
System.out.print((char) n);
}
fis.close();
- 4.新建一个FileInputDemo2的类,勾选主方法。
public class FileInputDemo2 {
public static void main(String[] args) {
try {
FileInputStream fis = new FileInputStream("chaixingsi.txt");
byte[] b = new byte[100];
fis.read(b, 1, 5);// 偏移1,从第二个字符位置开始存放数据,并且读取长度为5
System.out.println(new String(b));
fis.close();
} catch (FileNotFoundException e) {// 此处FileNotFoundException为IOException的子类
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
- 5.运行程序结果如下。
3.3、FileOutputStream
- 文件输出流是用来将数据写入到文件当中。
- 与文件输入流构造方法不同的是,文件输出流还有个带布尔值参数的构造方法,如下图,若在写数据时该文件已存在了,当布尔值为
true
时,会在文件的末尾追加数据;当布尔值为false
时,会将原有的数据覆盖掉。
- FileOutputStream类常用的方法有:
方法名 | 描述 |
---|---|
public int write(int b) | 将指定字节写入此文件输出流 |
public int write(byte[] b) | 将b.length个字节从指定byte数组写入此文件输出流中 |
public int write(byte[] b,int off,int len) | 将指定byte数组中从偏移量off开始的len个字节写入此文件输出流中 |
public int close() | 关闭此文件输出流并释放与此流有关的所有系统资源 |
- 1.新建一个
FileOutputDemo
类,我们将在上面文件的基础上在后面附加内容(hi)代码如下:
public class FileOutputDemo {
public static void main(String[] args) {
FileOutputStream fos;
try {
fos = new FileOutputStream("chaixingsi.txt", true);//布尔值为true时则在原有文件后追加内容
fos.write('h');
fos.write('i');
fos.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
- 2.打开文件查看内容。
下面我们来演示一张图片复制粘贴的操作,而复制和粘贴的过程便是读取和写入的过程。 |
- 1.新建一个
FileOutputDemo2
类,代码如下。
public class FileOutputDemo2 {
public static void main(String[] args) {
FileInputStream fis;
FileOutputStream fos;
try {
fis = new FileInputStream("chaixingsi.jpg");
fos = new FileOutputStream("chaixingsicopy.jpg");
byte[] b = new byte[1024];
int n = 0;
while ((n = fis.read(b)) != -1) {
fos.write(b, 0, n);
}
fis.close();
fos.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
- 2.运行代码结如下,成功拷贝了一张图片。
3.4、缓冲流
- 缓冲流分为:缓冲输入流(BufferedInputStream)和缓冲输出流(BufferedOutputStream)。
- 之前我们都是直接从硬盘上读取数据的,但这样的话读取速度是比较慢的,远没有直接从内存当中读取数据的速度快。而通过缓冲流便可以提高数据的读写速度。
- 以缓冲输入流为例,我们首先通过FileInputStream将数据读出来,然后加一个通道即BufferedInputStream,然后通过BufferedInputStream读到程序(比如说字节数组)当中;而写文件呢,首先写入到BufferedOutputStream当中,然后再通过FileOutputStream写入到指定文件中。
- 这里需要强调一下BufferedOutputStream的一个方法:flush()方法,即清空缓冲区。
- 首先我们说一下缓冲区的工作原理,缓冲区是有一定大小的,当存放到缓冲区中的数据大小刚好是缓冲区大小的时候,此时缓冲区会自动执行写操作,比如将输入写入到FileOutputStream当中(然后通过文件输出流写入到文件);
- 但是若存储到缓冲区的数据没有满时,这个时候并不会触发write方法去自动写入到文件输出流当中(即不会将数据写入到文件中),而此时调用flush()方法,则会清空缓冲区(强制把数据输出)。
- 更多的解释请见这篇博客:关于java中输出流flush()方法
- 新建一个类
BufferedDemo
,操作的数据还是之前的chaxingsi.txt
文件(运行及结果略)。
public class BufferedDemo {
public static void main(String[] args) {
try {
FileOutputStream fos = new FileOutputStream("chaixingsi.txt");
BufferedOutputStream bos = new BufferedOutputStream(fos);
FileInputStream fis = new FileInputStream("chaixingsi.txt");
BufferedInputStream bis = new BufferedInputStream(fis);
bos.write(50);
bos.write('a');
bos.flush();
System.out.println(bis.read());
System.out.println((char) bis.read());
bos.close();
bis.close();
fos.close();
fis.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
4、字符流
4.1、字符流概述
- 字符流也分为字符输入流和字符输出流。
- 字符输入流的类结构图如下
字符输出流的类结构图如下
- 学了这么多,我们先来总结一下:
- 处理字节流的抽象类:InputStream(字节输入流)、OutputStream(字节输出流)
- InputStream:是字节输入流的所有类的超类,一般我们使用它的子类,如FileInputStream等。
- OutputStream:是字节输出流的所有类的超类,一般我们使用它的子类,如FileOutputStream等。
- 处理字符流的抽象类:InputStreamReader、OutputStreamWriter
- InputStreamReader:是字节流通向字符流的桥梁,它将字节流转换为字符流。
- OutputStreamWriter:是字符流通向字节流的桥梁,它将字符流转换为字节流。
- BufferedReader与BufferedWriter
- BufferedReader由Reader类扩展而来,提供通用的缓冲方式文本读取,readLine读取一个文本行,从字符输入流中读取文本,缓冲各个字符,从而提供字符、数组和行的高效读取。
- BufferedWriter由Writer 类扩展而来,提供通用的缓冲方式文本写入,newLine使用平台自己的行分隔符,将文本写入字符输出流,缓冲各个字符,从而提供单个字符、数组和字符串的高效写入。
- 处理字节流的抽象类:InputStream(字节输入流)、OutputStream(字节输出流)
- 学了这么多,我们先来总结一下:
字节输入输出流要求读写的都是二进制格式的数据;
- 而字符输入输出流适用于这样一个场景:比如我们再qq上给朋友发消息,所发的消息为一串字符,我们将消息发送到网络传输时,实际上会将文字内容转换为二进制的字节形式进行传输,到达目的后,另一个人会读取网络传输过来的数据再转换为可读的一串字符。
4.2、字节字符转换流
- 字节字符转换流即上面总结的InputStreamReader和OutputStreamWriter。
- 下面会使用文件输入输出流和字节字符转换流来完成文件的拷贝(准备一个任意内容(随便写)的文本文件)。
- 之所以采用文件输入输出流来进行读写,是为了模拟网络环境下采用的是字节流传输,然后转换为字符的场景。
- 1.新建一个类
ReaderWriterDemo
,勾选主方法,直接在主方法中编写代码。
public class ReaderWriterDemo {
public static void main(String[] args) {
try {
FileInputStream fis = new FileInputStream("chaixingsi.txt");
InputStreamReader isr = new InputStreamReader(fis, "utf-8");// notepad++默认编码格式为utf-8,避免出现乱码
FileOutputStream fos = new FileOutputStream("chaixingsi1.txt");
OutputStreamWriter osw = new OutputStreamWriter(fos, "utf-8");//同上
int n = 0;
char[] cbuf = new char[10];
while ((n = isr.read(cbuf)) != -1) {
osw.write(cbuf, 0, n);
osw.flush();// flush()方法可以写到循环外面
}
fis.close();
fos.close();
isr.close();
osw.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
- 2.运行代码,发现项目中多了一个文本文件
chaixingsi1.txt
。
4.3、其他字符流
- 我们在上面代码的基础上进行修改,加入字符的缓冲输入流(BufferedReader)和缓冲输出流(BufferedWriter),以提高读写速度。
- 修改上面的代码如下:
public class ReaderWriterDemo {
public static void main(String[] args) {
try {
FileInputStream fis = new FileInputStream("chaixingsi.txt");
InputStreamReader isr = new InputStreamReader(fis, "utf-8");
BufferedReader br = new BufferedReader(isr);
FileOutputStream fos = new FileOutputStream("chaixingsi1.txt");
OutputStreamWriter osw = new OutputStreamWriter(fos, "utf-8");
BufferedWriter bw = new BufferedWriter(osw);
int n = 0;
char[] cbuf = new char[10];
while ((n = br.read(cbuf)) != -1) {
bw.write(cbuf, 0, n);
bw.flush();// flush()方法可以写到循环外面
}
fis.close();
fos.close();
isr.close();
osw.close();
br.close();
bw.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
- 关于FileReader和FIleWriter的用法详见:FileWriter和FileReader解析
5、对象序列化与反序列化
- 对象的读写会涉及到两个类:对象输入流(ObjectInputStream)和对象输出流(ObjectOutputStream)。
- 序列化:把Java对象转换为字节序列的过程。
- 反序列化:把字节序列恢复为Java对象的过程。
- 下面演示对象序列化包含这几个步骤:
- 创建一个类,继承Serializable接口;
- 创建对象;
- 将对象写入文件(由于不涉及网络,此处将写入文件);
- 从文件读取对象信息。
- 1.新建一个包名为
com.cxs.serial
,新建一个Goods
类并实现Serializable
接口。
public class Goods implements Serializable {
private String goodsId;
private String goodsName;
private double price;
public Goods(String goodsId, String goodsName, double price) {
this.goodsId = goodsId;
this.goodsName = goodsName;
this.price = price;
}
public String getGoodsId() {
return goodsId;
}
public void setGoodsId(String goodsId) {
this.goodsId = goodsId;
}
public String getGoodsName() {
return goodsName;
}
public void setGoodsName(String goodsName) {
this.goodsName = goodsName;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
@Override
public String toString() {
return "Goods [goodsId=" + goodsId + ", goodsName=" + goodsName + ", price=" + price + "]";
}
}
- 2.新建一个测试类GoodsTest。
public class GoodsTest {
public static void main(String[] args) {
Goods g1 = new Goods("g001", "电脑", 4999);
try {
FileOutputStream fos = new FileOutputStream("chaixingsi.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos);
FileInputStream fis = new FileInputStream("chaixingsi.txt");
ObjectInputStream ois = new ObjectInputStream(fis);
// 将Goods对象信息写入文件
oos.writeObject(g1);
oos.writeBoolean(true);//当然,这里还可以写入其他类型
oos.flush();
// 从文件读对象信息
try {
Goods g2 = (Goods) ois.readObject();
System.out.println(g2);
System.out.println(ois.readBoolean());
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
fos.close();
oos.close();
fis.close();
ois.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
- 3.运行代码结果如下。