八. IO流
-
I:输入(Input)、输入流(InputStream)
O:输出(Output)、输出流(OutputStream)
-
IO流的分类:
-
按照流的方向分类:
输入流:从硬盘读取到内存中(Input、Read)
输出流:从内存写入到硬盘中(Output、Write)
-
按照读取数据方式不同分类:
字节流:按字节方式读取数据,一次读取1字节byte(等同于一次读取8个二进制位)。这种流是万能的,可以读取任何类型的文件。
字符流:按字符方式读取文件,一次读取一个字符。这种流只能读取文本文件,不能读取图片、音频、视频等文件。
例如:读取文本文件内容"a字节se"。字节流第一次读取’a’字符,第二次读取’中’字符的一半,第三次读取’中’字符的另一半;字符流第一次读取’a’字符,第二次读取’中’字符。('a’字符在Windows系统中占1个字节,'中’字符在Windows系统中占2个字节)
-
-
Java中所有的流都在java.io.*;下
java.io.InputStream 字节输入流
java.io.OutputStream 字节输出流
java.io.Reader 字符输入流
java.io.Writer 字符输出流
- 以上四种都是抽象类,都实现了java.io.Closeable接口(可关闭的),都有close()方法。流是内存和硬盘间的通道,占用资源,用完后一定要关闭
- 所有输出流都实现了java.io.Flushable接口(可刷新的),都有flush()方法。输出流在最终输出后要调用flush()方法将通道中数据输出完/清空管道(不使用flush()方法可能导致数据丢失)
类名以Stream结尾的都是字节流;以Reader/Writer结尾的都是字符流
-
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
-
-
java.io.FileInputStream(掌握)
int read()方法:返回下一个字节的ASC码,若为空返回-1
FileInputStream fis = null;
try {
fis = new FileInputStream("C:\\Users\\10434\\Desktop\\a"); //绝对路径
/*
IDEA自动把路径中的“\“变成”\\“,也可以采用”/“:
fis = new FileInputStream("C:/Users/10434/Desktop/a");
文件a内容:abc
*/
for (int i = 0; i < 5; i++) {
System.out.println(fis.read()); //输出97 98 99 -1 -1
}
/*改进版:
int readData = 0;
while ((readData = fis.read()) != -1){
System.out.println(readData);
}*/
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) { //read()方法的异常
e.printStackTrace();
} finally { //finally语句块确保流一定关闭
if (fis != null) { //避免空指针异常
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
int read(byte[] b)方法:返回读取到的字节数量。一次读取b.length个字节,减少硬盘和内存的交互,提高执行效率
//IDEA默认当前路径是工程Project的根
fis = new FileInputStream("VideoLearn/src/IO/tempFile"); //文件内容:abcdef
byte[] bytes = new byte[4]; //定义长度为4的byte数组
int readCount = fis.read(bytes);
System.out.println(readCount); //输出4,第一次读取到4个字节
System.out.println(new String(bytes)); //abcd
readCount = fis.read(bytes);
System.out.println(readCount); //输出2,第二次读取到2个字节
System.out.println(new String(bytes)); //efcd
readCount = fis.read(bytes);
System.out.println(readCount); //输出-1,没有读取到任何字节返回-1
System.out.println(new String(bytes)); //efcd
//改进版:
public class FileInputStreamTest03 {
public static void main(String[] args) {
FileInputStream fis = null;
byte[] bytes = new byte[4];
try {
fis = new FileInputStream("VideoLearn/src/IO/tempFile");
int readCount = 0;
while ((readCount = fis.read(bytes)) != -1){
System.out.print(new String(bytes, 0, readCount)); //构造方法
} //输出abcdef
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fis != null){
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
int available()方法:返回流中剩余(没有读到的)字节数量
fis = new FileInputStream("VideoLearn/src/IO/tempFile");
/*System.out.println(fis.available()); //输出6
fis.read();
System.out.println(fis.available());*/ //输出5
//这种方法不适合大文件,因为byte[]数组不能太大
byte[] bytes = new byte[fis.available()];
fis.read(bytes);
System.out.println(new String(bytes)); //输出abcdef
long skip(long n)方法:跳过n个字节不读
fis.skip(2); //前两个字节是97 98
System.out.println(fis.read()); //输出99
-
java.io.FileOutputStream(掌握)
FileOutputStream(String name)构造方法:将原文件清空,然后写入
void write(byte[] b)方法:将byte数组全部写出
FileOutputStream fos = null;
byte[] bytes = {97, 98, 99, 100};
try {
fos = new FileOutputStream("VideoLearn/src/IO/tempFile0");
fos.write(bytes); //abcd
fos.write(bytes, 0, 2); //abcdab
fos.flush(); //输出流一定要刷新
}//catch语句块、finally语句块
FileOutputStream(String name, boolean append)构造方法:在原文件末尾追加写入,不清空原有内容
fos = new FileOutputStream("VideoLearn/src/IO/tempFile0", true);
fos.write(bytes); //abcd
fos.write(bytes); //abcdabcd
String s = "查拉图斯特拉如是说";
byte[] bs = s.getBytes(); //转换为byte数组
fos.write(bs); //abcdabcd查拉图斯特拉如是说
- 文件拷贝
FileInputStream fis = null;
FileOutputStream fos = null;
byte[] bytes = new byte[1024 * 1024];
try {
fis = new FileInputStream("C:\\Users\\10434\\Desktop/full_site.zip");
fos = new FileOutputStream("D:/full_site.zip");
int readCount = 0;
while ((readCount = fis.read(bytes)) != -1){
fos.write(bytes, 0, readCount); //边读边写
}
fos.flush();
} //catch语句块、finally语句块中两个close()方法要分别try/catch
FileReader fr = null;
try {
fr = new FileReader("VideoLearn/src/IO/tempFile");
char[] chars = new char[4];
int readCount = 0;
while ((readCount = fr.read(chars)) != -1){
for (char c : chars){
System.out.print(new String(chars, 0, readCount)); //abcdef
}
}
}
FileWriter fw = null;
try {
fw = new FileWriter("VideoLearn/src/IO/tempFile1", true);
char[] chars = {'非', '常', '棒'};
String s = "realheisenberg";
fw.write(chars);
fw.write(s);
}
-
字符流文件复制与字节流相同
-
BufferedReader
带有缓冲区的字符输入流,不需要定义char/byte数组
BufferedReader(Reader in)构造方法
String readLine()方法:读一行(不包括换行符)
/*
一个流的构造方法需要传入的流称为节点流
外部负责包装的流称为包装流/处理流
以下程序FileReader是节点流,BufferedReader是包装流
*/
FileReader fr = new FileReader("tempFile");
BufferedReader br = new BufferedReader(fr);
String s;
while ((s = br.readLine()) != null){ //判断条件是String不为空
System.out.println(s);
}
br.close(); //只需要关闭最外层流,里面的节点流会自动关闭
- InputStreamReader:将字符流转换为字节流
FileInputStream fis = new FileInputStream("tempFile");
//fis是节点流,isr是包装流
InputStreamReader isr = new InputStreamReader(fis);
//isr是节点流,br是包装流
BufferedReader br = new BufferedReader(isr);
//或者写成:
BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream("tempFile")));
br.close(); //只需要关闭最外层
BufferedWriter bf = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("newFile", true)));
bf.write("excited!");
bf.write("\n");
bf.write("吼哇!");
bf.flush();
-
DataOutputStream、DataInputStream
DataOutputStream:将数据和数据的类型一并写入文件(这个文件不是普通文本文档)
DataOutputStream写的文件只能用DataInputStream去读,且读取的顺序必须和写入的顺序相同
//构造方法必须传OutputStream类型
DataOutputStream dos = new DataOutputStream(new FileOutputStream("data"));
byte b = 20;
short s = 50;
int i = 100;
//long、float、double、boolean、char同理
dos.writeByte(b);
dos.writeShort(s);
dos.writeInt(i);
dos.flush();
dos.close();
DataInputStream dis = new DataInputStream(new FileInputStream("data"));
byte b = dis.readByte();
short s = dis.readShort();
int i = dis.readInt();
System.out.println(b);
System.out.println(s);
System.out.println(i);
dis.close();
PrintStream ps = System.out;
ps.println("hello world"); //标准输出流不需要手动关闭
//等同于System.out.println("hello world");
//改变输出方向
PrintStream printStream = new PrintStream(new FileOutputStream("log"));
System.setOut(printStream);
System.out.println("heisenberg"); //输出到“log”文件
public class LogTest01 {
public static void main(String[] args) {
MyLog myLog = new MyLog("用户登录");
}
}
class MyLog{ //日志工具
public MyLog(String s){
try {
PrintStream out = new PrintStream(new FileOutputStream("myLog", true));
System.setOut(out);
Date myTime = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
String strTime = sdf.format(myTime);
System.out.println(strTime + ":" + s);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
}
File file = new File("D:/MyFile");
System.out.println(file.exists()); //false,判断文件/路径是否存在
if (!file.exists()){
file.createNewFile(); //创建文件,需要try/catch
}
if (!file.exists()){
file.mkdir(); //创建目录
}
File newFile = new File("D:/real/heisenberg");
if (!newFile.exists()){
newFile.mkdirs(); //创建多重目录
}
File file = new File("E:/git/Git/bin");
String parent = file.getParent();
System.out.println(parent); //E:\git\Git
File file1 = file.getParentFile(); //获取上级目录
System.out.println(file1.getAbsolutePath()); //E:\git\Git,获取绝对路径
File file0 = new File("log");
System.out.println(file0.getParent()); //null
System.out.println(file0.getAbsolutePath()); //D:\javase\log
String getName()方法:获取文件名
boolean isDirectory()方法:判断是否为目录
boolean isFile()方法:判断是否为文件
long lastModified()方法:返回文件上次被修改的时间(从1970到当时的毫秒数)
long length()方法:获取文件大小(字节)
File f1 = new File("E:/照片/5班.jpg");
System.out.println(f1.getName()); //5班.jpg
System.out.println(f1.isDirectory()); //false
System.out.println(f1.isFile()); //true
long seconds = f1.lastModified();
Date time = new Date(seconds);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
System.out.println(sdf.format(time)); //2015-05-30 12:39:21 770
System.out.println(f1.length()); //3190573
File[] listFiles()方法:获取目录下的所有文件
File f2 = new File("E:/音乐");
File[] files = f2.listFiles();
for (File f : files){
//System.out.println(f.getName()); 输出所有文件名
System.out.println(f.getAbsolutePath()); //输出所有绝对路径
}
- 作业:拷贝文件夹及其中所有内容
public class CopyTest {
public static void main(String[] args) {
File oldFile = new File("E:/照片");
File newFile = new File("D:/");
copy(oldFile, newFile);
}
private static void copy(File oldFile, File newFile) {
if (oldFile.isFile()){
FileInputStream in = null;
FileOutputStream out = null;
byte[] bytes = new byte[1024 * 1024];
int count = 0;
try {
in = new FileInputStream(oldFile);
String path = newFile.getAbsolutePath() + oldFile.getAbsolutePath().substring(3); //截取字符串获取新路径
out = new FileOutputStream(path);
while ((count = in.read(bytes)) != -1){
out.write(bytes, 0, count);
}
out.flush();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (in != null){
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (out != null){
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return;
}
File[] files = oldFile.listFiles();
for (File file : files){
if (file.isDirectory()) {
String s = newFile.getAbsolutePath() + file.getAbsolutePath().substring(3);
File tempFile = new File(s);
if (!tempFile.exists()) {
tempFile.mkdirs();
}
}
copy(file, newFile);
}
}
}
-
ObjectInputStream、ObjectOutputStream
-
序列化和反序列化
序列化:Serialize,将java对象拆分存储为硬盘文件
反序列化:DeSerialize,将硬盘数据恢复为java对象
-
参与序列化和反序列化的对象必须实现Serializable接口
-
Serializable接口是一个标志接口(接口中没有代码),java虚拟机看到这个接口会自动生成一个序列化版本号
-
public class Student implements Serializable {
private int no;
private String name;
//set、get方法,无参、有参构造,toString
}
public class StudentTest {
public static void main(String[] args) throws Exception{
Student s1 = new Student(256, "zhangsan");
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("Students"));
oos.writeObject(s1);
oos.flush();
oos.close();
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("Students"));
Object obj = ois.readObject();
System.out.println(obj); //调用toString方法
ois.close();
}
}
-
一次序列化多个对象:必须使用集合,否则序列化第二个对象时会报错
参与序列化的集合及集合中的元素都要实现Serializable接口
List<Student> list = new ArrayList<>();
list.add(new Student(1, "zhangsan"));
list.add(new Student(2, "lisi"));
list.add(new Student(3, "heisenberg"));
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("Students"));
oos.writeObject(list);
oos.flush();
oos.close();
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("Students"));
Object obj = ois.readObject();
List<Student> studentList = (List<Student>)obj;
for (Student s : studentList){
System.out.println(s);
}
ois.close();
- transient关键字:表示游离的,不参与序列化
private transient String name; //表示name不参加序列化操作
//再执行上述程序,输出的name属性均为null
-
序列化版本号
当某个类的源代码改动后生成新的字节码文件,class文件再运行时java虚拟机生成的序列化版本号也会改变
java中区分类的机制:先对比类名,类名相同时对比序列化版本号
自动生成的序列化版本号缺点:修改代码会导致版本号改变(java虚拟机会认为是两个不同的类)
实现Serializable接口的类建议提供一个固定的序列化版本号
//例如在Student类中增加属性age再进行ObjectInputStream,编译报错:java.io.InvalidClassException
private static final long serialVersionUID = 1L; //规定版本号
-
IO+Properties的联合使用
经常改变的数据单独写到文件中,使用程序动态读取,这样不需要修改代码
这种文件称为配置文件,且当配置文件内容格式为key=value时(也可以使用“:”,等号左右最好不要有空格),称为属性配置文件
java规范中建议:属性配置文件以.properties结尾(不是必须的)
属性配置文件中使用井号“#”注释,key或value重复时自动覆盖
FileInputStream in = new FileInputStream("VideoLearn/src/IO/UserInfo");
//文件内容:username=heisenberg password=123
Properties pro = new Properties();
pro.load(in); //将文件数据加载到Map集合中,等号左边是key,右边是value
String username = pro.getProperty("username");
System.out.println(username);