1.什么是IO
I:Input
O:Output
通过IO可以完成文件的读与写
2.IO流的分类
有多种分类方式:
一种方式是按照流的方向进行方向分类:
以内存作为参照物,
往内存种去,叫做输入(Input)。或者叫做读(Read)。
从内存种出去,叫做输出(Output)。或者叫做写(Write)。
另一种方式是按照读取数据方式不同进行分类:
有的流是按照字节的方式读取数据,一次读取一个字节byte,等同于一次读取8个二进制位。这种流是万能的,什么类型的文件都可以读取。包括:文本文件,图片,声音文件,视频文件
有的流是按照字符的方式读取数据的,一次读取一个字符,这种流是为了方便读取普通文本文件存在的,这种流不能读取:图片,声音,视频等文件,只能读取纯文本文件,连word文件都无法读取
综上所述,流的分类
输入流,输出流
字节流,字符流
3.java种所有的流都是在java.io.*下。
4.java流这块有四大家族:
四大家族首领:
java.io.InputStream 字节输入流
java.io.OutputStream 字节输出流
java.io.Reader 字符输入流
java.io.Writer 字符输出流
四大家族首领都是抽象类(abstract class)。
所有的流都实现了:
java.io.Closeable接口,都是可关闭的,都有close()方法。
流毕竟是一个管道,这个是内存和硬盘之间的通道,用完之后一定要关闭,不然会耗费(占用)很多资源。用完流之后一定要关闭
所有的输出流都实现了:
java.io.Flushable接口,都是可刷新的,都有flush()方法。
输出流在最终输出之后,一定要记得flush()刷新一下。这个刷新表示将通道/管道当中剩余未输出的元素强行输出完(清空管道),刷新的所用就是清空管道
如果没有fluash()可能会导致丢失数据。
在java中只要类名以Stream结尾的都是字节流,以Reader/Writer结尾的都是字符流
5.java.io包下需要掌握的流有16个
文件专属
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.DataInputStream
java.io.DataOutputStream
标准输出流
java.io.PrintWriter
java.io.PrintStream
对象专属流
java.io.ObjectInputStream
java.io.ObjectOutputStream
6.java.io.FileInputStream
- 文字字节输入流,万能的,任何类型的文件都可以采用这个流来读
- 字节的方式,完成输入的操作,完成读的操作(硬盘-->内存)
- 创建文件字节输入流对象
- 构造方法
- FileInputStream(String name)
- 通过打开一个到实际文件的连接来创建一个
FileInputStream
,该文件通过文件系统中的路径名name
指定。- 使用try..catch语句对语句进行捕捉异常
- 在finally语句块当中确保流一定关闭。
- 流不是空。流是null的时候没必要关闭
- 方法
int read()
- 从此输入流中读取一个数据字节。如果没有输入可用,则此方法将阻塞。
- 一位一位读取,每调用一次读取一个字节,读到null返回-1
FileInputStream fis = null; try{ fis = new FileInputStream("D:\\temp"); int readData = 0; while((readData = fis.read()) != -1){ System.out.println(readData); } } catch(FileNotFoundException e){ e.printStackTrace(); } catch(IOException){ e.printStackTrace(); } finally { if(fis != null){ try{ fis.close(); } catch(IOException e){ e.printStackTrace; } } }
int read(byte[] b)
- 从此输入流中将最多
b.length
个字节的数据读入一个 byte 数组中。在某些输入可用之前,此方法将阻塞。- 减少硬盘和内存之间的交互,提高程序的执行效率,往byte[]数组当中读
- 工程Project的根就是IDEA的默认当前路径
- 返回读入缓冲区的字节总数,如果因为已经到达文件末尾而没有更多的数据,则返回
-1
。FileInputStream fis = null; try{ fis = new FileInputStream("D:\\temp"); byte[] bytes = new byte[4]; int readCount while((readCount = fis.read(bytes)) != -1){ //String(byte[] bytes,int offset,int length) //通过使用平台的默认字符集解码指定的 byte 子数组, //构造一个新的 String。新 String 的长度是字符集的函数,因此可能不等于该子数组的长度。 //bytes - 要解码为字符的 byte //offset - 要解码的第一个 byte 的索引 //length - 要解码的 byte 数 System.out.print(new String(bytes, 0, readCount)); } } catch(FileNotFoundException e){ e.printStackTrace(); } catch(IOException){ e.printStackTrace(); } finally { if(fis != null){ try{ fis.close(); } catch(IOException e){ e.printStackTrace; } } }
int available()
- 返回下一次对此输入流调用的方法可以不受阻塞地从此输入流读取(或跳过)的估计剩余字节数。下一次调用可能是同一个线程,也可能是另一个线程。一次读取或跳过此数量个字节不会发生阻塞,但读取或跳过的字节可能小于该数。
- 返回剩余的没有读到的字节数量
long skip(long n)
- 从输入流中跳过并丢弃
n
个字节的数据。
7.java.io.FileOutputStream
- 字节输出流,负责写
- 从内存到硬盘
FileOutputStream(String name)
- 创建一个向具有指定名称的文件中写入数据的输出文件流。创建一个新
FileDescriptor
对象来表示此文件连接。 FileOutputStream(String name, boolean append)
- 创建一个向具有指定
name
的文件中写入数据的输出文件流。如果第二个参数为true
,则将字节写入文件末尾处,而不是写入文件开始处。创建一个新FileDescriptor
对象来表示此文件连接。 void write(byte[] b)
- 将
b.length
个字节从指定 byte 数组写入此文件输出流中。FileOutputStream fos = null; try{ fos = new FileOutputStream("temp"); byte[] bytes = {97,98,99}; fos.write(bytes); fos.flush(); } catch(FileNotFoundException e){ e.printStackTrace(); } catch(IOException){ e.printStackTrace(); } finally { if(fos != null){ try{ fos.close(); } catch(IOException e){ e.printStackTrace; } } }
void write(byte[] b, int off, int len)
- 将指定 byte 数组中从偏移量
off
开始的len
个字节写入此文件输出流。
8.文件的复制
- 使用java.io.FileInputStream和java.io.FileOutputStream完成文件拷贝
- 拷贝过程:一边读一边写
FileInputStream fis = null; FileOutputStream fos = null; try{ //创建输入/输出对象 fis = new FileInputStream("D:\\temp"); fos = new FileOutputStream("temp", true); //边读边写 byte[] bytes = new byte[1024 * 1024]; //一次最多拷贝1Mb int readCount = 0; while((readCount = fis.rade(bytes)) != -1){ fis.write(bytes,0,readCount); } fos.flush(); } catch(FileNotFoundException e){ e.printStackTrace(); } catch(IOException){ e.printStackTrace(); } finally { if(fis != null){ try{ fis.close(); } catch(IOException e){ e.printStackTrace; } } if(fos != null){ try{ fos.close(); } catch(IOException e){ e.printStackTrace; } } }
- 使用以上字节流拷贝文件的时候,文件类型随意,万能的。什么文件都能拷贝
9.java.io.FileReader
- 文件输入流,只能读取普通文本
FileReader reader = null; try{ //创建输入/输出对象 reader = new FileReader("temp"); } char[] chars = new char[4]; //一次读取4个字符 int readCount = 0; while((readCount = reader.read(chars)) != -1){ System.out.print(new String(chars, 0, readCount); } } catch(FileNotFoundException e){ e.printStackTrace(); } catch(IOException){ e.printStackTrace(); } finally { if(reader != null){ try{ reader.close(); } catch(IOException e){ e.printStackTrace; } } }
读取文本内容时,比较方便,快捷
10.java.io.FileWriter
- 文件输出流,负责写
FileWriter out = null; try{ //创建输入/输出对象 out = new FileWriter("temp"); } fos.flush(); char[] chars = {"w","s"}; out.Write(chars); out.flush(); } catch(FileNotFoundException e){ e.printStackTrace(); } catch(IOException){ e.printStackTrace(); } finally { if(out != null){ try{ out.close(); } catch(IOException e){ e.printStackTrace; } } }
只能输出普通文本11.
11.java.io.BufferedReader
- 带有缓冲区的字符输入流
- 使用这个流的时候不需要自定义char数组,或者说不需要自定义byte数组。自带缓冲。
- 当一个流的构造方法中需要一个流的时候,被传进来的流叫做:节点流
- 外部负责包装的这个流,叫做:包装流。还有个名字叫:处理流
String readLine()
- 读取一个文本行。通过下列字符之一即可认为某行已终止:换行 ('\n')、回车 ('\r') 或回车后直接跟着换行。
FileReader reader = new FileReader("temp"); BufferedReader br = new BufferedReader(reader); String s = null; while((s = br.readLine()) != null){ System.out.println(s); } br.close();
对于包装流来说,只需要关闭最外层流就行,里面的节点流回会自动关闭
//InputStreamReader将字节流转换成字符流 BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream("temp"))); String line = null; while((line = br.readLine()) != null){ System.out.print(line); } br.close();
外部负责包装的这个流,叫做:包装流。还有个名字叫:处理流
12.java.io.BufferedWriter
- 带有缓冲的字符输出流
13.java.io.DataOutputStream
- 数据专属流
- 这个流可以将数据连同数据类型一并写入文件
DataOutputStream dos = new DataOutputStream(new FileOutputStream("data")); //写数据 byte b = 100; short s = 200; int i = 300; long l = 400L; float f = 3.0F; double d = 3.14; boolean sex = false; char c = 'a'; //把数据以及数据类型一并写入到文件夹中 doc.writeByte(b); dos.writeShort(s); dos.writeInt(i); dos.writeLong(l); dos.writeFloat(f); dos.writeDouble(d); dos.writeBoolean(sex); dos.writeChar(c); //刷新 dos.flush(); //关闭最外层 dos.close();
这个文件不是普通的文本文档(这个文件使用记事本打不开)
14.java.io.DataInputStream
- 数据字节输入流
DataInputStream dis = new DataInputStream(new FileInputStream("data")); //开始读 byte b = dis.readByte(); short s = dis.readShort(); int i = dis.readInt(); long l = dis.readLang(); float f = dis.readFloat(); double d = dis.readDouble(); boolean sex = 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(sex); System.out.println(c); //关闭最外层 dos.close();
DataOutputStream写的文件,只能使用DataInputStream去读。并且读的时候你需要提前知道写入的顺序。读的数据需要和写的数据一致。才可以正常读取
15.java.io.PrintStream
- 标准的字节输出流。默认输出到控制台
PrintStream ps = System.out; ps.println("hello");
标准输出流不需要手动close()关闭
//标准输出流不再指向控制台,指向"log"文件 PrintStream printStream = new PrintStream(new FileOutputStream("log")); System.setOut(printStream);
可以改变标准输出流的输出方向
16.java.io.File类
- 文件和目录路径名的抽象表示形式。
- File类和四大家族没有关系,所以File类不能完成文件的读和写
- 一个对象有可能对应的是目录,也可能是文件
- File类的常用方法
File(String pathname)
- 通过将给定路径名字符串转换为抽象路径名来创建一个新
File
实例。如果给定字符串是空字符串,那么结果是空抽象路径名。 boolean exists()
- 测试此抽象路径名表示的文件或目录是否存在。
- 当且仅当此抽象路径名表示的文件或目录存在时,返回
true
;否则返回false
boolean createNewFile()
- 当且仅当不存在具有此抽象路径名指定名称的文件时,不可分地创建一个新的空文件。检查文件是否存在,若不存在则创建该文件,这是单个操作,对于其他所有可能影响该文件的文件系统活动来说,该操作是不可分的。
如果指定的文件不存在并成功地创建,则返回
true
;如果指定的文件已经存在,则返回false
boolean mkdir()
- 创建此抽象路径名指定的目录。
- 当且仅当已创建目录时,返回
true
;否则返回false
boolean mkdirs()
- 创建此抽象路径名指定的目录,包括所有必需但不存在的父目录。注意,此操作失败时也可能已经成功地创建了一部分必需的父目录。
- 当且仅当已创建目录以及所有必需的父目录时,返回
true
;否则返回false
String getParent()
- 返回此抽象路径名父目录的路径名字符串;如果此路径名没有指定父目录,则返回
null
。 File getParentFile()
- 返回此抽象路径名父目录的抽象路径名;如果此路径名没有指定父目录,则返回
null
。 String getAbsolutePath()
- 返回此抽象路径名的绝对路径名字符串。
String getName()
- 返回由此抽象路径名表示的文件或目录的名称。该名称是路径名名称序列中的最后一个名称。如果路径名名称序列为空,则返回空字符串。
boolean isDirectory()
- 测试此抽象路径名表示的文件是否是一个目录。
long lastModified()
- 返回此抽象路径名表示的文件最后一次被修改的时间。
- 返回:表示文件最后一次被修改的时间的
long
值,用与时间点(1970 年 1 月 1 日,00:00:00 GMT)之间的毫秒数表示;如果该文件不存在,或者发生 I/O 错误,则返回0L
long length()
- 返回由此抽象路径名表示的文件的长度。如果此路径名表示一个目录,则返回值是不确定的。
File[] listFiles()
- 返回一个抽象路径名数组,这些路径名表示此抽象路径名表示的目录中的文件。
- 获取当前目录下的所有子文件
18.拷贝目录
public static void main(String[] args){ //拷贝源 File srcFile = new File("D:\\Data") //拷贝目标 File destFile = new File("C:\\"); //调用方法拷贝 copyDir(srcFile, desFile); } private static void copyDir(file srcFile, file desFile){ if(srcFile.isFile()){ FileInputStream in = null; FileOutputStream out = null; try{ in = new FileInputStream(srcFile); String path = destFile.getAbsolutepath().endWith("\\") ? destFile.getAbsolutepath() : destFile.getAbsolutepath() + "\\" + srcFile.getAbsolutepath().substring(3); out = new FileOutputStream() byte[] bytes = new byte[1024 * 1024]; //一次复制1Mb int readCount = 0; while((readCount = in.read(bytes)) != -1){ out.write(bytes, 0, readCount); } out.flush(); } } catch(FileNotFoundException e){ e.printStackTrace(); } catch(IOException){ e.printStackTrace(); } finally { if(out != null){ try{ out.close(); } catch(IOException e){ e.printStackTrace; } } if(in != null){ try{ in.close(); } catch(IOException e){ e.printStackTrace; } } } //srcFile是一个文件的话递归结束 return; } //获取源下面的子目录 File[] files = srcFile.listFiles(); for(File file : files){ if(file.isDirectory()){ String srcDir = file.getAbsolutepath(); String desDie = destFile.getAbsolutepath().endWith("\\") ? destFile.getAbsolutepath() : destFile.getAbsolutepath() + "\\" + srcDir.substring(3); file newFile = new File(des.Dir); if(!newFile.exists()){ newFile.mkdirs(); } } copyDir(srcFile, desFile); } }
19.序列化和反序列化
20.序列化
//创建java对象 Student s = new Student(111, "zhangsan"); //序列化 ObjectOutPutStream oos = new ObjectOutputStream(new FileOutPutStream("students")); //序列化对象 oos.writeObject(s); //刷新 oos.flush(); //关闭 oos.close();
- 参与序列化和反序列化的对象,必须实现Serializable接口
- 通过源代码发现Serializable接口只是一个标志接口
public interface Serializable{ }
这个接口当中什么也没有
起到标识的作用,标志的作用,java虚拟机看到这个类实现了这个这个接口,可能会对这个类进行特殊待遇
Serializable这个标志接口是给java虚拟机参考的,java虚拟机看到这个接口之后,会为该类自动生成一个序列化版本号
序列化版本号
java语言中只采用什么机制来区分类
首先通过类名进行比对,如果类名不一样,肯定不是同一个类
如果类名一样,靠序列版本号进行区分
这种自动化生成序列化版本号缺陷
一旦代码确定之后,不能进行后续的修改,因为只要修改,必然会编译,此时会省成全新的序列化版本号,这个时候java虚拟机会认为这是一个全新的类
凡是一个类实现了Serializable接口,建议给该类提供一个固定不变的序列化版本号。这样,以后这个类即使代码修改了,但是版本号不变,java虚拟机会认为是同一个类
建议把序列化版本号手动的写出来,不建议生成
private static final long serialVersionUID = 1L;
21.反序列化
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("students")); //开始反序列化,读 Object obj = ois.readObject(); //反序列化学生对象 System.out.println(obj); ois.clos();
22.序列化多个对象
List<User> userList = new ArrayList<>(); userList.add(new User(1, "zhangsan"); userList.add(new User(2, "lisi"); userList.add(new User(3, "wangwu"); ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("users")); //序列化一个集合 oos.writeObject(userList); oos.flush(); oos.close();
参与序列化的ArrayList集合以及集合中的元素User都需要实现java.io.Serializable接口
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("users")); List<User> userList = (List<User>)ois.readObject(); for(User user : userList){ System.out.println(user); } ois.clos();
transient关键字标识游离的,不参与序列化
22.IO + Properties联合使用
- IO流:文件的读和写
- Properties:是一个Map集合,key和value都是String类型
//新建一个输入流对象 FileReader reader = new FileReader("data"); //新建一个Map集合 Properties pro = new Properties(); //调用Propertise对象的load方法将数据文件加载到Map集合中 pro.load(reader);//文件中的数据顺着管道加载到Map集合中,其中等号左边作key,有边作value String usrname = pro.getProperty("username"); System.out.println(username);
以后经常改变的数据,可以单独写到一个文件中,使用程序动态读取。将来只需要修改这个文件的内容,java代码不需要改动,不需要重新编译,服务器也不需要重启。就可以拿到动态的信息。
类似于以上机制的文件被称为配置文件
并且当配置文件中的内容格式是:"key=value"的时候,我们把这种配置文件叫做属性配置文件
java规范中有要求:属性配置文件建议以.properties结尾,但这不是必须的
这种以.properties结尾的文件在java中被称为:属性配置文件。
其中Properties是专门存放属性配置文件内容的一个类
属性配置文件
注释是"#"
属性配置文件的key重复的话,value会自动覆盖
建议key和value使用=方式,=左边是key,=右边是value
最好不要有空格