1)IO流,什么是io?
I:Input
O:Output
通过IO可以完成硬盘文件的读和写
2)IO流的分类
一:按照流的方向进行分类
以内存为参照物,
往内存中去,叫输入(Input),或者叫读(Read)
从内存中出来,叫输出(Output),或者叫写(Write)
二:按照读取数据方式不同进行分类
第一种方式:有的流是按照字节的方式读取数据,一次读取一个字节byte(相当于一次读取8个二进制位)。这种流是万能的,什么类型的文件都可读取。
第二种方式:有的流是按照字符的方式读取数据的,一次读取一个字符,这种流是为了方便读取普通文本而存在,这种流不能读取:图片、声音、视频等文件。只能读取纯文本。
因此,流的分类为
输入流、输出流
字节流、字符流
注意
‘a’等字符在Windows系统中占用1个字节
‘啊’等中文字符在Windows系统中占用2个字节
3)IO流四大家族
四大家族的首领:
java.io.InputStream 字节输入流
java.io.OutputStream 字节输出流
java.io.Reader 字符输入流
java.io.Writer 字符输出流
四大家族的首领都是抽象类。(abstract class)
所有的流都实现了:
java.io.Closeable接口,都是可关闭的,都有close( )方法。
流毕竟是一个管道,是内存和硬盘之间的通道,用完之后一定要关闭,
不然会耗费(占用)很多资源。所以用完流一定要关闭。
所有的输出流都实现了:
java.io.Flushable接口,都是可刷新的,都有flush( )方法。
输出流在最终输出之后,一定要记得flush( ),刷新一下
&emap;&emap;这个刷新表示将通道/管道当中剩余未输出的数据强行输出完(清空管道)
刷新的作用是清空管道。如果没有flush( )可能会丢失数据
注意:在Java中只要“类名”以Stream结尾的都是字节流;以“Reader/Writer”结尾的都是字符流
4)java.io包下需要掌握的流有16个:
4.1、文件专属:
java.io.FileInputStream (掌握)
java.io.FileOutputStream (掌握)
java.io.FileReader
java.io.FileWriter
FileInputStream
public static void main(String[] args) {
FileInputStream fis=null;
try {
//路径可以写相对路径,也可以绝对路径
fis = new FileInputStream("tempfile"); //创建流对象
int temp;
//该方法没有参数传进时,则一次只读取一个字节,效率较低
//由于内存和硬盘交互太频繁,基本上时间/资源都耗费在交互上面了
//temp=fis.read(); // 返回值是:读取到的“字节”本身。
//System.out.println(temp);
byte[] arr=new byte[1024*1024];
//一次最多读取 arr.length 个字节。1个字节都没有读取到返回-1
//temp=fis.read(arr); //返回值是:读取到的字节数量。(不是字节本身)
while((temp=fis.read(arr))!=-1){
//把byte数组转换成字符串,读到多少个转换多少个。
System.out.println(new String(arr,0,temp));
}
} catch (FileNotFoundException e) {
e.printStackTrace();
}catch(IOException e){
e.printStackTrace();
}finally{
//在finally语句中确保流一定关闭
if(fis!=null){//避免空指针异常
// 关闭流的前提是:流不是空。流是null的时候没必要关闭。
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/*
FileInputStream类的其它常用方法:
int available():返回流当中剩余的没有读到的字节数量
long skip(long n):跳过几个字节不读。
*/
FileOutputStream
public static void main(String[] args) {
FileOutputStream fos=null;
try {
//文件输出流如果指向的文件不存在,会自动创建
//true 表示以追加的方式在文件末尾写入。不会清空原文件内容
fos=new FileOutputStream("E:\\JAVA\\tempfile",true);
String s="HelloWorld";
//将字符串转为数组,全部在写入文件中
fos.write(s.getBytes());
byte[] bytes={97,98,99,100};
//将byte数组一部分写出
fos.write(bytes,0,2);
//写完之后,一定要刷新
fos.flush();
} catch (FileNotFoundException e) {
e.printStackTrace();
}catch(IOException e){
e.printStackTrace();
}finally{
if(fos!=null){
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
使用FileReader + FileWriter 完成文本拷贝
拷贝过程是:一边读一边写
public static void main(String[] args) {
FileReader fr=null;
FileWriter fw=null;
try {
//创建一个输入流对象
fr=new FileReader("E:\\JAVA\\tempfile");
//创建一个输出流对象
fw=new FileWriter("E:\\JAVA\\tempfile",true);
//一边读,一边写
char[] chars=new char[512*1024]; //每次读取1MB
int readCount=0;
//read( )是按一个字符字符来读
while((readCount=fr.read(chars))!=-1){
fw.write(chars,0,readCount);
}
//刷新
fw.flush();
} catch (IOException e) {
e.printStackTrace();
}finally{
//要分开try
//一起try时,其中一个出现异常,可能会影响到另外一个流的关闭
if(fr!=null){
try {
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(fw!=null){
try {
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
4.2、转换流:(将字节流转换成字符流)
java.io.InputStreamReader
java.io.OutputStreamWriter
4.3、缓冲流专属
java.io.BufferdReader
java.io.BufferedWriter
上面两个缓冲流的构造方法中,需要传字符流。
也可以用字节流通过转换流变成字符流,再传进构造方法中
java.io.BufferedInputStream
java.io.BufferedOutputStream
上面两个缓冲流的构造方法中,需要传字节流
注意:
使用带有缓冲区的流不需要自定义char数组,或者不需要自定义byte数组。自带缓冲
BufferedReader的使用
public static void main(String[] args){
BufferedReader br=null;
FileReader fr= null;
try {
fr = new FileReader("E:\\JAVA\\filetemp");
//当一个流的构造方法中需要一个流时,这个被传进来的流叫做:节点流
//外部负责包装的这个流,叫做:包装流,又称:处理流
//当前程序中,FileReader就是节点流,BufferedReader为包装流/处理流
br=new BufferedReader(fr);
String s=null;
//br.readLine( )方法读取一个文本行,但不带换行符
while((s=br.readLine())!=null){
System.out.println(s);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
}catch(IOException e){
e.printStackTrace();
}finally{
//对于包装流来说,只需要关闭最外层流就行,里面的节流会自动关闭(看源代码)
if(br!=null){
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
BufferedWriter的使用
public static void main(String[] args) throws Exception{
BufferedWriter bw=null;
// FileOutputStream fis=new FileOutputStream("E:\\JAVA\\filetemp");
// OutputStreamWriter osw=new OutputStreamWriter(fis);
// bw=new BufferedWriter(osw);
//以上合再一起写
bw=new BufferedWriter(new OutputStreamWriter(new FileOutputStream("E:/JAVA/filetemp",true)));
String s="你好 世界";
bw.write(49); //写入文本的是,对应的ASCII码
bw.newLine();//换行
bw.write(s);
bw.write("\n"); //换行
bw.write(97);
bw.flush(); //刷新
bw.close();
}
4.4、数据流:(包装流)
java.io.DataInputStream
java.io.DataOutputStream
DataOutputStream这个流可以将数据连同数据的类型一起写入文件
注意:这个文件不是普通文本文档。(记事本打不开)
DataOutputStream写的文件,只能使用DataInputStream去读。
并且读的时候需要提前知道写入的顺序。读的顺序需要写的顺序一致,才可以正常取出数据。
4.5、标准输出流:
java.io.PrintWriter
java.io.PrintStream (掌握)
PrintStream:
1.标准的字节输出流,默认输出到控制台
2.标准输出流不需要手动close( )关闭
public static void main(String[] args) throws Exception{
System.out.println("Hello");
//分开写
PrintStream ps=System.out;
ps.println("你好");
// 标准输出流不再指向控制台,指向“tempfile”文件。
PrintStream out=new PrintStream(new FileOutputStream("E:\\JAVA\\tempfile",true));
//写日志信息有用
System.setOut(out);// 修改输出方向,将输出方向修改到"tempfile"文件。
out.println("HelloWorld");
}
4.6、对象流:
java.io.ObjectInputStream (掌握)
java.io.ObjectOutputStream (掌握)
4.6.1、对象的序列化与对象的反序列化
对象的序列化(Serialize)指Java对象的存储到文件中。将Java对象的状态保存下来的过程;
对象的反序列化(Deserialize)指将硬盘上的数据重新恢复到内存中,恢复成Java对象。
注意
- 参与序列化和反序列化的对象,必须实现Serializable接口。
- 并建议将序列化版本号写出来:
private static final long serialVersionUID = 1L; - transient关键字表示游离的,不参与序列化。在反序列化恢复该Java对象时,无法获得该属性值
4.6.2、Java语言是采用什么机制来区分类的?
第一:首先通过类名进行比对,如果类名不一样,肯定不是同一个类。
第二:如果类名一样,靠序列化版本号进行区分。
4.6.3、为什么建议自定序列化版本号?
Java虚拟机看到一个类实现了Serializable接口后,会为该类自动生成一个序列化版本号。
一旦代码确定之后,不能进行后续的修改,因为只要修改,必然会重新编译,此时会生成全新的序列化版本号,这个时候java虚拟机会认为这是一个全新的类。(这样不好)
序列化对象
//注意:
// 参与序列化的ArrayList集合以及集合中的元素User都需要实现 java.io.Serializable接口。
public class test {
public static void main(String[] args) throws Exception{
ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("users"));
List<User> list=new ArrayList<>();
list.add(new User(1,"zhangsan"));
list.add(new User(2, "lisi"));
list.add(new User(3, "wangwu"));
User u=new User(4,"zhaoliu");
// 一次序列化一个对象
oos.writeObject(u);
// 序列化一个集合,相当于一次序列化多个对象
oos.writeObject(list);
oos.flush();
oos.close();
}
}
反序列化对象
ObjectInputStream ois=new ObjectInputStream(new FileInputStream("users"));
List<User> list=new ArrayList<>();
//反序列化一个集合对象
list=(List<User>)ois.readObject();
for(User user:list){
System.out.println(user);
}
ois.close();
5)java.io.File类
- File类不能完成文件的读和写
- 一个File对象有可能对应的是目录,也可能是文件
- File只是一个路径名的抽象表示形式
6)IO和Properties的联合使用
6.1、设计理念:
经常改变的数据,可以单独写到一个文件中,使用程序动态读取。将来只需要修改这个文件的内容,Java代码不需要改动,不需要重新编译,服务器也不需要重启,就能拿到动态的信息。
这种文件被称为配置文件
当配置文件的内容格式是:
key1=value
key2=value
这样时,把这种配置文件叫做属性配置文件
6.2、以 .properties结尾的文件在java中被称为:属性配置文件。
其中Properties是专门存放属性配置文件内容的一个类。
注意:属性配置文件的key重复的话,value会自动覆盖
在属性配置文件中井号是注释
/*
Properties是一个Map集合,key和value都是String类型。
将tempfile文件中的数据加载到Properties对象当中。
*/
// 新建一个输入流对象
FileReader reader = new FileReader("tempfile");
// 新建一个Map集合
Properties pro = new Properties();
// 调用Properties对象的load方法将文件中的数据加载到Map集合中。
pro.load(reader); // 文件中的数据顺着管道加载到Map集合中,其中等号=左边作key,右边作value
// 通过key来获取value
String username = pro.getProperty("username");
System.out.println(username);