按照数据流向IO流可以分为输入流和输出流,按照数据类型可以分成字节流和字符流。如果用window的txt打开内容读得懂用字符流,否则用字节流
字节流
OutputStream
字节流抽象基类:OutputStream(抽象类)是所有字节输出流的超类。
1.FileOutputStream
public class FileOutputStream extends OutputStream
:文件输出流是将数据写入File的输出流
使用:
创建文件输出流以指定的名称写入文件:FileOutputStream(String name)
如果一开始输入的地址这没有这个文件,系统会自动创建这个文件
所以调用FileOutputStream
实际做了三件事:
1.调用系统创建文件(如果已有文件这步操作跳过)
2.创建了字节输出流对象
3.让字节输出流对象指向创建好的文件
public static void main(String[] args)throws FileNotFoundException {
// 创建字节输出流对象
File file=new File("D:\\eight_experiment\\temp.txt");
FileOutputStream fos=new FileOutputStream(file);
}
等价于
FileOutputStream fos=new FileOutputStream("D:\\eight_exeperiment\\temp.txt.txt");
2.void write(int b)
作用:将指定字节写入此文件输出流
注意一下!这里的抛出异常从throws FileNotFoundException
变成了throws IOException
,throws FileNotFoundException是throws IOException的子类,抛出父亲后儿子就不用抛出了。(后面抛异常都用throws IOException就行)
public static void main(String[] args)throws IOException {
// 创建字节输出流对象
FileOutputStream fos=new FileOutputStream("D:\\eight_exeperiment\\temp.txt");
fos.write(97);
fos.write(57);
fos.write(55);
}
这里输入的int并不是一个真正的int数据,而是的一个ASCII编码
所以在txt文件查看结果是:
a97
方法详细:
show使用:
public static void main(String[] args)throws IOException {
// 创建字节输出流对象
FileOutputStream fos=new FileOutputStream("D:\\eight_exeperiment\\temp.txt");
// 写入单个字节
fos.write(97);
fos.write(57);
fos.write(55);
// a97
// 一次写一个字节数组数据
byte[] bytes={97,57,55};
fos.write(bytes);
// a97
// 一次写一个字节数组的部分数据
fos.write(bytes,1,1);
// 9
}
这里扩充一个小知识点,不确定如何输入字节时可以通过String类的.getBytes()
方法获得字节。
byte[] bytes="abcde".getBytes();
txt的展示结果就是abcde
3.void close()
所以和IO操作有关的最后都要调用close()方法,能够实现:1 .关闭文件输出流并2.释放此流相关联的任何系统资源
尤其是在读取数据那块(之前载入10000学生数据又发生过这种情况),较少数据如果不调用close()看不出有什么问题,但是如果在数据较大的时候,不调用close会发现数据读取不完。.
fos.close
4.字节流写数据关于换行和追加写入
1.如何实现数据换行
我们可以直接写fos.write("\n".getBytes);
可能造成的结果就是在IDEA编译器里面的文本有换行,但是从系统文件打开却没有换行,这是因为不同系统的换行符号不一样。
window:\r\n
linux:\n
mac:\r
不过IDEA的编译器是识别各种换行符的。
2.追加写入:
按照我们先前的代码,实现的是内容覆盖/重写,那如何实现追加写入呢?
关于FileOutputStream有两种参数,一种是像之前的:public FileOutputStream(String name)
,还有一种是带boolean类型参数的public FileOutputStream(String name,boolean append)
boolean append如果为true表示追加写入,字节将写入文件末尾而不是开头。当append为false时从文件头部写入,就是覆盖,不含append参数默认为false。
FileOutputStream fos=new FileOutputStream("D:\\eight_experiment\\temp.txt",true);
fos.write("hello".getBytes());
第一次执行程序为txt里面是hello,第二次执行程序txt增加了一个hello为hellohello
6.字节流写数据加异常处理
除了throws 异常类还可以写try catch:
finally: 在异常处理时提供finally块来执行所有清除操作,比如IO流中的释放资源close()方法。
被finally控制的语句一定会执行,除非虚拟机JMV退出了。
比如:
这样子如果fos.write()发生异常fos.close()就无法执行
try {
FileOutputStream fos = new FileOutputStream("D:\\eight_experiment\\temp.txt", true);
fos.write("hello".getBytes());
fos.close();
}catch (IOException e){
e.printStackTrace();
}
所以写成:
FileOutputStream fos=null;
try {
fos = new FileOutputStream("D:\\eight_experiment\\temp.txt", true);
fos.write("hello".getBytes());
}catch (IOException e){
e.printStackTrace();
}finally {
if(fos!=null){
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
InputStream
字节流抽象基类:InputStream(抽象类)是所有字节输入流的超类,起的作用就是把文件txt的内容读取出来在控制台输出。
1. FileOutputStream
public class FileInputStream extends InputStream
:文件输入流是从文件系统中的文件获取输入字节,该文件由文件系统中的路径名name命名。
2.int read
使用:FileInputStream(String name)
通过打开与实际文件的链接来创建一个输入流int read()
:读取单个一个字节的数据,如果到达文件末尾返回-1。
public static void main(String[] args)throws IOException {
// 创建字节输入流对象
FileInputStream fis=new FileInputStream("temp.txt");
// 调用字节输入流对象读文件数据
// int read():从该输入流读取一个字节的数据
int b=fis.read();
System.out.println((char) b);
b=fis.read();
System.out.println((char) b);
fis.close();
}
输出结果是
a
b
(temp.txt文件内部内容是abc)
读取整个文件信息,注意读取结束一定要加close(),否则数据可能读不全。
public static void main(String[] args)throws IOException {
// 创建字节输入流对象
FileInputStream fis=new FileInputStream("temp.txt");
// 读取全部的文件数据
int data;
while((data=fis.read())!=-1){
System.out.print((char) data);
}
// 释放资源
fis.close();
}
输出结果是
abc
(temp.txt文件内部内容是abc)
同FileOutputStream的write()方法类似,read()方法可以从文件中读取一个字节数组的数据,最多读取字节数取决于 数组的大小。
int read(byte[] b);
返回的是实际读取的字节大小,当读取到结尾没有任何元素可以读取了返回的也是-1.
扩展: String类含有一个构造方法,使用包含字符码的数组构造新的String对象
System.out.println(new String(byte[] b));
数组大小大于可读内容:
public static void main(String[] args)throws IOException {
// 创建字节输入流对象
FileInputStream fis=new FileInputStream("temp.txt");
// 读取一个字节数组
byte[] bytes=new byte[10];
int len=fis.read(bytes);
System.out.println(new String(bytes));
System.out.println(len);
// 答案是6,abcdef的确六个字节
fis.close();
}
输出结果:
输出发现后面有空,那就实现只将读取的字节转化成字符串就行了
System.out.println(new String(byte[] b),int beg,int len);
System.out.println(new String(bytes,0,len);
输出就是:
abcdef
6
数组大小小于等于可读内容:
public static void main(String[] args)throws IOException {
// 创建字节输入流对象
FileInputStream fis=new FileInputStream("temp.txt");
// 读取一个字节数组
byte[] bytes1=new byte[3];
byte[] bytes2=new byte[3];
byte[] bytes3=new byte[3];
int len1=fis.read(bytes1);
int len2=fis.read(bytes2);
int len3=fis.read(bytes3);
System.out.println(new String(bytes1,0,len1));
System.out.println(new String(bytes2,0,len2));
System.out.println(new String(bytes3,0,len3));
fis.close();
}
输出结果:
abc
def
g
(txt的内容是abcdefg)
注意!如果文件有换行,换行也算一个字符会被读取
比如:
hello(\n\r)
world(\n\r)
读取五个字节,输出就是
hello
\n\rwor
ld\n\r
(\n\r不会显示出来,会以回车的方式出现)
我本来以为只是文本(txt)读取没想到图片也可以读取,不过也正常。
复制图片:
public static void main(String[] args)throws IOException {
// 创建字节输入流对象
FileInputStream fis=new FileInputStream("D:\\eight_experiment\\cutBaby.gif");
FileOutputStream fos=new FileOutputStream("D:\\eight_experiment\\copyCuteBaby.gif");
// 读取一个字节数组
byte[] bytes=new byte[1024];
int len=0;
while((len=fis.read(bytes))!=-1){
fos.write(bytes,0,len);
}
fos.close();
fis.close();
}
也可以复制视频,不过没有尝试,一个原因是电脑没素材,第二个原因是懒hh
复制视频就可以好好测试一下时间了
补充测试时间的方法:
long startTime=System.currentTimeMillis();
/*
...操作
*/
long endTime=System.currentTimeMillis();
// System.currentTimeMillis();是ms,更小的单位是System.nanoTime();表示的ns
// 1 ms = 0.001 s = 1E-3s, 1ns = 0.000 001 ms = 1E-6ns = 1E-9s
System.out.println(endTime-startTime+"ms");
字节缓冲流
BufferedInputStream和BufferedOutputStream都是缓冲流,缓冲流实际上里面有一个缓冲区,即字节数组,用流读取存储在硬盘上较大文件的时,频繁从硬盘中读取数据效率比较低,花费时间较长.缓冲流的作用就是将数据先读取到内存里面,然后从内存里面读取数据,读取速度得到很大的提升.
总结就是增加缓冲区进行操作提高效率
BufferedOutputStream
public class BufferedOutputStream extends FilterOutputStream
该类实现缓冲输出流,通过设置缓存输出流,应用程序可以向缓存输出流写入字节,而不必为写入的每个字节导致底层系统的调用。
解释一下:
FileOutputStream 文件输出流 ,无缓冲区,write一次,就往文件里面写一次数据,效率较低。 BufferedOutputStream 缓存输出流, 缓存区默认大小为8192byte,可通过构造函数定义, write方法将数据写入缓存区中,缓存区满时写入文件,更高效。
使用:
public static void main(String[] args)throws IOException {
// 创建字节输出流对象
// FileOutputStream fos=new FileOutputStream("D:temp.txt");
// 创造字节缓冲输出流
// BufferedOutputStream bos=new BufferedOutputStream(fos);
// BufferedOutputStream的构造方法需要的是字节流而不是具体的文件和路径
// 简化书写:
BufferedOutputStream bos=new BufferedOutputStream(new FileOutputStream("temp.txt"));
bos.write("hello".getBytes());
// 9
bos.close();
}
BufferedInputStream
public class BufferedInputStream extends FilterInputStream
调用BufferedInputStream将自动创建一个内部缓冲区数组,当从流中读取或者跳过字节时,内部缓冲区会根据需要从输入流中重新填充,一次多个字节进行读取。
使用
public static void main(String[] args)throws IOException {
// 创建字节输入流对象
// FileInputStream fis=new FileInputStream("D:temp.txt");
// 创造字节缓冲输入流
// BufferedInputStream bis=new BufferedOutputStream(fis);
BufferedInputStream bis=new BufferedInputStream(new FileInputStream("temp.txt"));
// int by;
// while((by= bis.read())!=-1){
// System.out.print((char)by);
// }
byte[] bytes=new byte[1024];
int len=bis.read(bytes);
System.out.print(new String(bytes,0,len));
// 和被备注掉的效果一样,输出hello在temp.txt里面
bis.close();
}
这里说明一下:BufferedInputStream自带的缓存区是用来存储字节流读取的数据的,而 byte[] bytes=new byte[1024];是字节流一次性读取1024字节个数据,两者不一样。
LOOK:
为什么要将此数组初始化为1024?
1KB(Kilobyte 千字节)=1024B,1MB(Megabyte 兆字节 简称"兆")=1024KB,1GB(Gigabyte 吉字节 又称"千兆")=1024MB,
这称为缓冲,每次循环时都覆盖缓冲区的内容。
只需以块的形式读取文件,而不是一次为文件内容分配内存。
现代操作系统的内存管理都具有分页机制,而内存页的大小都是1024的整数倍,定义1024整数倍大小的缓冲区在分配内存时将不会形成碎片。
总结:
BufferedOutputStream和BufferInputStream在用法上/写法上和FileOutputStream与FileInputStream几乎一样,不过效率比FileOutputStream要高。
要注意的是:
BufferedOutputStream和BufferInputStream的构造方法需要的都是字节流而不是具体的文件和路径:因为字节缓冲流只是提供缓冲区,真正的读写功还是得靠基本的字节流对象进行操作。
经过测试(复制视频的那个,我没写),发现耗时最少的方法就是用
BufferedOutputStream和BufferInputStream配合字节数组进行操作。
字符流
由于字节流操作中文不是特别方便,所以java提供了字符流。
.字符流=字节流+编码表
在程序中一个字符等于两个字节
用字节流复制文本时,文本文件也有中文,但是没问题,原因是最终底层操作会自动进行字节拼接成中文,如何判断是中文的呢?
汉字在存储的时候,无论选择哪种编码存储,第一个字节都是负数。
字符串中的编码解码问题:
用什么编码得到的字节数组就用什么解码来创造String类
编码:byte[] getBytes():
使用平台默认字符集将String编码未一系列字节,存储在字节数组中,返回一个字节数组
IDEA默认UTF-8
byte[] getBytes(String charsetName):
用指定字符集将String转化为字节数组,返回字节数组。
使用show:
public static void main(String[] args)throws IOException {
String str="中国";
byte[] bytes=str.getBytes();
byte[] bytes1=str.getBytes("UTF-8");
byte[] bytes2=str.getBytes("GBK");
System.out.println(Arrays.toString(bytes));
// [-28, -72, -83, -27, -101, -67]
System.out.println(Arrays.toString(bytes1));
// [-28, -72, -83, -27, -101, -67]
System.out.println(Arrays.toString(bytes2));
// [-42, -48, -71, -6]
}
解码:String(byte[] bytes):
用字节数组根据指定字符集构造成String
IDEA默认UTF-8
String(byte[] bytes,String charsetName):
用字节数组根据指定字符集构造成String
public static void main(String[] args)throws IOException {
byte[]bytes= {-28, -72, -83, -27, -101, -67};
System.out.println(new String(bytes));
System.out.println(new String(bytes,"GBK"));
// 中国
// 涓浗
byte[]bytes1={-42, -48, -71, -6};
System.out.println(new String(bytes1));
System.out.println(new String(bytes1,"GBK"));
// �й�
// 中国
}
InputStreamReader:
正常情况下,字节流可以对所有的数据进行操作,但是有些时候在处理一些文本时我们要用到字符流,比如,查看文本的中文时就是需要采用字符流更为方便。所以 Java IO 流中提供了两种用于将字节流转换为字符流的转换流。
字符流抽象基类
Reader:字符输入流抽象基类
Writer:字符输出流抽象基类
字符流中和编码解码问题相关的两个类:InputStreamReader(InputStream in):
字符输入流,读取文件,字节输入流转换为字符输入流OutputStreamWriter(OutputStream out):
字符输出流,写入文件,字节输出流转换为字符输出流
无论是写进文件展示的还是读取文件展示的都是字符
两个都是可以指定字符集的
使用:InputStreamReader(InputStream in)
方法和FileInputStream
的方法是对应的,只是一个是字符流一个是字节流。
IntputStream in通过FileInputStream创建一个对象实现
InputStreamReader isr=new InputStreamReader(new FileInputStream("temp.txt"));
// isr.read()一次读取一个字符
int ch;
while((ch=isr.read())!=-1){
System.out.print((char)ch);
}
// 我爱中国
isr.close();
文件里面是”我爱中国“ UTF-8编码类型,如果改成用"GBK"编码类型读取,会变成
InputStreamReader isr=new InputStreamReader(new FileInputStream("temp.txt"),"GBK");
//鎴戠埍涓浗
1.字符流读数据的int read方法的使用:
这是字节流里面read方法的使用:int read()
int read(byte[] b);
字符流和他唯一的差别就是读取的对象变成了一个字符数据或者一个字符数值,而不是读取字节了int read()
int read(char[] b);
一个字符可能由两个或三个,四个字节读取,编译器根据给出的编码规则进行字节合并,字符读取。
对字符进行字节读取:
FileInputStream fis=new FileInputStream("temp.txt");
// 中国
int b=fis.read();
System.out.println((char) b);
// 字节读取就是这个奇奇怪怪的玩意:ä
fis.close();
所有字符要通过字符读取:
读取单个字符:
InputStreamReader isr=new InputStreamReader(new FileInputStream("temp.txt"));
// 中国
System.out.println((char) isr.read());
// 中
isr.close();
读取一个字符数组
InputStreamReader isr=new InputStreamReader(new FileInputStream("temp.txt"));
// 中国
char[] ch=new char[2];
isr.read(ch);
// System.out.println(Arrays.toString(ch));
// [中, 国]
System.out.println(new String(ch));
// 中国
isr.close();
OutputStreamWriter
OutputStreamWriter(OutputStream out)
方法和FileOutputStream
其实也差不多一样
默认UTF-8编码
OutputStreamWriter osw=new OutputStreamWriter(new FileOutputStream("temp.txt"));
osw.write("我爱中国");
osw.flush();
// 第一次写没加flush,文件是创建了,但是里面没有数据,要么用flush要么用close
temp.txt文件里面被写入”我爱中国“(没用双引号)
1.flush刷新流和close关闭流
void flush
刷新流,write方法只是写到了BufferedWriter底层维护的一个数组中,flush才写到目的端。flush() 方法是用来刷新缓冲区的,即将缓冲区中的数据立刻写入文件,同时清空缓冲区,不需要是被动的等待输出缓冲区写入。void close
是关闭流,关闭流之前会先刷新,释放资源。
两个的区别:调用flush后还可以继续写数据,但是调用close流就关闭流,不能再写入数据。
既然close有flush的作用为什么还要调用flush:
为了提高效率,写入的数据会先放入缓冲区,然后写入文件。所以有时需要主动调用flush()方法, 不一定writer后一定要调用flash,close执行会flash的。flash是为了减少缓冲区堆积过多数据造成溢出。
2.字符流写数据void write方法的使用:
使用showvoid write(int c):
写一个字符
OutputStreamWriter osw=new OutputStreamWriter(new FileOutputStream("temp.txt"));
// 写入单个字符
osw.write(97);
// osw.write('a');
// 单个字符也会自动转换成ascii对应的数值
// a
osw.close();
void write(char[] ch):
写入一个字符数组void write(char[] ch,int beg,int len):
写入一个字符数组的部分
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("temp.txt"));
// 写入一个字符数组
char[] ch={'a','b','c'};
osw.write(ch);
// abc
osw.close();
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("temp.txt"));
// 写入一个字符数组的部分
char[] ch={'a','b','c','d','e'};
osw.write(ch,1,4);
// bcde
osw.close();
void wirte(String str):
写一个字符串void wirte(String str,int beg,int len):
写一个字符串的部分
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("temp.txt"));
// 写入一个字符串
osw.write("我爱中国");
// 我爱中国
osw.close();
对比字节流写入:字节流写入还要将字符串转化成对应的字节数,用字符流输入这一步就被省略了(暂时是这么理解字符流字节流的差异)
FileOutputStream fos=new FileOutputStream("temp.txt");
fos.write("我爱你中国".getBytes());
// 我爱你中国
fos.close();
写一个字符串的部分
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("temp.txt"));
// 写入一个字符串的部分
osw.write("我爱你中国",3,2);
// 中国
osw.close();
也尝试一下通过字符流实现文件复制
public static void main(String[] args) throws IOException {
InputStreamReader isr = new InputStreamReader(new FileInputStream("theOriginalFile.txt"));
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("copy.txt"));
int c;
while((c=isr.read())!=-1){
osw.write(c);
}
// 也可以用数组读取
osw.close();
isr.close();
}
InputStreamReader和OutputStreamWriter的自动挡
FileReader
public class FileReader extends InputStreamReader:
只要学会第三构造方法就行:
FilReader(String fileName):
FileWriter
public class FileWriter extends OutputStreamWriter:
只要记住最后两个就行:
FileWriter(String fileName):
FileWriter(String fileName,boolean append):
可在文件末尾追加
用上面那个文件复制测试一下:
public static void main(String[] args) throws IOException {
FileReader fr=new FileReader("theOriginalFile.txt");
FileWriter fw=new FileWriter("copy.txt");
int c;
while ((c=fr.read())!=-1){
fw.write(c);
}
fw.close();
fr.close();
}
字符缓冲流
缓冲的作用就是读取和写入的高效性
BuferReader
public class BuferReader extends Reader:
BuferWriter
public class BuferWriter extends Writer:
测试:
public static void main(String[] args) throws IOException {
BufferedReader br=new BufferedReader(new FileReader("theOriginalFile.txt"));
BufferedWriter bw=new BufferedWriter(new FileWriter("copy.txt"));
// 1.法一
// int ch;
// while((ch=br.read())!=-1){
// bw.write(ch);
// }
// 2.法二
char str[]=new char[1024];
int len;
while((len=br.read(str))!=-1){
bw.write(new String(str,0,len));
}
bw.close();
br.close();
}
字符缓冲流的特有功能
void newLine()
BufferWriter
有一个void newLine()
方法能够写一行行分隔符,分隔符字符串(\n,\r\n,\r)由系统属性定义.
使用show
public static void main(String[] args) throws IOException {
BufferedWriter bw=new BufferedWriter(new FileWriter("temp.txt"));
for (int i=0;i<5;i++){
bw.write("hello world");
// bw.write("\r\n");只适用windows系统
bw.newLine();
// 适用任何系统
}
bw.close();
}
结果:
hello world
hello world
hello world
hello world
hello world
public String readLine()
BufferReader
有一个public String readLine()
方法能够读一行文字,返回包含行内容的字符串,不包含任何行中止字符(换行符号:\n,\r\n,\r),如果流的结尾已经到达返回null
。
BufferedReader br=new BufferedReader(new FileReader("temp.txt"));
System.out.print(br.readLine());
System.out.print(br.readLine());
// 不包含换行符的
// hello world0hello world1
br.close;
}
原文件:
hello world0
hello world1
hello world2
hello world3
hello world4
文件复制
用字符流复制文件因为读取的一行不带由换行符,所有要调用void newLine()
:
public static void main(String[] args) throws IOException {
BufferedReader br=new BufferedReader(new FileReader("theOriginalFile.txt"));
BufferedWriter bw=new BufferedWriter(new FileWriter("copy.txt"));
String line;
while((line=br.readLine())!=null){
bw.write(line);
bw.newLine();
bw.flush();
}
bw.close();
br.close();
}
IO小结
字节流:
因为InputStream和OutputStream是抽象类,所以我们实现用的是
字符流
抽象类的实现:
FileReader
和FileWriter
简化了书写,但是涉及到编码问题只能使用InputStreamReader
和OutputStreamWriter
。