1.IO概述
简介
- IO:
I:input输入
0:out输出
站在内存的角度:从某个设备进去到内存,用的input去读取到内存中,从内存中输出到其他设备,out,用输出去写出到其他设备
- 包:IO
IO分类
- 分类的方式有两种:按照功能可以分类,按照流向也可以分类
- 按照功能分类:
读:Reader
写:Write
- 按照流向分类:
输入流:inputStream
输出流:OutPutStream
- IO流的体系结构,根据分类,有四种流对象的类型
字节流:
输入流:InputStream
输出流:OutPutStream
字符流:
输入流:Reader
输出流:Writer
IO程序书写流程
- 在操作之前,要导包,io包
- 在操作流对象的时候,要处理解决异常
- 在操作完流对象之后,需要关闭资源
2.字节流
概述
- 可以直接操作字节信息的流对象
- 根据流向,可以分成字节输入流和字节输出流
- 顶层抽象父类分别是:InputStream 和 OutputStream
- 根据交互设备的不同,有不同的具体子类
InputStream
- 字节输入流的顶层抽象父类
- 常用的方法
read():从当前的字节输入流中,获取一个字节
read(byte[] arr):将arr.length个字节,读取到arr中
3. InputStream是一个抽象类,不能直接创建对象,只能由子类创建对象
FileInputStream
- InputStream的一个具体子类,用于和磁盘上的文件进行交互
- FileInputStream不仅可以一次读取一个字节,也可以一次读取很多个字节;不仅可以读取纯文本文件,也可以读取图片、视频、音频等非纯文本文件。一切数据在计算机中都是以字节的形式在存储和计算
- 构造方法:
FileInputStream(File f):将File对象封装成字节输入流,将来可以读取这个文件中的信息
FileInputStream(String path):将字符串封装成字节输入流,将来可以读取信息
注意事项:无论是哪个构造方法,都只能封装文件的路径,封装文件夹的路径没有任何意义,因为文件夹本身没有任何数据,所以也不能使用流对象读取数据。
- 两个常用方法:
read():一次读取一个字节,返回值类型是int类型,原因要把返回的字节前面加上24个0,无论读取到的是负数还是整数,都会变成正数,只要是从文件中读取到的数据,都是整数;如果返回了-1,就说明不是从文件中读到的数据,而是整个方法专门准备的文件末尾的标记。说明:虽然每次调用该方法,但是返回的内容却不会相同,因为文件指针在不断向后移动。
read(byte[] arr):一次读取多个字节,存储到字节数组中
代码示例
InputStream in=new FileInputStream("a.txt");
File f=new File("a.txt");
InputStream ins=new FileInputStream(f);
//next()方法每读取一个字节,指针往后移动一次
/*
* System.out.println(ins.read()); System.out.println(ins.read());
* System.out.println(ins.read()); System.out.println(ins.read());
* System.out.println(ins.read()); System.out.println(ins.read());
* System.out.println(ins.read()); System.out.println(ins.read());
*/
int i;
while((i=ins.read())!=-1) {
System.out.print((char)i);
}
OutputStream
- 字节输出流的顶层抽象父类
- 常用方法:
write(int b):将一个字节信息写出到指定的设备中
write(byte[] arr):将一个字节数组中的所有信息,写出到指定设备中
write(byte[] arr, int offset, int len):将一个字节数组arr中的从offset索引开始,总共len个字节写出到指定的设备中。
- 这个类是抽象类,不能直接创建对象
FileOutputStream
- 说明:可以将字节数据写出到指定的文件中
- 构造方法:
FileOutputStream(File f):将f描述的路径文件封装成字节输出流对象
FileOutputStream(String path):将path描述的文件路径封装成字节输出流对象
使用字节输出流写出数据,就进入到关联的文件中。
- 注意事项:
1、成员方法都来自于抽象父类
2、写到磁盘上,存储的就是数字,既没有编码,也没有解码;如果使用文本编辑器读取这个文件,就会发生两步:先读取数据,通过编码表进行解码,让我们看到字符
代码示例
public static void main(String[] args) throws IOException {
File f=new File("b.txt");
if (!f.exists()) {
f.createNewFile();
}
//FileOutputStream(File file, boolean append)
//默认是false覆盖,true是追加
OutputStream os=new FileOutputStream(f,true);
os.write(98);
String str="hello,world,你好世界";
os.write(str.getBytes());
String s2="xyz";
//写出数组指定的位置
os.write(s2.getBytes(), 0, 1);
os.close();
}
文件拷贝
- 含义:
将一个文件中的数据,拷贝到另一个文件中
- 本质:
从一个文件中,使用输入流,读取一个字节
将这个字节,使用输出流,写出到另外一个文件中
- 图示:
代码示例
public static void main(String[] args) throws IOException { File file=new File("E://day17笔记.doc"); //System.out.println(file.getName()); InputStream ins=new FileInputStream(file); OutputStream os=new FileOutputStream("E://"+file.getName(),true); int i; while((i=ins.read())!=-1) { os.write(i); } os.close(); ins.close(); }
文件拷贝效率提升
- 使用一个字节一个字节拷贝的方式,效率非常低:IO的次数过多,有多少个字节,就要IO两倍的次数
- 提升的思路:一次多读一些数据,一次多写出一些数据,使用FileInputStream中的read(byte[] arr)和FileOutputStream中的write(byte[] arr)。数组的大小可以准备成和文件一样样大。配合InputStream中的available方法可以获取源文件的字节个数,用于创建数组的大小。
- 数组属于内存的,内存的大小是有限的,如果文件过大,就无法创建大小相同的数组。
- 只能考虑使用小一些的数组,每次拷贝源文件的一部分,多拷贝几次。涉及的方法:
InputStream中的read(byte[] arr):将数据读取到数组中,返回本次读到的有效字节的个数,如果返回值为-1,表示本次读到的有效字节个数为0,表示到达了文件末尾
OutputStream中的write(byte[] arr):将数组中的所有数据,都写出到了目标文件中
OutputStream中的write(byte[] arr, int offset, int len):将数组中的指定部分的数据,写出到目标文件中(在读取的时候,读到了多少有效字节,就将这么多有效字节写出到目标文件中),一般offset都选择0
- 注意事项:最终拷贝方案就是小数组
1、数组的大小可以任意选择,数组越大,拷贝次数越少,拷贝的效率越高
2、一般情况,数组的大小使用1024的整数倍,在jdk中喜欢使用1024*8大小作为缓冲区的大小。
代码示例
public static void main(String[] args) throws Exception { long start=System.currentTimeMillis(); test4_一次性拷贝(); long end=System.currentTimeMillis(); System.out.println("用时:"+(end-start)); } public static void test1_单字节拷贝() throws Exception { File file =new File("E:\\soft\\FeiQ.exe"); InputStream is=new FileInputStream(file); OutputStream os=new FileOutputStream("E://"+file.getName()); int i ; while((i=is.read())!=-1) { os.write(i); } is.close(); os.close(); } public static void test2_小数组拷贝() throws Exception { File file =new File("E:\\soft\\FeiQ.exe"); InputStream is=new FileInputStream(file); OutputStream os=new FileOutputStream("E://"+file.getName()); byte b[]=new byte[2]; while(is.read(b)!=-1) { os.write(b); } is.close(); os.close(); } public static void test3_大数组拷贝() throws Exception { File file =new File("E:\\soft\\FeiQ.exe"); InputStream is=new FileInputStream(file); OutputStream os=new FileOutputStream("E://"+file.getName()); byte b[]=new byte[1024]; int len; while((len=is.read(b))!=-1) { os.write(b,0,len);//读取多长就写多长 } is.close(); os.close(); } public static void test4_一次性拷贝() throws Exception { File file =new File("E:\\soft\\FeiQ.exe"); InputStream is=new FileInputStream(file); OutputStream os=new FileOutputStream("E://"+file.getName()); byte b[]=new byte[is.available()]; int len; while((len=is.read(b))!=-1) { os.write(b,0,len); } is.close(); os.close(); }
InputStream类型中read()方法和read(byte[] arr)方法的比较
- 读取字节个数的区别:
read()方法一次只能读取一个字节
read(byte[] arr)方法一次可以读取多个字节,取决于数组的大小
- 读取到的字节信息存储的位置不同:
read()读取到的文件字节信息是作为返回值进行返回的
read(byte[] arr)读取到的字节信息,存储到参数数组arr中
- 返回值的含义不同:两个方法的返回值类型都是int类型
read()方法返回的是读取的有效字节本身
read(byte[] arr)方法的返回是读取到的有效字节的个数(返回值的取值范围:-1,1~arr.length)
高效缓冲流
- BufferedInputStream和BufferedOutputStream
- 是包装类型:本身不具备读写的功能,只是在某个具体的流对象的基础上,对其进行加强,例如FileInputStream和FileOutputStream,原本效率较低,加强之后,就效率较高
- 构造方法:
BufferedInputStream(InputStream is):将指定的具体的字节输入流传入构造方法的参数,形成一个高效版本的字节输入流
BufferedOutputStream(OutputStream os):将指定的具体的字节输出流传入构造方法的参数,形成一个高效版本的字节输出流
- 使用:
这两个高效流还是字节流,还是InputStream和OutputStream,所以抽象父类中的那些方法仍然可以继续使用
- 原理:
1、BufferedInputStream高效的原理:在该类型中准备了一个数组,存储字节信息,当外界调用read()方法想获取一个字节的时候,该对象从文件中一次性读取了8192个字节到数组中,只返回了第一个字节给调用者。将来调用者再次调用read方法时,当前对象就不需要再次访问磁盘,只需要从数组中取出一个字节返回给调用者即可,由于读取的是数组,所以速度非常快。当8192个字节全都读取完成之后,再需要读取一个字节,就得让该对象到文件中读取下一个8192个字节了。
2、BufferedOutputStream高效的原理:在该类型中准备了一个数组,存储字节信息,当外界调用write方法想写出一个字节的时候,该对象直接将这个字节存储到了自己的数组中,而不刷新到文件中。一直到该数组所有8192个位置全都占满,该对象才把这个数组中的所有数据一次性写出到目标文件中。如果最后一次循环过程中,没有将数组写满,最终在关闭流对象的时候,也会将该数组中的数据刷新到文件中。
代码示例
public static void main(String[] args) throws Exception { File f=new File("E:\\soft\\java1.7api中文.rar"); InputStream is=new FileInputStream(f); //用带有缓冲区的类包装一下 BufferedInputStream bis=new BufferedInputStream(is); OutputStream os=new FileOutputStream("E://"+f.getName()); BufferedOutputStream bos=new BufferedOutputStream(os); int i; while((i=bis.read())!=-1) { bos.write(i); } bis.close(); //一定要关闭输出流,否则可能会导致部分字节输出不到文件中 bos.close(); }
案例--复制文件夹
ublic static void main(String[] args) throws Exception {
copyFile("E:\\upload", "E:\\copy");
}
public static void copyFile(String p1,String p2) throws Exception {
File f1=new File(p1);
if (f1.isFile()) {//如果直接传过来文件,直接复制
copy(p1, p2);
return;
}
File[]list = f1.listFiles();//传过来文件夹
File target=new File(p2);
if (!target.exists()) {//如果目标文件夹没有,就创建新的
target.mkdirs();
}
for(File f:list) {
if(f.isFile()) {//如果遍历的当前文件是文件
copy(f.getPath(), p2+"\\"+f.getName());
}else {
copyFile(f.getPath(), p2+"\\"+f.getName());
}
}
}
/**复制文件
* @param src
* @param target
* @throws Exception
*/
public static void copy(String src,String target) throws Exception {
BufferedInputStream bis=new BufferedInputStream(new FileInputStream(src));
BufferedOutputStream bos=new BufferedOutputStream(new FileOutputStream(target));
byte b[]=new byte[1024*8];
int len;
while((len=bis.read(b))!=-1) {
bos.write(b, 0, len);
}
bis.close();
bos.close();
}
输出流中的close()和flush()的区别
- close方法会先调用flush方法
- close方法用于流对象的关闭,一旦调用了close方法,那么这个流对象就不能继续使用了
- flush只是将缓冲区中的数据,刷新到相应文件中,而不会将流对象关闭,可以继续使用这个流对象。但是如果flush方法使用过于频繁,那么丧失了缓冲区的作用。
3..字符流
使用字节流处理字符的问题
- 使用字节流写字符
可以使用,但是需要先把字符串转成字节数组,再存储到文件中,比较麻烦
- 使用字节流读取字符
如果是纯英文,可以一次读取一个字节
如果是纯中文,可以一次读取两个字节(GBK)
如果是纯中文,而且UTF-8的编码一次性读取三个字节
如果是中英文混杂,每次不知道读取多少个字节,因此无论字节数组准备多大,都会出现乱码
代码示例
public static void main(String[] args) throws Exception {
//utf-8汉字占3个字节,gbk汉字占2个字节
// System.out.println("你".getBytes().length);
InputStream is=new FileInputStream("a.txt");
byte b[]=new byte[1024];//utf-8编码读取3个
//gbk一次性读取两个字节
//纯英文一次性读取一个字节也可以,随意
//中英文混合,解决不了
int i;
while((i=is.read(b))!=-1) {
System.out.print(new String(b));
}
}
解决方案
- 出现乱码的原因:
有可能汉字只读取了部分字节,转成字符串的时候,就会出现乱码
- 解决:
动态判断每次应该读取多少个字节
在GBK编码表中,如果是一个英文,那么一定读取到的字节是正数,如果读取到的字节是正数,那么就可以断定是英文字符,就读取一个字节转成字符即可
在GBK编码表中,如果是一个中文,那么就一定读取到的第一个字节是负数,如果读取到一个负数,就说明读到的是一个中文,就需要再次读取一个字节,两个字节一起转成字符。
- 说明:
如果在工程中频繁做这么底层的操作,太复杂,直接使用jdk中已经提供好的解决方案
字符流:不仅能动态判断GBK的每个字符是中文还是英文,还可以判断其他编码表的所有语种
字符流的使用
- 抽象顶层父类:Reader、Writer
- 常用方法:
输入流Reader:
read();
Read(char[]c)
输出流Writer
Write(int i)
Write(char[])
Write(char[],int start,int len)
- 抽象父类不能直接创建对象,需要使用具体的子类类型来创建
- 使用FileReader和FileWriter可以作为创建对象的类型
代码示例
public static void main(String[] args) throws Exception { /* * Reader reader=new FileReader("a.txt"); * int i; * while((i=reader.read())!=-1) { * System.out.print((char)i); } */ readAsArr(); } public static void readAsArr() throws Exception { FileReader reader=new FileReader("a.txt"); char c[]=new char[1]; while(reader.read(c)!=-1) { System.out.print(new String(c)); } }
总结
输入流
InputStream 抽象类,
FileInputStream
read() 返回的是读取的一个字节
read(byte[]b)返回的是读取长度,将读取的内容写到byte数组中
输出流:
OutputStream 抽象类
FileOutputStream
wirte(int i)写的是字节
write(byte [] b)写一个字节数组
write(byte[] ,int start,int len)写一个字节数组,从哪个位置开始写,写的长度
带有缓冲区的输入输出流:
作用:减少与磁盘的交互次数,提高效率
BufferedInputStream(InputStream)
BufferedOutputStream(OutputStream)