1.概念
IO流用来处理设备之间的数据传输
Java对数据的操作是通过流的方式
Java用于操作流的类都在IO包中 (也就是说使用时需要导包)
流按流向分为两种:输入流,输出流。
1.1 流按操作类型分为两种:
字节流 : 字节流可以操作任何数据,因为在计算机中任何数据都是以字节的形式存储的 字节流的抽象父类:
InputStream (字节输入流)
OutputStream (字节输出流)
字符流 : 字符流只能操作纯字符数据,比较方便。字符流的抽象父类:
Reader (字符输入流)
Writer (字符输出流)
1.2 IO程序书写
使用前,导入IO包中的类
使用时,进行IO异常处理
使用后,释放资源
1.2.1 InputStream是一个抽象类 不能直接实例化 所有我们先看看它的子类 FileInputStream 字节输入流
import java.io.FileInputStream; import java.io.IOException; public class Damo1 { public static void main(String[] args) throws IOException { // 为了看得更清楚 暂时先上抛异常 // 1 创建一个流对象 FileInputStream fileInputStream = new FileInputStream("D:\\test.txt"); // 2 从D:\test.txt上读取一个字节 int i = fileInputStream.read(); // 3 这里输出的是一个数字 // (因为硬盘存储的是二进制数 平时打开文件是二进制转十进制 然后十进制数根据字符编码表转换成字符) System.out.println(i); // 4 需要读取完毕需要关闭流 不然会占用系统资源 fileInputStream.close(); } }
疑问 (read()方法读取的是一个字节,为什么返回是int,而不是byte)
因为字节输入流可以操作任意类型的文件,比如图片音频等,这些文件底层都是以二进制形式的存储的,如果每次读取都返回byte,有可能在读到中间的时候遇到111111111那么这11111111是byte类型的-1,我们的程序是遇到-1就会停止不读了,后面的数据就读不到了,所以在读取的时候用int类型接收,如果11111111会在其前面补上24个0凑足4个字节,那么byte类型的-1就变成int类型的255了这样可以保证整个数据读完,而结束标记的-1就是int类型
1.2.2 FileOutputStream 字节输出流 方法write() 一次写出一个字节
import java.io.FileOutputStream; import java.io.IOException; public class Damo3 { public static void main(String[] args) throws IOException { //读取时必须需要文件存在 写出时在路径正确的情况下 文件不存在会自动创建 // 1 创建流 FileOutputStream fileOutputStream = new FileOutputStream("D:\\test.txt"); // 2 写出值 虽然写出的是一个int类型的值 但是到文件上是一个字节,会自动去除前面三个字节 fileOutputStream.write(97); // 3 关闭流 释放资源 fileOutputStream.close(); } }
注意 (每次写入文件时 FileOutputStream默认会将该文件内的内容清空,清空文件内容后再写入数据 )
在实例化FileOutputStream时 使用此构造方法FileOutputStream("bbb.txt",true) 即可解决此问题 该构造方法 会在文件末尾追加数据
import java.io.FileOutputStream; import java.io.IOException; public class Damo3 { public static void main(String[] args) throws IOException { //读取时必须需要文件存在 写出时在路径正确的情况下 文件不存在会自动创建 // 1 创建流 使用在文件末尾追加数据的构造方法 FileOutputStream("D:\\test.txt",true); FileOutputStream fileOutputStream = new FileOutputStream("D:\\test.txt",true); // 2 写出值 虽然写出的是一个int类型的值 但是到文件上是一个字节,会自动去除前面三个字节 fileOutputStream.write(97); // 3 关闭流 释放资源 fileOutputStream.close(); } }
1.3 小练习1 利用FileInputStream与FileOutputStream 复制图片 (复制mp3同理)
import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; public class Damo3 { public static void main(String[] args) throws IOException { //创建输入流对象,关联E:\koala.jpg 文件 FileInputStream fileInputStream = new FileInputStream("E:\\koala.jpg"); //创建输出流对象 关联D:\copy.jpg 文件 FileOutputStream fileOutputStream = new FileOutputStream("D:\\copy.jpg"); int i ; //不断的读取E:\koala.jpg内的每一个字节 -1为文件结束 while ((i=fileInputStream.read())!=-1){ //然后将 读取到的字节输出到D:\copy.jpg fileOutputStream.write(i); } //最后关闭流释放资源 这个很重要 要养成良好的习惯 fileInputStream.close(); fileOutputStream.close(); } }
弊端 :一个字节一个字节读取效率太低
优化: 创建与文件一样大小的字节数组 然后将文件读取到内存 再一次性写到文件上
(ps:不过这种方式也是有弊端的 我们的程序是运行在虚拟机上面的 虚拟机分配到的内存空间有限 要是文件过大 比如一部10G的电影 那么就会有内存溢出的风险 实际开发中并不推荐这种方式)
import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; public class Damo4 { public static void main(String[] args) throws IOException { //创建输入流对象,关联E:\koala.jpg 文件 FileInputStream fileInputStream = new FileInputStream("E:\\koala.jpg"); //创建输出流对象 关联D:\copy.jpg 文件 FileOutputStream fileOutputStream = new FileOutputStream("D:\\copy.jpg"); // 创建与文件一样大小的字节数组 byte[] arr = new byte[fileInputStream.available()]; fileInputStream.read(arr); // 将文件字节数组读到内存中 fileOutputStream.write(arr); //字节数组中的数据写到文件上 //最后关闭流释放资源 fileInputStream.close(); fileOutputStream.close(); } }
标准格式 小数组(创建一个1024整数倍的字节数组 先把文件数据写入字节数组 然后把字节数组内的内容写出到指定文件)
import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; public class Damo5 { //标准格式 public static void main(String[] args) throws IOException { // 1 建立字节输入流与字节输出流 FileInputStream fileInputStream = new FileInputStream("D:\\copy.jpg"); FileOutputStream fileOutputStream = new FileOutputStream("copy.jpg"); // 2 设置用于存放数据的字节数组 字节数组长度默认设置1024的整数倍 byte [] arr = new byte[1024*8]; // 3 定义变量记录 每次读取到的字节数 int len ; // 4 分次将文件内的内容读取到字节数组中 字节数组每次写入新数据会覆盖旧数据 while ((len=fileInputStream.read(arr))!=-1){ // 5 将字节数组内有效的数据写出 fileOutputStream.write(arr,0,len); } // 6 关闭流 fileInputStream.close(); fileOutputStream.close(); } }
2 缓冲字节流 BufferedInputStream和BufferOutputStream
缓冲思想(字节流一次读写一个数组的速度明显比一次读写一个字节的速度快很多,这是加入了数组这样的缓冲区效果,java本身在设计的时候,也考虑到了这样的设计思想(装饰设计模式 等有时间得看看),所以提供了字节缓冲区流)
2.1 BufferedInputStream:
BufferedInputStream内置了一个缓冲区(数组)从BufferedInputStream中读取一个字节时BufferedInputStream会一次性从文件中读取8192个字节, 存在缓冲区中, 返回给程序,程序再次读取时, 就不用找文件了, 直接从缓冲区中获取,直到缓冲区中所有的都被使用过, 才重新从文件中读取8192个
2.2 BufferedOutputStream:
BufferedOutputStream也内置了一个缓冲区(数组)程序向流中写出字节时, 不会直接写到文件, 先写到缓冲区中 直到缓冲区写满, BufferedOutputStream才会把缓冲区中的数据一次性写到文件里
小实例 利用缓冲字节流对文件进行操作
import java.io.*; public class Damo6 { public static void main(String[] args) throws IOException { // 1 建立标准的字节输入输出流 FileInputStream fileInputStream = new FileInputStream("D:\\copy.jpg"); FileOutputStream fileOutputStream = new FileOutputStream("copy1.jpg"); // 1.1 建立对字节流进行包装的字节缓冲流 BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream); BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream); // 2 缓冲字节输入流内置了一个长度为1024*8的字节数组 将文件数据读取到字节数组 int b ; while ((b=bufferedInputStream.read())!=-1){ // 2.1 缓冲字节输出流内置了一个长度为1024*8的字节数组 将文件数据输出到字节数组 bufferedOutputStream.write(b); } // 3 这里关闭流时 只需要关闭字节缓冲流 bufferedInputStream.close(); bufferedOutputStream.close(); } }
疑问 小数组的读写和带Buffered的读取哪个更快?
定义小数组如果是8192个字节大小和Buffered比较的话,定义小数组会略胜一筹,因为读和写操作的是同一个数组,而Buffered操作的是两个数组
2.3 flush和close方法的区别
flush()方法
用来刷新缓冲区的,刷新后可以再次写出(多用于实时刷新)
close()方法
用来关闭流释放资源的的,如果是带缓冲区的流对象的close()方法,不但会关闭流,还会再关闭流之前刷新缓冲区,关闭后不能再写出
3 字节流读取中文的问题(一个中文占两个字节 字节流在读中文的时候有可能会读到半个中文,造成乱码)
FileInputStream fileInputStream = new FileInputStream("D://T.txt"); byte [] b = new byte[2]; int len; while ((len = fileInputStream.read(b))!=-1){ System.out.println(new String(b,0,len)); } fileInputStream.close();
字节流写出中文的问题
字节流直接操作的字节,所以写出中文必须将字符串转换成字节数组
FileOutputStream fileOutputStream = new FileOutputStream("D://T.txt",true); fileOutputStream.write("测试".getBytes());//将字符串转换成字节数组 fileOutputStream.close();
// 写出回车换行 write("\r\n".getBytes());
4 IO流异常处理标准写法
4.1 jdk 1.6 IO流异常处理标准写法
import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; public class Damo_1.6 { //IO流属于对底层进行操作 不能catch它 这样子等于说把问题给隐藏了 以后系统做复杂之后再来处理会很麻烦 //所以要抛出异常 public static void main(String[] args) throws IOException { FileInputStream fileInputStream = null; FileOutputStream fileOutputStream = null; try { fileInputStream = new FileInputStream("D://T.txt"); fileOutputStream = new FileOutputStream("D://tt.txt"); int b; while ((b = fileInputStream.read()) != -1) { fileOutputStream.write(b); } } finally { // try fianlly嵌套的目的是关闭流时能关一个是一个 try { if (fileInputStream != null) fileInputStream.close(); } finally { if (fileOutputStream != null) fileOutputStream.close(); } } } }
4.2 jdk 1.7 IO流异常处理标准写法
格式 try(){}
小括号存放 流对象,中括号存放对流的处理 1.7新特性,流处理完之后流会自动关闭
自动关闭原理 在try()中创建的流对象必须实现了AutoCloseable这个接口,如果实现了,在try后面的{}(读写代码)执行后就会自动调用流对象的close方法将流关掉(新特性开发中用得少,了解即可)
import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; public class Damo1_7 { public static void main(String[] args) throws IOException { //小括号存放 流对象 try ( FileInputStream fileInputStream = new FileInputStream("D;//T.txt"); FileOutputStream fileOutputStream = new FileOutputStream("D://tt.txt"); ) { //中括号存放对流的处理 1.7新特性流处理完之后流会自动关闭 int b; while ((b = fileInputStream.read()) != -1) { fileOutputStream.write(b); } } } }
小练习 图片加密 (将写出的字节异或上一个数,这个数就是密钥,解密的时候再次异或就可以了)
import java.io.*; public class Damo8 { public static void main(String[] args) throws IOException { //字节流套缓冲流 BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream("E://Koala.jpg")); BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream("copy,jpg")); int b; while ((b = bufferedInputStream.read())!=-1){ //加密 你会发现这个时候新图片是打不的 b^123 重复两次会变成它本身 //解密 只需重新读取一次加密的那张图片即可 bufferedOutputStream.write(b^123); } //关闭流 bufferedInputStream.close(); bufferedOutputStream.close(); } }
小练习 在控制台录入文件的路径,将文件拷贝到当前项目下
import java.io.*; import java.util.Scanner; public class Damo9 { /** * 在控制台录入文件的路径,将文件拷贝到当前项目下 * 分析: * 1,定义方法对键盘录入的路径进行判断,如果是文件就返回 * 2,在主方法中接收该文件 * 3,读和写该文件 * @throws IOException */ public static void main(String args[]) throws IOException { // 获取一个File File file = getFile(); //创建缓冲字节输入输出流 BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream(file)); BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(file.getName())); //读取文件与写出文件 int b; while ((b= bufferedInputStream.read())!=-1){ bufferedOutputStream.write(b); } //关闭流 bufferedInputStream.close(); bufferedOutputStream.close(); } // 自定义获取文件方法 public static File getFile() { Scanner sc = new Scanner(System.in); System.out.println("请输入文件路径:"); String line; File file; while (true) { line = sc.nextLine(); file = new File(line); if (!file.exists()) { System.out.println("你录入的文件不存在,请重新输入"); } else if (file.isDirectory()) { System.out.println("你录入的是文件夹路径,请重新输入"); } else { return file; } } } }
小练习 (将键盘录入的数据拷贝到当前项目下的text.txt文件中,键盘录入数据当遇到quit时就退出)
import java.io.BufferedOutputStream; import java.io.FileOutputStream; import java.io.IOException; import java.util.Scanner; public class Damo10 { /** * 将键盘录入的数据拷贝到当前项目下的text.txt文件中,键盘录入数据当遇到quit时就退出 * <p> * 分析: * 1,创建键盘录入对象 * 2,创建输出流对象,关联text.txt文件 * 3,定义无限循环 * 4,遇到quit退出循环 * 5,如果不quit,就将内容写出 * 6,关闭流 * * @throws IOException */ public static void main(String[] args) throws IOException { // 1,创建键盘录入对象 Scanner scanner = new Scanner(System.in); // 2,创建输出流对象,关联text.txt文件 BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream("test.txt")); //3,定义无限循环 while (true) { System.out.println("请输入内容:"); String line; //将键盘录入的数据存储到line中 line = scanner.nextLine(); //4,遇到quit退出循环 if (line.equals("quit")) { System.out.println("输入结束!"); break; } else { // 5,如果不quit,就将内容写出 bufferedOutputStream.write(line.getBytes()); } } // 6,关闭流 bufferedOutputStream.close(); } }
5 字符流 ( 可以直接读写字符的IO流 )
字符流读取字符, 就要先读取到字节数据, 然后转为字符. 如果要写出字符, 需要把字符转为字节再写出.
5.1 FileReader ( FileReader类的read()方法可以所读字符大小自动判断所需读取的字节 )
import java.io.FileReader; import java.io.IOException; public class Damo1 { public static void main(String [] args) throws IOException { //创建输入流对象,关联T.txt FileReader fileReader = new FileReader("T.txt"); int ch;//将读到的字符赋值给ch //通过项目默认的码表读取字符 中文是两个字节读取时发现第一个字节为负数 就一次读取两个字节 while ((ch = fileReader.read())!=-1) { //将读到的字符强转后打印 System.out.print((char) ch); } fileReader.close();//关流 } }
5.2 字符流FileWriter( FileWriter类的write()方法可以自动把字符转为字节写出 )
import java.io.FileWriter; import java.io.IOException; public class Damo2 { public static void main(String[] ares) throws IOException { FileWriter fileWriter = new FileWriter("T.txt",true); fileWriter.write("测试"); //字符串自动转换成字节数组输出 fileWriter.close(); } }
小练习 (字符流的拷贝)
import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; public class Damo3 { public static void main(String[] args) throws IOException { // 1 创建对象 FileReader fileReader = new FileReader("T.txt"); FileWriter fileWriter = new FileWriter("TT.txt"); // 2读取与输出 int ch ; while ((ch = fileReader.read())!=-1){ fileWriter.write(ch); } // 3 关闭流 fileReader.close(); // 注意 Writer类 有一个2k的小缓冲区,如果流不关闭 缓冲区内的数据在缓冲区未满之前不会写到文件上 fileWriter.close(); } }
5.3 什么情况下使用字符流
字符流也可以拷贝文本文件, 但不推荐使用. 因为读取时会把字节转为字符, 写出时还要把字符转回字节(需经过转换 效率慢)
程序需要读取一段文本, 或者需要写出一段文本的时候可以使用字符流 (单程读写使用字符流)
读取的时候是按照字符的大小读取的,不会出现半个中文
写出的时候可以直接将字符串写出,不用转换为字节数组
疑问 字符流是否可以拷贝非纯文本的文件
不可以拷贝非纯文本的文件 因为在读的时候会将字节转换为字符,在转换过程中,可能找不到对应的字符,就会用?代替,写出的时候会将字符转换成字节写出去 如果是?,直接写出,这样写出之后的文件就乱了,看不了了
5.4 自定义字符数组的拷贝
import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; public class Damo4 { public static void main(String[] args) throws IOException { // 1 创建字符流 FileReader fileReader = new FileReader("TT.txt"); FileWriter fileWriter = new FileWriter("T.txt"); // 2 读写文件 // 2.1 创建存放数据的字符数组 char [] arr = new char[1024]; // 2.2 记录读取数据的长度 int len ; // 2.3 将数据读取到字符数组中 while ((len = fileReader.read(arr))!=-1){ //2.4 将数组中的数据写出 fileWriter.write(arr,0,len); } // 3 关闭流 fileReader.close(); fileWriter.close(); } }
5.5 带缓冲的字符流
BufferedReader的read()方法读取字符时会一次读取若干字符到缓冲区, 然后逐个返回给程序, 降低读取文件的次数, 提高效率
BufferedWriter的write()方法写出字符时会先写到缓冲区, 缓冲区写满时才会写到文件, 降低写文件的次数, 提高效率
import java.io.*; public class Damo5 { public static void main(String[] args) throws IOException { // 1 创建带缓冲的字符流 缓冲区空间 8019个字节 8*1024 BufferedReader bufferedReader = new BufferedReader(new FileReader("T.txt")); BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter("TT.txt")); // 2 读写文件 int i; while ((i = bufferedReader.read()) != -1) { bufferedWriter.write(i); } // 3 关闭流 bufferedReader.close(); bufferedWriter.close(); } }
5.6 readLine()和newLine()方法 (读取的时候遇到'\n'或'\r'则认为该行结束)
BufferedReader的readLine()方法可以读取一行字符(不包含换行符号)
BufferedWriter的newLine()可以输出一个跨平台的换行符号"\r\n"
import java.io.*; public class Damo6 { public static void main(String[] args) throws IOException{ // 1 创建带缓冲的字符流 BufferedReader bufferedReader = new BufferedReader(new FileReader("T.txt")); BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter("TT.txt")); // 2 读写文件 String line; while((line = bufferedReader.readLine())!=null){ bufferedWriter.write(line); bufferedWriter.newLine(); //输出跨平台的换行符 } // 3 关闭流 bufferedReader.close(); bufferedWriter.close(); } }
小练习 将文本反转 (将一个文本文档上的每一行文本进行反转,第一行和倒数第一行交换,第二行和倒数第二行交换。。。。)
分析: 1创建输入输出流对象 2创建集合对象 3将读到的数据存储在集合中 4倒着遍历集合将数据写到文件上 5关流
注意事项: 流对象尽量晚开早关
import java.io.*; import java.util.ArrayList; public class Tets1 { /** * @param args 将一个文本文档上的文本反转,第一行和倒数第一行交换,第二行和倒数第二行交换 * <p> * 分析: * 创建输入输出流对象 * 创建集合对象 * 将读到的数据存储在集合中 * 倒着遍历集合将数据写到文件上 * 关流 * @throws IOException 注意事项: * 流对象尽量晚开早关 */ public static void main(String[] args) throws IOException { // 创建集合对象 ArrayList<String> list = new ArrayList<>(); // 传缓冲输入流 将读取到的每一行数据存储到集合 BufferedReader bufferedReader = new BufferedReader(new FileReader("T.txt")); String line; while ((line = bufferedReader.readLine()) != null) { list.add(line); }// 流用完马上关闭 bufferedReader.close(); // 传缓冲输出流 将集合中的元素输出到指定文件 BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter("TT.txt")); for (int i = list.size() - 1; i >= 0; i--) { bufferedWriter.write(list.get(i)); bufferedWriter.newLine(); }// 流用完马上关闭 bufferedWriter.close(); } }
6 LineNumberReader (类成员变量 lineNumber = 0; 修改和查看行号都是通过改变这个数字实现的)
LineNumberReader是BufferedReader的子类, 具有相同的功能, 并且可以统计行号
调用getLineNumber()方法可以获取当前行号
调用setLineNumber()方法可以设置当前行号
import java.io.FileReader; import java.io.IOException; import java.io.LineNumberReader; public class Damo7 { public static void main(String[] args) throws IOException { LineNumberReader lineNumberReader = new LineNumberReader(new FileReader("T.txt")); String line; while((line = lineNumberReader.readLine())!= null){ // 查看行号 System.out.println(lineNumberReader.getLineNumber()+":"+line); } lineNumberReader.close(); } }
7 装饰设计模式 (好处是:耦合性不强,被装饰的类的变化与装饰类的变化无关)
讲个小故事 我们假设有一个普通的软件应届生 他在学校掌握的技能是JavaSE和数据库 这个时候是找不到工作的
那么他只能加强学习JavaWeb与框架部分的内容(也就是经过装饰模式 变得更强)才可以找到合适的工作 我们看看代码
1 设置一个接口(规定所有学生都必须学习技能)
interface Code{ public void code(); }
2 编写一个应届学生类 实现技能接口 描述自己掌握的技能
class Studer implements Code{ @Override public void code() { System.out.println("JavaSE"); System.out.println("MySql"); } }
3 找不到工作 加强学习 (经过装饰后的学生)
class GoodStuder implements Code { //1,获取被装饰类的引用 private Studer s; //2,在构造方法中传入被装饰类的对象 GoodStuder(Studer s){ this.s = s; } //3,加强学习 对原有的技能进行升级 @Override public void code() { s.code(); System.out.println("JavaEE"); System.out.println("ssh"); System.out.println("ssm"); } }
4 调用加强后的学生
public static void main(String[] args) { GoodStuder gs = new GoodStuder(new Studer()); gs.code(); }
这个时候我们可以看到 该学生的经过(装饰模式)加强训练之后已经变强了许多。
8 使用指定的码表读写字符
FileReader是使用默认码表(utf-8)读取文件, 如果需要使用指定码表读取, 那么可以使用InputStreamReader(字节流,编码表)
FileWriter是使用默认码表(utf-8)写出文件, 如果需要使用指定码表写出, 那么可以使用OutputStreamWriter(字节流,编码表)
疑问 为什么用字节流不用字符流 (因为字符流有默认utf-8编码表 读写转码会乱码 ) 下面看看具体操作
import java.io.*; public class Damo9 { public static void main(String [] args) throws IOException { // 注意 utf-8 一个中文3个字节 // 1 建立流 嵌套流高效的读写数据 BufferedReader bufferedReader = new BufferedReader( new InputStreamReader( new FileInputStream("T.txt"),"utf-8" )); BufferedWriter bufferedWriter = new BufferedWriter( new OutputStreamWriter( new FileOutputStream("TT.txt"),"utf-8")); // 2 读写数据 int c ; while ((c = bufferedReader.read())!= -1){ bufferedWriter.write(c); } // 3 关闭流 bufferedReader.close(); bufferedWriter.close(); } }
小练习 (需求 :获取一个文本上每个字符出现的次数,将结果写在TT.txt上)
1,创建带缓冲区的输入流对象
2,创建双列集合对象,目的是把字符当作键,把字符出现的次数当作值
3,通过读取不断向集合中存储,存储的时候要判断,如果不包含这个键就将键和值为1存储,如果包含就将键和值加1存储
4,关闭输入流
5,创建输出流对象
6,将结果写出
7,关闭输出流
import java.io.*; import java.util.HashMap; public class Test2 { public static void main(String[] args) throws IOException { // 创建缓冲字符输入流 关联T.txt文件 BufferedReader bufferedReader = new BufferedReader(new FileReader("T.txt")); // 创建用于统计数据的双列集合 HashMap<Character, Integer> hashMap = new HashMap<>(); // 读取字符 判断字符出现次数 将字符添加到集合中 int ch; while ((ch = bufferedReader.read()) != -1) { // 将数据强制类型转换成字符 char c = (char) ch; //添加字符 (字符,!该字符是否在存在?不存在添加1:已存在则原有数据+1) hashMap.put(c, !hashMap.containsKey(c) ? 1 : hashMap.get(c) + 1); } //读取完毕后及时关闭流 bufferedReader.close(); // 创建缓冲字符输出流 关联TT.txt文件 BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter("TT.txt")); //循环遍历集合 for (Character key : hashMap.keySet()) { //写出集合的键与值 使用switch对换行空格进行处理 switch (key) { case '\t': bufferedWriter.write("\\t" + "=" + hashMap.get(key)); break; case '\n': bufferedWriter.write("\\n" + "=" + hashMap.get(key)); break; case '\r': bufferedWriter.write("\\r" + "=" + hashMap.get(key)); break; default: bufferedWriter.write(key + "=" + hashMap.get(key)); break; } //写出换行 bufferedWriter.newLine(); } //关闭流 bufferedWriter.close(); } }
小练习 (试用版软件)
场景 :当我们下载一个试用版软件,没有购买正版的时候,每执行一次就会提醒我们还有多少次使用机会用学过的IO流知识,模拟试用版软件,试用10次机会,执行一次就提示一次您还有几次机会,如果次数到了提示请购买正版
分析:
1,创建带缓冲的输入流对象,因为要使用readLine方法,可以保证数据的原样性
2,将读到的字符串转换为int数
3,对int数进行判断,如果大于0,就将其--写回去,如果不大于0,就提示请购买正版
4,在if判断中要将--的结果打印,并将结果通过输出流写到文件上
import java.io.*; public class Test3 { public static void main(String[] args) throws IOException { // 创建一个缓冲输入流 关联TT.txt BufferedReader bufferedReader = new BufferedReader(new FileReader("TT.txt")); // 利用缓冲输入流的readLine()方法 读取一整行数据 String line = bufferedReader.readLine(); // 用完随手关闭流 bufferedReader.close(); // 将读取到的数据转换为数字 int number = Integer.parseInt(line); //创建一个输出流 关联TT.txt FileWriter fileWriter = new FileWriter("TT.txt"); // 如果剩余次数>0 输出提示后 将次数-1 后写出数据 if (number > 0) { System.out.println("你还有:" + number + "次使用机会"); number--; fileWriter.write(number + ""); } else { System.out.println("试用机会已用完请购买正版!"); } // 注意 用完一定要关闭流 FileWriter 有一个2k的小缓冲区 不关流数据会存放在缓冲区里 导致数据不能及时写出 fileWriter.close(); } }
9 看看递归 方法自己调用自己(后期可用来遍历文件的层级目录)
递归的弊端:(不能调用次数过多,容易导致栈内存溢出)
递归的好处:(不用知道循环次数)
构造方法是否可以递归调用?(构造方法不能使用递归调用)
递归调用是否必须有返回值?(可以有,也可以没有)
小练习 5的阶乘
5!
5 * 4 * 3 * 2 * 1
5 * fun(4)(代表4!)
4 * fun(3)(代表3!)
3 * fun(2)(代表2!)
2 * fun(1)(代表1!)
public class Test4 { public static void main(String[] args) { System.out.println(fun(5)); //注意调用次数不能过多 不然会导致栈内存溢出 } public static int fun(int num) { if (num == 1) { return num; } else { return num * fun(num - 1); } } }
10 小练习 (需求 利用递归 从键盘输入接收一个文件夹路径,打印出该文件夹下所有的.java文件名)
分析:
getFile()
从键盘接收一个文件夹路径
1,如果录入的是不存在,给与提示
2,如果录入的是文件路径,给与提示
3,如果是文件夹路径,直接返回
printTxtFile(File dir)
打印出该文件夹下所有的.txt文件名
1,获取到该文件夹路径下的所有的文件和文件夹,存储在File数组中
2,遍历数组,对每一个文件或文件夹做判断
3,如果是文件,并且后缀是.java的,就打印
4,如果是文件夹,就递归调用
import java.io.File; import java.util.Scanner; public class Test5 { public static void main(String[] args) { printTxtFile(getFile()); // 调用方法 } // 判断输入路径是否正确 正确返回一个File public static File getFile() { Scanner sc = new Scanner(System.in); // 键盘录入对象 System.out.println("请输入一个文件夹路径:"); while (true) { String line = sc.nextLine(); // 将录入的文件路径存储 File dir = new File(line); // 封装成File对象 if (!dir.exists()) { System.out.println("您输入的文件夹路径不存在,请重新输入"); } else if (dir.isFile()) { System.out.println("您输入的是一个文件,请输入文件夹路径"); } else { return dir; } } } // 打印文件方法 public static void printTxtFile(File dir){ // 将File对象里的文件名存到数组里 File [] subFiles = dir.listFiles(); //遍历数组 对每个File对象进行判断 for (File subFile : subFiles) { //如果subFile是一个以".txt"结尾的文件 打印该对象 if (subFile.isFile()&& subFile.getName().endsWith(".txt")){ System.out.println(subFile); // 如果是文件夹则递归调用 }else if(subFile.isDirectory()){ printTxtFile(subFile); } } } }
11 序列流(了解)
序列流可以把多个字节输入流整合成一个, 从序列流中读取数据时, 将从被整合的第一个流开始读, 读完一个之后继续读第二个, 以此类
11.1使用方式
整合两个: SequenceInputStream(InputStream, InputStream)
import java.io.*; public class Damo10 { public static void main(String [] args) throws IOException{ // 1 创建两个字节输入流 FileInputStream fis1 = new FileInputStream("T.txt"); FileInputStream fis2 = new FileInputStream("TT.txt"); // 2 将其整合成一个序列流输入 SequenceInputStream sequenceInputStream = new SequenceInputStream(fis1,fis2); // 3 创建一个字节输出流 FileOutputStream fos = new FileOutputStream("test.txt"); // 4 用序列输入流读取数据 字节输出流输出数据 int b; while((b = sequenceInputStream.read())!=-1){ fos.write(b); } // 5 注意 因为两个字节输入流已经整合成一个序列输入流了 所以这里只需要关闭一个序列输入流 sequenceInputStream.close(); // 6 关闭字节输出流 fos.close(); } }
整合多个: SequenceInputStream(Enumeration)
import java.io.*; import java.util.Enumeration; import java.util.Vector; public class Damo2 { public static void main(String[] args) throws IOException{ // 1 创建多个字节输入流 关联指定文件 FileInputStream fis1 = new FileInputStream("T.txt"); FileInputStream fis2 = new FileInputStream("TT.txt"); FileInputStream fis3 = new FileInputStream("TT.txt"); // 2 创建vector 集合对象并将流对象添加 Vector<FileInputStream> vector = new Vector<>(); vector.add(fis1); vector.add(fis2); vector.add(fis3); // 3 获取枚举的引用 Enumeration <FileInputStream> en = vector.elements(); // 4 传递给SequenceInputStream构造 SequenceInputStream sis = new SequenceInputStream(en); // 5 创建一个字节输出流 FileOutputStream fos = new FileOutputStream("test.txt"); // 6 序列输入流读取数据 字节输出流写出数据 int b; while((b = sis.read())!=-1){ fos.write(b); } // 7 注意 因为字节输入流已经整合成序列输入流 所以这里关闭序列输入流即可 sis.close(); // 8 关闭输出流 fos.close(); } }
12 内存输出流 ( 该输出流可以向内存中写数据, 把内存当作一个缓冲区, 写出之后可以一次性获取出所有数据)
使用方式
创建对象: new ByteArrayOutputStream()
写出数据: write(int), write(byte[])
获取数据: toByteArray()
应用场景:比如QQ聊天界面发送消息 消息尚未发送出去停留在输入框的时候
import java.io.*; public class ByteArrayOutputStream_Damo { public static void main(String[] args) throws IOException { // 创建字节输入流 FileInputStream fis = new FileInputStream("T.txt"); // 在内存中创建了可以增长的内存数组 ByteArrayOutputStream bos =new ByteArrayOutputStream(); // 用字节输入流读取数据 内存输出流输出数据(输出到内存) int b ; while((b = fis.read())!=-1){ bos.write(b); } //读取结束随手关流 bos.close(); // 获取写到内存中的数据 // 方式1 toByteArray() 得到一个字节数组 byte [] arr = bos.toByteArray(); System.out.println(new String(arr)); // 方式1 toString() 得到一个字符串 System.out.println(bos.toString()); // 注意 ByteArrayOutputStream 内存输出流是把数据写到内存的 故不用关流 } }
小练习 (需求 :定义一个文件输入流,调用read(byte[] b)方法,将T.txt文件中的内容打印出来(byte数组大小限制为5))
分析:
1,reda(byte[] b)是字节输入流的方法,创建FileInputStream,关联T.txt
2,创建内存输出流,将读到的数据写到内存输出流中
3,创建字节数组,长度为5
4,将内存输出流的数据全部转换为字符串打印
5,关闭输入流
import java.io.*; public class Tets1 { public static void main(String[] args) throws IOException { // 1,reda(byte[] b)是字节输入流的方法,创建FileInputStream,关联T.txt FileInputStream fis = new FileInputStream("T.txt"); // 2,创建内存输出流,将读到的数据写到内存输出流中 ByteArrayOutputStream baos = new ByteArrayOutputStream(); //3,创建字节数组,长度为5 byte [] bytes = new byte[5]; int len; while ((len = fis.read(bytes))!=-1){ baos.write(bytes,0,len); } //4,将内存输出流的数据全部转换为字符串打印 System.out.println(baos); //即使没有调用,底层也会默认帮我们调用toString()方法 //5,关闭输入流 fis.close(); } }
13 RandomAccessFile 随机访问流概述和读写数据(了解)
RandomAccessFile类不属于流,是Object类的子类。但它融合了InputStream和OutputStream的功能。
支持对随机访问文件的读取和写入。
常用方法:read(),write(),seek()
应用场景 (多线程下载 安卓用得多)
import java.io.*; public class Damo4 { public static void main(String[] args) throws IOException { RandomAccessFile raf = new RandomAccessFile("T.txt", "rw"); // 在指定设置指针 指定位置读写 raf.seek(5); // 指定在5的位置写入数据 写入数据时不会覆盖原来内容 raf.write(99); /* int x = raf.read(); // 读取数据 System.out.println(x);*/ raf.close(); } }
14 对象操作流ObjecOutputStream)(了解)
该流可以将一个对象写出, 或者读取一个对象到程序中. 也就是执行了序列化(将数据按照某种规定存储 比如玩游戏存档)和反序列化(将数据按照某种规定读取 玩游戏读档)的操作.
注意 :1 写出后文件的是乱码为正常 因为该流将对象转换为字节数组写出 写出到文件后 再根据码表进行翻译 数据会找不到匹配的值
2 要实现序列化的类需实现 Serializable 接口
import java.io.*; import java.util.ArrayList; public class Damo5 { public static void main(String[] args) throws IOException, ClassNotFoundException { // 实例化几个 Person 对象 Person p1 = new Person("小白", 1); Person p2 = new Person("小黑", 33); Person p3 = new Person("小红", 14); Person p4 = new Person("小绿", 35); // 将实例化的Person对象存储进集合 ArrayList<Person> list = new ArrayList<>(); list.add(p1); list.add(p2); list.add(p3); list.add(p4); // 1 创建对象输出流 对象输出流是序列化 把集合写到文件 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("T.txt")); oos.writeObject(list); // 关闭对象输出流 oos.close(); // 2 创建对象输入流 对象输出流是反序列化 把刚才存到文件的集合读取出来 ObjectInputStream ois = new ObjectInputStream(new FileInputStream("T.txt")); //注意 要进行强制类型转换 因为刚才存数据的时候 存的是Object // 这里会抛ClassNotFoundException异常(类不存在异常) ArrayList<Person> readObj = (ArrayList<Person>) ois.readObject(); // 遍历集合 输出对象 for (Person ps : readObj) { System.out.println(ps); } // 关闭对象输入流 ois.close(); } } // 定义Person方法 class Person implements Serializable { private String name; private int age; public Person() { } public Person(String name, int age) { this.name = name; this.age = age; } @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", age=" + age + '}'; } }
15 数据输入输出流(开发用得少 这里顺便了解一下即可)
DataInputStream, DataOutputStream可以按照基本数据类型大小读写数据
例如按Long大小写出一个数字, 写出时该数据占8字节. 读取的时候也可以按照Long类型读取, 一次读取8个字节.
使用方式 DataOutputStream(OutputStream), writeInt(), writeLong()
import java.io.*; public class Damo6 { public static void main(String[] args) throws IOException{ // 数据输出流 输出数据 // 注意 输出完毕查看文件乱码是正常的 因为它输出的时候转换成字节数组 然后文件再根据码表去翻译 有一些找不到会乱码 // 不过没关系 输入流读取的时候会自动转换的 DataOutputStream dos = new DataOutputStream(new FileOutputStream("TT.txt")); dos.writeInt(15); dos.writeInt(18); dos.close(); // 数据输入流 读取数据 DataInputStream dis = new DataInputStream (new FileInputStream ("TT.txt")); System.out.println(dis.readInt()); System.out.println(dis.readInt()); dis.close(); } }
16 打印流的概述和特点
该流可以很方便的将对象的toString()结果输出, 并且自动加上换行, 而且可以使用自动刷出的模式
System.out就是一个PrintStream(打印流), 其默认向控制台输出信息
import java.io.*; public class Damo7 { public static void main(String[] args) { PrintStream ps = System.out; // 获取标准输出流 ps.println(97); //其实底层用的是Integer.toString(x),将x转换为数字字符串打印 ps.write(97); //查询码表 找到对应的a并打印 ps.close(); } }
使用方式
打印: print(), println()
自动刷出: PrintWriter(OutputStream out, boolean autoFlush, String encoding) 没什么用 知道就好
import java.io.*; public class Damo7 { // PrintStream和PrintWriter分别是打印的字节流和字符流 public static void main(String[] args) throws IOException { PrintWriter pw = new PrintWriter(new FileOutputStream("T.txt"), true); // 自动输出功能 不关流把缓冲区内容刷出 只针对的是 println()方法 感觉没什么用 pw.println(97); } }
16.1 标准输入输出流(如果标准输入输出流没有与硬盘上的文件产生联系 那么我们不用关闭它 )
System.in是InputStream, 标准输入流, 默认可以从键盘输入读取字节数据
import java.io.IOException; import java.io.InputStream; public class Damo8 { public static void main(String[] args) throws IOException { InputStream is = System.in; //注意 假设输入48 会输出52 // 原理 int x = is.read(); // 输入48的时候 只会读取一个字节也就是 4 System.out.println(x);// 4 的码表值是52 // 注意 这里的流不用关 因为它没有和硬盘文件产生联系 } }
System.out是PrintStream, 标准输出流, 默认可以向Console(控制台)中输出字符和字节数据
public static void main(String[] args) { PrintStream ps = System.out; // 获取标准输出流 ps.println(97);//其实底层用的是Integer.toString(x),将x转换为数字字符串打印 ps.write(97); //查询码表 找到对应的a并打印
修改标准输入输出流(了解)
修改输入流: System.setIn(InputStream)
修改输出流: System.setOut(PrintStream)
import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.PrintStream; public class Damo8 { public static void main(String[] args) throws IOException { // 改变标准输入流,默认指向键盘,改变后指向T.txt System.setIn(new FileInputStream("T.txt")); // 改变标准输入流,默认指向控制台,改变后指向TT.txt System.setOut(new PrintStream("TT.txt")); // 获取改变后的流 InputStream is = System.in; PrintStream ps = System.out; // 读写数据 int b; while ((b = is.read()) != -1) { ps.write(b); } // 关闭流 is.close(); ps.close(); } }
17 Properties的概述(这个类配合着配置文件使用 将配置文件信息映射成为键值对集合 然后进行修改 )
Properties 类表示了一个持久的属性集。
Properties 可保存在流中或从流中加载。
属性列表中每个键及其对应值都是一个字符串。
Properties作为Map集合的使用
Properties prop = new Properties();
prop.put("abc", 123); // 属性列表中每个键及其对应值都是一个字符串。
System.out.println(prop);
17.1 Properties的特殊功能使用(了解)
Properties的特殊功能
public Object setProperty(String key,String value)
public String getProperty(String key)
public Enumeration<String> stringPropertyNames()
import java.util.Enumeration; import java.util.Properties; public class Damo9 { public static void main(String[] args) { Properties prop = new Properties(); prop.setProperty("name","小白"); prop.setProperty("name2","小白2"); Enumeration <String> en = (Enumeration <String>) prop.propertyNames(); while (en.hasMoreElements()){ String key = en.nextElement(); String value = prop.getProperty(key); System.out.println(key + "=" + value); } } }
17.2 Properties的load()和store()功能
load():加载配置文件 将文件上的键值对读取到集合中
store():将修改后的集合重新写到配置文件
import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.util.Properties; public class Damo10 { public static void main(String[] args)throws IOException { Properties prop = new Properties(); //将文件上的键值对读取到集合中 prop.load(new FileInputStream("config.properties")); // 修改集合中的值 prop.setProperty("tel", "18912345678"); //调用store()将修改后的集合重新写到配置文件 第二个参数是对列表参数的描述,可以给值,也可以给null prop.store(new FileOutputStream("config.properties"), null); System.out.println(prop); } }