Java基础部分比较重要的,大概Java集合、面向对象、IO、并发、反射、异常算是高频了,这篇文章主要总结一下Java中的IO流
IO流首先要理解"流"的概念,或者说要有"流"的思想!!流只是一种抽象的数据传输形式,也可以说是一种形式,总之要有自己的认识。
所谓IO是简称,全称是in/out.是相对程序而言的读取in和写出out动作.
总述
Java 的 I/O 大概可以分成以下几类:
- 磁盘操作:File
- 字节操作:InputStream 和 OutputStream
- 字符操作:Reader 和 Writer
- 对象操作:Serializable
- 网络操作:Socket(单独会有一篇文章,写个小案例)
- 新的输入/输出:NIO
File类
FIle类主要是操作文件数据,可以进行创建和删除文件等操作。在使用一个File类时,则必须向File类的构造方法中传递一个文件路径。
下面代码中测试了File类中的常用方法以及小案例:
package cn.*.io;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.Scanner;
//测试File
public class Test01_File {
public static void main(String[] args) throws IOException {
//创建对象 -- 封装文件或者文件夹
File f = new File("E:\\string\\1.txt");
//调用方法
System.out.println(f.length());//获取目标文件的字节量
System.out.println(f.exists());//判断文件是否存在
System.out.println(f.isFile());//判断是否是文件
System.out.println(f.isDirectory());//判断是否是文件夹
System.out.println(f.getName());//获取文件名
System.out.println(f.getParent());//获取父文件夹的路径
System.out.println(f.getAbsolutePath());//获取文件的完整路径
f = new File("E:\\string\\2.txt");
System.out.println(f.createNewFile());//新建文件,文件夹不存在会异常,文件已存在返回false
f = new File("E:\\string\\ioykx");
System.out.println(f.mkdir());//新建文件夹
f = new File("E:\\string\\ioykx\\x\\y\\z");
System.out.println(f.mkdirs());//新建多层文件夹
f = new File("E:\\string\\ioykx\\x\\y\\z");
System.out.println(f.delete());//删除文件或(空的)文件夹
f = new File("E:\\Photo");
String [] s = f.list();//获取文件名并存入String数组中
System.out.println(Arrays.toString(s));
// f = new File("E:\\Photo");
File [] file = f.listFiles();//获取文件 并存入File[]中,比较常用
System.out.println(Arrays.toString(file));
// 接受用户输入的一串路径
//判断如果是文件,求文件的字节量
//判断如果是文件夹,新建文件夹
String str = new Scanner(System.in).nextLine();
File fi = new File(str);
if(fi.isFile()){
System.out.println(fi.length());
}else if (fi.isDirectory()){
File [] fs = fi.listFiles();
System.out.println(Arrays.toString(fs));
}
}
}
还有一个比较常用的使用场景,也就是递归的使用
需求:递归(求目录总大小) 和 (删除文件夹)
- 1、列出文件夹的所有资源
- 2、判断当前资源是文件还是文件夹,
- 如果是文件,直接求字节大小(length),如果是文件夹,重复过程 1
代码实现:
package cn.*.io;
import java.io.File;
import java.util.Scanner;
//递归 : 就是在方法内部自己调用自己
public class Test02_Digui {
public static void main(String[] args) {
//文件夹路径
String path = new Scanner(System.in).nextLine() ;
File f = new File(path);
long total = size(f);
System.out.println("一共"+total+"字节");
//递归删除文件夹
del(f);
}
private static void del(File f) {
File [] fi = f.listFiles();
for (int i = 0; i < fi.length; i++) {
if (fi[i].isFile()){
fi[i].delete();
}else if (fi[i].isDirectory()){
del(fi[i]);//递归调用
}
}
f.delete();//删除空文件夹
System.out.println("文件夹删除成功!");
}
/**
* 创建size()求字节大小
* @param f
* @return
*/
private static long size(File f) {
long lon = 0;
File [] file = f.listFiles();
for (int i = 0; i < file.length; i++) {
if (file[i].isFile()){
lon = lon + file[i].length();
}else if(file[i].isDirectory()){
lon = lon + size(file[i]);
}
}
return lon;
}
}
字节操作
字节操作顾名思义就是针对字节单位的操作,不管是磁盘还是网络传输,最小的存储单元都是字节,而不是字符。
字节流
字节流:针对二进制文件
InputStream
- FileInputStream
- BufferedInputStream
- ObjectInputStream
OutputStream
- FileOutputStream
- BufferedOutputStream
- ObjectOutputStream
字节读取流
InputStream – FileInputStream – BufferedInputStream
InputStream:是字节读取流的父类,而且被设计为了抽象类。
常用方法:
abstract int read()
从输入流中读取数据的下一个字节。
int read(byte[ ] b)
从输入流中读取一定数量的字节,并将其存储在缓冲区数组 b 中。
int read(byte[ ] b, int off, int len)
将输入流中最多 len 个数据字节读入 byte 数组。
void close()
关闭此输入流并释放与该流关联的所有系统资源。
FileInputStream:FileInputStream 从文件系统中的某个文件中获得输入字节
FileInputStream(File file)
FileInputStream(String name)
BufferedInputStream:为另一个输入流添加一些功能,底层会创建一个内部缓冲区数组。
BufferedInputStream(InputStream in)
它们的常用方法在以下代码中有所体现
package cn.*.io;
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
//测试字节读取流
public class Test03_InputStream {
public static void main(String[] args) throws IOException {
// FIS();//FileInputStream字节读取流
BIS();//BufferedInputStream字节读取流,又称高级流(缓冲流)
}
private static void BIS() throws IOException {
//创建对象
InputStream file = new BufferedInputStream(new FileInputStream("E:\\string\\2.txt"));//方式一
// InputStream file2 = new BufferedInputStream(new FileInputStream(new File("")));//方式二
//开始读取
int b = 0;
while ( (b = file.read()) != -1) {//用循环读取
System.out.println(b);
}
//释放资源
file.close();
}
private static void FIS() throws IOException {
//创建对象
InputStream file = new FileInputStream("E:\\string\\2.txt");//方式一
// InputStream file2 = new FileInputStream(new File("D:\\iotest\\1.txt"));//方式二
//开始读取
int b = 0;
while ((b = file.read()) != -1) {//用循环读取
System.out.println(b);
}
//释放资源
file.close();
}
}
小结:
FileInputStream BufferedInputStream都可以用来读取
效率上来讲: BufferedInputStream > FileInputStream,原因是:FileInputStream 是一个字节一个字节的读取,BufferedInputstream 是一个数组一个数组的读取,本质上,底层就是维护了一个byte[]数组叫buf,用来缓冲数据,默认大小是8192字节,把数组里的数据一次性的给程序读取进来.减少了程序冲流里获取数据的次数,提高了效率
字节写出流
字节流写出:OutputStream – FileOutputStream – BufferedOutputStream
OutputStream:此抽象类是表示输出字节流的所有类的超类,被修饰成了抽象类,不能new,只能学习共性方法
常用方法:
void close()
关闭此输出流并释放与此流有关的所有系统资源。
void flush()
刷新此输出流并强制写出所有缓冲的输出字节。
void write(byte[ ] b)
将 b.length 个字节从指定的 byte 数组写入此输出流。
void write(byte[ ] b, int off, int len)
将指定 byte 数组中从偏移量 off 开始的 len 个字节写入此输出流。
abstract void write(int b)
将指定的字节写入此输出流。
FileOutputStream:文件输出流
创建对象:
FileOutputStream(String name)
FileOutputStream(File file)
FileOutputStream(String name, boolean append)
FileOutputStream(File file, boolean append)
BufferedOutputStream:该类实现缓冲的输出流
创建对象:
BufferedOutputStream(OutputStream out)
package cn.*.io;
import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
//测试字节写出流
public class Test04_OutputStream {
public static void main(String[] args) throws IOException {
// FOS();//FileOutputStream
BOS();//BufferedOutputStream
}
private static void BOS() throws IOException {
//创建对象 -- 追加
OutputStream file = new BufferedOutputStream(new FileOutputStream("E:\\string\\3.txt",true));//方式一
// OutputStream file2 = new BufferedOutputStream(new FileOutputStream(new File("")));//方式二
//开始写出
file.write(49);
file.write(50);
file.write(51);
//释放资源
file.close();
System.out.println("写出成功!");
}
private static void FOS() throws IOException {
//创建对象 -- 追加
OutputStream file = new FileOutputStream("E:\\string\\3.txt",true);//方式一,true是表示追加
// OutputStream file2 = new FileOutputStream(new File(""));//方式二
//开始写出
file.write(97);
file.write(98);
file.write(99);
byte [] a = {97,97,97};
file.write(a);
// file.write
//释放资源
file.flush();
System.out.println("写出成功!");
}
}
字符操作
字符流
字符流:针对文本文件。读写容易发生乱码现象,在读写时最好指定编码集为utf-8
Writer
- BufferedWriter
- OutputStreamWriter
Reader
- BufferedReader
- InputStreamReader
- PrintWriter/PrintStream
字符读取流
Reader – FileReader – BufferedReader
Reader:是字符读取流的抽象 父类,既然是抽象类就不能new,只能学习共性方法
常用方法:
abstract void close()
关闭该流并释放与之关联的所有资源。
int read()
读取单个字符。
int read(char[ ] cbuf)
将字符读入数组。
abstract int read(char[ ] cbuf, intoff, int len)
将字符读入数组的某一部分。
int read(CharBuffer target)
试图将字符读入指定的字符缓冲区。
FileReader:学习子类创建对象,用来读取字符文件的便捷类。
创建对象:
FileReader(File file)
FileReader(String fileName)
BufferedReader:从字符输入流中读取文本,缓冲各个字符,从而实现字符、数组和行的高效读取。 可以指定缓冲区的大小,或者可使用默认的大小。大多数情况下,默认值就足够大了。
创建对象:
BufferedReader(Reader in)
package cn.*.io;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
//测试字符读取流
public class Test05_Reader {
public static void main(String[] args) throws IOException {
FR();//FileReader读取流
BR();//BufferedReader读取流
}
private static void BR() throws IOException {
//创建对象
Reader file = new BufferedReader(new FileReader("E:\\string\\3.txt"));//方式一
//Reader file2 = new BufferedReader(new FileReader(new File("")));//方式二
//开始读取
int b = 0 ;
while (( b = file.read()) != -1){
System.out.println(b);
}
//释放资源
file.close();
System.out.println("读取成功!");
}
private static void FR() throws IOException {
//创建对象
Reader file = new FileReader("E:\\string\\3.txt");//方式一
//Reader file2 = new FileReader(new File(""));//方式二
//开始读取
int b = 0 ;
while (( b = file.read()) != -1){
System.out.println(b);
}
//释放资源
file.close();
System.out.println("读取成功!");
}
}
字符写出流
字符流写出:Writer – FileWriter – BufferedWriter
Writer:写出字符流的抽象类,被设计成了一个抽象类,不能new。
–常用方法: abstract void close()
关闭此流,但要先刷新它。 abstract void flush()
刷新该流的缓冲。 void write(char[] cbuf)
写入字符数组。 abstract void write(char[] cbuf, int off, int len)
写入字符数组的某一部分。
void write(int c)
写入单个字符。
void write(String str)
写入字符串。
void write(String str, int off, int len)
写入字符串的某一部分。
FileWriter:用来写出字符文件的便捷类
创建对象 :
FileWriter(String fileName)
FileWriter(File file)
FileWriter(String fileName, boolean append)
FileWriter(Filefile, boolean append)
BufferedWriter:将文本写入字符输出流,缓冲各个字符,从而提供单个字符、数组和字符串的高效写入。可以指定缓冲区的大小,或者接受默认的大小。在大多数情况下,默认值就足够大了。
创建对象 :
BufferedWriter(Writer out)
package cn.*.io;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
//测试字符流写出
public class Test06_Writer {
public static void main(String[] args) throws IOException {
FW();//FileWriter写出流
BW();//BufferedWriter写出流
}
private static void BW() {
Writer file = null;
//创建对象
try {
file = new BufferedWriter(new FileWriter("E:\\string\\4.txt",true));//方式一
//开始写出
file.write(49);
file.write(50);
file.write(51);
file.write("嘿嘿嘿嚯嚯嚯!!!");
} catch (IOException e) {
e.printStackTrace();
}
//释放资源
//finally语句块,就是保证无论会不会出现异常,都一定要执行的代码
finally{
try {
file.close();
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("写出成功!");
}
// Writer file2 = new BufferedWriter(new FileWriter(new File("")));;//方式二
}
private static void FW() throws IOException {
//创建对象
Writer file = new FileWriter("E:\\string\\4.txt",true);//方式一
// Writer file2 = new FileWriter(new File(""));//方式二
//开始写出
file.write(49);
file.write(50);
file.write(51);
file.write("奥利给giaogiao");
//释放资源
file.close();
System.out.println("写出成功!");
}
}
应用案例
- 利用IO流实现文件的复制
代码示例如下:
package cn.*.io;
import java.io.*;
import java.util.Scanner;
//测试文件的复制
public class Test07_Copy {
public static void main(String[] args) throws IOException {
System.out.println("请输入源文件路径:");
String frompath = new Scanner(System.in).nextLine();
File from = new File(frompath);
System.out.println("请输入目标文件路径:");
String topath = new Scanner(System.in).nextLine();
File to = new File(topath);
// ZFcopy(from,to);//字符流复制
// ZJcopy(from,to);//字节流复制
jdk1_7copy(from,to);//jdk1.7针对释放资源的优化 -- try with resource
}
/**
* jdk1.7针对释放资源的优化,不用自己写close();
* 直接用try()包起来
* @param from
* @param to
*/
private static void jdk1_7copy(File from, File to) {
try ( //读取流
InputStream file1 = new BufferedInputStream(new FileInputStream(from));
//写出流
OutputStream file2 = new BufferedOutputStream(new FileOutputStream(to))
)
{
//边读边写
int b = 0;
while ((b = file1.read()) != -1){
file2.write(b);//把数据写出到目标文件里去
}
System.out.println("文件复制成功!");
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 字节流复制:什么类型的文件数据都能复制
* @param from
* @param to
*/
private static void ZJcopy(File from, File to) {
InputStream file1 = null;
OutputStream file2 = null;
try {
//读取流
file1 = new BufferedInputStream(new FileInputStream(from));
//写出流
file2 = new BufferedOutputStream(new FileOutputStream(to));
//边读边写
int b = 0;
while ((b = file1.read()) != -1){
file2.write(b);//把数据写出到目标文件里去
}
} catch (IOException e) {
e.printStackTrace();
}
finally{
//释放资源
IOUtils.close(file1);
IOUtils.close(file2);
System.out.println("文件复制成功!");
}
}
/**字符流复制:只能复制文本文件,不能操作 图片音频视频等类型的文件
* @param from
* @param to
*/
private static void ZFcopy(File from, File to) {
Reader file1 = null;
Writer file2 = null;
try {
//读取流
file1 = new BufferedReader(new FileReader(from));
//写出流
file2 = new BufferedWriter(new FileWriter(to));
//边读边写
int b = 0;
while (( b = file1.read()) != -1){
file2.write(b);
}
} catch (IOException e) {
e.printStackTrace();
}
finally {
IOUtils.close(file1);
IOUtils.close(file2);
System.out.println("文件复制成功!");
}
}
}
//释放资源工具类
//提供公共的close(),
//是静态的,可以通过类名直接调用
class IOUtils{
public static void close(Closeable c){
try {
c.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
- 批量读写
package cn.*.out;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.io.Writer;
import java.util.Scanner;
//测试文件复制
public class Test3_Copy {
public static void main(String[] args) throws IOException {
//1,接收用户输入的 源文件 路径
System.out.println("请输入源文件的路径:");
String frompath = new Scanner(System.in).nextLine() ;
File from = new File(frompath);
//2,接收用户输入的目标文件路径
System.out.println("请输入目标文件的路径:");
String topath = new Scanner(System.in).nextLine() ;
File to = new File(topath);
//3,调用指定方法完成复制
// copy(from,to);//字节流
copy2(from,to);//字符流
}
//字符流 -- 真的只能操作 文本文件 ,不能操作 图片音频视频等类型的文件
private static void copy2(File from, File to) throws IOException {
//1,读取源文件from -- 字符流BufferedReader
Reader in = new BufferedReader( new FileReader(from)) ;
//2,写出到目标文件to中 -- 字符流BufferedWriter
Writer out = new BufferedWriter(new FileWriter(to)) ;
//3,边读边写
//目前是一个字符一个字符的读写,为了优化单个字符的读写效率,我们也可以批量读写,如果是字符流,维护一个字符数组
char[] buf = new char[8*1024];
int b = 0 ;//定义变量,记录读取到的数据
while( ( b= in.read(buf) ) != -1 ) {//优化成一个数组一个数组的读
//write(buf,0,b)--第一个参数是指要写出哪个数组里的数据,
//第二参数是指从数组的哪个位置开始写出数据,第三个参数是指要写出的数据长度
out.write(buf,0,b);//优化成一个数组一个数组的写出,必须用三个参数的,不然会多复制数据的!!
}
//4,释放资源
in.close();
out.close();
System.out.println("恭喜您,文件复制完成!!");
}
//完成复制 -- 字节流 -- 什么类型的数据都可以操作
private static void copy(File from, File to) throws IOException {
//1,读取源文件from -- 字节流BufferedInputStream
InputStream in = new BufferedInputStream( new FileInputStream(from)) ;
//2,写出到目标文件to中 -- 字节流BufferedOutputStream
OutputStream out = new BufferedOutputStream(new FileOutputStream(to)) ;
//3,边读边写
byte[] buf = new byte[8*1024] ;//为了优化字节流的单字节读写,可以改成批量读写,按照一个数组的容量去读写
int b = 0 ;//定义变量,记录读取到的数据
while( ( b= in.read(buf) ) != -1 ) {//优化了单字节读取效率
out.write(buf,0,b);//优化了单字节写出效率
}
//4,释放资源
in.close();
out.close();
System.out.println("恭喜您,文件复制完成!!");
}
}
总结
InputStream/Reader: 所有的输入流的基类,前者是字节输入流,后者是字符输入流。
OutputStream/Writer: 所有输出流的基类,前者是字节输出流,后者是字符输出流。
另——BIO,NIO,AIO 有什么区别?
- BIO (Blocking I/O): 同步阻塞 I/O 模式,数据的读取写入必须阻塞在一个线程内等待其完成。在活动连接数不是特别高(小于单机 1000)的情况下,这种模型是比较不错的,可以让每一个连接专注于自己的 I/O 并且编程模型简单,也不用过多考虑系统的过载、限流等问题。线程池本身就是一个天然的漏斗,可以缓冲一些系统处理不了的连接或请求。但是,当面对十万甚至百万级连接的时候,传统的 BIO 模型是无能为力的。因此,我们需要一种更高效的 I/O 处理模型来应对更高的并发量。
- NIO (Non-blocking/New I/O): NIO 是一种同步非阻塞的 I/O 模型,在 Java 1.4 中引入了 NIO 框架,对应 java.nio 包,提供了 Channel , Selector,Buffer 等抽象。NIO 中的 N 可以理解为 Non-blocking,不单纯是 New。它支持面向缓冲的,基于通道的 I/O 操作方法。 NIO 提供了与传统 BIO 模型中的 Socket 和 ServerSocket 相对应的 SocketChannel 和 ServerSocketChannel 两种不同的套接字通道实现,两种通道都支持阻塞和非阻塞两种模式。阻塞模式使用就像传统中的支持一样,比较简单,但是性能和可靠性都不好;非阻塞模式正好与之相反。对于低负载、低并发的应用程序,可以使用同步阻塞 I/O 来提升开发速率和更好的维护性;对于高负载、高并发的(网络)应用,应使用 NIO 的非阻塞模式来开发
- AIO (Asynchronous I/O): AIO 也就是 NIO 2。在 Java 7 中引入了 NIO 的改进版 NIO 2,它是异步非阻塞的 IO 模型。异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。AIO 是异步 IO 的缩写,虽然 NIO 在网络操作中,提供了非阻塞的方法,但是 NIO 的 IO 行为还是同步的。对于 NIO 来说,我们的业务线程是在 IO 操作准备好时,得到通知,接着就由这个线程自行进行 IO 操作,IO 操作本身是同步的。