把 硬盘 中的文件放到 内存 中,这个过程叫输入 (Intput) / 读 (Read)。
把硬盘中的文件放到内存中,可重新表述为:
①把硬盘中的文件 输入(Intput) 到内存中。
②把硬盘中的文件 读(Read) 进内存中。
- 内存中文件在关机后会丢失 。
- 硬盘中文件在关机后仍存在。
把 内存中的文件放到硬盘中,这个过程叫输出 (Output) / 写 (Write)。
把内存中的文件放到硬盘中,可表述为:
①把内存中的文件 输出 (Output) 到硬盘中。
②把内存中的文件 写 (Write)到硬盘中。
- 不管是输入还是输出,参照物都是内存。
- 从内存出来 (到硬盘中),叫输出。
- 进内存中 (从硬盘进到内存中),叫输入。
I: 输入 (Intput)
O : 输出 (Output)
通过 IO (输入 和 输出)可以完成硬盘文件的读和写。
在读和写的过程中,会产生 数据的流动,叫 IO流 (Input/Output Stream) 。
Java IO 有 四大家族:
四大家族的首领为:
- java.io.**InputStream **(字节输入流)
- java.io.OutputStream(字节输出流)
- java.io.Reader(字符输入流)
- Java.io.Writer (字符输出流)
四大家族的首领都是抽象类 ( abstract class )
在java中:
以类名 “Stream” 结尾的都是字节流。 以 “Reader / Writer” 结尾的都是字符流。
如:
java.io.BufferedReader 为字符流 ,其是java.io.Reader的子类。java.io.FileInputStream 是字节流,其是java.io.InputStream 是子类。
- 所有流实现了java.io.Closeable接口,都是可关闭的,都有close()方法,流毕竟是一个管道,是内存和硬盘之间的通道,用完之后一定要关闭,不然会耗费 (占用) 很多资源。用完流一定要关闭。
- 所有的输出流都实现了java.io.Flushable接口,都是可刷新的,都有flush()方法。输出流在最终输出之后,一定要记得flush()刷新一下。这个刷新表示通道/通道当中剩余的数据强行输出完 (清空管道!),刷新的作用就是清空管道。
如果没有flush()可能导致数据丢失。
按照**流的方向**进行分类分为:
java.io.InputStream : 字节输入流
java.io.OutputStream : 字节输出流字节流输入流 (InputStream) 是 读取字节数据的输入流。
字节流输出流 (InputStream) 是 写入字节数据的输出流。
字节流按照字节的方式读取数据,一次读取一个字节byte ( 即一次读取8个二进制位),字节流是万能的,
什么类型的文件都可以读取。包括文本文件、图片、声音文件、视频文件等。例子如:
假设文件file.txt中有以下字符组合 ,采用字节读的话就是:
a中国bc张三fe
第一次读:读一个字节,读到 ‘a’
第二次读:读一个字节,读 ‘中’的一半
第三次读:读一个字节,读 ‘中’的另一半
第四次读:读一个字节,读 ‘国’的一半
第五次读:读一个字节,读 ‘国’的另一半
第六次读:读一个字节,读 ‘b’
按照 读取数据的方式 进行分类:
java.io.Reader: 字符输入流
java.io.Writer: 字符输出流例子如:
假设文件file.txt中有以下字符组合 ,采用字符流读的话就是:
a中国bc张三fe第一次读:读一个字节,读到 ‘a’
第二次读:读一个字节,读 ‘中’的一半
第三次读:读一个字节,读 ‘中’的另一半
第四次读:读一个字节,读 ‘国’的一半
第五次读:读一个字节,读 ‘国’的另一半
第六次读:读一个字节,读 ‘b’
注意:
- 在windows系统中,一个英文字符‘a’占一个字节,一个中文字符‘中’占两个字节。
- 但在Java,一个英文字符‘a’占二个字节,一个中文字符‘中’占两个字节。(因为‘a’ 字符、‘中’字符 都是char类型,char类型占两个字节)。
- 上面的file.txt文件是windows操作系统上的普通文件,文件中‘a’占一个字节 ,‘中’占两个字节。
- 如果我们用java打开这个file.txt文件,如果用字节流读取,一个字节一个字节 (8个二进制位) 那样读取会识别不了,但如果用字符流进行读取,就可以识别(能一个字符一个字符的进行读取)。
- java.io.FileInputStream :文件字节输入流 (重点掌握)
- java.io.FileOutputStream :文件字节输出流 (重点掌握)
- java.io.FileReader :文件字符输入流
- java.io.FileWriter : 文件字符输出流
java.io.InputStreamReader : 将“字节输入流” 转换为 “字符输入流”
java.io.OutputStreamWriter : 将“字节输出流” 转换为 “字符输出流”
java.io.BufferedReader
java.io.BufferedWriter
java.io.BufferedInputStream
java.io.BufferedoutputStream
java.io.DataInptStream : 字节数据输入流
java.io.DataOutputStream:字节数据输出流
java.io.printWriter
java.io.printStream : (重点掌握)
java.io.ObjectInputStream (重点掌握)
java.io.ObjectOutputStream (重点掌握)
- 从输入流中读取一个字节的数据。
- 文件字节输入流是万能的,任何类型的 文件 都可以用这个流来读,以 字节 的方式完成 读 的操作。
例子如:
public class FileInputStreamTest01 { //文件输入流 public static void main(String[] args) throws IOException { //创建文件字节输入流对象 FileInputStream fis = null; try { fis = new FileInputStream("D:\\file.txt"); int readData; char DataInfo; //读入一个字节的数据(的Unicode值),如果到达文件末尾返回-1 while ((readData = fis.read()) != -1) { DataInfo = (char) readData; //将unicode值转换为具体的字符 System.out.println(DataInfo); } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }finally { //在finally语句中确保流的关闭 if (fis != null) { //关闭流的前提是: 流不是空 ,流是null是没必要关闭 //关闭输入流 fis.close(); } } } }
public class FileInputStreamTest02 { //文件输入流 public static void main(String[] args) throws IOException { FileInputStream fis = null; try { //创建文件字节输入流 fis = new FileInputStream("D:\\file.txt"); while (true) { int readData = fis.read(); if (readData == -1) { break; } char DataInfo = (char) readData; System.out.println(DataInfo); } //改造上面的while语句 int readData = 0; while ((readData = fis.read()) != -1) { } } catch (FileNotFoundException e) { e.printStackTrace(); }finally { if (fis != null) { try { fis.close(); } catch (IOException e) { e.printStackTrace(); } } }
从该输入流中读取最多b.length个字节的数据作为字节数组。
上面的.read( )方法 一次读取一个字节byte,这样内存和硬盘交互太频繁了,基本上时间/资源耗费在交互上,能不能一次读取多个字节呢?
可以,用 read( byte[ ] b ) 方法从输入流中读取b.length个字节数据,且返回一个byte数组 (字节数组 )。
例子如:
//文件字节输入流的 read(byte[] b)方法 : 获得指定长度的字节,存储到字节数组中 public class FileInputStream04 { public static void main(String[] args) { try ( //创建文件字节输入流对象 FileInputStream fis = new FileInputStream("D:\\file.txt")) { //里面有 abcdef //创建存储字节数组,用以存储读取的数据 byte[] bytes = new byte[5]; int bytesReadNum = 0; /** * 从字节数组中逐个输出字节数据 */ //返回值为读到的字节数,如果到字节末尾返回值为-1 while ((bytesReadNum=fis.read(bytes))!= -1) { // 处理读取到的字节数据 (将每个字节数据都转换为char类型数据) for (int i = 0; i < bytesReadNum; i++) { //逐个输出字节数据 System.out.print((char) bytes[i]); //不换行输出 : abcdef } } } catch (IOException e) { e.printStackTrace(); } } }
//文件字节输入流的 read(byte[] b)方法 : 获得指定长度的字节,存储到字节数组中 public class FileInputStream04 { public static void main(String[] args) { try ( //创建文件字节输入流对象 FileInputStream fis = new FileInputStream("D:\\file.txt")) { //里面有 abcdef //创建存储字节数组,用以存储读取的数据 byte[] bytes = new byte[5]; int bytesReadNum = 0; /** * 将获得的字节数组转换为“字符串”且输出 */ //返回值为读到的字节数,如果到字节末尾返回值为-1 //输出内容为: 第一行: abcde 第二行: fg while ((bytesReadNum=fis.read(bytes))!= -1) { System.out.println(new String(bytes,0,bytesReadNum)); //将字节数组转换为字符串 } } catch (IOException e) { e.printStackTrace(); } } }
文件字节输入流的最终工作规范版本:
public class FileInputStream05 { //文件字节输入流-最终版(工作规范版) public static void main(String[] args) { FileInputStream fis = null; try { fis = new FileInputStream("D:\\file.txt"); //准备一个byte数组 byte[] bytes = new byte[5]; while (true) { int readCount = fis.read(bytes); if (readCount==-1) { break; } //如果文件还未到末尾,同时输出上面获得的(字节数组转换成的)字符串 //读到多少个就输出多少个,没有读到就是-1 System.out.println(new String(bytes,0,readCount)); } // while ((bytesReadNum=fis.read(bytes))!= -1) { // System.out.println(new String(bytes,0,bytesReadNum)); //将字节数组转换为字符串 // } } catch (FileNotFoundException e) { } catch (IOException e) { e.printStackTrace(); } finally { if (fis != null) { try { fis.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
注意点:
IDEA中默认的当前路径是Project的根 / 根路径。
返回从输入流中可以读取 / 跳过的剩余字节数的估计值,而不会被下一次调用此输入流的方法阻塞。
例子如:
public class FileInputStream06 { public static void main(String[] args) { FileInputStream fis = null; try { fis = new FileInputStream("D:\\file.txt"); // 其中的内容为: abcdefg (7个字符/7个字节) //读一个字节 int Byte = fis.read(); //从该输入流中可以读取/跳过的字节数 int availableCount = fis.available(); System.out.println("剩下多少个字节没读: "+availableCount); // 6 } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } }
跳过并从输入流中丢弃n个字节的数据。
例子如:
public class FileInputStream07 { public static void main(String[] args) { // skip()方法: 跳过几个字节不读 FileInputStream fis = null; try { fis = new FileInputStream("D:\\file.txt"); // 其中的内容为: abcdefg (7个字符/7个字节) //用skip(long on)方法跳过指定的字节数 fis.skip(3); //读一个字节 int byte_num = fis.read(); System.out.println((char) byte_num); // d (前面已跳过了三个字节) } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } }
将b.length字节从指定的字节数组写入文件输出流。 (将字节数据写入到文件中)
例子1如:在文件开始处写入数据(覆盖)
//在文件开始处写入数据(覆盖) public class FileOutputStreamTest01 { //文件字节输出流 public static void main(String[] args) { try { //假设文件中有:: 123 FileOutputStream fos = new FileOutputStream("D:\\file.txt"); //覆盖 //创建一个byte数组 byte[] bytes = {97,98,99,100,101}; //用文件字节输入流进行存储 fos.write(bytes); //写 abcde 到文件(硬盘)中,文件最后有: abcde } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } }
例子2如:在文件末尾处写入数据(追加)
public class FileOutputStreamTest01 { //文件字节输出流 public static void main(String[] args) { try { //假设文件中有:: 123 FileOutputStream fos = new FileOutputStream("D:\\file.txt",true); //追加 //创建一个byte数组 byte[] bytes = {97,98,99,100,101}; String str = "世界你好!"; byte[] byte_Info =str.getBytes(); //用文件字节输入流进行存储 fos.write(bytes); //写 abcde 到文件(硬盘)中,文件中最后有: 123abcde fos.write(byte_Info); //最后文件中的信息为: 123abcde世界你好! } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } }
将len字节从位偏移量off的指定数组写入此文件的输出流。(将指定范围的字节写入到文件中)
例子如:
public class FileOutputStream02 { public static void main(String[] args) { try { FileOutputStream fos = new FileOutputStream("D:\\file.txt"); //创建一个byte数组 byte[] bytes = {97,98,99,100,101}; //数字分别代表: a b c d e /* 用文件字节输入流进行存储 将len字节从位偏移量off的指定数组写入此文件的输出流。(将指定范围的字节写入到文件中) */ fos.write(bytes,0,3); //写 abc 到文件(硬盘)中 } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } }
- 文件字符输入流,只能读取普通文本,读取文本内容时,比较方便、快捷。(读取文件中的字符)
- 文件字符输入流读取文件中的字符,一个字符一个字符地读取。
读取单个字符。返回值为 int类型数据 (代表该字符的 unicode码 ) ,如果达到文件末尾,则返回-1
例子如:
public class FileReader01 { //文件字符输入流 : 一个一个字符的读取 public static void main(String[] args) { try { //创建文件字符输入流 FileReader fr = new FileReader("D:\\file_reader.txt"); int readCount = 0; //如果到达文件末尾则返回值为-1,否则返回值为字符的unicode值 (int) while ((readCount = fr.read()) != -1) { System.out.print((char)readCount); } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } }
返回多个字符,且将字符存入到字符数组中。返回为读取到的字符个数,如果达到文件末尾,则返回-1。
例子如:
public class FileReader01 { public static void main(String[] args) { FileReader fr =null; try { //创建文件字符输入流 fr = new FileReader("D:\\file_reader.txt"); char[] chars = new char[5]; int readcount = 0; while ((readcount = fr.read(chars)) != -1) { //将获得的字符数组 转换为 字符串 //读取到多少个,则输出多少个 System.out.print(new String(chars,0,readcount)); } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }finally { if (fr != null) { try { fr.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
向文件中写入字符数据。
例子如:
public class FileWriter01 { public static void main(String[] args) { try { FileWriter fw = new FileWriter("D:\\file.txt"); //100, 101, 102 ,103 分别代表字符 d e f g char[] chars = {'中', '国'}; fw.write("世界你好!"); fw.write(100); fw.write(chars); fw.write(chars,0,2); fw.flush(); //刷新该输出流 } catch (IOException e) { e.printStackTrace(); } } }
使用FileInputStream + FileOutputStream完成文件的拷贝。(一边读一边拷贝)
文件字节输入流是万能的,什么类型的文件都能读取。
例子如:
public class FileCopy { //文件复制 public static void main(String[] args) { FileInputStream fis =null; FileOutputStream fos =null; //创建文件输入流 和 文件输出流 ,以此来进行文件复制 try { fis = new FileInputStream("D:\\file1.txt"); // file1.txt中内容为: 世界你好! fos =new FileOutputStream("D:\\file2.txt"); byte[] byte_Info = new byte[1024]; //存储在文件中获得的信息 //从硬盘(文件)中读取文件进内存 int readCount = 0; while ((readCount = fis.read(byte_Info)) != -1) { //存储获得信息进新的文件中 fos.write(byte_Info); //此时 file2.txt中内容为: 世界你好! } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } }
使用FileReader + FileWriter完成文件的拷贝。(一边读一边拷贝)
使用文件字符输入流 和 文件字符输出流,只能拷贝“普通文本”文件
(能用记事本编译的都是“普通文本”文件,不一定都是.txt文件)例子如:
public class FileCopy2 { //使用字符输入流 和 字符输出流 进行文件复制 public static void main(String[] args) throws IOException { FileReader fr = null; FileWriter fw = null; try { fr = new FileReader("D:\\file1.txt"); fw = new FileWriter("D:\\file2.txt"); char[] chars =new char[5]; int readCount = 0; while ((readCount = fr.read(chars)) != -1) { //一遍读取一遍写入新的文件 fw.write(chars,0,readCount); } //刷新 fw.flush(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }finally { if (fr != null) { fr.close(); } if (fw != null) { fw.close(); } } } }
BufferedReader :带有缓冲区的字符输入流。使用这个流不需要自定义char数组、byte数组,自带缓冲。
读取一个文本行。通过下列字符之一即可认为某行已终止:换行 (‘\n’)、回车 (‘\r’) 或回车后直接跟着换行。
如果已到达流末尾,返回值为null 。
readLine( )方法 读取一个文本行,但 不包括换行符。
例子如:
public class BufferedReader01 { // 带有缓冲区的字符输入流 public static void main(String[] args) throws IOException { /** file.txt文件的内容为: (两行数据) : 世界你好!中国中国 1231234helloWorld */ FileReader fr = new FileReader("D:\\file.txt"); //此时其为: 节点流 BufferedReader br = new BufferedReader(fr); //此时其为: 包装流 String s = null; //读取一行数据,遇到"换行符"或"回车符" 会自定结束读取; 如果已到达流末尾,返回值为null while ((s = br.readLine()) != null) { /** * 第一次循环输出: 世界你好!中国中国 * 第二次循环输出: 1231234helloWorld */ System.out.println(s); } //关闭流 br.close(); } }
BufferedReader :带有缓冲区的字符输出流。使用这个流不需要自定义char数组、byte数组,自带缓冲。
例子如:
public class BufferedWriter01 { //缓冲字符流 public static void main(String[] args) { //带有缓冲区的字符输出流 try { BufferedWriter out = new BufferedWriter(new FileWriter("D:\\file.txt")); //开始写(将数据写入到文件中) out.write("helloWorld"); out.write("\n"); out.write("12345"); //刷新 out.flush(); //关闭最外层 out.close(); } catch (IOException e) { e.printStackTrace(); } } }
当一个流的构造方法中需要一个流的时候,这个(作为参数)被传进来的流叫:节点流。
外部负责包装的流叫:包装流 / 处理流。
对应包装流来说,只需要关闭最外层就行,里面的节点流会自动关闭。
例子如:
// 代码中包括了: 包装流、节点流 public class BufferedReader01 { public static void main(String[] args) throws IOException { FileReader fr = new FileReader("D:\\file.txt"); //FileReader :节点流 BufferedReader br = new BufferedReader(fr); //BufferedReader: 包装流 String s = null; while ((s = br.readLine()) != null) { System.out.println(s); } //关闭流 br.close(); } }
当一个流的构造方法中需要一个流的时候,这个(作为参数)被传进来的流叫:节点流。
外部负责包装的流叫:包装流 / 处理流。
对应包装流来说,只需要关闭最外层就行,里面的节点流会自动关闭。
例子如:
// 代码中包括了: 包装流、节点流 public class BufferedReader01 { public static void main(String[] args) throws IOException { FileReader fr = new FileReader("D:\\file.txt"); //FileReader :节点流 BufferedReader br = new BufferedReader(fr); //BufferedReader: 包装流 String s = null; while ((s = br.readLine()) != null) { System.out.println(s); } //关闭流 br.close(); } }
转换流 :将字节流转换为字符流
InputStreamReader : 将字节输入流 转换 为 字符输入流
OutputStreamWriter :将字节输出流 转换为 字符输出流
例子如:
public class InputStreamReader01 { // public static void main(String[] args) throws IOException { FileInputStream fis = new FileInputStream("D:\\file.txt"); //使用转换流 : 将“字节流”转换为“字符流” InputStreamReader reader = new InputStreamReader(fis); /** BufferedReader()构造方法: 参数为: Reader类对象 但上面只有FileInputStream对象,可用“缓冲流”: 将“字节流”转换为“字符流” */ BufferedReader br = new BufferedReader(reader); String s = null; while ((s = br.readLine()) != null) { System.out.println(s); } //关闭流 br.close(); } }
public class InputStreamReader01 { public static void main(String[] args) throws IOException { //合并 BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream("D:\\file.txt"))); String s = null; while ((s = br.readLine()) != null) { System.out.println(s); } //关闭流 br.close(); } }
输出数据专属的流。这个流可以将数据连同数据类型一并 写入文件。
(这个文件不是普通的文本文件,其不能用记事本打开,用记事本打开会乱码)写的文件,只能用DataInputStream ( 数据字节输入流 )去读。并且读的时候需要提前知道写入的顺序。读的顺序和写的顺序一致,才可以正常取出数据。
例子如:
public class DataOutputStream01 { //字节数据输出流 public static void main(String[] args) { try { //创建数据专属的字节输出流 DataOutputStream dos =new DataOutputStream(new FileOutputStream("D:\\file.txt")); byte b =100; short s =200; int i =300; float f =3.0F; long l =400; double d =3.14; boolean istrue =false; char c = 'a'; //把 “数据” 连同 “数据类型” 一并写入文件当中。 dos.writeByte(b); dos.writeShort(s); dos.writeInt(i); dos.writeLong(l); dos.writeFloat(f); dos.writeBoolean(istrue); dos.writeChar(c); //刷新 dos.flush(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } }
用数据流写入的文件,只能用DataInputStream (数据字节输入流)去读。并且读的时候需要提前知道写入的顺序。读的顺序和写的顺序一致,才可以正常取出数据。
例子如:
public class DataInputStream01 { //字节数据输入流 public static void main(String[] args) { try { //创建字节数据输入流 DataInputStream dis = new DataInputStream(new FileInputStream("D:\\file.txt")); byte b=dis.readByte(); short s=dis.readShort(); int i = dis.readInt(); long l = dis.readLong(); float f = dis.readFloat(); double d = dis.readDouble(); boolean istrue = dis.readBoolean(); char c = dis.readChar(); //打印通过“字节数据输入流”得到的数据 System.out.println(b); System.out.println(s); System.out.println(i); System.out.println(l); System.out.println(f); System.out.println(d); System.out.println(istrue); System.out.println(c); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } }
打印输出流 (printStream) : 默认输出到控制台, 标准输出流不需要手动close( )关闭 。
可修改输出方向,将输出方向指向具体的文件,此时不再将信息输出到控制台。(这是日志框架的实现原理)
例子如:
// PrintStream : 打印输出流,默认输出到控制台 public class PrintStream01 { public static void main(String[] args) throws FileNotFoundException { /** * PrintStream : 打印输出流,默认输出到控制台 * (默认的输出方向为: 控制台) */ //联合起来写 System.out.println("Hello,World"); //分开写 //System.out的返回值为 : 打印输出流 PrintStream ps = System.out; ps.println("hello,zhangsan"); ps.println("hello,lisi"); ps.println("hello,wangwu"); /** * 修改打印输出流的 “输出方向” * * 修改输出方法为: D盘下的log文件,打印数据到该文件上 */ //打印输出流不再指向控制台,指向“log”文件 PrintStream ps2 = new PrintStream(new FileOutputStream("D:\\log")); //修改输出方向,将输出方向修改到“log”文件 System.setOut(ps2); //打印输出流,不需要手动close()关闭 } }
通过 修改输出方向,将输出方向指向具体的文件,来编写“日志工具”,收集日志信息。
例子如:
/** * 日志工具 */ public class Logger { /** * 记录日志的方法 */ public static void log(String msg) { try { //创建“打印输出流” : 指向一个日志文件 PrintStream out = new PrintStream(new FileOutputStream("log.txt")); //修改默认输出方法 System.setOut(out); //获得当前时间 Date nowTime = new Date(); //格式化时间 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS"); String strTime = sdf.format(nowTime); System.out.println(strTime+": "+msg); } catch (FileNotFoundException e) { e.printStackTrace(); } } }
日志工具类的测试类 例子:
public class LogTest { public static void main(String[] args) { //测试工具类是否好用 Logger.log("hello!"); Logger.log("调用了System类的gc()方法,建议启动垃圾回收"); // ... } }