文章目录
概念
-
IO流,什么是IO
I:Input
O:Output
通过IO可以完成硬盘文件的读和写
-
IO流的分类:
有多种分类方式:
- 一种方式是按照流的方向进行分类:
以内存为参照物,往内存中来,叫做输入,或者叫做读
往内存中出来,叫做输出,或者叫做写
- 另一种是按字节的方式读取数据,一次读取一个字节byte,等同一次读取八个二进制,这种流是万能的,什么类型的文件都可以读取。
- 按照字符的方式读取数据的,一次读取一个字符,这种流是为了方便读取普通文本文件而存在的,这种流不能读取图片、声音等,包括word文件也无法读取。
综上所述,流的分类:
输入流、输出流
字节流、字符流
-
java中的IO流都已经写好了,我们程序员不需要关心,我们最主要还是掌握,在Java中已经提供了哪些流,每个流的特点是什么,每个流对象上常用方法有哪些。
java中所有的流都是在java.io.*下
java中主要还是研究怎样new流对象
-
java.IO流有四大家族
java.io.InputStream 字节输入流
java.io.OutputStream 字节输出流
java.io.Reader 字符输入流
java.io.writer 字符输出流
四大家族首领都是抽象类
注意:在java中只要“类名”以Stream结尾的都是字节流,以“Reader/Writer”结尾的都是字符流
所有的流都实现了:java.io.Closeable接口,都是可关闭的,都有close()方法。流是一个管道,这个是内存和硬盘之间的通道,用完之后一定要关闭,不然会占用很多资源**,用完流一定要关闭**
所有的输出流都实现了java.io. Flushable接口,都是可以刷新的,都有flush()方法,养成一个好习惯,用完输出流的时候,一定要记得flush()刷新一下。这个刷新表示将通道/管道当中剩余未输出的数据强行输出完(清空管道)
注意:如果没有flush()可能会导致丢失数据。
需要掌握的流(16个)
-
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.BufferedOutStream
数据流专属:
java.io.DataInputStream
java.io.DataOutputStream
标准输出流:
java.io.ObjectInputStream
java.io.ObjectOutputStream (掌握)
对象专属流:
java.io.PrintWriter (掌握)
java.io.PrintStream (掌握)
FileInputStream
进阶版看— —>最终版
/*
java.io.FileInputStream:
1.文件字节输入流,万能的,任何类型的文件都可以采用这个流来读。
2.字节的方式,完成输入的操作,完成读的操作(硬盘-->内存)
*/
public class FileInputStreamTest01 {
public static void main(String[] args) {
//创建文件输入流对象
//文件路径:C:\Users\14505\Desktop(IDEA会自动把\编成\\,因为java中\表示转义字符)
//写成这样也是可以的C:/Users/14505/Desktop/1.txt
FileInputStream fis=null;
try {
fis =new FileInputStream("C:\\Users\\14505\\Desktop\\1.txt");
int readDate = fis.read();
System.out.println(readDate);
readDate =fis.read(); //如果读取完了,会返回-1
System.out.println(readDate);
readDate =fis.read();
System.out.println(readDate);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally{
//关闭流finally语句块中确保能够关闭
if (fis != null) { //避免空指针异常
//关闭流的前提是:流不是空,如果流为空,没必要关闭
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
也可以改进读取,变成循环
int readData=0;
while((readData=fis.read() )!=-1){
System.out.println(readData);
}
相对路径
相对路径一定是在当前所在的位置开始找。
Idea默认的当前路径在哪里?工程文件Project的根就是IDEA的默认当前路径
最终版
/*
最终版,需要掌握
*/
public class FileInputStreamTest04 {
public static void main(String[] args) {
FileInputStream fis =null;
try {
fis =new FileInputStream("2.txt");
//准备一个byte数组
byte [] bytes =new byte[4];
int readCount=0;
while((readCount=fis.read(bytes))!=-1){
//把byte转换成字符串,读到多少个转换多少个
System.out.print(new String( bytes,0,readCount));
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
FileInputStream其他常用方法
- int available(); 返回流中剩余没有读到的字节数量
- long skip(long n ); 跳过几个字节不读
使用方法:
public class FileInputStreamTest05 {
public static void main(String[] args) {
FileInputStream fis =null;
try {
/* fis =new FileInputStream("1.txt");
System.out.println("总字节数量:"+ fis.available());
//这个方法有什么用?
byte[] bytes =new byte[fis.available()]; //这种方式不适合大文件,因为byte数组不能太大
//不需要循环了,只需要读取一次就可以了
int readCount =fis.read(bytes);
System.out.println(new String(bytes,0,readCount));
*/
//跳过几个字节不读取,以后这个可能会用
fis =new FileInputStream("1.txt");
fis.skip(3);
System.out.println(fis.read());
} catch (IOException e) {
e.printStackTrace();
}finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
FileOutputStream
/*
文件字节输出流,负责写
从内存到硬盘
*/
public class FileOutputStreamTest01 {
public static void main(String[] args) {
FileOutputStream fos =null;
try {
//每一次写入会将原文件清空并写入
//如果名字不存在会新建
// fos = new FileOutputStream("2.txt");
//这种方式会在源文件的末尾添加,并不会清空文件
fos = new FileOutputStream("2.txt",true);
byte[] bytes ={98,99,100,101,102,103};
//将byte数组全部写入
// fos.write(bytes);
//将byte数组的一部分写入
fos.write(bytes,0 ,3);
//字符串
String s ="我是中国人我骄傲";
byte[] bs =s.getBytes();
fos.write(bs);
//写完之后,一定要刷新
fos.flush();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
文件的复制
/*
使用FileInputStream 和FileOUtputStream完成文件的拷贝
拷贝的过程是一边读一边写
使用以上字节流拷贝文件的时候,文件类型随意,万能的,什么样的文件都能拷贝
*/
public class CopyTest01 {
public static void main(String[] args) {
FileInputStream fis =null;
FileOutputStream fos=null;
try {
fis= new FileInputStream("C:\\Users\\14505\\Pictures\\Camera Roll\\zhaop\\2020-03\\123.jpg");
fos= new FileOutputStream("C:\\Users\\14505\\Desktop\\123.jpg");
//最核心的,一边读一边写
byte[] bytes =new byte[1024*1024*5]; //一次5mb
int readCount =0;
while((readCount =fis.read(bytes)) != -1){
fos.write(bytes,0,readCount); //读到多少写多少
}
//输出流最后要刷新
fos.flush();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
//关闭两个流,分开try,不然一个出异常另一个可能关不掉
if(fis !=null){
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fos != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
FileReader
/*
fileReader:
文件字符输入流,只能读取普通文本
读取文本内容时,比较方便快捷
*/
public class FileReaderTest01 {
public static void main(String[] args) {
FileReader fr=null;
try {
fr = new FileReader("1.txt");
char[] chr =new char[4];
int readCount =0;
while ((readCount =fr.read(chr))!=-1){
System.out.println(new String(chr,0,readCount));
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fr != null) {
try {
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
BufferedReader
有一个很特色的方法,readline()
该代码没有处理异常
15–19行十分重要
/*
BufferedReader 带有缓冲区的字符输入流
使用这个流的时候不需要使用char数组,或者说不需要自定义byte数组,自带缓冲
*/
public class BufferReaderTest01 {
public static void main(String[] args) throws IOException {
FileReader re =new FileReader("1.txt");
//当一个流的构造方法中需要一个流的时候,这个被传进来的这个流叫做:节点流
//外部负责包装这个流,叫做包装流,还有有一个名字叫做:处理流
//像当前这个程序来说,fileReader就是一个节点流,BufferedReader就是一个包装流
BufferedReader br =new BufferedReader(re);
//读一行
// String firstLine= br.readLine();
// System.out.println(firstLine);
//读取一个文本行,不带换行符
String s =null;
while((s=br.readLine())!= null){
System.out.println(s);
}
//关闭流
//对于包装流来说,只需要关闭最外层流就行,里面的节点流会自动关闭
br.close();
}
}
InputStreamReader(字符转换流)
/*
InputStreamReader 是转换流
*/
public class BufferedReaderTest02 {
public static void main(String[] args) throws IOException {
//字节流
FileInputStream in= new FileInputStream("1.txt");
//通过转换流转换(InputStreamReader将字节流转换为字符流 )
//in是节点流 reader是包装流
InputStreamReader reader = new InputStreamReader(in);
//这个构造方法只能传一个字符流,不能传一个字节流
//reader是节点流,br是包装流
BufferedReader br =new BufferedReader(reader);
//合并
BufferedReader br2 =new BufferedReader(new InputStreamReader(new FileInputStream("1.txt")));
String str =null;
while((str=br.readLine())!=null){
System.out.println(str);
}
//关闭只需要关闭最外层
}
}
数据专属流
DataOutputStream
/*
java.io.DataOutputStream:数据专属的流
这个流可以将数据联通数据的类型一并写入文件
注意:这个文件不是普通文本文档(用记事本打不开)
*/
public class DataOutputStreamTest01 {
public static void main(String[] args) throws IOException {
DataOutputStream dos =new DataOutputStream(new FileOutputStream("data"));
//写数据
byte b =100;
short s =200;
int i =300;
long l=400;
float f=500;
double d=600;
boolean a=true;
char c='我';
//写入
dos.writeByte(b);//数据以及数据的类型一并存入文件当中
dos.writeShort(s);
dos.writeInt(i);
dos.writeLong(l);
//刷新
dos.flush();
//关闭
dos.close();
}
}
DataInputStream
/*
DataInputStream: 数据字节输入流
DataOutputStream写的文件,只能使用DataInputStream去读,并且读的时候你需要提前知道写入的顺序
读的顺序需要和写的顺序一致,才能正常取出数据
*/
public class DataInputStreamTest01 {
public static void main(String[] args) throws IOException {
DataInputStream dis=new DataInputStream(new FileInputStream("data"));
//开始读
byte b =dis.readByte();
short s =dis.readShort();
int i =dis.readInt();
long l=dis.readLong();
System.out.println(b);
System.out.println(s);
System.out.println(i);
System.out.pri tln(l);
}
}
DataOutputStream写的文件,只能使用DataInputStream去读,并且读的时候你需要提前知道写入的顺序读的顺序需要和写的顺序一致,才能正常取出数据
File类
-
File类和四大家族没有关系,所以File类不能完成文件的读和写
-
File对象代表什么?
文件和目录路径名的抽象表示形式。
-
一个File对象有可能对应的是目录,也有可能是文件。
File只是一个路径名的抽象表示形式
-
File类的常用方法;
public static void main(String[] args) throws IOException { File f= new File("D:\\file"); //判断是否存在 System.out.println(f.exists()); //如果目标目录不存在,则以文件的方式创建出来 if(!f.exists()){ f.createNewFile(); } //如果不存在,以目录的方式创建出来 if(!f.exists()){ //以目录的形式新建(也可以以多层目录的形式新建,在目标目录弄长点) f.mkdir(); //多重目录 // f.mkdirs(); } File f3 =new File("C:\\Users\\14505\\Desktop\\1.txt"); //获取文件的父路径 String parentPath= f3.getParent(); System.out.println(parentPath); File parentFile = f3.getParentFile(); System.out.println("绝对路径"+ parentFile.getAbsolutePath()); //获取文件的绝对路径 File f4 =new File("1.txt"); System.out.println("绝对路径"+f4.getAbsolutePath()); //获取文件名 File f5 =new File("C:\\Users\\14505\\Desktop\\1.txt"); String str =f5.getName(); System.out.println(str); //判断是否是一个目录 System.out.println(f5.isDirectory()); //false //判断是否是一个文件 System.out.println(f5.isFile());//true //获取文件最后一次修改时间 long haoMiao =f5.lastModified();//这个毫秒数是1970年到现在的总毫秒数 //将毫秒转换成日期 Date date =new Date(haoMiao); SimpleDateFormat sdf=new SimpleDateFormat("yyy-MM-dd HH:mm:ss SSS"); String strTime = sdf.format(date); System.out.println(strTime); //获取文件大小 System.out.println(f5.length()); //6字节 }
listFiles()方法
public class FileTest02 {
public static void main(String[] args) {
// File[] listFiles()
//获取当前目录下所有的子文件
File f= new File("C:\\Users\\14505\\Desktop");
File[] files = f.listFiles();
for (File file : files) {
System.out.println(file);
}
}
}
作业:复制整个文件目录
package com.baidu.fileStream;
import java.io.*;
public class parctice02 {
public static void main(String[] args) {
//拷贝源
File srcFile =new File("C:\\Users\\14505\\Desktop\\哈哈哈");
//拷贝目标
File destFile =new File("E:\\复制博客文件\\");
//调用方法拷贝
copyDir(srcFile,destFile);
}
/**
* 拷贝目录
* @param srcFile 拷贝源
* @param destFile 拷贝目标
*/
private static void copyDir(File srcFile, File destFile){
if(srcFile.isFile()){
//是文件则需要拷贝,递归结束
//是文件的时候需要拷贝 一边读一边写
FileInputStream fis =null;
FileOutputStream fos =null;
try {
//读这个文件
fis =new FileInputStream(srcFile);
String path =(destFile.getAbsolutePath().endsWith("\\")?destFile.getAbsolutePath():destFile.getAbsolutePath()+"//") +srcFile.getAbsolutePath().substring(3);
fos =new FileOutputStream(path);
byte[] bytes =new byte[1024*1024*5];
int readCount=0;
while((readCount =fis.read(bytes))!=-1){
fos.write(bytes);
}
fos.flush();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
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();
}
}
}
return ; //结束递归
}
File[] files =srcFile.listFiles(); //获取源下面的子目录
for (File file : files) {
//获取所有文件的(包括目录和文件)绝对路径
// System.out.println(file.getAbsolutePath());
if(file.isDirectory()){
//新建对应目录
// System.out.println(file.getAbsolutePath());
String srcDir =file.getAbsolutePath();
String destDir = (destFile.getAbsolutePath().endsWith("\\")?destFile.getAbsolutePath():destFile.getAbsolutePath()+"//") +srcDir.substring(3);
File newFile =new File(destDir);
if(!newFile.exists()){
newFile.mkdirs(); //以多重目录新建
}
}
//递归调用
copyDir(file,destFile);
}
}
}
ObjectOutputStream
序列化和反序列化
-
参与序列化和反序列化的对象需要实现Serializable接口
-
通过源代码发现,Serializable接口只是一个标志接口
public interface Serializable{
}
这个接口中什么代码都没有
-
那么他起到一个什么作用?
起到了标识的作用,标志的作用,java虚拟机看到这个类实现了这个接口,可能会对这个类特殊待遇。
java虚拟机看到这个Serializable接口后,会自动生成一个序列化版本号
-
序列化版本号有什么用呢?
首先,java语言是采用什么机制来区分类的?
-
第一:首先通过类名进行比对,如果类名不一样,肯定不是同一个类
-
第二:如果类名一样,再怎么进行类的区别?靠序列化版本号进行区分
比如不同的人编写了同一个类,但是“这两个类确实不是同一个类”。这个时候序列化版本就起上作用了。
对于java虚拟机来说,java虚拟机是可以区分开这两个类的,因为这两个类都实现了Serializable接口,都有默认的序列化版本号,他们的序列化版本号不一样,所以区分开了。
请思考,这种自动生成的序列化版本号有什么缺陷?
这种自动生成的序列化版本号缺点是:一旦代码确定之后,不能进行后续的修改,因为只要修改,必然会重新编译,此时会生成全新的序列化版本号,这个时候java虚拟机会认为这是一个全新的类
-
最终结论:凡是一个类实现了Serializeable接口,建议给该类提供一个固定不变的序列化版本号,以后这个类即使代码改变了,但是序列号不变,java虚拟机会认为这是同一个类
建议将序列化版本号手动的写出来,不建议自动生成
private static final long serialVersionUID = 1L;
序列化
/*
一次序列化多个对象
可以将他们放到集合当中,序列化集合
提示:参与序列化的集合以及集合中的元素User都需要实现java.io.Serializable 接口
*/
List<Student> list =new ArrayList<>();
list.add(new Student(100,"张三"));
list.add(new Student(200,"李四"));
ObjectOutputStream oos =new ObjectOutputStream(new FileOutputStream("Student2"));
oos.writeObject(list);
oos.flush();
oos.close();
}
反序列化
ObjectInputStream ois =new ObjectInputStream(new FileInputStream("Student2"));
Object obj = ois.readObject();
System.out.println(obj instanceof List);
List<Student> list2 =(List<Student>)obj;
for (Student student : list2) {
System.out.println(student);
}
关键字tranSient
表示游离的,不参与序列化
IO和Properties的联合使用
/*
IO和Properties的联合使用
非常好的一个设计理念:
以后经常改变的文件,可以单独写到一个文件当中,使用程序动态读取
将来只需要修改这个文件的内容,java代码不需要改动,不需要重新
编译,服务器也不需要重启,就可以拿到动态的信息
类似于以上这种机制的文件被称为配置文件
并且配置文件中的内容格式是:
key1 =value1
key2 =value2
的时候,我们把这种配置文件叫做属性配置文件
java规范中有要求:属性配置文件建议以.properties结尾,但这不是必须的
这种以.properties结尾的文件在java中称为:属性配置文件
其中Properties是专门存放属性配置文件内容的一个类
在属性配置文件当中,#是注释
属性配置文件当中,key重复的话,value会自动覆盖
并且最好不要有空格
*/
```java
public class IoPropertiesTest01 {
public static void main(String[] args) throws IOException {
/*
Properties 是一个Map集合,key和value都是String类型
想将userinfo文件中的数据加载到Properties对象当中
*/
//新建一个输入流对象
FileReader fr =new FileReader("userinfo");
//新建一个Map集合
Properties pro =new Properties();
//调用Properties集合中的load方法将文件中的数据加载到Map集合当中
pro.load(fr); //文件中的数据顺着管道加载到Map集合当中,其中 =左边做key,右边做value
String str= pro.getProperty("userName");
System.out.println(str);
}
}