IO流
1.什么是IO?
通过IO可以完成硬盘文件的读和写
2.IO的分类
-
按照流的方向 :输入流 输出流
-
以内存为参照物
-
往内存中去为输入(Input),或者叫读(Read)
-
往内存中出来,叫做输出(Output),也叫做写(Write)
-
-
-
按找读取数据方式的不同 :字节流 字符流
-
以字节的方式读取数据,一次读取1个字节,等于一次读取8个二进制位。万能流,什么都可以读取。字节流
-
假设文件file.txt 里面的内容是 a 湖南,采用字节流读取
第一次读:一个字节,读到 ‘a’
第二次读:一个字节,读到 ’湖‘ 字符的一半
第三次读:一个字节,读 ’湖‘ 字符的另外一半
-
-
按字符的方式读取,一次读取一个字符,只能读取纯文本文件。txt结尾
-
假设文件file.txt 里面的内容是 a 湖南,采用字符流读取
第一次读:’a‘ 字符(’a‘字符在window系统中占一个字节)
第二次读:’湖‘ 字符 (’湖‘字符在Windows系统中占有2个字节)
-
-
3.IO的四大家族
3.1 流的四大家族首领:都是抽象类
-
Java.io.InputStream 字节输入流
-
Java.io.OutputStream 字节输出流
-
Java.io.Reader 字符输入流
-
java.io.Writer 字符输出流
-
只要类名以Stream结尾的都是字节流,以“Reader/Writer”结尾的都是字符流。
-
所有的流都实现了 Closeable接口,都是可关闭的
-
流毕竟是一个管道,内存和硬盘之间的通道,用完之后要关闭。会占有很多资源
3.2 输出流
- 所有的输出流都实现类Java.io.Flushable接口,都是课刷新的
- 输出流在最终输出之后,一定要flush()
- 刷新的作用就是清空管道,将管道中剩余为输出的数据强行输出
- 如果没有flush() 可能会导致丢失数据
4.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.OutputStreamWriter
-
数据流专属
- java.io.DataInputStream
- java.io.DataOutputStream
-
标准输出流
- java.io.PrintWriter
- java.io.PrintStream
-
对象专属流
- java.io.ObjectInputStream
- java.io.ObjectOutputStream
5.FileInputStream
-
java.io.FileInputStream读取数据
-
字节输入流是万能的,任何文件类型都可以读
-
字节的方式完成输入/读的操作(从硬盘到内存)
-
已经到文件的末尾了,就返回-1
-
汉字先读取半个,空格也是个字符
-
避免硬盘与内存交互频繁,为了提交执行效率,一般使用btye[ ]数组
-
idea默认的当前路径是工程Project的根(相对路径)
public class FileInputStreamTest01 { public static void main(String[] args) { FileInputStream fis = null; try { //创建文件字节输入流对象 绝对路径 fis = new FileInputStream("F:/IO流/temp.txt"); //准备一个数组 byte[] bytes = new byte[4]; int readCount = 0; //开始读取 while ((readCount=fis.read(bytes))!= -1) { //文件的末尾了,就返回-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(); } } } } }
-
-
java.io.FileInputStream的其他方法;
-
int available() ; 返回当前流中剩余没有读到的字节数量
-
具体用法
public class FileInputStreamTest02 { public static void main(String[] args) { FileInputStream fis = null; try { fis = new FileInputStream("F:/IO流/temp.txt"); //不太适合大文件,因为byte数组不能太多 System.out.println("获取总字节数:"+fis.available()); byte[] bytes = new byte[fis.available()]; //不需要循环了,直接读一次就ok int readCount = fis.read(bytes); System.out.println(new String(bytes)); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } }
-
-
int long skip(long n); 跳过几个字节不读取
-
具体用法
public class FileInputStreamTest02 { public static void main(String[] args) { FileInputStream fis = null; try { fis = new FileInputStream("F:/IO流/temp.txt"); //skip跳过几个字节不读取 fis.skip(6); System.out.println(fis.read()); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } }
-
-
6.FileOutputStream
-
java.io.FileOutputStream输出数据
-
文件字节输出流,负责写
-
从内存到硬盘
-
写完之后一定要刷新
-
文件不存在的时候会自动新建
-
不以追加方式new,会将原文件清空,然后重新写入
-
具体demo
public class FileOutputStreamTest01 { public static void main(String[] args) { FileOutputStream fos = null; try { //myFile不存在的时候会自动新建 //不加true会将源文件清空 fos = new FileOutputStream("myFile",true); //开始写 byte[] bytes = {97,98,99,100}; //将byte数组全部写出 fos.write(bytes); // abcd //将byte数组的一部分写出 fos.write(bytes,0,2); //再写出ab //字符串 String str = "我是中国人"; //将字符串转换为byte数组 byte[] bs = str.getBytes(); //写 fos.write(bs); //写完之后,最后一定要刷新 fos.flush(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } }
-
-
7.FileInputStream+FileOutputStream完成文件的拷贝
-
字节流拷贝文件的时候,文件类型随意
public class CopyTest1 { public static void main(String[] args) { FileInputStream fis = null; FileOutputStream fos = null; try { //创建一个输入流对象 fis = new FileInputStream("D:\\学习与工作软件\\笔记\\typora-setup-x64.exe"); //创建一个输出流对象 fos = new FileOutputStream("D:\\休闲娱乐\\游戏\\typora-setup-x64.exe"); //核心:一边读一边写 byte[] bytes = new byte[1024 * 1024]; //一次最多拷贝1MB 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 (fos == null) { try { fos.close(); } catch (IOException e) { e.printStackTrace(); } } if (fis == null) { try { fis.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
8.FileReader
-
文件字符输入流,只能读取普通文本
-
一次读取一个字符,比如字节流读取一个汉字只能先读一半
public class FileReaderTest { public static void main(String[] args) { FileReader reader = null; try { //创建文件字符输入流 reader = new FileReader("F:/IO流/temp.txt"); //开始读取 char[] chars = new char[4]; //一次读取4个字符 int readCount = 0; 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(); } } } } }
-
9.FileWriter
-
文件字符输出流,写
-
只能输出普通文本
-
每次执行会清空,重新写。可追加
public class FileWriterTest { public static void main(String[] args) { FileWriter out = null; try { //创建文件字符输出流对象 out = new FileWriter("file"); //每次执行会清空,重新输。可追加 //开始写 char[] chars = {'中','国','人'}; out.write(chars); out.write("我是一名工程师"); //刷新 out.flush(); } catch (IOException e) { e.printStackTrace(); }finally { if (out != null) { try { out.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
10.FileReader+FileWriter拷贝文件
-
只能拷贝普通文件
public class CopyTest2 { public static void main(String[] args) { FileReader in = null; FileWriter out = null; try { //读 in = new FileReader("src\\com\\lcy\\io\\CopyTest1.java"); //写 out = new FileWriter("CopyTest1.java"); //一边读,一边写 char[] chars = new char[1024 * 512];//1MB int readCount = 0; while ((readCount=in.read(chars))!=-1){ out.write(chars,0,readCount); } //刷新 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(); } } } } }
11.BufferedReader
-
使用这流的时候,不需要自定义数组,自带缓冲流
-
一个流的构造方法中需要一个流的时候,被传进来的流叫做:节点流
-
外部包装的流叫做:包装流/处理流。是相对而言的
-
readLine方法读取一个文本行,但不带换行符
-
对于包装流来说,只需要关闭最外层的就行,里面的会自动关闭
public class BufferedReaderTest01 { public static void main(String[] args) throws Exception { FileReader reader = new FileReader("CopyTest1.java"); //当一个流的构造方法中需要一个流的时候,被传进来的流叫:节点流 //外部包装的这个流叫:包装流/处理流 //当前程序:FileReader就是节点流,BufferedReader就是包装流/处理流 BufferedReader br = new BufferedReader(reader); //br.readLine()方法读取一个文本行,但不带换行符 String s = null; while ((s = br.readLine())!= null) { System.out.println(s); } //关闭流:(对于包装流来说,只需要关闭最外层的就行,里面的会自动关闭) br.close(); } }
-
此构造方法只能传一个字符流
-
字节流需要进行转换流转换
public class BufferedReaderTest02 { public static void main(String[] args) throws Exception { //字节流 FileInputStream in = new FileInputStream("CopyTest1.java"); //通过转换流转换(InputStreamReader将字节流转换为字符流) //in是节点流,reader是包装流 InputStreamReader reader = new InputStreamReader(in); //这个构造方法只能传一个字符流 //reader是节点流,br是包装流 BufferedReader br = new BufferedReader(reader); String line = null; while ((line = br.readLine()) != null) { System.out.println(line); } //关闭 br.close(); } }
12.BufferedWirter
-
示例
public class BufferedWriterTest01 { public static void main(String[] args) { try { //带有缓冲区的字符输出流 BufferedWriter out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("test",true))); //开始写 out.write("hello world"); out.write("\n"); out.write("hello kitty!"); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } }
13.DataOutputStream
- 数据专属的流
- 可以将数据连同数据的类型一起写入文件
- 这个文件不是普通文本类型
public class DataOutputStreamTest {
public static void main(String[] args) {
try {
//创建数据专属的字节输出流
DataOutputStream dos = new DataOutputStream(new FileOutputStream("data"));
//写数据
byte b = 100;
short s = 200;
int i = 300;
long l = 400L;
float f = 3.0f;
double d = 3.14;
//写 把数据以及数据的类型一起带进去
dos.writeByte(b);
dos.writeShort(s);
dos.writeInt(i);
dos.writeLong(l);
dos.writeFloat(f);
dos.writeDouble(d);
//刷新
dos.flush();
//关闭
dos.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
14.DataInputStream
-
数据字节输入流
-
DataOutputStream写的文件,只能使用DataInputStream去读,并需要提前知道写入的顺序
-
读的顺序需要和写的顺序一致,才可正常取出数据
public class DataInputStreamTest { public static void main(String[] args) { try { DataInputStream dis = new DataInputStream(new FileInputStream("data")); //开始读 读写顺序需要一致 byte b = dis.readByte(); short s = dis.readShort(); int i = dis.readInt(); long l = dis.readLong(); float f = dis.readFloat(); double d = dis.readDouble(); System.out.println(b); System.out.println(s); System.out.println(i); System.out.println(l); System.out.println(f); System.out.println(d); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } }
15.PrintStream
-
标准输出流不需要手动关闭
-
可以改变输出方向,不再指向控制台
public class PrintStreamTest01 { public static void main(String[] args) { //联合写 System.out.println("Hello world!"); //分开写,这些输出到控制台 PrintStream ps = System.out; ps.println("张三"); ps.println("李四"); ps.println("王五"); try { //可以改变标准输出流的输出方向 PrintStream printStream = new PrintStream(new FileOutputStream("log",true)); //修改了输出方向,不再到控制台,到log文件 System.setOut(printStream); //再输出 System.out.println("hello world"); System.out.println("hello lcy"); } catch (FileNotFoundException e) { e.printStackTrace(); } } }
16.日志工具
-
日志工具类
public class Logger { /** * 记录日志的方法 * @param msg */ public static void log(String msg){ try { //指向一个日志文件 PrintStream out = new PrintStream(new FileOutputStream("log.txt"), true); //改变输出方向 System.setOut(out); //日期当前时间 Date nowTime = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS"); String strTime = sdf.format(nowTime); System.out.println(strTime+":"+msg); } catch (FileNotFoundException e) { e.printStackTrace(); } } }
-
测试类
public class LogTest { public static void main(String[] args) { //测试工具类是否好用 Logger.log("掉用了System类的gc()方法,建议启动垃圾回收"); Logger.log("调用了UserService的方法"); Logger.log("用户尝试进行登陆"); } }
-
log.txt
2020-05-05 11:53:10 930:掉用了System类的gc()方法,建议启动垃圾回收 2020-05-05 11:53:10 961:调用了UserService的方法 2020-05-05 11:53:10 962:用户尝试进行登陆
17.File
-
常用方法
-
File和四大家族没关系,所以不能完成文件的读写操作
-
File对象代表的是文件和目录路径名的抽象表示形式
-
练习1
public class FileTest01 {
public static void main(String[] args) throws IOException { //创建一个新目录 File file = new File("F:\\file"); if (!file.exists()) { //file.exists()判断是否存在 file.createNewFile(); //以文件的形式创建 file.mkdir(); //以目录的形式创建 } File f2 = new File("F:a/b/c"); if (!f2.exists()) { //多重目录的形式创建 f2.mkdirs(); } File f3 = new File("F:a/b/c"); //获取文件的父路径 String parentPath1 = f3.getParent(); File parentPath2 = f3.getParentFile(); //F:\\a\b System.out.println("获取的绝对路径是:"+parentPath2.getAbsolutePath()); System.out.println(parentPath1); //F:\a\b }
-
练习2
public class FileTest02 { public static void main(String[] args) { File f1 = new File("F:\\计网\\答题卡\\计网2016十月.txt"); //获取文件名 System.out.println("文件名:"+f1.getName()); //文件名:计网2016十月.txt //判断是否是一个目录 System.out.println(f1.isDirectory()); //false //判断是否是一个文件 System.out.println(f1.isFile()); //true //获取文件最后一次修改时间 long haoMiao = f1.lastModified(); //这个毫秒是1970到现在的总毫秒数 //将总毫秒数转换成日期 Date time = new Date(haoMiao); SimpleDateFormat sdf = new SimpleDateFormat("yyyy--MM-dd HH:mm:ss SSS"); String strTime = sdf.format(time); System.out.println(strTime); //2020--03-06 17:24:20 577 //获取文件大小 System.out.println(f1.length()); //2902字节 } }
-
练习3
public class FileTest03 { public static void main(String[] args) { //File[] listFiles() //获取当前目录下所有的子文件 File f = new File("F:\\计网\\答题卡"); File[] files = f.listFiles(); for (File file :files) { System.out.println(file.getAbsolutePath()); //获取绝对路径 System.out.println(file.getName()); //获取文件名 } } }
-
-
17 .文件夹Copy(递归)!!!!!!!!有bug
public class CopyAll {
public static void main(String[] args) {
//拷贝源
File srcFile = new File("F:\\计网");
//拷贝目标
File destFile = new File("D:");
//调用方法拷贝
copyDir(srcFile,destFile);
}
/**
* 拷贝目录
* @param srcFile 拷贝源
* @param destFile 拷贝目标
*/
private static void copyDir(File srcFile, File destFile) {
if (srcFile.isFile()){
//如果srcFile是一个文件的话,递归结束
//是文件的时候需要拷贝
FileInputStream in = null;
FileOutputStream out = null;
try {
//读这个文件
in = new FileInputStream(srcFile);
//写到这个文件中
String path = (destFile.getAbsolutePath().endsWith("\\")?destFile.getAbsolutePath() : destFile.getAbsolutePath()+ "\\") +srcFile.getAbsolutePath().substring(3);
System.out.println(path);
out = new FileOutputStream(path);
//一边读一边写
byte[] bytes = new byte[1024 * 1024]; //一次复制1MB
int readCount = 0;
while ((readCount = in.read(bytes)) != -1) {
out.write(bytes,0,readCount);
}
//刷新
out.flush();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (out != null) {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (in != null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return;
}
//获取源下面的子目录
File[] files = srcFile.listFiles();
for (File file :files) {
//获取所有文件的绝对路径
//System.out.println(file.getAbsolutePath());
if (file.isDirectory()) {
//新建对应的目录
//F:\\计网 源目录
//D:\\计网
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);
}
}
}
18.ObjectOutputStream与ObjectInputStream
- 参与序列化和反序列化的对象,必须实现Serializable接口
- 通过源码发现,Seriablizable只是一个标志接口,起标识作用
- JVM看到这个接口,会为该类生成一个序列化版本号
-
Java中线通过类名进行对比,如果类名一样靠序列化版本号进行区分
-
不同的人编写了同一个类,两个类确实不是同一个,这个时候版本号就起作用了(优点)
-
自动生成版本号的缺点是一旦代码确定,不能进行后续的修改
-
建议给类提供一个固定不变的序列化版本号
-
ObjectOutputStreamTest01序列化
public class ObjectOutputStreamTest01 { public static void main(String[] args) throws Exception { //创建Java对象 Stu stu = new Stu(111, "张三"); //序列化 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("stu")); //序列化对象 oos.writeObject(stu); //刷新 oos.flush(); //关闭 oos.close(); } }
-
实体类
public class Stu implements Serializable { private static final long serialVersionUID = -1910710510701402051L; //虚拟机先通过类名,然后通过版本号区分 //过了很久,Stu这个类源代码改动 //源码改动之后,需要重新编译,生成了全新的字节码文件 //并且class文件再次运行的时候,Java虚拟机生成的序列化版本号也会发生改变 private Integer no; private String name; private String email; private int age; @Override public String toString() { return "Stu{" + "no=" + no + ", name='" + name + '\'' + '}'; } public Stu() { } public Stu(Integer no, String name) { this.no = no; this.name = name; } public Integer getNo() { return no; } public void setNo(Integer no) { this.no = no; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
-
ObjectInputStreamTest01反序列化
public class ObjectInputStreamTest01 { public static void main(String[] args) throws Exception { ObjectInputStream ois = new ObjectInputStream(new FileInputStream("stu")); //开始反序列化,读 Object obj = ois.readObject(); //反序列化回来的是一个学生对象,所以会调用学生对象的toString方法 System.out.println(obj); //Stu{no=111, name='张三'} //关闭流 ois.close(); } }
-
序列化集合,可以把对象放在集合中
-
参与序列化的ArrayList集合以及集合中的元素User都需要实现Serializable接口
public class ObjectOutputStreamTest02 { public static void main(String[] args) throws Exception { List<User> userList = new ArrayList(); userList.add(new User(1,"张三")); userList.add(new User(2,"李四")); userList.add(new User(3,"王五")); ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("user")); //序列化一个集合,这个集合对象中放了很多其他对象 oos.writeObject(userList); oos.flush(); oos.close(); } }
-
反序列化集合
-
如果不使用集合,直接存多个对象,反序列化存储的第二个对象会报错
public class ObjectInputStreamTest02 { public static void main(String[] args) throws Exception { ObjectInputStream ois = new ObjectInputStream(new FileInputStream("user")); //判断是不是List集合 //System.out.println(obj instanceof List); //true /** * User{no=1, name='张三'} * User{no=2, name='李四'} * User{no=3, name='王五'} */ List<User> userList = (List<User>) ois.readObject(); for (User user :userList) { System.out.println(user); } //关闭流 ois.close(); } }
-
transient关键字
public class User implements Serializable { private Integer no; //transient关键字表示游离,不参与序列化 private transient String name; //name不参与序列化操作 @Override public String toString() { return "User{" + "no=" + no + ", name='" + name + '\'' + '}'; } public User() { } public User(Integer no, String name) { this.no = no; this.name = name; } public Integer getNo() { return no; } public void setNo(Integer no) { this.no = no; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
19.Io+Properties的联合应用
-
设计理念
-
经常改变的数据,单独写在一个文件中,程序动态获取。
-
配置文件中的内容格式是键值对的时候,叫做配置文件。
-
Properties是专门存放属性配置文件内容的一个类。
-
属性配置文件key重复的话,value会自动覆盖。最好不要有空格,冒号
public class IoPropertiesTest01 { public static void main(String[] args) throws Exception { /* Properties是一个Map集合,key和value都是String类型 想将userinfo文件中的数据加载到Properties对象当中 */ //新建一个输入流对象 FileReader reader = new FileReader("userinfo"); //新建一个Map集合 Properties pro = new Properties(); //调用Properties对象的load方法将文件中的数据加载到Map集合中 pro.load(reader); //文件中的数据顺着管道加载daoMap集合中,键值对 //通过key获取value String username = pro.getProperty("username"); System.out.println(username); } }
-
-
配置文件
username=admin password=123 this.no = no; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
19.Io+Properties的联合应用
-
设计理念
-
经常改变的数据,单独写在一个文件中,程序动态获取。
-
配置文件中的内容格式是键值对的时候,叫做配置文件。
-
Properties是专门存放属性配置文件内容的一个类。
-
属性配置文件key重复的话,value会自动覆盖。最好不要有空格,冒号
public class IoPropertiesTest01 { public static void main(String[] args) throws Exception { /* Properties是一个Map集合,key和value都是String类型 想将userinfo文件中的数据加载到Properties对象当中 */ //新建一个输入流对象 FileReader reader = new FileReader("userinfo"); //新建一个Map集合 Properties pro = new Properties(); //调用Properties对象的load方法将文件中的数据加载到Map集合中 pro.load(reader); //文件中的数据顺着管道加载daoMap集合中,键值对 //通过key获取value String username = pro.getProperty("username"); System.out.println(username); } }
-
-
配置文件
username=admin password=123