目录
1、JAVA IO
- IO:
- Input , Output 即 :输入与输出 (读与写)
- java io 可以让我们用标准的读写操作来完成对不同设备的读写数据工作.
- java 将 IO 按照方向划分为输入与输出,参照点是我们写的程序.
- 输入 : 用来 读取 数据 的 , 是从 外界 到 程序 的方向,用于 获取数据.
- 输出 : 用来 写出 数据 的 , 是从 程序 到 外界 的方向,用于 发送数据.
java将IO比喻为"流",即:stream. 就像生活中的"电流","水流"一样,它是以同一个方向顺序移动的过程.只不过这里流动的是字节(2进制数据).所以在IO中有输入流和输出流之分,我们理解他们是连接程序与另一端的"管道",用于获取或发送数据到另一端.
2、Java定义了两个超类(抽象类):
- java.io.InputStream:
- 所有字节 输入流 的超类 , 其中定义了 读取数据的方法.
- 因此将来不管读取的是什么设备(连接该设备的流)都有这些读取的方法,
- 因此我们可以用相同的方法读取不同设备中的数据
- java.io.OutputStream:
- 所有字节 输出流 的超类 , 其中定义了 写出数据的方法.
3、java将流分为两类 :
节点流 与 处理流 :
- 节点流:
- 也称为 低级流 .
- 节点流的另一端是明确的 , 是实际读写数据的流 ,
- 读写一定是建立在节点流基础上进行的.
- 处理流:
- 也称为高级流 .
- 处理流不能独立存在 , 必须连接在其他流上 ,
- 目的是当数据流经当前流时 , 对数据进行加工处理来 简化 我们对数据的该操作.
- 常见的高级流 :
* 缓冲流 : 作用是加快读写效率
* 压缩流 : 进行数据的压缩与解压缩(读写zip文件)
* 音频流 : 进行音频编解码
* 对象流 : 进行java对象的序列化与反序列化实际应用中 , 我们可以通过串联一组高级流到某个低级流上以流水线式的加工处理对某设备的数据进行读写 , 这个过程也成为 流的连接 , 这也是IO的精髓所在.
4、文件流
- 文件流是一对 低级流 ,
- 用于 读写 文件数据的流.
- 用于连接程序与文件(硬盘)的"管道". 负责 读写文件数据.
- 文件输出流:
- java.io.FileOutputStream
package io;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class FOSDemo {
public static void main(String[] args) throws IOException {
//需求:向当前目录的文件fos.dat中写入数据
/*
在创建文件输出流时,文件输出流常见的构造器:
FileOutputStream(String filename)
上述构造器会在创建时将该文件创建出来(如果该文件不存在才会这样做),自动创建
该文件的前提是该文件所在的目录必须存在,否则会抛出异常。
*/
/*
一个小技巧:在指定相对路径时,如果是从"当前目录"(./)开始的,那么"./"是可以忽略不写
的
因为在相对路径中,默认就是从"./"开始
*/
// FileOutputStream fos = new FileOutputStream("./fos.dat");
FileOutputStream fos = new FileOutputStream("fos.dat");//与上面一句位置相同
/*
总结:
File类
File类的每一个实例可以表示硬盘(文件系统)中的一个文件或目录(实际上表示的是一个抽象路 径)
使用File可以做到:
1:访问其表示的文件或目录的属性信息,例如:名字,大小,修改时间等等
2:创建和删除文件或目录
3:访问一个目录中的子项
OutputStream(所有字节输出流的超类)中定义了写出字节的方法:
其中:
void write(int d)
写出一个字节,将给定的参数int值对应的2进制的"低八位"写出。
文件输出流继承OutputStream后就重写了该方法,作用是将该字节写入到文件中。
*/
/*
向文件中写入1个字节
fow.write(1)
将int值的1对应的2进制的"低八位"写如到文件第一个字节位置上
1个int值占4个字节,每个字节是一个8为2进制
int 1的2进制样子:
00000000 00000000 00000000 00000001
^^^^^^^^
写出的字节
write方法调用后,fos.dat文件中就有了1个字节,内容为:
00000001
再次调用:
fos.write(2)
int 2的2进制样子:
00000000 00000000 00000000 00000010
^^^^^^^^
写出的字节
write方法调用后,fos.dat文件中就有了2个字节,内容为:
00000001 00000010
上次写的 本次写的
*/
fos.write(1);
fos.write(2);
System.out.println("写出完毕!");
//注意!流使用完毕后要关闭,来释放底层资源
fos.close();
}
}
4.1、文件复制
package io;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class CopyDemo {
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("xi.jpg");
FileOutputStream fos = new FileOutputStream("xi_cp.jpg");
int d;//用来记录每次读取的字节
//可以用 .currentTimeMillis() 来调取时间,以记录复制所消费的时间
long start = System.currentTimeMillis(); //获取当前系统时间 赋值到start
while ((d = fis.read()) !=-1 ){//先复制一次d,然后判断d是否等于-1 ,不等于继续,等于while循环结束 复制while循环
fos.write(d);
}
long end = System.currentTimeMillis(); //获取当前系统时间 赋值到end
System.out.println("复制完毕!耗时:"+(end-start)+"ms");
//养成好习惯,用完就关,犹如水龙头一样
fis.close();
fos.close();
}
}
4.2、块读写的文件复制操作
- 读取操作 :int read ( byte[ ] data )
- 一次性从 文件中读取 给定的字节数组 总长度的字节量,并存入到该数组中。
- 返回值为 实际读取到的字节量。
- 若返回值为 -1 ,则表示读取到了 文件末尾。
- 块写操作 : void write ( byte[ ] data )
- 一次性将给定的字节数组 所有字节 写入到文件中
- void write ( byte[ ] data , int offset , int len )
- 一次性 将给定的字节数组 从下标offset处 开始的连续len个字节 写入文件
void write ( byte data , int offset , int len ) 本次读取字节数组 , 从哪里开始写 , 本次读取字节长度
package io;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
/**
* 通过提高每次读写的数据量,减少实际读写的次数可以达到读写效率提高的效果
*/
public class CopyDemo2 {
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("flower.jpg");
FileOutputStream fos = new FileOutputStream("flower_cp.jpg");
int len = 0; //记录每次实际读取到的字节数
long start = System.currentTimeMillis(); //获取当前系统时间
byte[] data = new byte[1024*10]; //块读的字节:10kb 这里考虑的是性价比,高了硬盘和内存所消耗的时间会上去,低了自己悟
/**
* 1byte 1字节 00000000(8位2进制)
* 1024byte 1kb
* 1024kb 1mb
* 1024mb 1gb
* 1024gb 1tb
* 1024tb 1pb
*/
while ((len = fis.read(data)) !=-1){//输入流中添加 data 块读的字节数组 表示一次 读 这么多字节
//len 表示本次读取字节长度,
//fos.write(data);//输出流中添加 data 块读的字节数组 表示一次 写 这么多字节
/**
* 字节输出流超类OutputStream上定义了块写操作:
* void write(byte data)
* 一次性将给定的字节数组中所有字节写出
*
* void write ( byte data , int offset , int len )
* 本次读取字节数组, 从哪里开始写 , 本次读取字节长度
* 一次性将给定的字节数组从下标offset处开始的连续len个字节写出
*/
fos.write(data,0,len);
}
long end = System.currentTimeMillis(); //获取当前系统时间
System.out.println("复制完毕!耗时:"+(end-start)+"ms");
fis.close();//用完就关 养好习惯
fos.close();//用完就关 养好习惯
/*
在字节输入流超类InputStream上定义了块读操作
块读:一次读取一组字节
int read(byte[] data)
一次性读取给定数组data总长度的字节并从data数组第一个字节位置开始存入到该数组中。
返回值为实际读取到的字节数。如果返回值为-1则表示流读取到了末尾。
-------------------------------------------------
假设:
ppt.pptx文件内容(8个字节):
11110000 00001111 10101010 01010101 11001100 00110011 11100010 00011100
创建一个byte数组
byte[] data = new byte[3];
data数组初始内容(十进制):[0, 0, 0]
data数组初始内容(二进制):[00000000, 00000000, 00000000]
int len=0;//记录每次读取的字节数
-------------------------------------------------------
第一次调用:
len = fis.read(data);
由于data数组长度为3,因此fis会尝试从文件中一次性读取3个字节并转入到data中
读取ppt.pptx文件内容:
11110000 00001111 10101010 01010101 11001100 00110011 11100010 00011100
^^^^^^^^ ^^^^^^^^ ^^^^^^^^
|--第一次读取的3个字节内容---|
将其装入data数组,此时data数组内容:
data(2进制):[11110000, 00001111, 10101010]
read方法的返回值为3,表达本次真实读取到了3个字节。因此len=3;
len:3
------------------------------------------------------------
第二次调用:
len = fis.read(data);
由于data数组长度为3,因此fis会尝试从文件中一次性读取3个字节并转入到data中
读取ppt.pptx文件内容:
11110000 00001111 10101010 01010101 11001100 00110011 11100010 00011100
^^^^^^^^ ^^^^^^^^ ^^^^^^^^
|--第二次读取的3个字节内容---|
将其装入data数组,此时data数组内容:
data(2进制):[01010101, 11001100, 00110011]
read方法的返回值为3,表达本次真实读取到了3个字节。因此len=3;
len:3
-------------------------------------------------------------------------
第三次调用:
len = fis.read(data);
由于data数组长度为3,因此fis会尝试从文件中一次性读取3个字节并转入到data中
读取ppt.pptx文件内容:
11110000 00001111 10101010 01010101 11001100 00110011 11100010 00011100 文件末尾了
^^^^^^^^ ^^^^^^^^ ^^^^^^^^
第三次读取时只能读2个字节内容
将其装入data数组,此时data数组内容:
data(2进制):[11100010, 00011100, 00110011]
|--本次读取的2字节--| |-旧数据-|
read方法的返回值为2,表达本次真实读取到了2个字节。因此len=2;
len:2
----------------------------------------------------------------------
第四次调用:
len = fis.read(data);
由于data数组长度为3,因此fis会尝试从文件中一次性读取3个字节并转入到data中
读取ppt.pptx文件内容:
11110000 00001111 10101010 01010101 11001100 00110011 11100010 00011100 文件末尾了
^^^^^^^^ ^^^^^^^^ ^^^^^^^^
已经没有数据了
本此data数组没有任何变化:
data(2进制):[11100010, 00011100, 00110011]
|----------旧数据------------|
read方法的返回值为-1,表达流已经读取到末尾了。因此len=-1;
len:-1
---------------------------------------------------------------
字节输出流超类OutputStream上定义了块写操作:
void write(byte data)
一次性将给定的字节数组中所有字节写出
void write(byte data,int offset,int len)
一次性将给定的字节数组从下标offset处开始的连续len个字节写出
*/
}
}
4.3、写文本数据
- String提供方法: byte[ ] getBytes ( String charsetName )
标准字符集UTF-8 . getBytes ( StandardCharsets.UTF_8 )
将当前字符串转换为一组字节
- 参数为字符集的名字,常用的是UTF-8。
- 其中 中文字3字节 表示1个,英文1字节 表示1个。
UTF-8 是互联网上最常用的字符集,也称为:万国码 UTF:unicode 的转换编码
package io;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
/**
* 向文件写入字符串
*/
public class WriteStringDemo {
public static void main(String[] args) throws IOException {
//向文件test.txt 中写入文本数据
FileOutputStream fos = new FileOutputStream("test.txt");
String line = "哆啦A梦!";
/**
* UTF-8 是互联网上最常用的字符集,也称为:万国码
* UTF:unicode的转换编码
* 在UTF-8中,每个英文、数字、符号都占1个字节
* 中文(中文字、中文符号)每个字符占3个字节
*
* String 提供了将字符串转换为一组字节的方法:
* byte[] getBytes(Charset charset)
* 根据指定的字符集将当前字符串转换为一组字节
* .getBytes(StandardCharsets.UTF_8) //给定的转字节
*/
byte[] data = line.getBytes(StandardCharsets.UTF_8);
fos.write(data);
fos.write("大雄你又挨打了~".getBytes(StandardCharsets.UTF_8));
//节略写法,将上面三句并一句
System.out.println("写出完毕");
fos.close();//用完就关,养好习惯
}
}
4.4、文件输出流-追加模式
重载的构造方法 可以将文件 输出流 创建为 追加模式:
FileOutputStream ( String path , boolean append )
FileOutputStream ( File file , boolean append )
当第二个参数传入 true 时,文件流为 追加模式(传false和不写一样)
即 : 指定的文件若存在,
则原有数据保留,新写入的数据会被 顺序的追加 到文件中/**
文件流有两种创建方式:
1:覆盖模式,对应的构造器:
FileOutputStream(String filename)
FileOutputStream(File file)
所谓覆盖模式:
文件流在创建是若发现该文件已存在,
则会将该文件原内容全部删除。
然后在陆续将通过该流写出的内容保存到文件中。2:追加模式,对应的构造器
FileOutputStream(String filename,boolean append)
FileOutputStream(File file,boolean append)
当第二个参数为true时,那么就是追加模式。
所谓追加模式:
文件流在创建时若发现该文件已存在,
则原内容都保留。
通过当前流陆续写出的内容都会被陆续追加到文件末尾。*/
package io;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
/**
* 使用文件输出流向文件中写入文本数据
*/
public class WriteStringDemo {
public static void main(String[] args) throws IOException {
/*
1:创建一个文件输出流
2:将写出的文字先转换为2进制(一组字节)
3:关闭流
文件流有两种创建方式:
1:覆盖模式,对应的构造器:
FileOutputStream(String filename)
FileOutputStream(File file)
所谓覆盖模式:
文件流在创建是若发现该文件已存在,
则会将该文件原内容全部删除。
然后在陆续将通过该流写出的内容保存到文件中。
2:追加模式,对应的构造器
FileOutputStream(String filename,boolean append)
FileOutputStream(File file,boolean append)
当第二个参数为true时,那么就是追加模式。
所谓追加模式:
文件流在创建时若发现该文件已存在,
则原内容都保留。
通过当前流陆续写出的内容都会被陆续追加到文件末尾。
*/
FileOutputStream fos = new FileOutputStream("fos.txt",true);
String line = "斯国一!";
byte[] data = line.getBytes(StandardCharsets.UTF_8);
fos.write(data);
line = "奥里给!";
data = line.getBytes(StandardCharsets.UTF_8);
fos.write(data);
System.out.println("写出完毕!");
fos.close();
}
}
4.5、读取文本数据
package io;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
/**
* 从文件中读取文本数据
*/
public class ReadStringDemo {
public static void main(String[] args) throws IOException {
//获取当前项目目录下的test.txt 文件
// File file = new File("test.txt");
//可以将当前源代码输出带控制台
File file = new File("./src/main/java/io/ReadStringDemo.java");
FileInputStream fis = new FileInputStream(file);
//创建一个与test.txt文件等长的字节数组
byte[] data = new byte[(int)file.length()];//因为length规定是long类型,所有要强转一下 (大转小有可能会丢失精度,但int类型大概能装7亿多字符串,一般情况足够)
//使用块读操作 一次性将文件所有字节读入到数组中
fis.read(data);
//使用String的构造器可以将给定字节数组 所有字节按照UTF-8 编码转换为字符串
String line = new String(data, StandardCharsets.UTF_8);
System.out.println(line);
fis.close();
}
}
5、高级流
流连接示意图:
5.1、缓冲流
- 缓冲流是一对高级流,
作用是提高读写数据的效率.
- java.io.BufferedOutputStream 和 BufferedInputStream
- 缓冲流内部有一个字节数组,默认长度是8kb。
- 缓冲流读写数据时 一定是将数据的读写方式转换为 块读写 来保证读写效率.
- 使用缓冲流完成文件复制操作:
package io;
import java.io.*;
/**
* java将流分为节点流与处理流两类
* 节点流:也称为低级流,是真实连接程序与另一端的"管道",负责实际读写数据的流。
* 读写一定是建立在节点流的基础上进行的。
* 节点流好比家里的"自来水管"。连接我们的家庭与自来水厂,负责搬运水。
* 处理流:也称为高级流,不能独立存在,必须连接在其他流上,目的是当数据经过当前流时
* 对其进行某种加工处理,简化我们对数据的同等操作。
* 高级流好比家里常见的对水做加工的设备,比如"净水器","热水器"。
* 有了它们我们就不必再自己对水进行加工了。
* 实际开发中我们经常会串联一组高级流最终连接到低级流上,在读写操作时以流水线式的加工
* 完成复杂IO操作。这个过程也称为"流的连接"。
*
* 缓冲流,是一对高级流,作用是加快读写效率。
* java.io.BufferedInputStream和java.io.BufferedOutputStream
*
*/
public class CopyDemo3 {
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("ppt.pptx");
BufferedInputStream bis = new BufferedInputStream(fis);
FileOutputStream fos = new FileOutputStream("ppt_cp.pptx");
BufferedOutputStream bos = new BufferedOutputStream(fos);
int d;
long start = System.currentTimeMillis();
while((d = bis.read())!=-1){//使用缓冲流读取字节
bos.write(d);//使用缓冲流写出字节
}
long end = System.currentTimeMillis();
System.out.println("耗时:"+(end-start)+"ms");
bis.close();//关闭流时只需要关闭高级流即可,它会自动关闭它连接的流
bos.close();
}
}
5.2、缓冲输出流写出数据时的缓冲区问题
- 通过缓冲流写出的数据 会被临时存入缓冲流内部的字节数组,
直到数组存满数据才会真实写出一次
/** * 缓冲流的flush方法是强制将缓冲流中已经缓存的数据一次性强制写出 * * 实际上 flush 方法是被定义在 Flushable 接口上的, * * 而该接口被字节输出流的超类 : OutputStream 实现了, * 这意味着 java 中所有的字节输出流都有 flush 方法。 * * 只不过其他的高级输出流的 flush 方法, * 默认的实现是调用其连接的流的 flush 方法, * * 目的是将 flush 动作向下传递,最终传递给缓冲输出流, * 使其真正做到 flush 工作。 */何时用:具体案例 具体分析,根据使用情境去决定要不要使用 flush()
bos.flush(); //强制将没有装满 bos 的写出一次bos.close(); //缓冲流关闭时会自动 flush 一次
package io;
import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
/**
* 缓冲输出流写出数据的缓冲区问题
*/
public class FlushDemo {
public static void main(String[] args) throws IOException {
FileOutputStream fos = new FileOutputStream("bos.txt");
BufferedOutputStream bos = new BufferedOutputStream(fos);
String line = "郎给的诱惑~";
byte[] data = line.getBytes(StandardCharsets.UTF_8);
bos.write(data);
/**
* 缓冲流的flush方法是强制将缓冲流中已经缓存的数据一次性强制写出
*
* 实际上 flush 方法是被定义在Flushable接口上的,
* 而该接口被字节输出流的超类:OutputStream实现了,
* 这意味着java中所有的字节输出流都有flush方法。
* 只不过其他的高级输出流的flush方法,默认的实现是调用其连接的流的 flush方法,
* 目的是将 flush 动作向下传递,最终传递给缓冲输出流,使其真正做到 flush工作。
*/
bos.flush();//强制将没有装满 bos 的写出一次 //单词flush:冲水
/**
* 何时用:具体案例 具体分析,根据使用情境去决定要不要使用 flush()
*/
System.out.println("写出完毕");
bos.close();//缓冲流关闭时会自动flush一次
}
}