1、什么是IO流?
I:Input 译:读
O:Output 译:写
通过IO流可以完成硬盘文件的读和写。
2、IO流的分类:
有多重分类方式:
一种是按照流的方向进行分类:
以内存作为参照物:
从硬盘往内存中去,叫做输入流(Input),或者叫做读(Reader)。
从内存中出来,叫做输出流(Output),或者叫做写(Writer)。
另一种方式是按照读取数据方式不同进行分类:
有的流是按照字节的方式读取数据,一次读取1个字节(1byte),等同于一次读取8个二进制位。
这种流是“万能流”,什么类型的文件都可以读取,包括:音频、视频、文本、图片。。。。等等。
有的流是按照字符的方式读取数据,一次读取一个字符,这种流是为了方便读取普通文本文件而存在的。
这种流不能读取音频、视频、图片等文件,只能读取纯文本文档,连word文件都无法读取。(word文档不是普通文本文件)
综上所述:流的分类可以分为:
输入流、输出流。
字节流、字符流。
谨记:凡是以Stream结尾的,都是字节流,凡是以Reader/Writer结尾的,都是字符流。
例:InputStreamReader
虽然此流中包含了Stream,但并不是以它结尾,所以该流还是字符流。
此IO流属于:“转换流”。将字节流转换为字符流。
3、java中的IO流都已经写好了
可以暂时不去研究底层的实现原理(反正现在也看不懂源码,不如先学会怎么使用这些流,后期有兴趣再慢慢啃)。
IO流主要掌握:
java中已经提供了哪些流 ?
每个流的特点是什么?
每个流对象上的常用方法有哪些?
java中所有的流都是在 java.io.*; 下。
学习要点:主要研究怎么new流对象、调用流对象哪个方法是读、哪个方法是写、哪个流可以将文件写入到硬盘、哪个流可以将文件读入到内存、如何转换流、如何包装流。
4、javaIO流这里有四大家族:
字节输入/输出流:
java.io.FileInputStream
java.io.FileOutputStream
字符输入/输出流:
java.io.Reader
java.io.Writer
以上四个流为四大家族“首领”,都是抽象类。(abstract)
所有的流都实现了:
java.io.Closeable 接口,接口内有close() 方法,都是可以关闭的。
比方说流是一个管道,内存和硬盘之间的通道,用完之后一定要记得关闭,否则会耗费很多资源,需要养成好的习惯,用完流一定记得关。
所有的输出流都实现了:
java.io.Flushable接口,接口内有flush()方法,所有的输出流都是可以刷新的。
跟上面管道的例子一样,输出流在最终输出之后,一定要记得刷新,否则有可能导致数据的丢失。
(异常中有一个finally() 方法,可以保证流一定可以关闭,需要注意的是如果有多个流需要关闭,建议分开try…catch否则有可能造成有些流关闭不了。)
小结:close()方法是关闭流,输入和输出流都实现了该方法,都可以调用。
flush()方法是刷新流,输出流才需要刷新,确保数据不会丢失。
5、java.io包下需要掌握的流有:
文件专属流:
java.io.FileInputStream
java.io.FileOutputStream
java.io.FileReadet
java.io.FileWriter
转换流:
java.io.InputStreamReadet
java.io.OutputStreamWriter
缓冲流:
java.io.BufferedReader
java.io.BufferdWriter
java.io.BufferedInputStream
java.io.BufferedOutputStream
数据流:
java.io.DataInputStream
java.io.DataOutputStream
标准输出流:
java.io.PrintWriter
java.io.PrintStream
对象专属流:
java.io.ObjectInputStream
java.io.ObjectOutputStream
提示:没有学习IO流的朋友不要因为看到了这么多种类而产生抵触心理,其实只要学懂了前两个文件流中的字节输入流和字节输出流,其他的流就不算难了,因为用法都差不多,只不过有个别的地方用法不一样,多练习就好!
6、java.io.File类:
文件和目录路径名的抽象表示。
可以理解为输入或输出流的路径。
/*
File常用方法
*/
public class FileTest {
public static void main(String[] args) {
//通过将给定的路径名字符串转换为抽象路径名来创建新的File实例。
File file=new File("F:\\homework\\chapter01 第一章");
//两种构造方法其实一样。
String path="F:\\homework\\chapter01 第一章";
File file1=new File(path);
System.out.println(file.getName());//获取该文件的文件名
System.out.println(file.getParent());//获取文件的父路径名
System.out.println(file.getAbsolutePath());//获取该文件的绝对路径
System.out.println(file.exists());// 判断路径是否为空
System.out.println(file.isFile());//判断是否是一个文件
System.out.println(file.isDirectory());//判断是否是一个目录
File [] files=file.listFiles();//返回一个File数组
//返回的是毫秒数,从1970年1月1日0:0:0毫秒开始到现在的时间。
// 需要创建Date时间对象并创建SimpleDateFormat对象初始化时间格式配合使用
long time=file.lastModified();//返回该文件最后的修改时间
// file.createNewFile();//以文件的形式新建
file.mkdir();//以目录的方式新建指定的路径
file.mkdirs();//以多重目录的方式新建指定的路径
}
}
7、对象专属流:
ObjectOutputStream:序列化
ObjectInputStream: 反序列化
序列化:将内存中的文件,保存到硬盘当中,或理解为:写入。
反序列化:将硬盘中的文件,加载到内存当中,或理解为:读。
可以序列化单个对象,也可以序列化多个对象:
/*
例:
序列化单个对象:
*/
public static void main(String[] args) {
//创建java对象
Student s1=new Student(111,"李丽");
//声明序列化对象
ObjectOutputStream oos=null;
try {
//序列化对象
oos=new ObjectOutputStream(new FileOutputStream("Students"));
oos.writeObject(s1);
//刷新流
oos.flush();
} catch (IOException e) {
e.printStackTrace();
}finally{
//关闭流
if (oos != null) {
try {
oos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/*
反序列化单个对象
*/
public class ObjectFileInputStreamTest011 {
public static void main(String[] args) {
ObjectInputStream ofi=null;//声明在这里是为了在下面方便关闭流。
try {
//因ObjectInputStream的参数只能是(InputStream)所以可以new出一个FileInputStream作为一个包装流将文件传入。
ofi=new ObjectInputStream(new FileInputStream("Student1"));
// Object obj=ofi.readObject();
//System.out.println(obj);
//这里的返回类型也可以根据输入流内的文件类型进行强转方便操作
Student s=(Student)ofi.readObject();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally{
//判断流不为空就关闭,养成好习惯。
if (ofi != null) {
try {
ofi.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
序列化多个对象,需要创建一个ArrayList集合,将对象存放到集合当中,然后序列化集合,假如不用集合的话,存储第二个对象会报错。
/*
例:
序列化多个对象:
*/
public static void main(String[] args) {
List<User> userList=new ArrayList<>();
userList.add(new User(11,"王红"));
userList.add(new User(22,"赵敏"));
userList.add(new User(33,"李丽"));
//声明序列化对象
ObjectOutputStream oos=null;
try {
oos=new ObjectOutputStream(new FileOutputStream("Users"));
//将List集合序列化
oos.writeObject(userList);
} catch (IOException e) {
e.printStackTrace();
}finally{
//流不为空就关闭
if (oos != null) {
try {
oos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/*
一次序列化多个对象
*/
public class ObjectOutputStreamTest02 {
public static void main(String[] args) {
//创建ArrayList集合
List<User> userList=new ArrayList<>();
userList.add(new User(11,"王红"));
userList.add(new User(22,"赵敏"));
userList.add(new User(33,"李丽"));
//声明序列化对象。声明在这里方便下面关闭流
ObjectOutputStream oos=null;
try {
//默认保存在项目根路径下
oos=new ObjectOutputStream(new FileOutputStream("Users"));
//将List集合序列化
oos.writeObject(userList);
} catch (IOException e) {
e.printStackTrace();
}finally{
//流不为空就关闭
if (oos != null) {
try {
oos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
注意:不管是序列化单个对象还是多个对象,需要序列化的类型必须实现Serializable接口。
Seriaizable接口属标志接口:
这个接口当中什么代码都没有。
标志接口的作用:
起到标识的作用,标志的作用,java虚拟机看到这个类实现了这个接口,可能会对这个类进行特殊待遇。
Serializable这个标志接口是给java虚拟机参考的,java虚拟机看到这个接口之后,会为该类自动生成 一个序列化版本号。
假如不想让对象中的某一属性参与序列化,可以使用transient关键字修饰。
transient关键字表示游离的,不参与序列化。
如何使用IDEA自动生成序列化版本号:
建议将序列化版本号手动写出来,不建议自动生成。
java虚拟机识别一个类的时候,先通过类名,如果类名一致,再通过序列化版本号,如果一样。则证明是同一个类。
设置修改好了之后,当新建了自定义类型想手动给定序列化版本号的话,光标停到类名上,按Alt+回车,第一个选项就是,可以使用电脑自动生成的数值,也可以自定义数值,两者都可以!
为什么要使用序列化版本号?
假如某年写了某些代码,现在需要作出修改,如果没有自定义序列号版本号的话,修改后的代码重新编译之后会自动生成新的序列化版本号,这时java虚拟机就会认为这跟原本的代码不一致,是两个程序,但这就违背了程序员的本意,它其实还是本来的代码本来的程序,但自定义序列版本号后,即使程序作出了修改和重新编译,但序列化版本号还是和原来保持一致,这时java会认为是同一代码,同一个类。
8、IO+Properties集合的联合使用:
设计理念:
以后经常改变的数据,可以单独写到一个文件中,使用程序动态读取。
将来只需要修改这个文件的内容,java代码不需要改动,不需要重新编译,服务器也不需要重启。
就可以拿到动态信息。
类似于以上机制的这种文件被称为配置文件。
并且当配置文件中的内容格式是:
key1=value;
key2=value;
的时候,这类文件被称为“属性配置文件”。
java规范中要求:属性配置文件建议以.properties结尾,但这不是必须的,只是建议使用!
java中Properties是专门存放属性配置文件内容的一个类。
public class IpPropertiesTest01 {
public static void main(String[] args) {
//输入流
FileInputStream fis=null;
try {
fis=new FileInputStream("chapter23/userinfo.properties");
//创建Map集合
Properties pt=new Properties();
//调用Properties集合的load()方法,将文件中的数据加载到Map集合中
//pt.load();形参可以为Reader,也可以为InputStream.
//lad()方法会将文件中等号“=”的左边作为key的值,右边作为value的值(也可以使用分号 “:”)分隔key和value
pt.load(fis);
//通过key获取value的值
String username=pt.getProperty("username");
System.out.println(username);
String password=pt.getProperty("password");
System.out.println(password);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (InvalidPropertiesFormatException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally{
//流不等于空,使用后关闭
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
配置文件示例:
总结:IO流章节很重要,建议各个流都多练习几次,各个常用方法可以背不会,但在IO流使用的过程中(需要某个方法时)通过 “.” 的形式,看到某个方法时要大概知道是干什么用的。
建议,记方法的中文翻译,这样比较好记。
当需要使用某个方法时:
一、在IDEA中直接使用 " . " 来寻找
二、可以查API帮助文档
三、可以借助“有道”等翻译软件,直接输入汉字看翻译,看翻译可以记起大概怎么写。
另外:在写代码时,用到某些方法不要只输入前几个字母靠IDEA的提示直接回车选择方法,除非你能够将方法名全部背下,否则建议手动把方法名写全,帮助和加深记忆。