引言
debug 中 Step Into 和 Force Step Into 的区别
Step Into会进入你自己写的方法;而Force Step Into能够进入所有的方法,比如jdk的方法。如下图:
Step Into就会直接过去。
而Force Step Into就能进到substring方法中。
【意思就是如果调用了某个jdk的方法,那么Step Into会直接跳过去,而Force Step Into则会进到substring方法中一步一步去执行。但是比如在文章“常用类”中探究String常量和变量相加底层是如何实现时,str + “abc” 语句并未调用jdk方法,因此Step Into也可以查看出底层是如何一步一步执行的】
本篇文章在探究为何在使用FileWriter后必须要关闭(close)或者刷新(flush)才能写入到指定的文件的问题以及为何关闭外层流后内层节点流也会自动关闭的问题时,就是利用debug中的Force Step Into进入jdk方法来探究明白的
文件
概念
文件是 保存数据的地方 ,比如word文档,txt文件,excel文件,都是文件。它能保存图片、视频、声音等等。
在我们整个IO编程的过程中会经常提到流的概念,那么这里的流主要指的就是 文件流。那么为什么会有流的概念呢?这是因为文件在程序中是以流的方式或者形式来操作的,入下图:
文件在程序中时以流的形式来操作的
流:数据在数据源(文件)和程序(内存)之间经历的路径
输入流:数据从数据源(文件)到程序(内存)的路径
输出流:数据从程序(内存)到数据源(文件)的路径
判断输入/输出时,一定是针对内存而言。即将数据流入内存中就是输入流;而从内存流出数据则是输出流
常用操作
创建文件
- 相关方法
new File(String pathname) //根据路径构建一个File对象
new File(File parent,String child) //根据父目录文件+子路径构建
new File(String parent, String child) //根据父目录+子路径构建
createNewFile 创建新文件
示例代码:
package com.hspedu.file;
import org.junit.jupiter.api.Test;
import java.io.*;
/**
* @author 韩顺平
* @version 1.0
* 演示创建文件
*/
public class FileCreate {
public static void main(String[] args) {
}
//方式1 new File(String pathname)
@Test
public void create01() {
String filePath = "e:\\news1.txt";
File file = new File(filePath);
try {
file.createNewFile();
System.out.println("文件创建成功");
} catch (IOException e) {
e.printStackTrace();
}
}
//方式2 new File(File parent,String child) //根据父目录文件+子路径构建
//e:\\news2.txt
@Test
public void create02() {
File parentFile = new File("e:\\");
String fileName = "news2.txt";
//!!!这里的file对象,在java程序中,只是一个对象
//!!!只有执行了createNewFile 方法,才会真正的,在磁盘创建该文件
File file = new File(parentFile, fileName);
try {
file.createNewFile();
System.out.println("创建成功~");
} catch (IOException e) {
e.printStackTrace();
}
}
//方式3 new File(String parent,String child) //根据父目录+子路径构建
@Test
public void create03() {
//String parentPath = "e:\\";
String parentPath = "e:\\";
String fileName = "news4.txt";
File file = new File(parentPath, fileName);
try {
file.createNewFile();
System.out.println("创建成功~");
} catch (IOException e) {
e.printStackTrace();
}
}
//下面四个都是抽象类
//
//InputStream
//OutputStream
//Reader //字符输入流
//Writer //字符输出流
}
获取文件信息
File的类图:
Serializable表示可以串行化、Comparable表示可比较
- 常用方法
getName:获取文件名
getAbsolutePath:获取绝对路径
getParent:获取文件的上级目录
length:文件大小(返回字节大小)
exists:文件是否存在
isFile:是否是文件
isDirectory:是不是一个文件目录
package com.hspedu.file;
import org.junit.jupiter.api.Test;
import java.io.File;
/**
* @author 韩顺平
* @version 1.0
*/
public class FileInformation {
public static void main(String[] args) {
}
//获取文件的信息
@Test
public void info() {
//先创建文件对象
File file = new File("e:\\news1.txt");
//调用相应的方法,得到对应信息
System.out.println("文件名字=" + file.getName());
//getName、getAbsolutePath、getParent、length、exists、isFile、isDirectory
System.out.println("文件绝对路径=" + file.getAbsolutePath());
System.out.println("文件父级目录=" + file.getParent());
System.out.println("文件大小(字节)=" + file.length());
System.out.println("文件是否存在=" + file.exists());//T
System.out.println("是不是一个文件=" + file.isFile());//T
System.out.println("是不是一个目录=" + file.isDirectory());//F
}
}
目录操作
- 目录操作和文件删除
mkdir创建一级目录、mkdirs创建多级目录、delete只能删除空目录或者文件
注意:delete只能删除 空目录,若某个目录下面有子目录或文件,需要先将相关子目录或文件清除掉才可以删除。
示例代码:
package com.hspedu.file;
import org.junit.jupiter.api.Test;
import java.io.File;
import java.io.InputStream;
import java.io.OutputStream;
/**
* @author 韩顺平
* @version 1.0
*/
public class Directory_ {
public static void main(String[] args) {
//
}
//判断 d:\\news1.txt 是否存在,如果存在就删除
@Test
public void m1() {
String filePath = "e:\\news1.txt";
File file = new File(filePath);
if (file.exists()) {
if (file.delete()) {
System.out.println(filePath + "删除成功");
} else {
System.out.println(filePath + "删除失败");
}
} else {
System.out.println("该文件不存在...");
}
}
//判断 D:\\demo02 是否存在,存在就删除,否则提示不存在
//这里我们需要体会到,在java编程中,目录也被当做文件(可以将目录当作比较特殊的文件)
@Test
public void m2() {
String filePath = "D:\\demo02";
File file = new File(filePath);
if (file.exists()) {
if (file.delete()) {
// delete方法会返回一个boolean值
System.out.println(filePath + "删除成功");
} else {
System.out.println(filePath + "删除失败");
}
} else {
System.out.println("该目录不存在...");
}
}
//判断 D:\\demo\\a\\b\\c 目录是否存在,如果存在就提示已经存在,否则就创建
@Test
public void m3() {
String directoryPath = "D:\\demo\\a\\b\\c";
File file = new File(directoryPath);
if (file.exists()) {
System.out.println(directoryPath + "存在..");
} else {
if (file.mkdirs()) { //创建一级目录使用mkdir() ,创建多级目录使用mkdirs()
System.out.println(directoryPath + "创建成功..");
} else {
System.out.println(directoryPath + "创建失败...");
}
}
}
}
注意:创建多级目录一定要使用mkdirs,用mkdir不好使。mkdir只能用来创建一级目录的。
IO流原理及流的分类
- JAVA IO流原理
1.I/O 流是Input/Output的缩写,I/O技术是非常实用的技术,用于处理数据传输。如读/写文件,网络通讯等。
2.JAVA程序中,对于数据的输入/输出操作以"流"(Stream)的方式进行。
3.Java.io 包下提供了各种"流"类和接口,用以获取不同种类的数据,并通过方法输入或输出数据。 - Java IO流原理
4.输入input:读取外部数据(磁盘、光盘等存储设备的数据)到程序(内存)中
5.输出output:将程序(内存)数据输出到磁盘、光盘等存储设备中
- 流的分类
按照操作数据单位的不同分为:字节流(8 bit) 二进制文件、字符流 文本文件。【从效率上看,字符流的效率肯定是稍微高一点的,那么字节流的作用是什么呢?其实在操作一些二进制文件,如:声音、视频、word文件时,若使用字节流的方式去操作,它可以保证这种二进制文件是无损操作;而字符流用来操作文本文件】
按照数据流的流向不同分为:输入流、输出流。
按流的角色的不同分为:节点流、处理流/包装流。
示例代码:
public class Stream {
// 1.四个都是抽象类,不能够实例化,也只能实例化对应的子类对象才能操作
//OutputStream;
//InputStream;
//Reader;
// Writer;
}
(1)Java的IO流共涉及40多个类,实际上非常规则,都是从以上4个抽象基类派生的。
(2)由这四个类派生出来的子类名称都是以其父类名作为子类名后缀。
- 1.IO流体系图
- 2.文件 VS 流
文件是计算机管理数据的基本单位,同时也是应用程序保存和读取数据的一个重要场所;而 流是字节序列的抽象概念。
节点流和处理流
基本介绍
1.节点流可以从一个 特定的数据源(就是存放数据的地方) 读写数据,如FileReader、FileWriter(源码)
除了操作文件的节点流,还有一些其他的节点流,如下图- 节点流和处理流一览图 :
2.处理流(也叫 包装流 )是"连接"在已存在的流(节点流或处理流)之上,为程序提供更为强大的读写功能,如BufferedReader、BufferedWriter
以BufferedReader为例,其类中有属性Reader,即可以封装一个节点流,该节点流可以是任意的,只要是Reader的子类即可。这样就实现了不局限于一个数据源
再如BufferedWriter:
处理流设计模式
解释说明:节点流虽然可以直接针对某个数据源进行操作,但它的灵活性和效率会受到一些影响,从而功能不是很强大。因此Java设计者就提供了另外一种流叫处理流,它是对节点流进行一个包装从而使流的功能更加的强大。以BufferedReader类为例,在BufferedReader类中就含有一个属性Reader,即可以封装任意一个节点流(只要是Reader子类就可以)从而使BufferedReader类不再局限于一个具体的数据源。即若为某个BufferedReader类实例传递了一个CharArrayReader类对象,则BufferedReader类实例就可以对字符数组进行操作;若传递的是一个FileReader类对象,则BufferedReader类实例就可以对文件进行操作,也可以传递其它类,只要是Reader类的子类即可。
- 节点流和处理流的区别和联系
1.节点流是底层流/低级流,直接跟数据源相接。
2.处理流( 包装流 )包装节点流,既可以消除不同节点流的实现差异,也可以提供更方便的方法来完成输入和输出。
3.修饰器设计模式,不会直接与数据源相连
模拟修饰器设计模式:(为了和系统区分开来,所有类均以下划线_结尾)
package com.hspedu;
/**
* @author 韩顺平
* @version 1.0
*/
public abstract class Reader_ { //抽象类
public void readFile() {
}
public void readString() {
}
//在Reader_ 抽象类,使用read方法统一管理.
//后面在调用时,利于对象动态绑定机制, 绑定到对应的实现子类即可.
//public abstract void read();
}
package com.hspedu;
/**
* @author 韩顺平
* @version 1.0
* 节点流
*/
public class FileReader_ extends Reader_ {
public void readFile() {
System.out.println("对文件进行读取...");
}
}
package com.hspedu;
/**
* @author 韩顺平
* @version 1.0
* 节点流
*/
public class StringReader_ extends Reader_ {
public void readString() {
System.out.println("读取字符串..");
}
}
package com.hspedu;
/**
* @author 韩顺平
* @version 1.0
* 做成处理流/包装流
*/
public class BufferedReader_ extends Reader_{
private Reader_ reader_; //属性是 Reader_类型
//接收Reader_ 子类对象
public BufferedReader_(Reader_ reader_) {
this.reader_ = reader_;
}
// 实现封装
public void readFile() { //封装一层
reader_.readFile();
}
//让方法更加灵活, 多次读取文件, 或者加缓冲byte[] ....
//相当于在BufferedReader_扩展了方法
public void readFiles(int num) {
for(int i = 0; i < num; i++) {
reader_.readFile();
}
}
//扩展 readString, 批量处理字符串数据
public void readStrings(int num) {
for(int i = 0; i <num; i++) {
reader_.readString();
}
}
}
package com.hspedu;
import java.io.*;
/**
* @author 韩顺平
* @version 1.0
*/
public class Test_ {
public static void main(String[] args) {
BufferedReader_ bufferedReader_ = new BufferedReader_(new FileReader_());
bufferedReader_.readFiles(10);
//bufferedReader_.readFile();
//Serializable
//Externalizable
//ObjectInputStream
//ObjectOutputStream
//这次希望通过 BufferedReader_ 多次读取字符串
BufferedReader_ bufferedReader_2 = new BufferedReader_(new StringReader_());
bufferedReader_2.readStrings(5);
}
}
- 处理流的功能主要体现在以下两个方面:
1.性能的提高:主要以增加缓冲的方式来提高输入输出的效率。【比如加上缓冲byte[]】
2.操作的便捷:处理流可能提供了一系列便捷的方法来一次输入输出大批量的数据,使用更加灵活方便。【比如通过传入一个参数来多次读取文件或字符串】
字符处理流
- BufferedReader和BufferedWriter属于字符流,是按照字符来读取数据的【那这个时候如果使用BufferedReader和BufferedWriter来操作的话,尽量是文本文件,而不是二进制文件。因为二进制文件在组织的时候是按字节来组织的,文本文件用字符来操作会比较高效】
- 在关闭处理流时,只需要关闭外层流即可【后面通过debug也可以看到:其实真正来进行数据读取的是我们的节点流,而不是处理流。处理流只是做了一个包装。所以在其底层代码中关闭外层流时,本质会自动关闭封装在外层流中实际工作的流。(简单来说就是在关闭处理流时,它的底层会自动去关闭其包装的节点流)】
BufferedReader
示例代码:
package com.hspedu.reader_;
import java.io.BufferedReader;
import java.io.FileReader;
/**
* @author 韩顺平
* @version 1.0
* 演示bufferedReader 使用
*/
public class BufferedReader_ {
public static void main(String[] args) throws Exception {
String filePath = "e:\\a.java";
//创建bufferedReader
BufferedReader bufferedReader = new BufferedReader(new FileReader(filePath));
//读取
String line; //按行读取, 效率高
//说明
//1. bufferedReader.readLine() 是按行读取文件
//2. 当返回null 时,表示文件读取完毕
while ((line = bufferedReader.readLine()) != null) {
System.out.println(line);
}
//关闭流, 这里注意,只需要关闭 BufferedReader ,因为底层会自动的去关闭 节点流 FileReader
//BufferedReader 中 close 方法的源码如下:
/*
public void close() throws IOException {
synchronized (lock) {
if (in == null)
return;
try {
in.close();//in 就是我们传入的节点流 new FileReader(filePath), 关闭了.
} finally {
in = null;
cb = null;
}
}
}
*/
bufferedReader.close();
}
}
BufferedWriter
示例代码:
package com.hspedu.writer_;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
/**
* @author 韩顺平
* @version 1.0
* 演示BufferedWriter的使用
*/
public class BufferedWriter_ {
public static void main(String[] args) throws IOException {
String filePath = "e:\\ok.txt";
//创建BufferedWriter
//说明:
//1. new FileWriter(filePath, true) 表示以追加的方式写入
//2. new FileWriter(filePath) , 表示以覆盖的方式写入
BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(filePath));
bufferedWriter.write("hello, 韩顺平教育!");
bufferedWriter.newLine();//插入一个和系统相关的换行
bufferedWriter.write("hello2, 韩顺平教育!");
bufferedWriter.newLine();
bufferedWriter.write("hello3, 韩顺平教育!");
bufferedWriter.newLine();
//说明:关闭外层流即可 , 传入的节点流 new FileWriter(filePath) 会在底层自动关闭
bufferedWriter.close();
}
}
字节处理流
BufferedInputStream
BufferedInputStream是 字节流,在创建 BufferedInputStream 对象时,会创建一个内部缓冲区数组
BufferedOutputStream
BufferedOutputStream是 字节流,实现缓冲的输出流,可以将多个字节写入底层输出流中,而不必对每次字节写入调用底层系统【即write()方法】
对象处理流
-
需求
1.将int num = 100 这个int数据保存到文件周昂,注意不是 100 数字,而是int 100,并且能够从文件中直接回复int 100
2.将 Dog dog = new Dog(“小黄”, 3)这个dog对象 保存到 文件中,并且能够从文件恢复
【注意:恢复回来之后需要仍然是一个Dog对象】
3.上面的要求,就是 能够将 基本数据类型 或者 对象 进行 序列化 和 反序列化操作
【序列化 :简单来说就是当保存一个数据时,既保存了它的值也保存了它的数据类型;把它再重新恢复回来称之为 反序列化 】
总的来说就是,在我们实际开发中肯定会存在一种需求:即同时将数据的值和数据类型都保存下来。而保存的过程我们就称为序列化,恢复的过程就称为反序列化。 -
序列化和反序列化
1.序列化就是在保存数据时,保存数据的值和数据类型
2.反序列化就是在恢复数据时,恢复数据的值和数据类型
3.需要让某个对象支持序列化机制,则必须让其类是可序列化的,为了让某个类是可序列化的,该类必须实现如下两个接口之一:
(1)Serializable // 译为“可序列化”,标记接口就是一个声明性质的( Kim:有点儿像就是说只是一个标记,如果一个类继承了这个接口就相当于有了这个标记,那么这个标记干什么的呢?就是Java底层看见这个标记了就知道你想让你设计的这个类是可序列化的 ),里面没有任何的方法,源码如下图所示:
(2)Externalizable // 译为“可外化的/部化的”,其实也是继承了Serializable,但是下面有两个抽象方法。若实现该接口需要重写这两个方法,所以一般不会去选用它。源码如下图所示:
扩充:Java序列化和反序列化
什么是序列化和反序列化?
如果我们需要持久化 Java 对象比如将 Java 对象保存在文件中,或者在网络传输 Java 对象,这些场景都需要用到序列化。
简单的说:
序列化: 将数据结构或对象转换成二进制字节流的过程
反序列化: 将在序列化过程中所生成的二进制字节流转换成数据结构或者对象的过程
对于 Java 这种面向对象编程语言来说,我们序列化的都是对象(Object)也就是实例化后的类(Class),但是在 C++这种半面向对象的语言中,struct(结构体)定义的是数据结构类型,而 class 对应的是对象类型。
下面是序列化和反序列化常见应用场景:
①对象在进行网络传输(比如远程方法调用 RPC 的时候)之前需要先被序列化,接收到序列化的对象之后需要再进行反序列化;
②将对象存储到文件之前需要进行序列化,将对象从文件中读取出来需要进行反序列化;
③将对象存储到数据库(如 Redis)之前需要用到序列化,将对象从缓存数据库中读取出来需要反序列化;
④将对象存储到内存之前需要进行序列化,从内存中读取出来之后需要进行反序列化。
综上:序列化的主要目的是通过网络传输对象或者说是将对象存储到文件系统、数据库、内存中。
- 基本介绍
功能:提供了对基本类型或对象类型的序列化和反序列化的方法
需要注意的是ObjectInputStream和ObjectOutputStream仍然是处理流(由其构造方法和类图都可以看出来,如下图),即还是要遵守修饰器设计模式的。
- 注意事项和细节说明
(1)读写顺序要一致
(2)要求序列化或反序列化对象时,需要实现Serializable接口
(3)序列化的类中建议添加 serialVersionUID(它是final static类型),为了提高版本的兼容性
【如下面Dog类中增添了private static final long serialVersionUID = 1L;这样一个属性,那么在添加了这个属性后,当对Dog类增加或修改某个属性时,Java会认为其只是Dog类的升级版或修改版,而不是新的一个类,这样就即使不重新执行序列化的代码直接执行反序列化的代码也不会报错。但如果不加肯定会报异常:java.io.InvalidClassException】
(4)序列化对象时,默认将里面属性都进行序列化,但除了static或transient(transient 译为“短暂的/临时的”,其作用是:被transient修饰的变量不参与序列化和反序列化)修饰的成员
(5)序列化对象时,要求里面的属性的类型也需要实现序列化接口(如下面示例代码中的Master类)
(6)序列化具备可继承性,也就是如果某类已经实现了序列化,则它的所有子类也已经默认实现了序列化(如下图中Inter类实际上并未实现Serializable接口,但由于其父类Number类实现了该接口,故它也就默认实现了序列化)
ObjectOutputStream(字节流)
提供 序列化功能
示例代码:
Master类
package com.hspedu.outputstream_;
import java.io.Serializable;
/**
* @author 韩顺平
* @version 1.0
*/
public class Master implements Serializable {
}
Dog类
package com.hspedu.outputstream_;
import java.io.Serializable;
/**
* @author 韩顺平
* @version 1.0
*/
//如果需要序列化某个类的对象,实现 Serializable
public class Dog implements Serializable {
private String name;
private int age;
//序列化对象时,默认将里面所有属性都进行序列化,但除了static或transient修饰的成员
private static String nation;
private transient String color;
//序列化对象时,要求里面属性的类型也需要实现序列化接口
private Master master = new Master();
//serialVersionUID 序列化的版本号,可以提高序列化的兼容性
private static final long serialVersionUID = 1L;
public Dog(String name, int age, String nation, String color) {
this.name = name;
this.age = age;
this.color = color;
this.nation = nation;
}
@Override
public String toString() {
return "Dog{" +
"name='" + name + '\'' +
", age=" + age +
", color='" + color + '\'' +
'}' + nation + " " +master;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
序列化
package com.hspedu.outputstream_;
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
/**
* @author 韩顺平
* @version 1.0
* 演示ObjectOutputStream的使用, 完成数据的序列化
*/
public class ObjectOutStream_ {
public static void main(String[] args) throws Exception {
//序列化后,保存的文件格式,不是存文本,而是按照他的格式来保存
// 但是能够依稀看出来是将数据的值和类型都存放在了文件当中去
String filePath = "e:\\data.dat";
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(filePath));
//序列化数据到 e:\data.dat
oos.writeInt(100);// int -> Integer (实现了 Serializable)
oos.writeBoolean(true);// boolean -> Boolean (实现了 Serializable)
oos.writeChar('a');// char -> Character (实现了 Serializable)
oos.writeDouble(9.5);// double -> Double (实现了 Serializable)
oos.writeUTF("韩顺平教育");//String
//保存一个dog对象
oos.writeObject(new Dog("旺财", 10, "日本", "白色"));
oos.close();
System.out.println("数据保存完毕(序列化形式)");
}
}
ObjectInputStream(字节流)
提供 反序列化功能
示例代码:
反序列化
package com.hspedu.inputstream_;
import com.hspedu.outputstream_.Dog;
import java.io.*;
/**
* @author 韩顺平
* @version 1.0
*/
public class ObjectInputStream_ {
public static void main(String[] args) throws IOException, ClassNotFoundException {
//指定反序列化的文件
String filePath = "e:\\data.dat";
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filePath));
//读取
//老师解读
//1. 读取(反序列化)的顺序需要和你保存数据(序列化)的顺序一致
//2. 否则会出现异常
System.out.println(ois.readInt());
System.out.println(ois.readBoolean());
System.out.println(ois.readChar());
System.out.println(ois.readDouble());
System.out.println(ois.readUTF());
//dog 的编译类型是 Object , dog 的运行类型是 Dog
Object dog = ois.readObject();
System.out.println("运行类型=" + dog.getClass());
System.out.println("dog信息=" + dog);//底层 Object -> Dog
//这里是特别重要的细节:
//1. 如果我们希望调用Dog的方法, 需要向下转型
//2. 需要我们将Dog类的定义,放在可以引用的位置
Dog dog2 = (Dog)dog;
System.out.println(dog2.getName()); //只会输出旺财
//关闭流, 关闭外层流即可,底层会自动关闭传入的字节流:FileInputStream 流
ois.close();
}
}
标准输入输出流(字节流)
System.in 标准输入。系统的输入就叫标准输入 这里的标准输入指的是这个in,in是System里面的一个属性。in对应的类型是InputStream,注意InputStream其实是它的编译类型,它真正的运行类型是BufferedInputStream,它代表的是键盘。
System.out 标准输出。系统的输出就叫标准输出 这里的标准输出指的是这个out,out是System里面的一个属性。out对应的类型是PrintStream,它代表的是显示器。
代码
package com.hspedu.standard;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.Scanner;
/**
* @author 韩顺平
* @version 1.0
*/
public class InputAndOutput {
public static void main(String[] args) {
//System 类 的 public final static InputStream in = null;
// System.in 编译类型 InputStream
// System.in 运行类型 BufferedInputStream
// 表示的是标准输入 键盘
System.out.println(System.in.getClass());
//老韩解读
//1. System.out public final static PrintStream out = null;
//2. 编译类型 PrintStream
//3. 运行类型 PrintStream
//4. 表示标准输出 显示器
System.out.println(System.out.getClass());
// System.out.println("");是使用 out 对象将 数据输出到 显示器
System.out.println("hello, 韩顺平教育~");
// 为 Scanner 扫描器传入 System.in 其实就是 BufferedInputStream。而 System.in 又代表是一个键盘(标准输入),所以当有数据输入时,Scanner 扫描器 就会去键盘的输入也就是控制台去获取数据
Scanner scanner = new Scanner(System.in);
System.out.println("输入内容");
String next = scanner.next();
System.out.println("next=" + next);
}
}
总结:System.in其实代表的就是一个流,即标准输入流;而System.out代表的就是标准输出流。且他们对应的默认位置一个为键盘、一个为显示器
转换流
听其名而知其意,就是将一种字节流转换为字符流
引出(乱码问题):
package com.hspedu.transformation;
import java.io.*;
/**
* @author 韩顺平
* @version 1.0
* 看一个中文乱码问题
*/
public class CodeQuestion {
public static void main(String[] args) throws IOException {
//读取e:\\a.txt 文件到程序
//思路
//1. 创建字符输入流 BufferedReader [处理流]
//2. 使用 BufferedReader 对象读取a.txt
//3. 默认情况下,读取文件是按照 utf-8 编码 -> 但是如果编码发生了问题,不再是 utf-8 编码,就有可能会出现乱码的问题
String filePath = "e:\\a.txt";
BufferedReader br = new BufferedReader(new FileReader(filePath));
String s = br.readLine();
System.out.println("读取到的内容: " + s);
br.close();
//InputStreamReader
//OutputStreamWriter
}
}
上述代码出现的这个乱码问题其根本原因其实就是因为并未指定读取这个文件的编码方式。而字节流就可以指定某一个编码方式去操作文件,再通过转换流将其 转换为 字符流,这样就不会再出现乱码问题了。这就是中间转换流的一个价值
- 介绍
1.InputStreamReader:Reader的子类,可以将InputStream(字节流) 包装(转换) 成Reader(字符流)
2.OutputStreamWriter:Writer的子类,实现将OutputStream(字节流) 包装(转换) 成Writer(字符流)
3.当处理纯文本数据时,因为使用字符流效率更高,并且可以有效解决中文问题,所以建议将字节流转换成字符流
4.可以在使用时指定编码格式(比如utf-8, gbk, gb2312, ISO8859-1等)
为什么叫包装?因为实际上它在底层并没有创建一个新的流,用的是在构造器中传入的字节流
为什么叫转换?但是在处理的时候,它是按照字符的方式处理的
InputStreamReader
由图可知,它是 Reader 的一个子类,故仍然是属于字符流的
示例代码:
package com.hspedu.transformation;
import java.io.*;
/**
* @author 韩顺平
* @version 1.0
* 演示使用 InputStreamReader 转换流解决中文乱码问题
* 将字节流 FileInputStream 转成字符流 InputStreamReader, 指定编码 gbk/utf-8
*/
public class InputStreamReader_ {
public static void main(String[] args) throws IOException {
String filePath = "e:\\a.txt";
//解读
//1. 把 FileInputStream 转成 InputStreamReader
//2. 指定编码 gbk
//InputStreamReader isr = new InputStreamReader(new FileInputStream(filePath), "gbk");
//3. 把 InputStreamReader 传入 BufferedReader
//BufferedReader br = new BufferedReader(isr);
//将2 和 3 合在一起
BufferedReader br = new BufferedReader(new InputStreamReader(
new FileInputStream(filePath), "gbk"));
//4. 读取
String s = br.readLine();
System.out.println("读取内容=" + s);
//5. 关闭外层流
br.close();
}
}
OutputStreamWriter
示例代码:
package com.hspedu.transformation;
import java.io.*;
/**
* @author 韩顺平
* @version 1.0
* 演示 OutputStreamWriter 使用
* 把FileOutputStream 字节流,转成字符流 OutputStreamWriter
* 指定处理的编码 gbk/utf-8/utf8
*/
public class OutputStreamWriter_ {
public static void main(String[] args) throws IOException {
String filePath = "e:\\hsp.txt";
String charSet = "utf-8";
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(filePath), charSet);
osw.write("hi, 韩顺平教育");
osw.close();
System.out.println("按照 " + charSet + " 保存文件成功~");
}
}
注意:如果以追加的方式将数据写入文件需要将编码保持和源文件的编码一致,若不同则会出现乱码现象(前提:源文件中有内容,要是源文件中什么都没有就没什么影响)
打印流
- 打印流有两种分别为:PrintStream和PrintWriter (打印流只有输出流,没有输入流) - 【理解:因为打印流是用于打印的,它会将信息打印到指定的一个位置,如:显示器、文件等】
PrintStream(字节流)
示例代码:
package com.hspedu.printstream;
import java.io.IOException;
import java.io.PrintStream;
/**
* @author 韩顺平
* @version 1.0
* 演示PrintStream (字节打印流/本质是输出流)
*/
public class PrintStream_ {
public static void main(String[] args) throws IOException {
// 由于System中out这个静态属性就是PrintStream类型的,故可以将其赋给一个PrintStream声明的引用
PrintStream out = System.out;
//在默认情况下,PrintStream 输出数据的位置是 标准输出,即显示器(可以修改其输出的位置,因为即使它是个打印流,但其本质还是个输出流)
// print方法源码如下:
/*
public void print(String s) {
if (s == null) {
s = "null";
}
write(s);
// 真正的打印或输出是通过PrintStream类中write方法调用的Writer类中的write方法,源码如下:
}
*/
/*
private void write(String s) {
try {
synchronized (this) {
ensureOpen();
textOut.write(s);
// 这里调用的是Writer类中的write方法
textOut.flushBuffer();
charOut.flushBuffer();
if (autoFlush && (s.indexOf('\n') >= 0))
out.flush();
}
}
catch (InterruptedIOException x) {
Thread.currentThread().interrupt();
}
catch (IOException x) {
trouble = true;
}
}
*/
out.print("john, hello");
//这里也可以通过调用write方法进行打印/输出,此时调用的write方法是父类FilterOutputStream类中的write方法
out.write("韩顺平,你好".getBytes());
// getBytes()是Java中将字符串转化为字节数组的方法
out.close();
//我们可以去修改打印流输出的位置/设备
//1. 输出修改成到 "e:\\f1.txt"
//2. "hello, 韩顺平教育~" 就会输出到 e:\f1.txt
//3. setOut方法源码如下:
// public static void setOut(PrintStream out) {
// checkIO();
// setOut0(out); // native 方法,修改了out
// }
// 我们可以看到在setOut方法中当执行完checkIO()方法后调用了setOut0(out)这个方法,这个方法是个native方法,即是个底层方法-由C/C++实现。setOut0(out)会去修改System中的静态属性out,如本例中就将其改变为指向f1这个文件了,而不是打印/输出到显示器上(System中这个静态属性out默认是指向显示器的,即控制台)
System.setOut(new PrintStream("e:\\f1.txt"));
// 即总的来说就是,通过调用setOut方法去修改了System类中静态属性out打印/输出的位置/设备
System.out.println("hello, 韩顺平教育~");
}
}
PrintWriter(字符流)
示例代码:
package com.hspedu.transformation;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
/**
* @author 韩顺平
* @version 1.0
* 演示 PrintWriter 使用方式
*/
public class PrintWriter_ {
public static void main(String[] args) throws IOException {
//PrintWriter printWriter = new PrintWriter(System.out);
PrintWriter printWriter = new PrintWriter(new FileWriter("e:\\f2.txt"));
// 这里此时传入一个文件路径其实就相当于一个重定向了,定向到了文件
printWriter.print("hi, 北京你好~~~~");
// 这里的print方法中还是调用了本类的write方法实现最终的打印/输出的
printWriter.close();//flush + 关闭流, 才会将数据写入到文件..
// 与FileWriter类似,真正写数据的地方是调用了writeBytes()这个方法,而writeBytes()这个方法中又通过FileOutputStream类对象out调用了write方法,从而实现了最终的打印/输出。- 从close方法里追进去可以清楚的看到底层是如何实现的
// 注意:一定要刷新或关闭流后,才能正确的打印/输出
}
}
从上面两个打印流类的构造器中可以看出,打印流不仅可以把信息打印在显示器上(即以处理流的方式),也可以打印到一个文件里
输入流🚩
InputStream:字节输入流
InputStream抽象类是所有字节输入流类的超类
InputStream常用的子类:
1.FileInputStream:文件输入流
2.BufferedInputStream:缓冲字节输入流(当然也可以操作文件,它是带缓冲了)
3.ObjectInputStream:对象字节输入流(它可以去处理对象,而且是按照字节的方式来进行操作的)
FileInputStream
示例代码:
package com.hspedu.inputstream_;
import org.junit.jupiter.api.Test;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
/**
* @author 韩顺平
* @version 1.0
* 演示FileInputStream的使用(字节输入流 文件--> 程序)
*/
public class FileInputStream_ {
public static void main(String[] args) {
}
/**
* 演示读取文件...
* 单个字节的读取,效率比较低
* -> 使用 read(byte[] b)
*/
@Test
public void readFile01() {
String filePath = "e:\\hello.txt";
int readData = 0;
FileInputStream fileInputStream = null;
try {
//创建 FileInputStream 对象,用于读取 文件
fileInputStream = new FileInputStream(filePath);
//从该输入流读取一个字节的数据。 如果没有输入可用,此方法将阻止。
//如果返回-1 , 表示读取完毕
while ((readData = fileInputStream.read()) != -1) {
// 读取的是一个字节,若文本文件里含有汉字,则一定会出现乱码;如在UTF-8编码中汉字就占3个字节,而每次读取又只能读取一个字节,故无法正确读取文本内的汉字内容
// “UTF-8”和“Unicode”之间的关系是包含关系,因为“UTF-8”是“Unicode”的实现方式之一,它规定了字符如何在计算机中存储、传输等,而其他实现方式还包括“UTF-16”和“UTF-32”。
System.out.print((char)readData);//转成char显示
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//关闭文件流,释放资源.
try {
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 使用 read(byte[] b) 读取文件,提高效率
*/
@Test
public void readFile02() {
String filePath = "e:\\hello.txt";
//字节数组
byte[] buf = new byte[8]; //一次读取8个字节.
int readLen = 0;
FileInputStream fileInputStream = null;
try {
//创建 FileInputStream 对象,用于读取 文件
fileInputStream = new FileInputStream(filePath);
//从该输入流读取最多b.length字节的数据到字节数组。 此方法将阻塞,直到某些输入可用。
//如果返回-1 , 表示读取完毕
//如果读取正常, 返回实际读取的字节数
while ((readLen = fileInputStream.read(buf)) != -1) {
System.out.print(new String(buf, 0, readLen));//显示
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//关闭文件流,释放资源.
try {
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
BufferedInputStream
ObjectInputStream
Reader:字符输入流
字符输入流操作单位是按字符来控制的,所以在操作汉字时也不会出现乱码的问题。如:假设某文件的编码是UTF-8,那么当字符输入流去读取一个汉字的时候,一个字符就是按三个字节来读取
FileReader
常用方法:
(1)new FileReader(File/String)
(2)read:每次读取单个字符,返回该字符,如果到文件末尾返回-1
(3)read(char[])批量读取多个字符到数组,返回读取到的字符数,如果到文件末尾则返回-1
// 这里由于是按字符来读取的,因此就不可以放byte数组了,而是放一个char数组
相关的API:
new String(char[]):将char[]转换成String
new String(char[],off,len):将char[]的指定部分转换成String
示例代码:
package com.hspedu.reader_;
import org.junit.jupiter.api.Test;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
/**
* @author 韩顺平
* @version 1.0
*/
public class FileReader_ {
public static void main(String[] args) {
}
/**
* 单个字符读取文件
*/
@Test
public void readFile01() {
String filePath = "e:\\story.txt";
FileReader fileReader = null;
int data = 0;
//1. 创建FileReader对象
try {
fileReader = new FileReader(filePath);
//循环读取 使用read, 单个字符读取
while ((data = fileReader.read()) != -1) {
System.out.print((char) data);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (fileReader != null) {
fileReader.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 字符数组读取文件
*/
@Test
public void readFile02() {
System.out.println("~~~readFile02 ~~~");
String filePath = "e:\\story.txt";
FileReader fileReader = null;
int readLen = 0;
char[] buf = new char[8];
//1. 创建FileReader对象
try {
fileReader = new FileReader(filePath);
//循环读取 使用read(buf), 返回的是实际读取到的字符数
//如果返回-1, 说明到文件结束
while ((readLen = fileReader.read(buf)) != -1) {
System.out.print(new String(buf, 0, readLen));
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (fileReader != null) {
fileReader.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
BufferedReader
输出流🚩
OutputStream:字节输出流
FileOuputStream
使用 FileOutputStream 对象的write方法可以将指定的字节写入此文件输出流。如果文件不存在,会创建文件(注意:前提是目录已经存在)
示例代码:
package com.hspedu.outputstream_;
import org.junit.jupiter.api.Test;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
/**
* @author 韩顺平
* @version 1.0
*/
public class FileOutputStream01 {
public static void main(String[] args) {
}
/**
* 演示使用FileOutputStream 将数据写到文件中,
* 如果该文件不存在,则创建该文件
*/
@Test
public void writeFile() {
//创建 FileOutputStream对象
String filePath = "e:\\a.txt";
FileOutputStream fileOutputStream = null;
try {
//得到 FileOutputStream对象
//老师说明
//1. new FileOutputStream(filePath) 创建方式,当写入内容时,会覆盖原来的内容
//2. new FileOutputStream(filePath, true) 创建方式,当写入内容时,是追加到文件后面
fileOutputStream = new FileOutputStream(filePath, true);
//写入一个字节
//fileOutputStream.write('H');//
//写入字符串
String str = "hsp,world!";
//str.getBytes() 可以把 字符串-> 字节数组
//fileOutputStream.write(str.getBytes());
/*
write(byte[] b, int off, int len) 将 len个字节从位于偏移量 off 的指定字节数组写入此文件输出流
*/
fileOutputStream.write(str.getBytes(), 0, 3);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
fileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
BufferedOutputStream
ObjectOutputStream
Writer:字符输出流
字符输出流是按照字符来操作IO
FileWriter
常用方法:
(1)new FileWriter(File/String):覆盖模式,相当于流的指针在首端
(2)new FileWriter(File/String,true):追加模式,相当于流的指针在尾端
(3)write(int):写入单个字符
(4)write(char[]):写入指定数组
(5)write(char[],off,len):写入指定数组的指定部分
(6)write(String):写入整个字符串
(7)write(string,off,len):写入字符串的指定部分
相关的API:
String类:toCharArray:将String转换成char[]
注意:
FileWriter使用后,必须要关闭(close)或者刷新(flush),否则写入不到指定的文件!
// 如果最后同时进行flush和close,那么数据也只会写入文件一次。当两个方法都有时,flush一定要在前,否则流关闭了也没办法对其进行操作了。那么为什么只会写入一次呢?可以这么理解:在flush时就已经将流内的内容全部输出至(即调用FileOutputStream的write方法)文件了,故调用完flush方法后可以认为流内就是空的了,所以在调用close方法时,close底层的write方法也就没有可输出的内容了,故只会输出一次。
示例代码:
package com.hspedu.writer_;
import java.io.FileWriter;
import java.io.IOException;
/**
* @author 韩顺平
* @version 1.0
*/
public class FileWriter_ {
public static void main(String[] args) {
String filePath = "e:\\note.txt";
//创建FileWriter对象
FileWriter fileWriter = null;
char[] chars = {'a', 'b', 'c'};
try {
fileWriter = new FileWriter(filePath);//默认是覆盖写入
// 3) write(int):写入单个字符
fileWriter.write('H');
// 4) write(char[]):写入指定数组
fileWriter.write(chars);
// 5) write(char[],off,len):写入指定数组的指定部分
fileWriter.write("韩顺平教育".toCharArray(), 0, 3);
// 6) write(string):写入整个字符串
fileWriter.write(" 你好北京~");
fileWriter.write("风雨之后,定见彩虹");
// 7) write(string,off,len):写入字符串的指定部分
fileWriter.write("上海天津", 0, 2);
//在数据量大的情况下,可以使用循环操作.
} catch (IOException e) {
e.printStackTrace();
} finally {
//对应FileWriter , 一定要关闭流,或者flush才能真正的把数据写入到文件
//老韩看源码就知道原因.
/*
看看代码
private void writeBytes() throws IOException {
this.bb.flip();
int var1 = this.bb.limit();
int var2 = this.bb.position();
assert var2 <= var1;
int var3 = var2 <= var1 ? var1 - var2 : 0;
if (var3 > 0) {
if (this.ch != null) {
assert this.ch.write(this.bb) == var3 : var3;
} else {
this.out.write(this.bb.array(), this.bb.arrayOffset() + var2, var3);
// out对象是FileOutputStream类,且这里out对象调用的write方法才是真正干活的地方
}
}
this.bb.clear();
}
*/
// 无论是close()还是flush()方法,最后都是调用了FileOutputStream的write()方法,所以需要在刷新或关闭FileWriter流对象后数据才能写到指定的文件中去
try {
//fileWriter.flush();
//关闭文件流,等价 flush() + 关闭
fileWriter.close();
} catch (IOException e) {
e.printStackTrace();
}
}
System.out.println("程序结束...");
}
}
BufferedWriter
Properties类
看一个需求:有如下一个配置文件 mysql.properties
ip=192.168.0.13
user=root
pwd=12345
请使用编程读取 ip、user 和 pwd 的值
传统方式代码:
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
public class Properties01 {
public static void main(String[] args) throws IOException {
//读取mysql.properties 文件,并得到ip, user 和 pwd
BufferedReader br = new BufferedReader(new FileReader("src\\mysql.properties"));
String line = "";
while ((line = br.readLine()) != null) { //循环读取
String[] split = line.split("=");
//如果我们要求指定得到ip值
if("ip".equals(split[0])) {
System.out.println(split[0] + "值是: " + split[1]);
}
}
br.close();
}
}
按照传统的IO流读取文件的方式可以得到配置文件中的值,但在获得或修改某一个值时还是比较麻烦的。故我们可以采取新的方案,即,使用Properties类方便的实现对配置文件的读写。
使用Properties类操作配置文件代码:
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.Properties;
public class Properties02 {
public static void main(String[] args) throws IOException {
//使用Properties 类来读取mysql.properties 文件
//1. 创建Properties 对象
Properties properties = new Properties();
//2. 加载指定配置文件
properties.load(new FileReader("src\\mysql.properties"));
//3. 把k-v显示控制台
properties.list(System.out);
//4. 根据key 获取对应的值
String user = properties.getProperty("user");
String pwd = properties.getProperty("pwd");
System.out.println("用户名=" + user);
System.out.println("密码是=" + pwd);
}
}
- 基本介绍
1)专门用于读写配置文件的集合类
要求配置文件的格式:
键=值
2)注意:键值对不需要有空格,值不需要用引号扩起来,默认类型是String
3)Properties的常见方法
list:将数据显示到指定设备/流对象(显示器、文件)
unicode码查询工具:https://tool.chinaz.com/tools/unicode.aspx
案例:
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Properties;
public class Properties03 {
public static void main(String[] args) throws IOException {
//使用Properties 类来创建 配置文件, 修改配置文件内容
Properties properties = new Properties();
//创建文件
//1.如果该文件没有key 就是创建
//2.如果该文件有key ,就是修改
/*
Properties 父类是 Hashtable , 底层就是Hashtable 核心方法
public synchronized V put(K key, V value) {
// Make sure the value is not null
if (value == null) {
throw new NullPointerException();
}
// Makes sure the key is not already in the hashtable.
Entry<?,?> tab[] = table;
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
@SuppressWarnings("unchecked")
Entry<K,V> entry = (Entry<K,V>)tab[index];
for(; entry != null ; entry = entry.next) {
if ((entry.hash == hash) && entry.key.equals(key)) {
V old = entry.value;
entry.value = value;//如果key 存在,就替换
return old;
}
}
addEntry(hash, key, value, index);//如果是新k, 就addEntry
return null;
}
*/
properties.setProperty("charset", "utf8");
properties.setProperty("user", "汤姆");//注意:保存的是中文的 unicode码值
properties.setProperty("pwd", "888888");
//将k-v 存储文件中即可
properties.store(new FileOutputStream("src\\mysql2.properties"), null);
System.out.println("保存配置文件成功~");
}
}
只能读取后缀为 properties 的文件
应用
文件的拷贝(FileInputStream & FileOutputStream)-针对图片/音乐等二进制文件
代码
package com.hspedu.outputstream_;
import com.hspedu.inputstream_.FileInputStream_;
import java.io.*;
/**
* @author 韩顺平
* @version 1.0
*/
public class FileCopy {
public static void main(String[] args) {
//完成 文件拷贝,将 e:\\Koala.jpg 拷贝 c:\\
//思路分析
//1. 创建文件的输入流 , 将文件读入到程序
//2. 创建文件的输出流, 将读取到的文件数据,写入到指定的文件.
String srcFilePath = "e:\\Koala.jpg";
String destFilePath = "e:\\Koala3.jpg";
FileInputStream fileInputStream = null;
FileOutputStream fileOutputStream = null;
try {
fileInputStream = new FileInputStream(srcFilePath);
fileOutputStream = new FileOutputStream(destFilePath);
//定义一个字节数组,提高读取效果
byte[] buf = new byte[1024];
int readLen = 0;
while ((readLen = fileInputStream.read(buf)) != -1) {
//读取到后,就写入到文件 通过 fileOutputStream
//即,是一边读,一边写
fileOutputStream.write(buf, 0, readLen);//一定要使用这个方法
}
System.out.println("拷贝ok~");
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
//关闭输入流和输出流,释放资源
if (fileInputStream != null) {
fileInputStream.close();
}
if (fileOutputStream != null) {
fileOutputStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
文件的拷贝(BufferedReader & BufferedWriter)
代码
package com.hspedu.writer_;
import java.io.*;
/**
* @author 韩顺平
* @version 1.0
*/
public class BufferedCopy_ {
public static void main(String[] args) {
//老韩说明
//1. BufferedReader 和 BufferedWriter 是按照字符操作
//2. 不要去操作 二进制文件[声音,视频,doc, pdf ], 可能造成文件损坏
//BufferedInputStream
//BufferedOutputStream
String srcFilePath = "e:\\a.java";
String destFilePath = "e:\\a2.java";
// String srcFilePath = "e:\\0245_韩顺平零基础学Java_引出this.avi";
// String destFilePath = "e:\\a2韩顺平.avi";
BufferedReader br = null;
BufferedWriter bw = null;
String line;
try {
br = new BufferedReader(new FileReader(srcFilePath));
bw = new BufferedWriter(new FileWriter(destFilePath));
//说明: readLine 读取一行内容,但是没有换行
while ((line = br.readLine()) != null) {
//每读取一行,就写入
bw.write(line);
//插入一个换行
// newLine()换行方法插入的换行符是和系统相关的,即会根据系统的不同,插入系统对应的换行符号。如:windows中的换行符是\r\n,而linux/unix下的换行符是\n
bw.newLine();
}
System.out.println("拷贝完毕...");
} catch (IOException e) {
e.printStackTrace();
} finally {
//关闭流
try {
if(br != null) {
br.close();
}
if(bw != null) {
bw.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
文件的拷贝(BufferedInputStream & BufferedOutputStream)-针对图片/音乐等二进制文件
代码
package com.hspedu.outputstream_;
import java.io.*;
/**
* @author 韩顺平
* @version 1.0
* 演示使用BufferedOutputStream 和 BufferedInputStream使用
* 使用他们,可以完成二进制文件拷贝.
* 思考:字节流可以操作二进制文件,可以操作文本文件吗?当然可以
* 因为文本文件最终底层还是按字节为基本单位去处理,故肯定可以,但是容易出乱码的情况
*/
public class BufferedCopy02 {
public static void main(String[] args) {
// String srcFilePath = "e:\\Koala.jpg";
// String destFilePath = "e:\\hsp.jpg";
// String srcFilePath = "e:\\0245_韩顺平零基础学Java_引出this.avi";
// String destFilePath = "e:\\hsp.avi";
String srcFilePath = "e:\\a.java";
String destFilePath = "e:\\a3.java";
//创建BufferedOutputStream对象BufferedInputStream对象
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
try {
//因为 FileInputStream 是 InputStream 子类
bis = new BufferedInputStream(new FileInputStream(srcFilePath));
bos = new BufferedOutputStream(new FileOutputStream(destFilePath));
//循环的读取文件,并写入到 destFilePath
byte[] buff = new byte[1024];
int readLen = 0;
//当返回 -1 时,就表示文件读取完毕
while ((readLen = bis.read(buff)) != -1) {
bos.write(buff, 0, readLen);
}
System.out.println("文件拷贝完毕~~~");
} catch (IOException e) {
e.printStackTrace();
} finally {
//关闭流 , 关闭外层的处理流即可,无需手动关闭节点流,底层会关联地去关闭节点流
try {
if(bis != null) {
bis.close();
}
if(bos != null) {
bos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}