IO流
·······················································································································································································································
通过OutputStream对象,完成向文本文件中输出” hello world”
注意到,OutputStream是抽象类,如果要使用OutputStream对象,只能使用其 子类对象,完成写入功能。
同时,因为是向外部设备(硬盘)中的,文件中,写入数据,所以使用子类FileOutputStream对OutputStream做间接实例化
虽然说,在文本文件中,写入文本数据,应该用字符流来做,但是节流也可以实现,先用字节流完成
- 要完成数据传输,根据流模型,第一步,我们要搭建好数据传输通道——>流对象
-
FileOutputStream的构造方法
FileOutputStream(File file)
// file对象,用来表示,输出流,输出数据的目标文件 -
FileOutputStream(String name)
// name路径字符串,用来表示,输出流,输出数据的目标文件
这两个构造方法本质上没有任何区别,只是目标文件的表示方式不一样。
2. 对于输出流而言,利用写操作,让数据在传输通道中,流动起来
-
public void write(int b)
将指定的字节写入此输出流。
write 的常规协定是:向输出流写入一个字节(因为char占一个字节)。要写入的字节是参数 b 的八个低位。b 的 24 个高位将被忽略。
转化成b对应的ASCII表中的字符 -
public void write(byte[] b)
//将一整个字节数组中的多个字节数据,一次性写入流中(让数据在流中流动起来) -
public void write(byte[] b,int off,int len)
// 将一个字节数中的从offset个位置开始的,len个字节数组,写入到流中,进行数据传输
//第一种一次写入一个字节数据,后两种一次写入多个字节数据
3. 关闭流
这三步:搭桥、运输、拆桥。
public static void myCode() throws IOException {
File file = new File("F:\\test\\test01.txt");
if(!file.exists()){
file.createNewFile();
}
///父类引用指向子类实例,我管你子类怎么实现父类的方法的,
//我只要知道父类的write()方法,我就能对ByteArrayOutputStream, FileOutputStream, FilterOutputStream, ObjectOutputStream, OutputStream, PipedOutputStream 这些子类进行写操作了
OutputStream fos = new FileOutputStream(file);
//1.
fos.write(97);
//2.
String s = "hello world";
byte[] bytes = s.getBytes();
fos.write(bytes);
//3.
fos.write(bytes,6,5);
//关闭流
fos.close();
}
·······················································································································································································································
字节流写数据常见问题:
- 创建字节输出流到底做了哪些事情?
1). FileOutputStream对象在被创建之前,jvm会首先到操作系统中,找目标文件
a. 找到,首先,清空已经存在的目标文件内容(因为默认向文件中写入数据中的方式,从文件头开始写入)
b. 找不到,jvm会创建一个新的目标文件
2). 在jvm内存中,创建FileOutputStream对象
3). 在FileOutputStream对象和目标文件之间,建立数据传输通道
其实就是我要在内存和外设之间搭桥,我得先找到一端,即外设中的a.txt,然后再找到内存中的FileOutputStream对象,然后才在两端建立数据传输通道。接下来只需在数据传输通道中写入数据并让数据流动起来,数据就完成传输了。
简单理解:搭桥三步骤:找外设端、找内存端、搭桥。
-
数据写成功后,为什么要close()?
1.关闭此输出流,即断开数据传输通道
2.并释放与此流有关的所有系统资源。 -
如何实现数据的换行?
核心,是向文件中写入换行字符
windows操作系统: ‘\r’’\n’(在不同的windows操作系统上,可能表现不一样)
类unix操作系统:’\n’ -
如何实现数据的 追加写入? (两次写入)
FileOutputStream(String name, boolean append)
创建一个向具有指定 name 的文件中写入数据的输出文件流。
如果第二个参数为 true,则将字节写入文件末尾处,而不是写入文件开始处
FileOutputStream(File file, boolean append)
创建一个向指定 File 对象表示的文件中写入数据的文件输出流。
如果第二个参数为 true,则将字节写入文件末尾处,而不是写入文件开始处 -
给I/0流操作加上异常处理
//虽然这段代码有点丑,但是至少是对的,后面会讲稍微清爽点的代码
public static void myCode2() {
//1.一定要事先声明FileOutputStream对象,如果在try块中声明,只会在try{}作用域中起作用,
//finally就会报错,因为finally不认识try块中声明的FileOutputStream对象
//2.FileOutputStream对象首先赋值为null,如果try块中OutputStream fos = new FileOutputStream("G:\\test\\test02.txt",true);
//这句代码一定会报错:FileNotFoundException,因为就没有G盘。
//继续往下执行,catch块捕获到FileNotFoundException,执行catch块中的代码。
//下一步到了finally,finally一看,FileOutputStream对象为null,然而你要让它close(),
//一定会报错:NullPointerException。
//所以一定要在close()之间,先判断FileOutputStream对象是否为null
OutputStream fos = null;
try {
fos = new FileOutputStream("F:\\test\\test02.txt" ,true);
fos.write('\r');
fos.write('\n');
fos.write("Yes,I will".getBytes());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (fos != null) {
fos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
·······················································································································································································································
我们使用InputStream从该文本中,读出刚刚写入的内容,并输出在控制台。
同时,注意到InputStream是抽象类,不能直接实例化,需要使用其子类对象来实现功能
FileInputStream的构造方法
-
FileInputStream(File file)
// 告诉FileInputStream,从哪个以File对象表示的目标文件,读取数据到内存 -
FileInputStream(String name)
//告诉FileInputStream,从哪个以路径名字符串name表示的目标文件,读取数据到内存
创建一个FileInputStream对象,jvm作了哪些工作:
- FileInputStream对象咋被创建之前,jvm会首先到操作系统中,找目标文件
a. 找到,就不做任何额外工作
b. 找不到,则直接抛出异常FileNotFoundException - 在jvm内存中,创建FileInputStream对象
- 在FileInputStream对象和目标文件之间,建立数据传输通道(然后在数据传输通道中写入数据
并让数据流动起来,数据就完成传输了)
InputStream中的成员方法:
-
int read()
从输入流中读取数据的下一个字节。
返回 0 到 255 范围内(8位无符号整数)的 int 字节值。
如果因为已经到达流末尾而没有可用的字节(读完了,后面没数据了),则返回值 -1 -
int read(byte[] b)
从输入流中读取一定数量的字节,并将其存储在缓冲区数组 b 中。(再从缓冲区数组中取出数据)
返回值:
1.以整数形式返回实际读取的字节数
2.如果因为已经到达流末尾而不再有数据可用,则返回 -1。 -
int read(byte[] b, int off, int len) 不重要,了解其意思
将输入流中最多 len 个数据字节读入 byte 数组。
返回值:
1.以整数形式返回实际读取的字节数
2.如果因为已经到达流末尾而不再有数据可用,则返回 -1。
void close()
关闭此输入流并释放与该流关联的所有系统资源。
read()方法示意图:
public static void myCode() {
InputStream fis = null;
try {
fis = new FileInputStream("F://test//test02.txt");
//1.int read() 这个方法执行一次,只读取一个字节的数据
// int readByte;
// while ((readByte = fis.read()) != -1) {
// char c = (char) readByte;
// System.out.print(c);
// }
// System.out.println();
//2.
// 字节数组大小通常给1024的整数倍 1字节(byte)=8位(bit)
// byte[] bytes = new byte[1024];
// fis.read(bytes);
// System.out.println(new String(bytes));
//3.
byte[] bytes2 = new byte[1024];
fis.read(bytes2, 5, 20);
System.out.println(new String(bytes2));
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (fis != null) {
fis.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
·······················································································································································································································
字节流复制数据练习
把d:\a.txt内容复制到e:\b.txt中
把e:\**.jpg内容复制到当前项目目录下的mn.jpg
//按字节数组读取
public static void copyFileByBytes(String srcpath, String destpath) {
InputStream fis = null;
OutputStream fos = null;
try {
//读出
fis = new FileInputStream(srcpath);
fos = new FileOutputStream(destpath);
byte[] bytes = new byte[1024];
int len;
//假如src文件有4.5KB,先写4次都是fos.write(bytes,0,1024)
//第五次是fos.write(bytes,0,512)
//读取一个字节数组的数据,向目标文件中,写入一个字节数组数据
while ((len = fis.read(bytes)) != -1) {
fos.write(bytes, 0, len);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
myClose(fis);
myClose(fos);
}
}
//一个字节一个字节读取
public static void copyFileByByte(String srcpath, String destpath) {
InputStream fis = null;
OutputStream fos = null;
try {
//读出
fis = new FileInputStream(srcpath);
fos = new FileOutputStream(destpath);
int readByte;
// 判断有没有读完
while ((readByte = fis.read()) != -1) {
//读取一个字节数组的数据,向目标文件中,写入一个字节数组数据
fos.write(readByte);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
myClose(fis);
myClose(fos);
}
}
//InputStream和OutputStream有公共的父类接口:Closeable,并且都覆盖了其成员方法close()
//利用多态
public static void closeQuietly(Closeable closeable) {
try {
if (closeable != null) {
closeable.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
·······················································································································································································································
FileInputStream的成员方法
public int read()
public int read(byte[] b)
字节流读取数据两种方式比较:
一次读取一个字节
一次读取一个字节数组
从效率角度讲,哪种方式比较好呢?为什么?
·······················································································································································································································
字节缓冲流
字节流一次读写一个数组的速度明显比一次读写一个字节的速度快很多,这是加入了数组这样的缓冲区效果。java本身在设计的时候,也考虑到了这样的情况,所以提供了字节缓冲区流
字节缓冲输出流
BufferedOutputStream
构造方法:
BufferedOutputStream(OutputStream out)
//创建一个新的缓冲输出流,以将数据写入指定的底层输出流。
//参数OutputStream out其实是一个继承了OutputStream抽象类的子类
字节缓冲输入流
BufferedInputStream
构造方法
BufferedInputStream(InputStream in)
//创建一个 BufferedInputStream 并保存其参数,即输入流 in,以便将来使用。
//参数InputtputStream in其实是一个继承了OutputStream抽象类的子类
在创建一个流对象的时候,需要基于其他的流对象来创建的,这种流对象 -> 包装流
对于包装流而言,如果我们要关闭包装流,只需要调用流的close方法即可,不用try–catch。因为,包装流自己会保证,正常关闭,包装流锁关闭的底层流。
字符缓冲流如何减小通信代价呢?
·······················································································································································································································
练习:把d:\a.txt内容复制到e:\b.txt中
public class Exercise {
public static void main(String[] args) throws IOException {
// 利用缓冲流,一次复制一个字节数组的方式
copyFileByBufferBytes("d:\\a.txt", "e:\\b.txt");
}
public static void copyFileByBufferBytes(String srcPath, String destPaht)
throws IOException {
// 创建缓冲输入字节流对象
InputStream in = new BufferedInputStream(
new FileInputStream("d:\\a.txt"));
// 创建缓冲字节输出流对象
OutputStream out = new BufferedOutputStream(
new FileOutputStream("e:\\b.txt"));
int len;
byte[] buffer = new byte[1024];
//复制文件
while((len = in.read(buffer)) != -1) {
out.write(buffer, 0, len);
}
// 对于包装流的关闭,只需要关闭包装流本身即可,包装流锁保证的底层流不需要专门关闭
in.close();
out.close();
}
}
·······················································································································································································································
关于缓冲输出流的注意事项:
-
void flush()
刷新此缓冲的输出流。 flush可以可以帮助我们,强制把缓冲流中的字节数字,写入底层流中,从而写入目标文件中 -
BufferedOutputStream的close
方法先调用其 flush 方法,然后调用其基础输出流的 close 方法
比较四种复制方式的效率
- 不使用缓冲流
a. 一次复制一个字节数据
b. 一次复制一个字节数组的数据 - 使用缓冲流
a. 一次复制一个字节数据
b. 一次复制一个字节数组的数据
copyByte:最慢
copyByteInBuf: 434
copyBytes: 50
copyBytesInBuf: 24
public class Comparison {
public static void main(String[] args) throws IOException {
File src = new File("E:\\百度网盘下载项\\账号2\\StringBuffer, Date DateFormat,Math\\视频\\02.mp4");
File dest1 = new File("F:\\copy-vedio1.mp4");
File dest2 = new File("F:\\copy-vedio2.mp4");
File dest3 = new File("F:\\copy-vedio3.mp4");
File dest4 = new File("F:\\copy-vedio4.mp4");
//不用缓冲流,一次复制一个字节
//copyByte(src, dest1);
//使用缓冲流,一次复制一个字节
copyByteInBuf(src, dest2);
//不使用缓冲流,一次复制一个字节数组数据
//copyBytes(src, dest3);
//使用缓冲流,一次复制一个字节数组数据
//copyBytesInBuf(src, dest4);
}
/**
* 一次读写一个字节
*/
public static void copyByte(File srcFile, File destFile) throws IOException {
//创建输入流对象
FileInputStream fis = new FileInputStream(srcFile);
//创建输出流对象
FileOutputStream fos = new FileOutputStream(destFile);
int readByte;
//记录当前时间的毫秒值
long start = System.currentTimeMillis();
//开始一个字节一个字节的复制
while ((readByte = fis.read()) != -1) {
fos.write(readByte);
}
//记录复制完成之后的一个时间的毫秒值
long end = System.currentTimeMillis();
System.out.println("copyByte: " + (end - start));
fis.close();
fos.close();
}
/**
* 一次读写一个字节 但是用的是缓冲流
*/
public static void copyByteInBuf(File srcFile, File destFile) throws IOException {
//创建输入流对象
FileInputStream fis = new FileInputStream(srcFile);
BufferedInputStream bis = new BufferedInputStream(fis);
//创建输出流对象
FileOutputStream fos = new FileOutputStream(destFile);
BufferedOutputStream bos = new BufferedOutputStream(fos);
int readByte;
//开始一个字节一个字节的复制
long start = System.currentTimeMillis();
while ((readByte = bis.read()) != -1) {
//读到缓冲区中了,然后一块写出到外设
bos.write(readByte);
}
long end = System.currentTimeMillis();
System.out.println("copyByteInBuf: " + (end - start));
bis.close();
bos.close();
}
/**
* 一次读写一个字节数组
*/
public static void copyBytes(File srcFile, File destFile) throws IOException {
//创建输入流对象
FileInputStream fis = new FileInputStream(srcFile);
//创建输出流对象
FileOutputStream fos = new FileOutputStream(destFile);
int len;
byte[] buffer = new byte[2048];
long start = System.currentTimeMillis();
//开始一个字节一个字节的复制
while ((len = fis.read(buffer)) != -1) {
fos.write(buffer, 0, len);
}
long end = System.currentTimeMillis();
System.out.println("copyBytes: " + (end - start));
fis.close();
fos.close();
}
/**
* 一次读写一个字节数组,用缓冲流
*/
public static void copyBytesInBuf(File srcFile, File destFile) throws IOException {
//创建输入流对象
FileInputStream fis = new FileInputStream(srcFile);
BufferedInputStream bis = new BufferedInputStream(fis);
//创建输出流对象
FileOutputStream fos = new FileOutputStream(destFile);
BufferedOutputStream bos = new BufferedOutputStream(fos);
int len;
byte[] buffer = new byte[2048];
long start = System.currentTimeMillis();
//开始一个字节一个字节的复制
while ((len = bis.read(buffer)) != -1) {
bos.write(buffer, 0, len);
}
long end = System.currentTimeMillis();
System.out.println("copyBytesInBuf: " + (end - start));
bis.close();
bos.close();
}
}
对于copyBytes假如bytes[]是1024byte,读4.5kb的文件,得读取5次,jvm得向操作系统发出5次读请求。但对于copyBytesInBuf,假如缓冲区为5kb,读取一次即可,jvm向操作系统发出1次读请求,然后byte[]数组读取缓冲区中的内容,但这一环节并没有通信代价。
copyByteBuf从源文件(4.5kb)读取数据,将读缓冲区(5kb)读满 或读完源文件的数据 之后再一个字节一个字节的取出,放入写缓冲区(5kb),然后写缓冲区再一次性的写入目标文件。
·······················································································································································································································
作业:
- 有这样的一个words数组,数组中每个字符串的格式为“词性:单词”
String[] words = {“verb:eat”,“verb:drink”,“verb:sleep”,“verb:play”,“noun:rice”,“noun:meat”,“noun:hand”,“noun:hair”};
根据单词性质动词verb全部存入verb.txt文件中
根据单词性质名词noun全部存入noun.txt文件中
public class Homework01 {
public static void main(String[] args) throws IOException {
String[] words = {"verb:eat", "verb:drink", "verb:sleep", "verb:play",
"noun:rice", "noun:meat", "noun:hand", "noun:hair"};
teacherCode(words);
}
public static void myCode(String[] words) throws IOException {
BufferedOutputStream bos1 = new BufferedOutputStream(new FileOutputStream("F:\\test\\verb.txt"));
BufferedOutputStream bos2 = new BufferedOutputStream(new FileOutputStream("F:\\test\\noun.txt"));
for (String word : words) {
if (word.substring(0, 4).equals("verb")) {
bos1.write(word.substring(5, word.length()).getBytes());
bos1.write('\r');
bos1.write('\n');
} else if (word.startsWith("noun")) {
bos2.write(word.substring(5, word.length()).getBytes());
bos2.write('\r');
bos2.write('\n');
}
}
bos1.close();
bos2.close();
}
private static void teacherCode(String[] words) {
String verb = "verb";
String noun = "noun";
//将所有的动词和名词分别拼接到verbs和nouns两个字符串中
//让后直接将包含所有 动词的字符串verbs 和 包含所有 名词的字符串nouns,分别写入verb.txt和noun.txt中
String verbs = "";
String nouns = "";
for (String word : words) {
if (word.contains(verb)) {
verbs += word;
verbs += "\n";
}
if (word.contains(noun)) {
nouns += word;
nouns += "\n";
}
}
File verbFile = new File("F:\\test\\verb.txt");
File nounFile = new File("F:\\test\\noun.txt");
FileOutputStream verbFileOutputStream = null;
FileOutputStream nounFileOutputStream = null;
try {
//初始化输出流
verbFileOutputStream = new FileOutputStream(verbFile);
nounFileOutputStream = new FileOutputStream(nounFile);
//获得字节数组
byte[] verbBytes = verbs.getBytes();
byte[] nounBytes = nouns.getBytes();
//输出
verbFileOutputStream.write(verbBytes);
nounFileOutputStream.write(nounBytes);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
//资源释放
try {
if (verbFileOutputStream != null) {
verbFileOutputStream.close();
}
if (nounFileOutputStream != null) {
nounFileOutputStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
- 递归查找指定目录中(包括子目录中),所有的.java文件,
并且,把所有这些找到的java文件,复制到一个指定的目录下
目录结构同昨天,递归删除那道题的firstLevel
public class Homework02 {
public static void main(String[] args) {
FileHandler fileHandler = null;
try {
//要查找的目录
File dir = new File("d:\\firstLevel");
//利用fileHandler对象,完成递归查找,并将查找结果,存到d:\result目录中
fileHandler = new FileHandler("d:\\result");
//由FileFilter接口的匿名内部类对象,指明目标文件的筛选条件,开始递归查找,
// 并对找到的目标文件做相应处理
fileHandler.findFile(dir, new FileFilter() {
@Override
public boolean accept(File pathname) {
return pathname != null
&& pathname.isFile()
&& pathname.getName().endsWith(".java");
}
});
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/*
利用该类,完成文件的递归查找功能
*/
class FileHandler {
//保存指定目录中,所有java文件的目录路径
File dir;
public FileHandler(String recordPath) throws FileNotFoundException {
//利用目标文件的路径字符串,初始化,表示用来存储文件名的文件的File对象
dir = new File(recordPath);
//确保目标目录存在
dir.mkdirs();
}
/*
利用该方法,使用由FileFilter对象,所指明的筛选条件,筛选出符合条件的文件
并将这些文件的绝对路径,保存到指定文件中
*/
public void findFile(File file, FileFilter filter) throws IOException {
File[] files = file.listFiles();
if (files == null) {
//说明当前File dir其实表示的是一个文件
if (filter.accept(file)) {
//满足筛选条件,就将满足条件的文件复制到目标目录下
//创建File对象,表示复制的目标文件
File destFile = new File(dir, file.getName());
copyFile(file, destFile);
}
return;
}
for (int i = 0; i < files.length; i++) {
findFile(files[i], filter);
}
}
//利用缓冲流完成文件的复制
public void copyFile(File srcFile, File destFile) {
// 创建缓冲输入字节流对象
InputStream in = null;
// 创建缓冲字节输出流对象
OutputStream out = null;
try {
in = new BufferedInputStream(
new FileInputStream(srcFile));
out = new BufferedOutputStream(
new FileOutputStream(destFile));
int len;
byte[] buffer = new byte[1024];
//复制文件
while((len = in.read(buffer)) != -1) {
out.write(buffer, 0, len);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
//关闭输入流
closeQuietly(in);
//关闭输出流
closeQuietly(out);
}
}
private void closeQuietly(Closeable closeable) {
//关闭输出流
try {
if (closeable != null) {
closeable.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}