内存与硬盘之间的“往来”:
在内存中我们如果想要将文件写入硬盘中需要一个输出流(OutputStream)执行write()方法来完成,而如果想要将硬盘中的文件写入内存中则需要一个输入流(InputStream)执行read()方法来完成。
流分为很多种,但是可以根据读写的方式分为两大类:
1.字符流
字符流在读取的时候是一次读取一个字符,但是字符流只能读取纯文本文件,甚至连word文件都不能读取,当然图像,语音,视频等文件更无法读取。
2.字节流
字节流读取的时候相对灵活,字节流一次只读取一个字节,但是读取的范围很广,可以读取语音,视频等任何形式的文件。
如何判断一个流是字符流or字节流:
类名以"Stream"结尾的流是字节流
类名以"Reader"或者"Writer"结尾的流是字符流
使用时注意事项:
所有的输出流都是可以刷新的,通过执行flush()方法,将管道中未输出完全的内容全部输出出去,可以理解为刷新或者冲刷管道。
所有的流(任何输入流和输出流)都是可以关闭的,也必须要关闭,这样可以节省很多的资源。通过执行close()方法来关闭流文件。
常用流的举例:
1.FileInputStream 以及 FileOutputStream
FileInputStream:
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class FileInputStreamTest {
public static void main(String[] args) {
FileInputStream fileInputStream = null;
try {
fileInputStream = new FileInputStream("chuiniubi");
//在这里新建一个byte数组,每一次依然读取一个字节,但是我们可以将读到的字节都装在byte数组里面,这样我们就不需要每在硬盘文件中读一个字节就将它传送到内存文件中,减少硬盘与内存中间的"往返次数"
byte[] bytes = new byte[4];
int readCount = 0;
while((readCount = fileInputStream.read(bytes)) != -1){
String s = new String(bytes,0,readCount);
System.out.print(s);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally{
if(fileInputStream != null){
try {
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
运行结果:
这里面所用到的文件"chuiniubi"
FileInputStream中的常用方法:available()
available()方法:测试文件中未被读的字节个数
public class FileInputStreamTest2 { public static void main(String[] args) { FileInputStream fileInputStream = null; try { fileInputStream = new FileInputStream("chuiniubi"); //10 System.out.println("文件的字符数量是多少:"+fileInputStream.available()); //中间读一个字节,所以应该还有9个字节未被读 int count = fileInputStream.read(); //9 System.out.println("现在读完一个了,还剩多少:"+fileInputStream.available()); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally{ if(fileInputStream != null){ try { fileInputStream.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
运行结果:
FileInputStream中常用方法:skip()
skip(int num):跳过几个字节不读
import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; public class FileInputStreamTest3 { public static void main(String[] args) { FileInputStream fileInputStream = null; try { fileInputStream = new FileInputStream("chuiniubi"); //测试skip()函数 跳过几个字节不读:ay //因为这里设定的是2,所以ayuismydog前两个字节不读取 也就是"ay" fileInputStream.skip(2); byte[] bytes = new byte[5]; int readCount = 0; while((readCount = fileInputStream.read(bytes)) != -1){ String s = new String(bytes,0,readCount); //uismydog System.out.print(s); } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally{ if(fileInputStream != null){ try { fileInputStream.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
运行结果:
FileOutputStream:
import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; public class FileOutputStreamTest4 { public static void main(String[] args) { FileOutputStream fileOutputStream = null; try { //fileOutputStream("myfile",append = true) //这里面把append设置为true的话在写文件的时候,就会自动从最后面添加 //不会清空之前的内容 //但是程序执行几次,就会不断在后面添加写入的内容 fileOutputStream = new FileOutputStream("myfile",true); String s = "I love you,Iria."; byte[] bytes = s.getBytes(); fileOutputStream.write(bytes); fileOutputStream.flush(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally{ if(fileOutputStream != null){ try { fileOutputStream.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
运行多次之后的myfile文件
读写操作同时进行:
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class FileInputStreamTest5 {
public static void main(String[] args) {
FileInputStream fileInputStream = null;
FileOutputStream fileOutputStream = null;
try {
fileInputStream = new FileInputStream("D:\\BaiduNetdiskDownload\\Tips.txt");
fileOutputStream = new FileOutputStream("C:\\Tips.txt");
byte[] bytes = new byte[1024 * 1024];//1MB
int readCount = 0;
//边读边写,复制文件的本质就是先将一个文件从硬盘中读取到内存中,然后再将内存中读到的内容写到新的硬盘地址当中。
while((readCount = fileInputStream.read(bytes)) != -1){
fileOutputStream.write(bytes,0,readCount);
}
fileOutputStream.flush();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if(fileInputStream != null){
try {
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(fileOutputStream != null){
try {
fileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
2.FileReader以及FileWriter
FileReader:
import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; /* FileReader: 1.文件字符输入流,只能读取普通文本。 2.读取文本内容时,比较方便,快捷。 */ public class FileReaderTest { public static void main(String[] args) { FileReader reader = null; try { reader = new FileReader("chuiniubi"); char[] chars = new char[4]; int readCount = 0; //一个字符一个字符的读,与之前FileInputStream测试程序不同的是这里需要创建的是char数组,并不是byte数组,因为在读的时候是一个字符一个字符的读。 while((readCount = reader.read(chars)) != -1){ System.out.print(new String(chars,0,readCount)); } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally{ if(reader != null){ try { reader.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
FileWriter:
import java.io.FileWriter; import java.io.IOException; /* FileWriter: 文件字符输出流。写。 只能输出普通文本 */ public class FileWriterTest { public static void main(String[] args) { FileWriter writer = null; try { writer = new FileWriter("myfile2",true); char[] chars = {'我','叫','小','王'}; String s = "我今年15岁了"; writer.write("\r\n"); writer.write(chars); //同样的,这里不需要调用s.getBytes()方法,传入字符,字符串,字符数组都可以。 writer.write(s); writer.flush(); } catch (IOException e) { e.printStackTrace(); } finally{ if(writer != null){ try { writer.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
代码执行多次后myfile2中的内容:
3.BufferedReader以及BufferedWriter(BufferedWriter在转换流部分有提到)
BufferedReader和BufferedWriter代表有缓冲区的字符输入和输出流,也就是说我们在使用这两个流的时候,不需要去自定义字符数组char[],BufferedReader在使用的时候比较特殊,它的构造方法里面需要我们传入一个参数,而这个参数是一个流,可以是FileReader流等别的以Reader结尾的流(因为以Reader结尾的流都实现了Reader接口)。
被当作参数传入的流叫做节点流,需要传入节点流才能实现实例化的流叫做包装流,也叫做处理流,也就是说在这个测试代码中 FileReader是节点流,BufferedReader是包装流。
BufferedReader作为包装流在读的时候,每执行一次readline()方法都可以读一整行,但是每次读到的内容都会在后面不断累加,并不会自动换行。
import java.io.BufferedReader; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; public class BufferedReaderTest { //带有缓冲区的字符流 public static void main(String[] args) { try { //节点流:被当作参数传进去的流叫做节点流 FileReader reader = new FileReader("AppLikeWechat/src/MyServer.java"); BufferedReader br = new BufferedReader(reader); String s = null; //读一整行文本。但是不带换行符号 while((s = br.readLine()) != null){ System.out.println(s); } //对于包装流来说,只需要关闭最外层流就行了,里面的节点流会自动关闭。 br.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } }
运行结果:这里读的时候,我随便选择了一个java文件,而输出结果换行了是因为我在输出的时候选的是System.out.println()函数,有自动换行的功能。
4.InputStreamReader(转换流)
InputStreamReader被称为转换流,如代码所示,传入一个字节流给InputStreamReader,它可以返回一个字符流。
import java.io.*; public class BufferedReaderTest2 { public static void main(String[] args) { BufferedReader br = null; try { //字节流,是转换流的节点流 FileInputStream fis = new FileInputStream("AppLikeWechat/src/MyServer.java"); //转换流:传入一个字节流 返回一个 字符流 //是字节流的包装流,也是缓冲流的节点流 InputStreamReader reader = new InputStreamReader(fis); //是转换流的包装流 br = new BufferedReader(reader); String s = null; 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测试:
import java.io.*; public class BufferedWriterTest { public static void main(String[] args) { BufferedWriter bw = null; try { FileOutputStream fos = new FileOutputStream("myfile4",true); OutputStreamWriter writer = new OutputStreamWriter(fos); bw = new BufferedWriter(writer); String s = "I have no limitations \r\n"; bw.write(s); bw.flush(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { if(bw != null){ try { bw.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
多次运行后结果:myfile4
5.DataOutputStream:
import java.io.DataOutputStream;
import java.io.FileOutputStream;
/*
数据输出流 测试:
这个文件不是普通文档,打不开。
*/
public class DataOutputStreamTest {
public static void main(String[] args) throws Exception{
FileOutputStream fos = new FileOutputStream("data");
DataOutputStream dataOutputStream = new DataOutputStream(fos);
byte a = 10;
int b = 20;
Long c = 30L;
float d = 90;
Double e = 3.14;
char f = '我';
boolean s = true;
dataOutputStream.writeByte(a);
dataOutputStream.writeInt(b);
dataOutputStream.writeLong(c);
dataOutputStream.writeFloat(d);
dataOutputStream.writeDouble(e);
dataOutputStream.writeChar(f);
dataOutputStream.writeBoolean(s);
//冲刷一下
dataOutputStream.flush();
//关闭流
dataOutputStream.close();
}
}
import java.io.FileInputStream; public class DataOutputStreamTest2 { public static void main(String[] args) throws Exception{ FileInputStream fis = new FileInputStream("data"); DataInputStream dataInputStream = new DataInputStream(fis); System.out.println(dataInputStream.readByte()); System.out.println(dataInputStream.readInt()); System.out.println(dataInputStream.readLong()); System.out.println(dataInputStream.readFloat()); System.out.println(dataInputStream.readDouble()); System.out.println(dataInputStream.readChar()); System.out.println(dataInputStream.readBoolean()); dataInputStream.close(); } }
运行结果:
6.PrintStream(标准的字节输出流)
import java.io.FileOutputStream; import java.io.PrintStream; public class PrintStreamTest { public static void main(String[] args) throws Exception{ //标准输出流默认输出到控制台上面,而且不需要手动关闭流 PrintStream ps = System.out; ps.println("123"); ps.println("456"); ps.println("789"); //从这里开始更改流的输出方向 FileOutputStream fos = new FileOutputStream("myfile6"); PrintStream printStream = new PrintStream(fos); System.setOut(printStream); printStream.println("123"); printStream.println("456"); printStream.println("789"); } } 运行结果:
myfile6中的内容:
7.ObjectInputStream以及ObjectOutputStream(对象流)
序列化的过程:将内存中的Java对象和数据信息保存下来的过程,保存到硬盘当中,所以使用的是ObjectOutputStream流,在这个过程中,将Java对象和数据信息有顺序的拆分成很多部分,并且按照顺序存放在硬盘当中。
import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectOutputStream; /* Java对象需要去实现一个Serializable接口。Java虚拟机看到这个标志接口的时候, 会自动为该对象生成一个序列化版本号。 */ public class SeriazaleTest { public static void main(String[] args) throws IOException { //创建java对象 Student s = new Student("123", "王鹏"); //序列化 FileOutputStream fos = new FileOutputStream("students"); ObjectOutputStream oos = new ObjectOutputStream(fos); //序列化对象 oos.writeObject(s); oos.flush(); oos.close(); } }
反序列化的过程:将硬盘中的Java对象和数据信息重新组装的过程,并且恢复到内存当中所以这里使用的是ObjectInputStream流
import java.io.*; public class SerializableTest2 { //反序列化测试 public static void main(String[] args) throws IOException, ClassNotFoundException { //反序列化 FileInputStream fis = new FileInputStream("students"); ObjectInputStream ois = new ObjectInputStream(fis); //反序列化操作 Object o = ois.readObject(); System.out.println(o); //关闭流 ois.close(); } }
运行结果:
这里no都是null是因为,在student对象中,我将no这个属性前面加了transient修饰符,代表”游离的“,不参与序列化的。也就是说在这个过程中,我只序列化student中name这个属性。如果我将no属性前面的transient修饰符去掉的话,那么no的值输出到控制台之后不会为空。
student类如下图所示:
序列化多个对象:一定要使用容器将多个对象装起来,否则会报错。本质其实是序列化这个容器。
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.List;
public class SerializableTest3 {
//序列化多个对象
//一定要使用集合或其他适当容器去存储多个对象,否则会报错。
public static void main(String[] args) throws IOException {
List<Student> stuList = new ArrayList<>();
stuList.add(new Student("123","张三"));
stuList.add(new Student("124","李四"));
stuList.add(new Student("125","王五"));
//序列化
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("students"));
oos.writeObject(stuList);
oos.flush();
oos.close();
}
}
反序列化多个对象:
import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.ObjectInputStream; import java.util.List; public class SerializableTest4 { //多个java对象反序列化 public static void main(String[] args) throws IOException, ClassNotFoundException { ObjectInputStream ois = new ObjectInputStream(new FileInputStream("students")); List<Student> stuList = (List<Student>)ois.readObject(); for(Student stu : stuList){ System.out.println(stu); } ois.close(); } }
运行结果如下:
序列化版本号:
在序列化的过程中,Java虚拟机会自动给每个序列化的对象加上一个序列化版本号,这个序列化版本号是全球唯一的,所以在我们序列化之后,能够通过反序列化将对象返回到内存当中。但是如果我们已经完成了序列化的过程,但是却在后来我们人为的修改了代码,那么再反序列化就无法得到对象了因为Java虚拟机在我们修改代码的时候,就已经将对象换了一个新的版本号了。
所以自动生成版本号有一个缺点就是,我们不能修改之前的代码。
因此我们可以手动的写出序列化版本号,这样我们怎么修改代码都没关系了。
8.Properties属性配置文件:
以.properties结尾的文件,其实是一个map集合里面存储都是按照key=value的形式存储的,并且key和value的类型都是String
import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.util.Properties; public class IOPropertiesTest { public static void main(String[] args) throws IOException { //Java程序在内存中,需要将磁盘中的properties文件读取 //因此,需要一个输入流 FileInputStream fis = new FileInputStream("AppLikeWechat/Student.properties"); //Properties文件就是一个Map集合(key=value,且key和value都是String类型) //我们只需要将Map集合中的内容读出来 Properties properties = new Properties(); //properties加载内容 properties.load(fis); //获取内容 String name = properties.getProperty("name"); System.out.println(name); String birth = properties.getProperty("birth"); System.out.println(birth); String height = properties.getProperty("height"); System.out.println(height); String hobby = properties.getProperty("hobby"); System.out.println(hobby); } }
文件内容:
运行结果: