一. IO流
(一) IO流概述
1.IO流介绍:
I和O,分别是Input和Output两个单词的缩写,Input是输入,Output是输出。
流:是一种抽象概念,是对数据传输的总称.也就是说数据在设备间的传输称为流
常见的应用: 文件复制、文件上传、文件下载等。
2.IO流分类:
按照数据的流向:
输入流:读数据
输出流:写数据
按照数据类型:
字节流:
字节输入流和字节输出流
字符流:
字符输入流和字符输出流
字节流和字符流的使用场景:
如果操作的是纯文本文件,优先使用字符流
如果操作的是图片、视频、音频等二进制文件,优先使用字节流
如果不确定文件类型,优先使用字节流.字节流是万能的流
3.IO流程序书写流程:
(1) 在操作之前,要导包,java.io包
(2) 在操作流对象的时候,要处理解决异常(IOException)
(3) 在操作完流对象之后,必须关闭资源, 所有流资源的关闭 close();
(二) 字节流
2.1字节流概述
1. 字节流抽象基类:
(1) InputStream:这个抽象类是表示字节输入流所有类的超类
(2) OutputStream:这个抽象类是表示字节输出流所有类的超类
根据交互设备的不同,有不同的具体子类
2.2字节输入流FileInputStream
1、FileInputStream是InputStream一个具体子类,用于和磁盘上的文件进行交互
2、FileInputStream不仅可以一次读取一个字节,也可以一次读取很多个字节;不仅可以读取纯文本文件,也可以读取图片、视频、音频等非纯文本文件。一切数据在计算机中都是以字节的形式在存储和计算
3、构造方法:
FileInputStream(File f):将一个File对象所表示的文件路径封装在一个字节输入流中 FileInputStream(String path):将一个字符串所表示的文件路径封装在一个字节输入流中
注意事项:无论是哪个构造方法,都只能封装文件的路径,封装文件夹路径没有任何意义,因为文件夹本身作为容器不是作为文件数据,所以不能使用流对象进行读写
4. 读取文件的方法:
1) int read():从当前的字节输入流中,读取并返回一个字节,返回值结果int类型, 表示读取到的字节对应的整数结果, 如果返回-1表示证明文件读取完毕(每一个文件都有结束标志,当流资源读取到结束标志,返回-1证明文件读取完毕)
2) int read(byte[] arr):从当前的字节输入流中最多读取arr.length个字节,读取到的字节放置到参数arr数组中,返回值结果int类型, 表示本次读取到的字节个数, 如果读到-1,证明文件读取完毕
注意 : 数组读取效率优于单个字节读取效能
3) void close():关闭该流对象
字节输入流单个字节读取代码
import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; public class FileInputStreamDemo { public static void main(String[] args) throws IOException { // 1. 封装出一个字节输入流: 相当于绑定一个数据源 FileInputStream fis = new FileInputStream("D:\\newFile.txt"); // FileInputStream fis2 = new FileInputStream(new File("D:\\newFile.txt")); /*int first = fis.read(); System.out.println(first);// 97 int second = fis.read(); System.out.println(second);// 98 int thrid = fis.read(); System.out.println((char)thrid);// c int four = fis.read(); System.out.println((char)four);// d int test1 = fis.read(); System.out.println(test1);// -1 int test2 = fis.read(); System.out.println(test2);*/ // len表示每次读取到的字节结果 int len; // 优化: 循环读取文件内容 while((len = fis.read()) != -1){ System.out.print((char)len); } fis.close(); } }
字节数组读取
import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; public class FileInputStreamReadArray { public static void main(String[] args) throws IOException { FileInputStream fis = new FileInputStream("D:\\newFile.txt"); byte[] b = new byte[4]; /*int i = fis.read(b); System.out.println(i);// 4 System.out.println(new String(b));// abcd int i2 = fis.read(b); System.out.println(i2);// 4 System.out.println(new String(b));// 123? int i3 = fis.read(b); System.out.println(i3);// 4 System.out.println(new String(b));// ?>qq int i4 = fis.read(b); System.out.println(i4);// 1 System.out.println(new String(b));// 6>qq int i5 = fis.read(b); System.out.println(i5);// -1 System.out.println(new String(b));// 6>qq*/ // len表示每次读取到的字节的个数 int len; // 优化成循环读取 while((len = fis.read(b)) != -1){ // new String(b,0,len) : 将字节数组的一部分转换成字符串; 将字节数组中的数值参考平台编码表,转换成符 // 拼接字符成字符串 // 将b数组从0索引位置开始,截取len个字节转换成目标字符串 System.out.print(new String(b,0,len)); } fis.close(); } }
2.3字节输出流FileOutputStream
1. FileOutputStream和磁盘做交互的一个字节输出流对象,用于把内存当中的字节输出到磁盘当中去
2. 构造方法:
FileOutputStream(File f):将f描述的路径文件封装在字节输出流对象中
FileOutputStream(String path):将path描述的文件路径封装在字节输出流对象中
FileOutputStream(String path,boolean append):如果第二个参数为true,则字节将写入参数path对应文件的末尾而不是开头(追加写入)
FileOutputStream(File path,boolean append):如果第二个参数为true,则字节将写入参数path对应文件的末尾而不是开头(追加写入)
3. 字节流写数据的方式:
1) void write(int b): 将指定的字节写入此文件输出流一次写一个字节数据
2) void write(byte[] b): 将b.length字节从指定的字节数组写入此文件输出流
3) void write(byte[] b, int off, int len): 把b数组的off索引开始的len个字节通过字节输出流写出
4. 字节流写数据实现换行
(1)windows:\r\n
(2)linux:\n
(3)mac:\r
import java.io.File; import java.io.FileOutputStream; import java.io.IOException; public class FileOutputStreamDemo { public static void main(String[] args) throws IOException { // 注意: 使用输出流资源: 如果只提供文件名称,表示数据覆盖式写入 /* FileOutputStream fos = new FileOutputStream("D:\\0222Java系统班\\test.txt"); //FileOutputStream fos1 = new FileOutputStream(new File("D:\\0222Java系统班\\test.txt")); // 1. 写入单个字节 fos.write(97);// a // 2. 写入字节数组 byte[] b = {65,66,67,68,69}; fos.write(b);// ABCDE // 3. 写入字节数组的一部分: 从指定索引位置开始,写入一定个数 fos.write(b,0,2);// AB // 4. 写入字符串 fos.write("今天下雪了!".getBytes()); fos.close();*/ // 输出流资源,构造方法第二个参数,设计true数值,那么表示未来写入的数据是追加写入 FileOutputStream fos1 = new FileOutputStream("D:\\0222Java系统班\\test.txt",true); fos1.write("\r\n".getBytes()); fos1.write("添加".getBytes()); fos1.close(); } }
2.4文件拷贝
1. 案例 : 复制一张图片或一个视频
例如: 把“D:\1.jpg”复制到“D:\copy.jpg”。(文件可以是任意类型文件)
实现步骤:
复制文件,其实就把文件的内容从一个文件中读取出来(数据源),然后写入到另一个文件中(数据目的)
数据源:
D:\1.jpg --- 读数据 --- InputStream --- FileInputStream
目的地:
D:\copy.jpg --- 写数据 --- OutputStream --- FileOutputStream
import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; public class CopyPicture { public static void main(String[] args) throws IOException { // 1. 定义出字节输入流: 绑定一个数据源 FileInputStream fis = new FileInputStream("D:\\0222Java系统班\\day17\\斗地主分析.png"); // 2. 定义出字节输出流: 绑定数据目的 FileOutputStream fos = new FileOutputStream("D:\\copy.png"); // 3. 复制图片 int len; while((len = fis.read()) != -1){ fos.write(len); } fis.close(); fos.close(); } }
1. 字节流拷贝效率提升
(1) 使用一个字节一个字节拷贝的方式,效率非常低:IO的次数过多,有多少个字节,就要IO两倍的次数。
(2) 提升的思路:一次多读一些数据,一次多写出一些数据
使用FileInputStream中的read(byte[] arr)和 FileOutputStream中的write(byte[] arr)。数组的大小可以准备成和文件一样大。配合InputStream中 的available方法可以获取源文件的字节个数,用于创建数组的大小。
(3) 数组是存储于内存的,内存的大小是有限的,如果文件过大,就无法创建大小相同的数组。
只能考虑使用小一些的数组,每次拷贝源文件的一部分,多拷贝几次。涉及方法: a: InputStream中的read(byte[] arr):将数据读取到数组中,返回本次读到的有效字节的个数,如果返回值为-1,表示本次读到的有效字节个数为0,表示到达了文件末尾
b: OutputStream中的write(byte[] arr):将数组中的所有数据,都写出到了目标文件中
c: OutputStream中的write(byte[] arr, int offset, int len):将数组中的指定部分的数据,写出 到目标文件中(在读取的时候,读到了多少有效字节,就将这么多有效字节写出到目标文件中),一般offset都选择0
(4) 注意事项:最终拷贝方案就是小数组
a: 数组的大小可以任意选择,数组越大,拷贝次数越少,拷贝的效率越高
b: 一般情况,数组的大小使用1024的整数倍,在jdk中喜欢使用1024*8大小作为缓冲区的大小。
import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; public class CopyPictureUseArray { public static void main(String[] args) throws IOException { // 1. 定义出字节输入流: 绑定一个数据源 FileInputStream fis = new FileInputStream("D:\\0222Java系统班\\day17\\斗地主分析.png"); // 2. 定义出字节输出流: 绑定数据目的 FileOutputStream fos = new FileOutputStream("D:\\copy1.png"); byte[] b = new byte[1024]; long begin = System.currentTimeMillis(); // len表示每次读取到的字节个数 int len; // 3. 图片复制 while((len = fis.read(b)) != -1){ fos.write(b,0,len); } long end = System.currentTimeMillis(); System.out.println(end - begin);// 2 fis.close(); fos.close(); } }
2.5缓冲字节流
1. 字节缓冲流介绍:
BufferedOutputStream:是OutputStream的子类, 表示高效字节输出流。流资源在创建时, 提供默认的数组缓冲区, 按照缓冲区数组大小进行写入, 实现效率
BufferedInputStream:是InputStream的子类, 表示高效字节输入流。资源在创建时, 提供默认的数组缓冲区, 按照缓冲区数组大小进行读取, 实现效率
注意:这两个流是包装类型:本身不具备读写的功能,只是在某个具体的流对象的基础上,对其进行加强,例如FileInputStream和FileOutputStream,原本效率较低,加强之后,就效率较高。
2. 构造方法
1) BufferedOutputStream(OutputStream out): 创建字节缓冲输出流对象
2) BufferedInputStream(InputStream in): 创建字节缓冲输入流对象
2.6缓冲字节流实现原理
1. BufferedOutputStream原理分析:
BufferedOutputStream高效的原理:在该类型中准备了一个数组,存储字节信息,当外界调用write方法想写出一个字节的时候,该对象直接将这个字节存储到了自己的数组中,而不刷新到文件中。一直到该数组所有8192个位置全都占满,该对象才把这个数组中的所有数据一次性写出到目标文件中。如果最后一次循环过程中,没有将数组写满,最终在关闭流对象的时候,也会将该数组中的数据刷新到文件中。
2. BufferedInputStream原理分析:
BufferedInputStream高效的原理:在该类型中准备了一个数组,存储字节信息,当外界调用read()方法想获取一个字节的时候,该对象从文件中一次性读取了8192个字节到数组中,只返回了第一个字节给调用者。将来调用者再次调用read方法时,当前对象就不需要再次访问磁盘,只需要从数组中取出一个字节返回给调用者即可,由于读取的是数组,所以速度非常快。当8192个字节全都读取完成之后,再需要读取一个字节,就得让该对象到文件中读取下一个8192个字节了。
import java.io.*; public class BufferedStream { public static void main(String[] args) throws IOException { BufferedInputStream bis = new BufferedInputStream( new FileInputStream("D:\\0222Java系统班\\day17\\视频\\01.day16内容回顾.mp4")); BufferedOutputStream bos = new BufferedOutputStream( new FileOutputStream("D:\\copyday16.mp4") ); long begin = System.currentTimeMillis(); // len表示每次读取到的字节的结果 int len; while((len = bis.read()) != -1){ bos.write(len); } long end = System.currentTimeMillis(); System.out.println(end - begin); bis.close(); bos.close(); } }
2.7flush()方法和close()方法区别
1. close方法会先调用flush方法
2. close方法用于流对象的关闭,一旦调用了close方法,那么这个流对象就不能继续使用了
3. flush只是将缓冲区中的数据,刷新到相应文件中,而不会将流对象关闭,可以继续使用这个流对象。但是如果flush方法使用过于频繁,那么丧失了缓冲区的作用。
import java.io.BufferedOutputStream; import java.io.FileOutputStream; import java.io.IOException; public class TestFlushAndClose { public static void main(String[] args) throws IOException { BufferedOutputStream bos = new BufferedOutputStream( new FileOutputStream("abc.txt") ); // BufferedOutputStream底层是带8192大小字节数组,写数据时候,没有达到8192,存储在 // 底层数组缓冲区,没有向文件中自动同步 bos.write("abcdef".getBytes()); // 1. flush() : 刷新的效果就是将数组缓冲区中的数据, 同步到文件中 // bos.flush(); // 2. close() : 表示流资源关闭,在关闭资源之前,先调用一次flush方法,将缓冲区中的数据进行同步, // 后关闭资源 bos.close(); } }
2.8IO中保证流对象关闭的标准格式
1. 捕获异常和声明异常的原则
1) 如果知道如何处理异常,那么就使用try...catch来进行处理;如果不知道如何处理异常,那么就使用throws来对异常进行声明,或者将当前的异常包装成其他的异常类型,进行声明,因为越到代码的高层,拥有更多的资源、更高的位置和权力,知道如何处理,底层被调用的方法,虽然是出现异常的位置,但是并不知道如何处理。
2) 如果你希望程序出现异常之后,继续运行下去,那么就使用try...catch;如果出现异常之后,希望当前方法的代码停止运行,那么就使用throws
2. IO中保证流对象关闭的格式(jdk1.7之前)
try{
流对象的使用;
}catch(异常类名 变量名){
异常的处理代码;
}finally{
关闭流资源;
}
3. IO中保证流对象关闭的格式(jdk1.7之后)
try (
流对象的创建;
) {
流对象的使用;
}catch(IOException e){
异常处理方式;
}
特点:
流对象使用之后,不需要手动关闭,因为这个格式会在流资源使用完毕后,自动关闭了流对象.
JDK1.7之前异常处理
mport java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; public class IOExceptionDeal { public static void main(String[] args) { byte[] b = new byte[1024]; FileInputStream fis = null; FileOutputStream fos = null; try{ fis = new FileInputStream("D:\\0222Java系统班\\day17\\斗地主分.png"); fos = new FileOutputStream("D:\\copy1.png"); // len表示每次读取到的字节个数 int len; // 3. 图片复制 while((len = fis.read(b)) != -1){ fos.write(b,0,len); } } catch (IOException e) { e.printStackTrace(); }finally{ try { if(fis != null){ fis.close(); } } catch (IOException e) { e.printStackTrace(); }finally{ try { if(fos != null){ fos.close(); } } catch (IOException e) { e.printStackTrace(); } } } } }
JDK1.7之后异常处理
import java.io.*; public class JDK7_IOException { public static void main(String[] args) { /* try( 流资源创建; ){ 可能会发生异常代码; }catch(预计发生异常类型 变量名){ 异常解决方案; } 在JDK1.7版本时, 推出了针对于IO流资源处理新的格式,try小括号中进行资源创建,同时在资源使用完毕之后会自动关闭流资源 */ try( FileInputStream fis = new FileInputStream("abc.txt"); BufferedInputStream bis = new BufferedInputStream(fis); FileOutputStream fos = new FileOutputStream("abcCopy.txt"); BufferedOutputStream bos = new BufferedOutputStream(fos); ){ // len表示每次读取到的字节个数 int len; // 3. 图片复制 while((len = bis.read()) != -1){ bos.write(len); } } catch (IOException e) { e.printStackTrace(); } } }
3.2编码表
1. 什么是字符集:
是一个系统支持的所有字符的集合,包括各国家文字、标点符号、图形符号、数字等
计算机要准确的存储和识别各种字符集符号,就需要进行字符编码,一套字符集必然至少有一套字符编码。
2. 常见字符集有: ASCII字符集、GBXXX字符集、Unicode字符集等。
(1) ASCII字符集:
ASCII:是基于拉丁字母的一套电脑编码系统,用于显示现代英语,主要包括控制字符(回车键、退格、换行键等)和可显示字符(英文大小写字符、阿拉伯数字和西文符号)
基本的ASCII字符集,使用7位表示一个字符,共128字符。ASCII的扩展字符集使用8位表示一个字符,共256字符,方便支持欧洲常用字符。是一个系统支持的所有字符的集合,包括各国家文字、标点符号、图形符号、数字等
(2) GBXXX字符集:
GBK:最常用的中文码表。是在GB2312标准基础上的扩展规范,使用了双字节编码方案,共收录了21003个汉字,完 全兼容GB2312标准,同时支持繁体汉字以及日韩汉字等
(3) Unicode字符集:是计算机科学领域里的一项业界标准,包括字符集、编码方案等。Unicode是为了解决传统的字符编码方案的局限而产生的,它为每种语言中的每个字符设定了统一并且唯一的二进制编码,以满足跨语言、跨平台进行文本转 换、处理的要求。
(4) UTF-8编码:可以用来表示Unicode标准中任意字符,它是电子邮件、网页及其他存储或传送文字的应用中,优先采用的编码。互联网工程工作小组(IETF)要求所有互联网协议都必须支持UTF-8编码。它使用一至四个字节为每个字符编码.
3.3字符输出流
1. 字符流输出流介绍:
Writer: 用于写入字符流的抽象父类
FileWriter: 用于写入字符流的常用子类
2. FileWriter构造方法
1) FileWriter(File file): 根据给定的 File 对象构造一个 FileWriter 对象
2) FileWriter(File file, boolean append): 根据给定的文件名以及指示是否附加写入数据的 boolean 值来构造 FileWriter 对象
3) FileWriter(String fileName): 根据给定的文件名构造一个 FileWriter 对象
4) FileWriter(String fileName, boolean append)
根据给定的文件名以及指示是否附加写入数据的 boolean 值来构造 FileWriter 对象
3. 写的方法:
1) void write(int c): 写一个字符
2) void write(char[] cbuf): 写入一个字符数组
3) void write(char[] cbuf, int off, int len): 写入字符数组的一部分
4) void write(String str): 写一个字符串
5) void write(String str, int off, int len): 写一个字符串的一部分
import java.io.FileWriter; import java.io.IOException; public class FileWriterDemo { public static void main(String[] args) throws IOException { FileWriter fw = new FileWriter("chinese.txt"); //P天a金天下天ok的,我很好k的,我 // 1)void write(int c): 写一个字符 fw.write('P'); fw.write('天'); fw.write(97); // 2)void write(char[] cbuf): 写入一个字符数组 char[] ch = {'金','天','下'}; fw.write(ch); // 3) void write(char[] cbuf, int off, int len): 写入字符数组的一部分 fw.write(ch,1,1);// 天 // 4)void write(String str): 写一个字符串 String s = "ok的,我很好"; fw.write(s); // 5) void write(String str, int off, int len): 写一个字符串的一部分 fw.write(s,1,4);// k的,我 fw.close(); } }
练习:
将键盘录入的用户名和密码保存到本地实现持久化存储
实现步骤:
1.获取用户输入的用户名和密码
2.将用户输入的用户名和密码写入到本地文件中
username = ?
password = ?
3.关流,释放资源
import java.io.FileWriter; import java.io.IOException; import java.util.Scanner; public class FileWriterTest { public static void main(String[] args) throws IOException { Scanner sc = new Scanner(System.in); System.out.println("请输入用户名:"); String name = sc.next(); System.out.println("请输入密码:"); String pssword = sc.next(); FileWriter fw = new FileWriter("userMessage.txt"); fw.write("username = " + name); fw.write("\r\n"); fw.write("psssword = " + pssword); fw.close(); } }