Java IO 流
把文件里存的东西放到内存里的过程叫:输入(Input),数据的流动我门叫:输入流(InputStream),输入的过程又被称为:读(Read)。
从内存里出来到到硬盘叫:输出(Output),数据流动的过程叫:输出流(OutputStream),又被叫:写(Write)
IO是什么?
通过IO可以完成文件的读写。IO就是Input和Output首字母。
I:Input
O:Output
IO流的分类
IO流有多种分类方式:
- 一种方式是按照流的方向进行分类:
- 以内存作为参造物,往内存中去,叫做输入或者叫做读。(Input)
- 从内存中出来叫做输出或者叫做写。(Output、Write)
- 另一种方式是按照读取数据方式不同进行分类:
- 有的流是按照字节的方式读取数据,一次读取1个字节byte,等同于一次读取8个二进制位。这种流是万能的,什么类型的文件都能读取。(字节流)
- 有的流是按照字符的方式读取数据的,一次读取一个字符,这种流是为了方便读取普通文本而存在的,这种流不能读取:图片、声音、视频等文件。只能读取纯文本文件,连word文件都无法读取。
假设文件file.txt,其内容是:
a中国bc
如果采用字节流的方式去读:
第一次读:一个字节,正好读到 ‘a’
第二次读:一个字节,正好读到‘中’字符的一半(原因是在Windows系统中,中文占用两个字节,字节流一次只能读取一个字节)
第三次读:一个字节,正好读到‘中’的另一半。
如果采用字符流来对该文件读取:
第一次读:‘a’字符
第二次读:‘中’字符
输入流、输出流、字节流、字符流
‘a’英文字母,在Windows操作系统中是1个字节,但是‘a’字符在java中占用2个字节,flex.txt和java没有关系,因为这个文件只是Windows操作系统上的普通文件。Windows操作系统中文件中的‘a’字符占用的是1个字节(读文件是调用Windows操作系统,操作系统读取文件的时候,认为一个字符是一个字节,所以和JVM没有关系)
‘流’应该怎么学习
Java中的IO流都已经写好了,我们学习时不需要去关心底层是如何去实现的,我们最主要的还是掌握,在java中已经提供了哪些流,每个流的特点是什么,每个流对象上的常用方法有哪些??
Java中所有的流都是在:java.io.*;
下。
Java中主要学习怎么new流对象,调用流对象的哪个方法是读,哪个方法是写。
Java IO流中的四大家族
-
java.io.InputStream 字节输入流
-
java.io.OutputStream 字节输出流
-
java.io.Reader 字符输入流
-
java.io.Writer 字节输出流
所有的流都实现了:
java.io.Closeable接口,都是可关闭的,都有close方法。流毕竟是一个管道,这个是内存和硬盘之间的通道,用完之后一定要关闭,不然会耗费很多资源。
四大家族的首领都是抽象类 abstract
在java中只要‘类名’以Stream结尾的都是字节流。以‘Reader/Writer’结尾的都是字符流。
所有的输出流都实现了:
java.io.Flushable接口,都是可刷新的,都有flush方法,输出流在最终输出之后,一定要记得flush刷新一下。这个刷新表示将通道/管道当中剩余未输出的数据强行输出完(清空管道)。
如果没有flush可能会导致丢失数据。并不是所有的流都有flush方法,只有输出流实现了java.io.Flushable接口
需要掌握哪些流
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
看着很多,但实际上把文件专属流前两个搞懂,其它的都差不多。
FileInputStream
- 文件字节输入流,万能的,任何类型的文件都可以采用这个流来读。
- 字节的方式,完成输入的操作,完成读的操作(硬盘 --> 内存)
创建一个文件字节输入流对象,path
为你的文件物理路径
FileInputStream fis = new FileInputStream("/Users/yct/Desktop/citys.js");
创建流后,当流不等于空时,必须要关闭流。
fis.close();
FileInputStream fis = null;
try {
fis = new FileInputStream(PATH);
....
}catch(IOException e) {
System.out.println(e.getMessage());
}finally {
// 关闭资源
if (fis != null) {
fis.close();
}
}
使用try-with-resources
Java7的编辑器和运行环境支持新的try-with-resources,称为ARM块(Automatic Resource Management )自动资源管理。可自动关闭任何可关闭的资源,这些可关闭的资源必须实现java.lang.AutoCloseable接口。
try(FileInputStream fis = new FileInputStream(PATH)) {
....
}catch (IOException e) {
System.out.println(e.getMessage());
}
读取文件
使用read()
方法读取文件,读取原理与集合next()
方法一致,next读取原理传送门
read()
返回值是读取到的字节本身,例如读取一个文本,文本中内容为:abcdef
当第一次调用read()
方法时,读取a,而a的字节码为97,将读取到的字节返回 --> 97
再一次调用方法时,read()
再向后移动一位,读取b,将读取到的字节返回 --> 98
以此类推,当读到文件末尾时,如果再次读取时,读不到任何数据read()
会返回-1
try(FileInputStream fis = new FileInputStream(PATH)) {
System.out.println(fis.read());
}catch (IOException e) {
System.out.println(e.getMessage());
}
使用循环读取
使用while
死循环读取文件,读到-1退出while
循环
try(
FileInputStream fis = new FileInputStream("/Users/yct/Desktop/TEST.txt")
){
int byteData = 0;
while ((byteData = fis.read()) != -1) {
System.out.println(byteData);
}
}catch(IOException e) {
System.out.println(e.getMessage());
}
}
一次读取一个字节byte,这样内存和硬盘交互太频繁,基本上时间/资源都消耗在交互上面流。
使用int read(byte[] b)
read()
一次只能读取一个字节,int read(byte[] b)
可以一次最多读取b.length
个字节,减少硬盘和内存的交互,提高程序的执行效率。
准备一个长度为4的byte数组byte[4]
,我们读取的文件同上。read(byte[] b)
这个方法返回值是:读取到的字节数量。(不是字节本身)
try(
FileInputStream fis = new FileInputStream("/Users/yct/Desktop/TEST.txt")
){
byte[] fileByte = new byte[4];
int readCount1 = fis.read(fileByte);
System.out.println(readCount1);
}catch(IOException e) {
System.out.println(e.getMessage());
}
第一次读取时读到了4个字节,当前byte数组中的数据为[97,98,99,100]
。
当我们第二次再尝试读取到这个数组中时,读取到了2个字节,当前byte数组中的数据为[101,102,99,100]
try(
FileInputStream fis = new FileInputStream("/Users/yct/Desktop/TEST.txt")
){
byte[] fileByte = new byte[4];
int readCount1 = fis.read(fileByte);
System.out.println(readCount1);
int readCount2 = fis.read(fileByte);
System.out.println(readCount2);
}catch(IOException e) {
System.out.println(e.getMessage());
}
原因是read(byte[] b)
方法在往byte数组中放数据时,是从byte数组的第0位开始放数据,文本有6个字节,而byte数组长度只有4,当读到第4个字节时停止读取(byte数组已满)。而第二次再去读取时,read(byte[] b)
上次读到了第4个字节还剩下2个字节,将剩余的字节读到byte数组时是从0开始,所以将剩余的两个字节读到了byte数组的0、1位置中此时覆盖掉了当前索引中旧的数据。
如果第三次再尝试去读取的话将返回**-1**,因为read(byte[] b)
已经将数据读完了,一个字节都没有读到。
将读取到的数据转成字符串,使用String类中的构造方法可将byte数组转成字符串,因为read(byte[] b)
方法在往byte数组中放数据时,是从byte数组的第0位开始放数据,所以开始位置是0,而结束位置则是读取到的字节数,这样就保证了程序读到什么就输出什么。
try(
FileInputStream fis = new FileInputStream("/Users/yct/Desktop/TEST.txt")
){
byte[] fileByte = new byte[4];
int readCount1 = fis.read(fileByte);
System.out.println(new String(fileByte, 0, readCount1));
int readCount2 = fis.read(fileByte);
System.out.println(new String(fileByte, 0, readCount2));
}catch(IOException e) {
System.out.println(e.getMessage());
}
输出
-- abcd
-- ef
改造
将上面的程序全部改造一下
try (FileInputStream fis = new FileInputStream("/Users/yct/Desktop/TEST.txt")) {
byte[] bytes = new byte[4];
StringBuilder sb = new StringBuilder(8);
int count = 0;
while ((count = fis.read(bytes)) != -1) {
sb.append(new String(bytes, 0, count));
}
System.out.println(sb);
} catch (IOException e) {
System.out.println(e.getMessage());
}
输出
-- Hello World!
FileInputStream其它常用方法
int available()
返回流当中剩余的字节数量long skip()
跳过几个字节不读
int available()
读取一个字节,查看还有多少个字节未读取,文件内容为:abcdef
try (FileInputStream fis = new FileInputStream("/Users/yct/Desktop/TEST.txt")) {
byte[] bytes = new byte[4];
StringBuilder sb = new StringBuilder(8);
System.out.println(fis.read());
System.out.println(fis.available());
} catch (IOException e) {
System.out.println(e.getMessage());
}
输出
-- 5
available正确用法:new 一个byte数组,让read()方法读取一次添加到byte数组中,不需要循环。
try (FileInputStream fis = new FileInputStream("/Users/yct/Desktop/TEST.txt")) {
byte[] bytes = new byte[fis.available()];
fis.read(bytes);
System.out.println(new String(bytes));
} catch (IOException e) {
System.out.println(e.getMessage());
}
这种方式不太适合读取大文件,因为byte[]数组不能太大
long skip()
使用skip()
可以跳过几个字节不读取,文件内容为:abcdef
try (FileInputStream fis = new FileInputStream("/Users/yct/Desktop/TEST.txt")) {
byte[] bytes = new byte[fis.available()];
fis.skip(3);
fis.read(bytes);
System.out.println(new String(bytes));
} catch (IOException e) {
System.out.println(e.getMessage());
}
输出
-- def
FileOutputStream
- 文件字节输出流,负责写
- 从内存到硬盘
创建一个FileOutputStream
对象,传入一个PATH,这个PATH是文件输出的位置加文件名。使用这种方式会将原文件清空,然后重新写入。
FileOutputStream fos = new FileOutputStream(PATH);
使用write读取一个byte数组。
try(FileOutputStream fos = new FileOutputStream("/Users/yct/Desktop/test.txt");) {
byte[] bytes = {97, 98, 99};
fos.write(bytes);
} catch (IOException e){
System.out.println(e.getMessage());
}
如果需要在原文件末尾追加内容时,不可使用上面的方式FileOutputStream提供了一个构造函数FileOutputStream(String name, boolean append)
指在原文件末尾追加元素(以追加的方式在文件末尾写入,不会清空原文件内容)。例如原文件有一个Hello
FileOutputStream fos = new FileOutputStream(PATH, true);
try(FileOutputStream fos = new FileOutputStream("/Users/yct/Desktop/test.txt", true);) {
byte[] bytes = {97, 98, 99};
fos.write(bytes);
} catch (IOException e){
System.out.println(e.getMessage());
}
只输出2位字节,可以使用write(byte[] b, int off, int len)
fos.write(bytes, 0, 2);
文件内容
-- ab
写完之后一定要刷新
文件复制
- 需要使用FileInputStream加FileOutputStream完成文件的拷贝。
- 拷贝的过程应该是一边读一边写。
- 使用以上的字节流拷贝文件的时候,文件类型随意,万能的。什么样的文件都能拷贝。
编写的源码
public static void main(String[] args) {
if (copy("/Volumes/UpYou 1/SystemOS/Win10_1909(全家桶)x64.iso", "/Users/yct/Win10_1909(全家桶)x64.iso")) {
System.out.println("文件复制成功!");
} else {
System.out.println("文件复制失败!");
}
}
/* 复制文件 */
public static boolean copy(String sourcePath, String targetPath) {
try (FileInputStream fis = new FileInputStream(sourcePath);
FileOutputStream fos = new FileOutputStream(targetPath);) {
int readLong = 1024 * 1024;// 一次读取的长度
long fisLength = fis.available();// 流总长度*2=位,/8转成byte
long int2percentage = fisLength / 100; // 将长度转换成百分比
byte[] bytes = new byte[readLong];
int nowReadCount = 0;
while ((nowReadCount = fis.read(bytes)) > -1) {
fos.write(bytes, 0, nowReadCount);
long r = fisLength - fis.available();// 已读取量
System.out.println("====>>>>" + r / int2percentage + "%");
}
fos.flush();// 刷新管道
return true;
} catch (IOException e) {
System.out.println(e.getMessage());
}
return false;
}
创建每次读取的长度,这是必须的,不可采用fis.available();
的方式来设置数组的长度,因为如果遇到大文件,计算机找一块符合大小、连续地址的内存空间很难,可能都找不到,所以需要限定每次最大只能读取多大!(切片读取)
int readLong = 1024 * 1024;
byte[] bytes = new byte[readLong];
最后将读取到的字节输入到byte数组中,采用读取多少字节输入多少的方式fos.write(bytes, 0, nowReadCount);
因为要确保最后一次读取的准确性。
long nowReadCount = 0;
while ((nowReadCount = fis.read(bytes)) > -1) {
fos.write(bytes, 0, nowReadCount);
}
最后一步一定要刷新管道
fos.flush();
读取一个5G的文件
原文件
java程序拷贝的后的文件
FileReader
- 文件字符输入流,只能读取普通文本。读取文件时,比较方便快捷。
创建一个FileReader
FileReader fr = new FileReader(PATH);
将文件读到char数组中
char[] txt = new char[1024 * 1024];
int readerCount = 0;
while ((readerCount = fr.read(txt)) > -1) {
System.out.println(new String(txt, 0, readerCount));
}
FileWriter
- 文件字符输出流,只能输出普通文本。
用法与其它输出流一样,但记得一定要flush()
try (FileReader fr = new FileReader("/Users/yct/Desktop/text.txt");
FileWriter fw = new FileWriter("/Users/yct/Desktop/text2.txt");) {
char[] txt = new char[1024 * 1024];
int readerCount = 0;
while ((readerCount = fr.read(txt)) > -1) {
fw.write(txt, 0, readerCount);
}
fw.flush();
} catch (IOException e) {
System.out.println(e.getMessage());
}
BufferedReader
-
带有缓冲期的字符输入流,使用这个流的时候不需要自定义char数组、byte数组,自带缓冲。
-
当一个流的构造方法中需要一个流的时候,这个被传进来的流叫做:节点流。外部负责包装的这个流,叫做:包装流,还有一个名字叫做:处理流。
-
对包装流来说,只需要关闭最外层就行,里面的节点流会自动关闭。
源码:
public void close() throws IOException { synchronized (lock) { if (in == null) return; try { in.close(); } finally { in = null; cb = null; } } }
构造函数
BufferedReader(Reader in)
创建一个使用默认大小输入缓冲区的缓冲字符输入流。
BufferedReader(Reader in, int sz)
创建一个使用指定大小输入缓冲区的缓冲字符输入流。
FileReader fr = new FileReader("/Users/yct/Desktop/text.txt");
BufferedReader br = new BufferedReader(fr);
br.close();
readLine
readLine()
可读取一行文本,不包括末尾的换行符,文本读完时返回null
try (FileReader fr = new FileReader("/Users/yct/Desktop/text.txt"); BufferedReader br = new BufferedReader(fr);) {
String s = null;
while((s = br.readLine()) != null) {
System.out.println(s);
}
} catch(IOException e) {
}
BufferedWriter
- 带有缓冲区的字符输出流。
BufferedWriter out = new BufferedWriter(new FileWriter(PATH));
输出一个内容为abc的普通文本
final static String WRITER_PATH = "/Users/yct/Desktop/copy.txt"; // 文件输出的位置
public static void main(String[] args) {
try (BufferedWriter bw = new BufferedWriter(new FileWriter(WRITER_PATH))) {
byte[] bytes = {97, 98, 99};
bw.write(new String(bytes));
bw.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
DataOutputStream
java.io.DataOutputStream 数据专属流,这个流可以将数据连同数据的类型一并写入文件
这个文件不是普通文本文档
创建数据专属的字节输出流
try (FileOutputStream fileOutputStream = new FileOutputStream(FILE_PATH);
DataOutputStream dos = new DataOutputStream(fileOutputStream);) {
dos.flush();
} catch (IOException e) {
e.printStackTrace();
}
创建不同数据类型,一并写入文件
byte b = 100;
short s = 200;
int i = 300;
long l = 400L;
float f = 3.0f;
double d = 3.14;
boolean sex = false;
char c = 'a';
写入数据
try (FileOutputStream fileOutputStream = new FileOutputStream(FILE_PATH);
DataOutputStream dos = new DataOutputStream(fileOutputStream);) {
dos.writeByte(b);
dos.writeShort(s);
dos.writeInt(i);
dos.writeLong(l);
dos.writeFloat(f);
dos.writeDouble(d);
dos.writeBoolean(sex);
dos.writeChar(c);
dos.flush();
} catch (IOException e) {
e.printStackTrace();
}
DataInputStream
- 数据字节输入流。
- DataOytputStream写的文件,只能使用DataInputStream来读取。并且读的时候要知道写入的顺序。读的顺序需要和写的顺序一致,才可以正常取出数据。
读取上个栗子写入的数据
try (FileInputStream fis = new FileInputStream(FILE_PATH);
DataInputStream dis = new DataInputStream(fis);) {
byte b = dis.readByte();
short s = dis.readShort();
int i = dis.readInt();
long l = dis.readLong();
float f = dis.readFloat();
double d = dis.readDouble();
boolean sex = dis.readBoolean();
char c = dis.readChar();
System.out.println(b);
System.out.println(s);
System.out.println(i);
System.out.println(l);
System.out.println(d);
System.out.println(sex);
System.out.println(c);
} catch (IOException e) {
e.getStackTrace();
}
-- 100
-- 200
-- 300
-- 400
-- 3.0
-- 3.14
-- false
-- a
PrintlnStream
- 标准的字节输出流,默认输出到控制台
标准输出流不需要手动关闭
我们常用的System.out.println
其实调用的就是PrintStream
:
public final static PrintStream out = null;
public static void main(String[] args) {
PrintStream ps = System.out;
ps.print("Hello");
ps.println(" World!");
}
-- Hello World!
改变标准输出流的输出方向
标准输出流不再指向控制台,指向“log”文件。
PrintStream ps = new PrintStream(new FileOutputStream(FILE_PATH));
修改输出方向,将输出方向修改到“log ”文件。
System.setOut(ps);
再输出
public static void main(String[] args) throws FileNotFoundException {
PrintStream ps = new PrintStream(new FileOutputStream(FILE_PATH));
System.setOut(ps);
System.out.print("Hello");
System.out.println(" World!");
}
File类
- File类和四大家族没有关系,所以File类不能完成文件的读和写。
- File对象代表什么?
File是文件和目录路径的抽象表示形式
/Users/yct/Pictures 这是一个File对象
/Users/yct/Pictures/IMG_0168.jpeg 这也是一个File对象。
一个File对象对应的可能是目录,也可能是文件,File只是一个路径名的抽象表示形式。
exists
判断File对象是否存在,可以使用exists()
方法
例如读取桌面上一个不存在的File对象
File f = new File(FILE_PATH);
System.out.println(f.exists());
-- false
createNewFile
当一个File对象不存在时创建,可以使用createNewFile
方法,以文件的形式新建。
public static void main(String[] args) throws IOException {
File f = new File(FILE_PATH);
if (!f.exists()) {
f.createNewFile()
}
}
mkdir
当一个File对象不存在时创建,可以使用mkdir
方法,以目录的形式新建。
public static void main(String[] args) throws IOException {
File f = new File(FILE_PATH);
if (!f.exists()) {
f.mkdir();
}
}
mkdirs
需要新建多重目录需要使用mkdirs
方法
final static String FILE_PATH = "/Users/yct/Desktop/log/a/b/c/d/e/f"; // 文件输出的位置
public static void main(String[] args) throws IOException {
File f = new File(FILE_PATH);
if (!f.exists()) {
f.mkdirs();
}
}
getParent
获取文件的父路径
final static String FILE_PATH = "/Users/yct/Desktop/log"; // 文件输出的位置
public static void main(String[] args) throws IOException {
File f = new File(FILE_PATH);
String parent = f.getParent();
System.out.println(parent);
}
-- /Users/yct/Desktop
getParentFile
获取父文件的File对象
final static String FILE_PATH = "/Users/yct/Desktop/log"; // 文件输出的位置
public static void main(String[] args) throws IOException {
File f = new File(FILE_PATH);
File parent = f.getParentFile();
// 输出绝对路径
System.out.println(parent.getAbsolutePath());
-- /Users/yct/Desktop
getName
获取文件名
final static String FILE_PATH = "/Users/yct/Desktop/log"; // 文件输出的位置
public static void main(String[] args) throws IOException {
File f = new File(FILE_PATH);
File parent = f.getParentFile();
System.out.println(parent.getName());
}
-- Desktop
isDirectory
判断是否是一个目录
final static String FILE_PATH = "/Users/yct/Desktop/log"; // 文件输出的位置
public static void main(String[] args) throws IOException {
File f = new File(FILE_PATH);
File parent = f.getParentFile();
System.out.println(parent.isDirectory());
}
-- true
isFile
判断是否是一个标准文件
final static String FILE_PATH = "/Users/yct/Desktop/log"; // 文件输出的位置
public static void main(String[] args) throws IOException {
File f = new File(FILE_PATH);
File parent = f.getParentFile();
System.out.println(parent.isFile());
}
-- false
lastModified
获取文件最后修改时间,获取出来的是毫秒,需要用SimpleDateFormat
转成自定义格式
final static String FILE_PATH = "/Users/yct/Desktop/log"; // 文件输出的位置
public static void main(String[] args) throws IOException {
File f = new File(FILE_PATH);
Date da = new Date(f.lastModified());
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println(sdf.format(da));
}
-- 2020-10-02 17:12:08
listFiles
获取当前目录下所有的子文件。
final static String FILE_PATH = "/Users/yct/Desktop"; // 文件输出的位置
public static void main(String[] args) throws IOException {
File f = new File(FILE_PATH);
File[] files = f.listFiles();
for (File file : files) {
System.out.println(file.getAbsolutePath());
}
}
交作业:文件夹拷贝
本文由 UpYou 创作,采用 知识共享署名4.0
国际许可协议进行许可
本站文章除注明转载/出处外,均为本站原创或翻译,转载前请务必署名
最后编辑时间为: Oct 2,2020