1、IO流
用于处理设备之间的数据传输。不仅仅是内存到硬盘,还包括不同设备之间通过网络进行传输。涉及到输入输出就需要使用IO流
I input 输入
O output 输出
输入输出是一个相对概念。
以内存为中心,内存到硬盘就是输出、硬盘到内存就是输入。
以硬盘为中心,内存到硬盘就是输入,硬盘到内存就是输出。
JAVA写的是程序,程序运行在内存当中,因此,JAVA 是以内存为中心谈输入与输出。
外界内容到内存中,叫做输入。
内存到外界,叫做输出。
流的分类:
按照操作数据的单位不同,分为字节流,一个字节8位。字符流,16位。
按照流向分为:输入流、输出流
按照流的角色:节点流:字节作用在文件上;处理流:在流的基础上,再包装一下。包装流的流
JAVA中对IO的操作主要就是上面这4个抽象基类派生而出的。
字节流可以处理一切数据,为什么还需要字符流呢?因为文本类数据处理非常常见,使用字符流一次处理2个字节,更快。
访问文件有4个典型的节点流。
下面的都是处理流。
以Stream结尾的都是字节流,其他以Reader 或者Writer 结尾的都是字符流。
上图中蓝色背景的流比较常用,需要重点掌握。
1.1 节点流
1.1.1 FileReader 读文件字符流
读文件字符流实际是 内存加载文件,即文件输入到内存中。属于输入流的一种。
使用这个流一般用来操作文本等字符内容。要求文件一定要存在。不然会有文件找不到的异常。
有一个 hello.txt 文件。内容是 IO流测试文件
// 1、实例化File 类的对象
File file = new File("hello.txt");
// 2、选择合适的流,传入File对象。File对象的作用是告诉流要操作哪一个文件。
FileReader fr = new FileReader(file);
// 3、数据的读入
// read 方法返回读入的一个字符,如果达到文件末尾,就返回-1
int data = fr.read();
while (data != -1){
System.out.print((char)data);
data = fr.read();
}
// 4、流的关闭操作
fr.close();
// 输出 IO流测试文件
代码优化: 看起来更简洁一些
// 1、实例化File 类的对象
File file = new File("hello.txt");
// 2、选择合适的流,传入File对象。File对象的作用是告诉流要操作哪一个文件。
FileReader fr = new FileReader(file);
// 3、数据的读入
// read 方法返回读入的一个字符,如果达到文件末尾,就返回-1
int data;
while ((data = fr.read()) != -1) {
System.out.print((char) data);
}
// 4、流的关闭操作
fr.close();
// 输出 IO流测试文件
代码优化,使用try catch final 处理异常。为了保证不管是否有异常,流都要关闭
FileReader fr = null;
try {
// 1、实例化File 类的对象
File file = new File("hello.txt");
// 2、选择合适的流,传入File对象。File对象的作用是告诉流要操作哪一个文件。
fr = new FileReader(file);
// 3、数据的读入
// read 方法返回读入的一个字符,如果达到文件末尾,就返回-1
int data;
while ((data = fr.read()) != -1) {
System.out.print((char) data);
}
}catch (Exception e){
e.printStackTrace();
}finally {
// 4、流的关闭操作
try {
if ( fr != null){
fr.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
// 输出 IO流测试文件
上述代码中,使用了 read 方法进行读取。下面对读取操作进行优化。因为read是一个字符一个字符进行读取,速度慢。
使用read的重载方法,一次性读一个数组。加快速度。这个方法返回 真实读取的个数。 读取的数据保存到了数组中!
注意,如果使用 for 来遍历读取的数据,下面这种写法是错误的!
FileReader fr = null;
try {
// 1、实例化File 类的对象
File file = new File("hello.txt");
// 2、选择合适的流,传入File对象。File对象的作用是告诉流要操作哪一个文件。
fr = new FileReader(file);
// 3、数据的读入
// read 方法返回读入的一个字符,如果达到文件末尾,就返回-1
int len;
char[] buf = new char[4];
while ((len = fr.read(buf)) != -1) {
// i 的条件这里有错误,不是 buf.length
for (int i = 0; i < buf.length; i++) {
System.out.print(buf[i]);
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 4、流的关闭操作
try {
if (fr != null) {
fr.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
// 输出
//IO流测试文件
//文件
用 增强for 也是不对的。
FileReader fr = null;
try {
// 1、实例化File 类的对象
File file = new File("hello.txt");
// 2、选择合适的流,传入File对象。File对象的作用是告诉流要操作哪一个文件。
fr = new FileReader(file);
// 3、数据的读入
// read 方法返回读入的一个字符,如果达到文件末尾,就返回-1
int data;
// 使用一个数组来接收读取的数据,一次性读数组长度这么多
char[] buf = new char[2];
while ((data = fr.read(buf)) != -1) {
for (char c : buf) {
System.out.print(c);
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 4、流的关闭操作
try {
if (fr != null) {
fr.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
//输出
//IO流测试文件
//文件
错误的原因是因为 装内容的 数组是重复利用的,当第一次读取4个字符时,读取了 "IO流测试", 刚好放满了 数组。还没有读完,继续读取
因为只有 "文件",因此 "文件" 会覆盖 之前数组的前面的2个位置,"IO流",就变成了 "文件文件"
上面是全量输出 整个buf。实际上需要按需输出。
注:英文是占用1个字节,所以 IO 占用2个字节,而1个汉字是占用2个字节的【utf-8是3个字节,不指定编码默认是2个字节】。一个char 类型是2个字节。所以一个char类型的数组2个位置是4个字节。
正确的代码:
FileReader fr = null;
try {
// 1、实例化File 类的对象
File file = new File("hello.txt");
// 2、选择合适的流,传入File对象。File对象的作用是告诉流要操作哪一个文件。
fr = new FileReader(file);
// 3、数据的读入
// read 方法返回读入的一个字符,如果达到文件末尾,就返回-1
int len;
char[] buf = new char[4];
while ((len = fr.read(buf)) != -1) {
// i 的条件应该是 len 而不是 buf的长度
for (int i = 0; i < len; i++) {
System.out.print(buf[i]);
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 4、流的关闭操作
try {
if (fr != null) {
fr.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
//输出
//IO流测试文件
还可以这样写:使用String 的 String(char value[], int offset, int count) 构造方法
FileReader fr = null;
try {
// 1、实例化File 类的对象
File file = new File("hello.txt");
// 2、选择合适的流,传入File对象。File对象的作用是告诉流要操作哪一个文件。
fr = new FileReader(file);
// 3、数据的读入
// read 方法返回读入的一个字符,如果达到文件末尾,就返回-1
int len;
char[] buf = new char[4];
while ((len = fr.read(buf)) != -1) {
// String(char value[], int offset, int count)
String s = new String(buf, 0, len);
System.out.println(s);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 4、流的关闭操作
try {
if (fr != null) {
fr.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
1.1.2 FileWriter 写文件字符流
写文件字符流 是将内存中数据输出搭配硬盘中,属于输出流的一种。
文件字符输出流:写入文件到硬盘中
FileWriter fw = null;
try {
// 1、实例化File 类的对象. 这个文件可以是不存在的!
File file = new File("hello1.txt");
// 2、选择合适的流,传入File对象。File对象的作用是告诉流要操作哪一个文件。
fw = new FileWriter(file);
// 3、数据的写出
// write 方法 写出数据
fw.write("FileWriter 文件字符流");
fw.write("写出测试");
} catch (Exception e) {
e.printStackTrace();
} finally {
// 4、流的关闭操作
try {
if (fw != null) {
fw.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
// hello1.txt 文件是不存在的,不存在的文件写出会自动生成这个文件,并写入内容到文件中
// 文件中的内容:FileWriter 文件字符流写出测试
// 可以看到,虽然分2次写入,但是并没有换行。换行 需要希尔换行符号 "\n"
如果是对已存在的文件进行写入呢?注意:对文件是写入,对java叫做从内存输出到文件!用的是输出流。
如果存在的情况非常简单,上述代码再执行一遍就知道了。
结果文件内容还是:FileWriter 文件字符流写出测试
即,如果文件存在,会先清空文件内容,再重新写入数据。
事实上,有时候我们需要追加内容。那些在new FileWriter 时,调用重载的构造器。
FileWriter fw = null;
try {
// 1、实例化File 类的对象
File file = new File("hello1.txt");
// 2、选择合适的流,传入File对象。File对象的作用是告诉流要操作哪一个文件。
// 第二个参数 为 是否要对文件进行追加,true代表追加在后面。false则表示清空重新写入
fw = new FileWriter(file, true);
// 3、数据的写出
// write 方法 写出数据
// "\n" 表示换行
fw.write("\n FileWriter 文件字符流");
fw.write("写出测试");
} catch (Exception e) {
e.printStackTrace();
} finally {
// 4、流的关闭操作
try {
if (fw != null) {
fw.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
// 文件内容是:
//FileWriter 文件字符流写出测试
//FileWriter 文件字符流写出测试
使用文件写和文件读就可以实现文件的复制了。
FileWriter fw = null;
FileReader fr = null;
try {
// 1、实例化File 类的对象
File src = new File("hello1.txt"); // 源文件
File tar = new File("hello.txt"); // 目标文件
// 2、选择合适的流,传入File对象。File对象的作用是告诉流要操作哪一个文件。
fw = new FileWriter(tar);
fr = new FileReader(src);
// 3、数据的读与写
char[] buf = new char[4];
int len;
while ((len = fr.read(buf))!=-1){
// 将buf写入,从index=0 开始写,写len个
// 这个就不是全部将buf写入,而是可以控制写那一部分。
fw.write(buf,0, len);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 4、流的关闭操作
try {
if (fw != null) {
fw.close();
}
if (fr != null){
fr.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
注意,文件字符流不能处理图片视频类数据。需要使用字节流处理
1.1.3 FileInputStream 、FileOutputStream
FileInputStream :输入文件字节流 ,将文件输入到内存中。
FileOutputStream: 输出文件字节流,将文件从内存输出到硬盘
FileInputStream fi = null;
try {
// 1、实例化File 类的对象
File src = new File("hello1.txt");
// 2、选择合适的流,传入File对象。File对象的作用是告诉流要操作哪一个文件。
fi = new FileInputStream(src);
// 3、数据的读取。使用 byte型数组
byte[] buf = new byte[4];
int len;
while ((len = fi.read(buf))!=-1){
String s = new String(buf, 0, len);
System.out.print(s);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 4、流的关闭操作
try {
if (fi != null) {
fi.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
// 输出
//FileWriter �����字符�����出测���
//FileWriter �����字符�����出测���
可以看到,使用输入文件字节流读取文本文本,出现了乱码。
什么是文本文件:常用的有 .txt、.java、.c、.cpp、.md、.csv、
非文本文件:常用的有:.jpg、.mp3、.mp4、.avi、.doc | .docx、.ppt、.pdf 等
但是对于非文本文件,就可以使用字节流。
实现对图片的复制。
以下面的图片为例
FileInputStream fi = null;
FileOutputStream fo = null;
try {
// 1、实例化File 类的对象
File src = new File("java.jpg");
File tar = new File("newJava.jpg");
// 2、选择合适的流,传入File对象。File对象的作用是告诉流要操作哪一个文件。
fi = new FileInputStream(src);
fo = new FileOutputStream(tar);
// 3、数据的读与写
byte[] buf = new byte[4];
int len;
while ((len = fi.read(buf))!=-1){
fo.write(buf, 0, len);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 4、流的关闭操作
try {
if (fi != null) {
fi.close();
}
if (fo != null) {
fo.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
结果会生成新的图片文件 newJava.jpg 与源文件一样。
可以看到,基本和文件的复制没有什么区别。
文件复制使用的是char数组,而字节流则是使用 byte数组。
事实上,使用字节流也是可以直接复制文本文件的。因为以什么方式读取,就以什么方式写入,直接以字节形式复制,不涉及到编码
但是字符流是不可以复制图片等数据的,因为会以编码的形式读取,再以编码的形式写入。改变了字节文件的编码。
还有一个文件,不管是字符数组,还是字节数组,数组的大小如何确定呢?
没有一个固定的数,一般以 1024 作为数组的大小。因为这个数组大了,速度快,但是消耗内存。小了省内存,但是速度慢。
前面,使用的都是 4,是因为文件都太小了。这也说明,这个数不是固定的。但是有常用的 1024 或者 1024的倍数。
1.2 缓冲流
以上四个节点流是基本的流,用的较少。一般使用速度更快的 缓存流。
缓存流也有四个,分别是对节点流的包装。
BufferedInputStream、BufferedOutputStream、BufferedReader、BufferedWriter
包装流不能自接作用于文件对象上。需要接收对应的节点流对象。
提高速度的原因是:内部提供了一个缓冲区 默认大小是 8192 个字节大小。当达到这个大小时,才会进行flush操作。
flush是将缓冲区的数据写入到文件并清空缓冲区。
1.2.1 BufferedInputStream、BufferedOutputStream
输入缓冲字节流、输出缓冲字节流。
// 指定文件
File src = new File("java.jpg");
File tar = new File("buff_java.jpg");
// 节点流
FileInputStream fi = null;
FileOutputStream fo = null;
// 缓冲流
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
try {
// 节点流,直接接收文件对象
fi = new FileInputStream(src);
fo = new FileOutputStream(tar);
// 包装流,接收对应的节点流对象
bis = new BufferedInputStream(fi);
bos = new BufferedOutputStream(fo);
// 文件的读写
byte[] buf = new byte[1024];
int len;
while ((len = bis.read(buf))!=-1){
bos.write(buf, 0, len);
}
}catch (Exception e){
e.printStackTrace();
}finally {
try {
// 先关闭外层流,再关闭内存流。同级别流无顺序。
// 但是关闭外层流时,内层流会自动关闭,因此内层流的关闭可以省略。
if (bos != null){
bos.close();
}
if (bis != null){
bis.close();
}
}catch (Exception e){
e.printStackTrace();
}
}
上面是 使用 缓冲字节流对图片的复制代码。使用时基本与节点流一致。
只是需要注意 流的关闭顺序。
代码优化: 节点流是内层流,无序手动关闭。可使用匿名对象方式进行优化
// 指定文件
File src = new File("java.jpg");
File tar = new File("buff_java.jpg");
// 缓冲流
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
try {
bis = new BufferedInputStream(new FileInputStream(src));
bos = new BufferedOutputStream(new FileOutputStream(tar));
// 文件的读写
byte[] buf = new byte[1024];
int len;
while ((len = bis.read(buf))!=-1){
bos.write(buf, 0, len);
}
}catch (Exception e){
e.printStackTrace();
}finally {
try {
// 先关闭外层流,再关闭内存流。同级别流无顺序。
// 但是关闭外层流时,内层流会自动关闭,因此内层流的关闭可以省略。
if (bos != null){
bos.close();
}
if (bis != null){
bis.close();
}
}catch (Exception e){
e.printStackTrace();
}
}
1.2.2 BufferedReader、BufferedWriter
输入缓冲字符流、输出缓冲字符流。
这2个流的使用与字节流类似,只不过是适用于文本数据。
// 指定文件
File src = new File("hello.txt");
File tar = new File("buff_hello.txt");
// 缓冲流
BufferedReader br = null;
BufferedWriter bw = null;
try {
br = new BufferedReader(new FileReader(src));
bw = new BufferedWriter(new FileWriter(tar));
// 文件的读写
char[] buf = new char[1024];
int len;
while ((len = br.read(buf))!=-1){
bw.write(buf, 0, len);
}
}catch (Exception e){
e.printStackTrace();
}finally {
try {
// 先关闭外层流,再关闭内存流。同级别流无顺序。
// 但是关闭外层流时,内层流会自动关闭,因此内层流的关闭可以省略。
if (bw != null){
bw.close();
}
if (br != null){
br.close();
}
}catch (Exception e){
e.printStackTrace();
}
}
BufferedReader 多一个方法 readLine() 可以对文本进行一行一行的读
// 指定文件
File src = new File("hello.txt");
File tar = new File("buff_hello.txt");
// 缓冲流
BufferedReader br = null;
BufferedWriter bw = null;
try {
br = new BufferedReader(new FileReader(src));
bw = new BufferedWriter(new FileWriter(tar));
// 文件的读写
String data;
// 注意:readLine 方法当读取到文件默认会返回null,并且 data 是 不包含换行符的
while ((data = br.readLine())!=null){
bw.write(data);
}
}catch (Exception e){
e.printStackTrace();
}finally {
try {
// 先关闭外层流,再关闭内存流。
// 但是关闭外层流时,内层流会自动关闭,因此内层流的关闭可以省略。
if (bw != null){
bw.close();
}
if (br != null){
br.close();
}
}catch (Exception e){
e.printStackTrace();
}
}
使用这种方式,读取的行数据被去掉了换行符号。可以自己处理一下。
// 指定文件
File src = new File("hello.txt");
File tar = new File("buff_hello.txt");
// 缓冲流
BufferedReader br = null;
BufferedWriter bw = null;
try {
br = new BufferedReader(new FileReader(src));
bw = new BufferedWriter(new FileWriter(tar));
// 文件的读写
String data;
// 注意:readLine 方法当读取到文件默认会返回null,并且 data 是 不包含换行符的
while ((data = br.readLine())!=null){
bw.write(data);
// 可以自己手动写一个换行符; 使用 bw.write(data+"\n"); 也是可以的
bw.newLine();
}
}catch (Exception e){
e.printStackTrace();
}finally {
try {
// 先关闭外层流,再关闭内存流。
// 但是关闭外层流时,内层流会自动关闭,因此内层流的关闭可以省略。
if (bw != null){
bw.close();
}
if (br != null){
br.close();
}
}catch (Exception e){
e.printStackTrace();
}
}
1.3 转换流
提供字节流与字符流之间的转换。
转换流本身都是属于字符流:
字节到字符:InputStreamReader 输入字节流转输入字符流。 在加载到内存时,将字节转字符方便显示。
字符到字节:OutputStreamWriter 输出字符流转输出字节流。在输出到硬盘时,将字符以字节形式写入。
1.3.1 InputStreamReader 字节转字符
// 指定文件
File src = new File("hello1.txt");
// 缓冲流
InputStreamReader isr = null;
try {
// 接收的是 字节流 以及 字符集,自己本身是字符流。实现字节输入流到字符输入流的转换
isr = new InputStreamReader(new FileInputStream(src), "UTF-8");
// 文件的读写
char[] buf = new char[1024];
int len;
// 注意:readLine 方法当读取到文件默认会返回null,并且 data 是 不包含换行符的
while ((len = isr.read(buf))!= -1){
String s = new String(buf, 0, len);
System.out.print(s);
}
}catch (Exception e){
e.printStackTrace();
}finally {
try {
if (isr != null){
isr.close();
}
}catch (Exception e){
e.printStackTrace();
}
}
// 这里是输出到控制台 不会乱码
1.3.2 OutputStreamWriter 字符转字节
例子,将utf8编码的文本文件以gbk输出
// 指定文件
File src = new File("hello1.txt");
File tar = new File("gbk_hello.txt");
// 缓冲流
InputStreamReader isr = null;
OutputStreamWriter osw = null;
try {
// 接收的是 字节流 以及 字符集,自己本身是字符流。
isr = new InputStreamReader(new FileInputStream(src), "UTF-8");
// 接收的是 字节流 以及 字符集,自己本身是字符流。
osw = new OutputStreamWriter(new FileOutputStream(tar), "GBK");
// 文件的读写
char[] buf = new char[1024];
int len;
while ((len = isr.read(buf))!= -1){
osw.write(buf,0, len);
}
}catch (Exception e){
e.printStackTrace();
}finally {
try {
if (isr != null){
isr.close();
}
if (osw != null){
osw.close();
}
}catch (Exception e){
e.printStackTrace();
}
}
1.4 字符集
ASCII :美国标注信息交换码 用1个字节的7位表示
ISO8859-1: 拉丁码表,是欧洲码表 用1个字节的8位表示
GB2312: 中文码表,最多2个字节 编码。 看最高位是0 表示是1个字节表示的,是1表示需要2个字节表示
BGK:中国的中文码表升级,融合了更多的中文字符,最多2个字节。也是看最高位区分
Unicode:国际标准码,融合了目前人类使用的所有字符。为每一个字符都分配唯一的字符码,所有的字符都用2个字节表示。
Unicode不够完美,因为计算机不知道如何区分Unicode和ASCII ,如果采用和gbk或者GB2312使用最高位来区分,就无法表示很多字符。直到互联网出现,面向传输的UTF标注出现了。
每次8个位传输数据就是utf-8,每次16位就是utf-16
因此,Unicode知识定义了一个庞大的、全球通用的字符集,具体如何存储,取决于编码方案。就是一个标准。
而 utf-8或者utf-16 是这个标准的不同的实现。
utf-8:变长的编码方式,用1-4个字节表示一个字符。
什么是ANSI编码呢? ANSI是美国国家标准学会
指的是平台的默认编码,在英文操作系统中,就是ISO-8859-1,中文系统就是GBK。
而Unicode是一种编码规范,utf系列是该规范的实现。
1.5 标准的输入、输出流、打印流
标准输出流 很早就见过了就是
System.out.println();
// System 类对 in的 定义,可以看到是一个 输入字节流
public final static InputStream in = null;
System.out 就是标准的输出流,默认从控制台输出
// System 类对 out的 定义,可以看到是一个 PrintStream 打印流,也是一个字节流
public final static PrintStream out = null;
System.in 标准的输入流,默认从键盘输入
可以通过System类的 setIn、setOut 方法重新指定输入或者输出的位置。
// 使用System.in 做类似于Scanner类的事。从键盘接收数据并打印。
//idea的 try catch 快捷键 ctrl+ alt + t
BufferedReader br = null;
try {
// 使用了 字节转字符流,System.in 是字节流。最后传给缓冲流
br = new BufferedReader(new InputStreamReader(System.in));
while (true){
System.out.println("请输入字符串,输入'e'或者'exit' 退出");
String data = br.readLine();
if ("e".equalsIgnoreCase(data) || "exit".equalsIgnoreCase(data)){
System.out.println("退出!");
break;
}
String s = data.toUpperCase(Locale.ROOT);
System.out.println(s);
}
br.close();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (br != null){
br.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
打印流 有2个 PrintStream、PrintWriter 一个是字节打印流,一个是字符打印流
PrintStream ps = null;
try {
FileOutputStream fos = new FileOutputStream("ascii.txt");
// 字节打印流
ps = new PrintStream(fos, true);
// 设置标注输出为 字节打印流而不再是 控制台
System.setOut(ps);
for (int i = 0; i < 255; i++) {
// 输出 字符,由于改成向 ps对象输出了,即向文件写入字符
System.out.print((char) i);
// 每10个
if (i % 10 == 0){
// 向文件写入 换行
System.out.println();
}
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
if (ps != null) {
ps.close();
}
}
以上的代码是将 askii码对应的字符输出到文件"ascii.txt"中。
可以看到使用 FileOutputStream 并没有传入一个文件对象,而是直接传入了路径。是的,这个也是FileOutputStream的一个构造方法。
1.6 数据流
有2个, DataInputStream、DataOutputStream
为了方便的操作java的基本数据类型个String类型的数据。可以使用数据流
DataInputStream 包装在 InputStream 的子类流上
DataOutputStream 包装在 OutputStream 的子类流上
DataInputStream有如下的一些常用方法:
readBoolean、readChar、readDouble、readLong、readUTF、readByte、reeadFloat、readShort、readInt、readFully(byte[] b)
DataOutputStream有如下的一些常用方法:
writeBoolean、writeChar、writeDouble、writeLong、writeUTF、writeByte、reeadFloat、writeShort、writeInt、writeFully(byte[] b)
写文件
DataOutputStream dos = null;
try {
dos = new DataOutputStream(new FileOutputStream("hello.txt"));
dos.writeUTF("xxx");
dos.writeInt(23);
dos.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (dos != null) {
dos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
读文件:主要先写的要先读
DataInputStream dis = null;
try {
dis = new DataInputStream(new FileInputStream("hello.txt"));
// 一定要先写的先读
String s = dis.readUTF();
int i = dis.readInt();
System.out.println(i);
System.out.println(s);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (dis != null) {
dis.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
1.7 对象流 【序列化】
有2个:ObjectOutputStream 、ObjectInputStream
对象流也可以用来存取基本数据类型数据,但是它还可以用来存取对象类型的数据即 class 类型
可以将对象写入到数据源当中,也能把对象从数据源还原出来。
-
序列化: 用ObjectOutputStream 类保存基本类型或者对象的机制
-
反序列化:用ObjectInputStream 类读取基本类型数据或对象的机制
ObjectOutputStream 和ObjectInputStream 不能序列化 static 和 transient 修饰的成员变量。
原因是 static 修饰的不属于对象,而属于 类或者接口。
而 transient 作用就是我们需要某些变量不要序列化,就用 transient 修饰。transient 是专门解决这个需求而出现的关键字,所以被transient 修饰的不能序列化。
序列化机制是允许将内存中的 java对象 转换为与平台无关的二进制流,从而允许将这种二进制流持久的保存到磁盘上,当然你也可以将这种二进制流通过网络传输给另一个接收者。当其他人接收之后,可以恢复为原来的java对象。
如果要使某个对象支持这种序列化机制,是有条件的。即 这个类必须要实现 2个接口之一。
Serializable 或者 Externalizable
以String 为例,String类实现了 Serializable 接口。
ObjectOutputStream oos = null;
try {
oos = new ObjectOutputStream(new FileOutputStream("object.data"));
// 写对象
oos.writeObject(new String("我爱你"));
// 刷新
oos.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if ( oos != null){
oos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
执行完上述代码,会生成一个 object.data 文件。 这就是序列化,将String 对象写入到了 硬盘中的 object.data 文件内。
注意。这个文件不能直接打开,只能通过反序列化进行读取。
ObjectInputStream ois = null;
try {
ois = new ObjectInputStream(new FileInputStream("object.data"));
// 读对象
Object o = ois.readObject();
// 已知是 String 类型,可以进行强转
String str = (String) o;
System.out.println(str); // 我爱你
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if ( ois != null){
ois.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
以上是反序列化过程,读取 object.data 文件中的 String 对象。
如果是自定义的对象,必须要 实现 Serializable 或者 Externalizable 接口。
// Serializable 接口源码
public interface Serializable {
}
可以看到,Serializable 是一个空接口。里面即没有属性也没有方法。这是一种标识接口。即标记这个类可以干什么事的。
除了实现这个接口还不行,还需要提供一个 【不是必须,但是强烈建议提供】
public static final long serialVersionUID = xxx;
xxx 可以自己设置,是一个long型整数。
提供这个整数,主要是为了防止很多对象在反序列化时不会乱套。
public class Cat implements Serializable {
public static final long serialVersionUID = 1L;
}
上面这个Cat 类就是可序列化的了。
其实还不够,还要求 Cat 类的所有属性是可序列化的。这里的Cat吗,没有属性,因此它就是可序列化的。
如果Cat里面有个 属性不是 static、也没有被transient 修饰,就必须也是可序列化的。
public class Cat implements Comparable, Serializable {
public static final long serialVersionUID = 1L;
private Car car;
}
看这个例子,Cat 里面有个私有属性 是Car 类型的car,那么Car也必须满足 是可序列化的。
-
实现 Serializable 或者 Externalizable 接口
-
提供这个 public static final long serialVersionUID = xxx;
-
属性必须也是可序列化的
-
基本数据类型都是可序列化的
1.7.1 serialVersionUID
这个long型值用来表明类的不同版本间的兼容性。其目的是以序列化对象进行版本控制,有关各版本反序列化是否兼容。
如果类没有显示定义这个静态变量,它的值是java运行时环境根据类的内部细节自动生成。若类的实例变量做了修改,
serialVersionUID 可能发生变化。因此需要显示声明。
什么时候会用到呢?
当接收了对象流之后,进行反序列化之前,会进行 本地类的 serialVersionUID 与接收的 serialVersionUID 对比,如果相同就认为是可以反序列化的,否则认为 版本不一致,不能反序列化。
1.8 随机存取文件流
RandomAccessFile 类
这个类 声明在java.io 包下,但是是直接继承的Object 类
并且实现了DataInput、DataOutput 这2个接口。意味着这个类可读可写。
public class RandomAccessFile implements DataOutput, DataInput, Closeable {}
上面是这个类的类头,没有继承其他类,说明是直接继承的Object类,还实现了 Closeable 接口。
这个类支持随机访问,即可以跳转到文件的任意位置进行 读写操作。
-
支持只访问文件的部分内容
-
可以向已存在的文件后追加内容
这个类有一个记录指针,用于记录当前位置。并且这个类是可以自由移动这个记录指针。
getFilePointer方法 获取位置、seek方法移动位置
// 获取文件记录指针的当前位置
public native long getFilePointer() throws IOException;
// 将文件记录指针定位到pos位置
public void seek(long pos) throws IOException {
if (pos < 0) {
throw new IOException("Negative seek offset");
} else {
seek0(pos);
}
}
复制图片测试
/** 模式
* r: 只读 。 如果文件不存在会报错!
* rw: 读写。 文件不存在会创建文件。如果存在,写内容会覆盖,写了多少覆盖多少。
* rws:读写,同步文件内容和元数据更新。文件不存在会创建文件。
* rwd:读写,同步文件内容的更新。文件不存在会创建文件。
* */
RandomAccessFile r = null;
RandomAccessFile rw = null;
try {
r = new RandomAccessFile("java.jpg", "r");
rw = new RandomAccessFile("rw_java.jpg", "rw");
byte[] buf = new byte[1024];
int len;
while ((len=r.read(buf))!=-1){
rw.write(buf, 0, len);
}
} catch (Exception e) {
e.printStackTrace();
}finally {
try {
if (r != null){
r.close();
}
if (rw != null){
rw.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
文件的插入覆盖
RandomAccessFile rw = null;
try {
rw = new RandomAccessFile("hello.txt", "rw");
// 移动位置到 角标为 3,即第4个位置
rw.seek(3);
// 写数据,需要些 字节类型的数据。
rw.write("xgz".getBytes(StandardCharsets.UTF_8));
} catch (Exception e) {
e.printStackTrace();
}finally {
try {
if (rw != null){
rw.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
// 原始文件内容:FileWriter 文件字符流写出测试
// 执行结果:Filxgziter 文件字符流写出测试
可以看到,write 方法就是覆盖,通过seek方法移动了位置。现在是从 位置3开始执行write方法。写了3个字符xgz,就从位置3开始 覆盖了3个。
如果想要追加在末尾,可以实体seek方法移动到文件末尾。使用File对象的length方法获取文件长度。
如何实现一种插入效果,而不是覆盖呢?
RandomAccessFile rw = null;
try {
rw = new RandomAccessFile("hello.txt", "rw");
// 移动位置到 角标为 3,即第4个位置
rw.seek(3);
// 用一个String来存。使用 StringBuilder 类来构造长的字符串,为了避免扩容,可以用文件长度来初始化。
StringBuilder sb = new StringBuilder((int) new File("hello.txt").length());
byte[] buf = new byte[1024];
int len;
while ((len=rw.read(buf))!=-1){
sb.append(new String(buf, 0, len));
}
rw.seek(3);
rw.write(("xgz"+ sb).getBytes(StandardCharsets.UTF_8));
} catch (Exception e) {
e.printStackTrace();
}finally {
try {
if (rw != null){
rw.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
采用的方法是 先移动位置,再将后面的数据读取保存。然后再移动位置,将需要的数据和保存的数据一起写入即可。
可以看到,插入操作很慢的。我们一般是使用追加操作。
使用这个类可以实现多线程断点下载。
下载前会建立2个临时文件,一个与下载文件大小相同的空文件,另一个是记录文件指针的位置文件。每次暂停都会保存上一次的指针,然后断点下载时,会继续从上一次的地方进行下载,从而实现断点或上传的功能。
2、NIO
NIo 可以是叫new io 新的Io、或者叫 Non-Blocking Io 非阻塞IO。
是从JDK 4.0 开始引入的一套全新的IO api可以替换标准的java io api。
NIO 与原来的IO具有相同的作用和目的。但是使用方式完全不同。Nio是支持面向缓冲区的,基于通道的IO操作,而io是面向流的。
NIO会以更加高效的方式进行文件的读写操作。
NIO有2套,一套是针对标准输入输出的NIO、另一套是网络编程NIO
NIO2 是jdk7对Nio的增强,为了区分4到6的Nio,将7以后的NIo称为Nio2
2.1 Path、Paths、Files 核心API
早期的java只提供了一个File类来访问文件系统,但是File类功能有限,提供的方法性能也不高,大多数方法仅返回成功或失败,不会返回失败的原因。
NIO2 为了弥补不足,引入了Path 接口。代表与平台无关的路径。描述了目录结构中文件的位置。Path可以看做是File的升级版。
Path也可以是不存在的文件路径。
以前这样写:
File file = new File("hello.txt");
现在这样写:
Path path = Paths.get("hello.txt");
Nio2 提供了 Files、Paths工具类、Files包含了大量的静态工具方法来操作文件;Paths则包含了2个返回Path的静态工厂方法。
上面的例子就算使用Paths类的静态方法get 来获取Path接口的实现类对象。get方法还有一个重载方法,接收的是URI对象参数。
public static Path get(String first, String... more) {
return FileSystems.getDefault().getPath(first, more);
}
public static Path get(URI uri) {
String scheme = uri.getScheme();
if (scheme == null)
throw new IllegalArgumentException("Missing scheme");
if (scheme.equalsIgnoreCase("file"))
return FileSystems.getDefault().provider().getPath(uri);
for (FileSystemProvider provider: FileSystemProvider.installedProviders()) {
if (provider.getScheme().equalsIgnoreCase(scheme)) {
return provider.getPath(uri);
}
}
throw new FileSystemNotFoundException("Provider \"" + scheme + "\" not installed");
}
这里只是简单介绍,有机会再详细说明。
2.2 第三方jar 包的使用
使用 apache.commons.io 包提供的api进行文件操作。
先引入Maven依赖:
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.9.0</version>
</dependency>
// 导包
import org.apache.commons.io.FileUtils;
File src = new File("java.jpg");
File tar = new File("apa_java.jpg");
try {
// 使用
FileUtils.copyFile(src, tar);
} catch (IOException e) {
e.printStackTrace();
}
上面是复制文件的一个例子。