输入流输出流
按照流的流向来分 , 可以分为输入流和输出流
- 输出流 :以内存为基准把内存当中的数据写出到磁盘文件或网络介质中
- 输入流 : 以内存为基准把磁盘文件或者网络中的数据读取到内存中去
字节输入流 | 字节输出流 | 字符输入流 | 字符输出流 |
---|---|---|---|
InputStream | OutputStream | Reader(抽象类) | Writer(抽象类) |
FileInputStream | FileOutputStream | FileReader(实现类) | FileWriter(实现类) |
字节流字符流
- 字节流和字符流的用法几乎完全一样,区别在于字节流和字符流所操作的数据单元不同字节流操作的数据单元是 8 位的字节,而字符流操作的数据单元是16位的字符。字节流主要由 InputStream 和 OutputStream作为基类 , 而字符流则主要由 Reader 和 Writer作为基类。
- 一切文件数据(文本、图片、视频等)在存储时,都是以二进制数字的形式保存,都一个一个的字节,传输时一样如此。所以,字节流可以传输任意文件数据。在操作流的时候,我们要时刻明确,无论使用什么样的流对象,底层传输的始终为二进制数据。
InputStream输入流
/*构造方法*/
FileInputStream(File file)
通过打开与实际文件的连接创建一个 FileInputStream ,该文件由文件系统中的 File对象 file命名。
FileInputStream(FileDescriptor fdObj)
创建 FileInputStream通过使用文件描述符 fdObj ,其表示在文件系统中的现有连接到一个实际的文件。
FileInputStream(String name)
通过打开与实际文件的连接来创建一个 FileInputStream ,该文件由文件系统中的路径名 name命名。
/*逐个字节读取*/
int read()
从该输入流读取一个字节的数据,读取完毕没有数据时返回-1
/*读取字节数组*/
int read(byte[] b)
从该输入流读取最多 b.length个字节的数据为字节数组。
int read(byte[] b, int off, int len)
从该输入流读取最多 len字节的数据为字节数组。
/*关闭IO流资源*/
void close()
关闭此文件输入流并释放与流相关联的任何系统资源。
读取数据–>按照字节
读取一个字节,返回值就是读到的数据值,如果读取的数据值等于-1,则读取到文件末尾
public static void main(String[] args) throws IOException {
//创建文件
File file = new File("E:\\杂七杂八\\TestFile.txt");
//创建低级输入流FileInputStream();
InputStream is = new FileInputStream(file);
/*
System.out.println((char) is.read());
System.out.println((char) is.read());
System.out.println((char) is.read());
System.out.println(is.read());//读取完毕没有数据时返回-1
*/
//使用循环读取
int ch = 0;
while((ch = is.read()) != -1){
System.out.print((char)ch+" ");
}
}
/*tips:
逐字节读取性能极差,几乎不用,了解即可
无论是单次读取还是循环读取,都是逐个字节读取
只能读取数字、字母等,不能读取中文字符
中文字符占3字节,强行读取会将其截断出现乱码
读取数据–>字节数组
- 从字节输入流中读取字节到字节数组中去,返回读取的字节数量,没有字节可读返回-1
public static void main(String[] args) throws IOException {
//创建字节输入流与文件接通
FileInputStream fis = new FileInputStream("Hello.txt");
//定义字节数组读取数据
byte[] buffer = new byte[3];//1024--->1kb
//将从fis通道中读取的字节装入buffer,返回读取的字节数量
/*读取第一组*/
int leng = fis.read(buffer);
System.out.println("读取的字节数:"+leng);//3
//将字节数组转化为String字符串
String str = new String(buffer);
System.out.println(str);//abc
/*读取第二组*/
int leng1 = fis.read(buffer);
System.out.println("读取的字节数:"+leng1);//1
String str1 = new String(buffer);
System.out.println(str1);//9bc
/* 读取的字节数:3
abc
读取的字节数:1
9bc
原因:
buffer数组会将数组内的全部数据放入String字符串中,所以出现了该bug
首先知道设置的buffer数组长度为3,第一次读取到“abc”三个字节,此时剩余一个字节”9“
第二次读取,由于只剩余一个字节所以将其读出并放在buffer数组第一个位置
剩余两个位置并未读取到新数据,所以依然存放的是第一次读取的数据
所以:
在讲buffer数组数据注入到字符串当中时,应该调用String的另一个构造器
String(byte[] bytes, int offset, int length)
通过使用平台的默认字符集解码指定的字节子阵列来构造新的 String 。
如下所示:
*/
int leng1 = fis.read(buffer);
System.out.println("读取的字节数:"+leng1);//1
String str1 = new String(buffer,0,leng1);
System.out.println(str1);//9bc
}
读取数据–>循环数组
public static void main(String[] args) throws IOException {
//创建FileInputStream连通目标文件
FileInputStream fileInputStream = new FileInputStream("Hello.txt");
//创建字节数组
byte[] buffer = new byte[3];
//记录每次读取的字节数量--->防止最后一组读取不满
int leng = 0;
//循环读取完毕为止--->判断fileInputStream.read(buffer)是否返回-1
while((leng = fileInputStream.read(buffer)) != -1){
String str = new String(buffer,0,leng);
System.out.print(str+" ");
}
}
- 利用字节数组循环读取字节内容效率高,性能好
- 但是以上的字节流操作均不适用读取字符即文本内容,单个字符为3个字节,读取时很难保证每次都在3的倍数读取到字符,一旦出现字符乱码,后边的所有内容也将会强行懵逼
解决字节流读取字符乱码问题
- 直接将字节数组的长度设置为文件的长度,读取时直接一次性将其全部读出
- 目标文件较小时可以利用此方法,但是过大的文件将会极其影响性能和效率
public static void main(String[] args) throws IOException {
File file = new File("Hello.txt");
FileInputStream fileInputStream = new FileInputStream(file);
//创建字节数组--->长度与文件大小一致
byte[] buffer = new byte[(int) file.length()];
//获取读取的长度,并将字节读取到数组中
int len = fileInputStream.read(buffer);
System.out.println("读取字节数:"+len);
//数组内字节打成字符串输出
String str = new String(buffer);
System.out.println(str);
}
/*JDK1.9之后提供了专门的API用来将数据一次性读出并装入字节数组*/
public static byte[] readAllBytes(Path path)
OutputStream输出流
- 以内存为基准,将内存中的数据以字节的形式写出到磁盘文件中
- IO流管道默认是数据覆盖管道,即每次创建管道时都会把之前的数据清空再写出
- 但是利用append进行数据追加时就不会覆盖之前的数据
/*构造器*/
public FileOutputStream(File file):
//创建字节输出流管道通向文件对象
public FileOutputStream(String file)
//创建字节输出流管道通向文件路径(简化写法)
public FileOutpurStream(File file,boolean append)
//创建一个追加数据的字节输出流管道通向文件对象
public FileOutputStream(String file,boolean append)
//创建一个追加数据的字节输出流管道通向文件路径
/*常用API*/
//写一个字节出去
public void write(int a)
//写一个字节数组出去
public void write(byte[] buffer)
//写一个字节数组的的一部分出去
public void write(byte[] buffer,int pos,int len)
//pos起始索引位置,len写出字节数
写出数据
public static void main(String[] args) throws Exception {
/*创建字节输出流与磁盘文件接通*/
FileOutputStream fos = new FileOutputStream("Hello.txt");
/*写字节数据出去到文件*/
fos.write(97);//a
fos.write('b');//b
fos.write('Q');
fos.write('Q');
/*写字节数组出去到文件*/
byte[] bytes = new byte[]{97,98,99,100,101,102};
fos.write(bytes);
/*将字符串打成字节数组并写出到文件*/
byte[] arrs = "\nJava是世界上最美的语言".getBytes();
//默认以当前代码编码集UTF-8编码
//也可以利用getBytes()设置编码集
//byte[] arrs = "\nJava是世界上最美的语言".getBytes("GBK");
fos.write(arrs);
/*写字节数组的一部分出去*/
byte[] arr = "\nJava是世界上最美的语言".getBytes();
fos.write(arr,0,8);//---->Java是
//换行符/n的兼容性较差,可以使用\r\n作为换行符
System.out.println("\r\n".length());
//\r\n占据两个字节
fos.flush();//刷新缓存,确保数据加载到文件中 刷新之后管道可以继续使用
fos.close();//关闭IO资源
}
append追加数据写出
public static void main(String[] args) throws Exception {
FileOutputStream fos = new FileOutputStream("Hello.txt",true);
//append:true--->第二次运行添加的数据不会覆盖第一次的数据,不会出现层叠
byte[] bytes = "我爱Java\r\n".getBytes();
fos.write(bytes);
fos.flush();
fos.close();
}
字节流实现文件复制
- 利用字节输入流
FileInputStream
将源文件与内存连通,将数据读取到内存当中。 - 再利用
FileOutputStream
将内存与目标文件连通,将先前读取到内存当中的数据写出到目标文件
public static void main(String[] args){
/* Hello.txt----->Nice.txt */
//后续关闭IO流资源需要再finally中,所以需要将IO流在try-catch外部定义为空
FileInputStream fis = null;
FileOutputStream fos = null;
try {
/*创建字节输入流将源文件与内存接通*/
fis = new FileInputStream("E:\\杂七杂八\\TestFile.txt");
/*创建字节输出流将内存与目标文件接通*/
fos = new FileOutputStream("E:\\Thinking\\Copy.txt");
/*创建字节数组接收读取到内存的数据*/
byte[] buffer = new byte[32];
int len = -1;
/*循环读取源文件数据直到读取完毕*/
while ((len = fis.read(buffer)) != -1){
fos.write(buffer,0,len);
}
System.out.println("复制完成标志!");
} catch (IOException e) {
e.printStackTrace();
}finally {
//关闭IO资源前先进行非空校验
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
FileReader输入流
- 以内存为基准,将源文件的字符逐个读取到内存中去
/*构造器*/
public FileReader(File file)
//创建一个字符输入流与源文件对象接通
public FileRReader(String filePath)
//创建一个字符输入流通向源文件路径
/*常用API*/
public int read()
//读取一个字符的编号返回,读取完毕返回-1
public int read(char[] buffer)
//读取一个字符数组,读取到多少个字符就返回多少个数量
FileReader读取数据
public static void main(String[] args) throws IOException {
FileReader fr = null;
//创建字符输入流连通内存和源文件
fr = new FileReader("Hello.txt");
/*逐个字符读取
System.out.println((char) fr.read());
*/
//字符数组读取
char[] buffer = new char[10];
int len;
while((len = fr.read(buffer)) != -1){
String str = new String(buffer,0,len);
System.out.println(str);
}
}
FileWriter输出流
- 构造器及常用API
/*构造器*/
public FileWriter(File file)
public FileWriter(String filePath)
public FileWriter(File file,boolean append)
public FileWriter(String filePath,boolean append)
/*常用API*/
public void write(int c)
public void write(String c)
public void write(char[] buffer)
public void write(String str, int off, int len)
FileWriter写出字符数据
public static void main(String[] args) throws IOException {
FileWriter fw = new FileWriter("Hello.txt");
char[] buffer = "你好,你好不好,你不好我好".toCharArray();
fw.write(buffer);
fw.flush();
fw.close();
}
字符流实现文件复制
public static void main(String[] args) {
FileReader fr = null;
FileWriter fw = null;
try {
fr = new FileReader("Hello.txt");
fw = new FileWriter("Nice.txt");
char[] buffer = new char[10];
int len = -1;
while((len = fr.read(buffer)) != -1){
fw.write(buffer,0,len);
}
System.out.println("复制完成标志...");
}catch (IOException e){
e.printStackTrace();
}finally {
if(fr != null) {
try {
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(fw != null) {
try {
fw.flush();
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
拓展API
- 在String类下有两个API用于操作字符串
/* 1. 通过getBytes() 将字符串打成字节数组*/
byte[] arrs = "\nJava是世界上最美的语言".getBytes();
/* 2. 通过toCharArray() 将字符串打成字符数组*/
char[] buffer = "你好,你好不好,你不好我好".toCharArray();