Java IO
IO流
1. 传统方式划分
1.1 简介
-
传统方式将流划分为两种:字节流与字符流
字节流:用来处理二进制文件,例如图片、MP3 、视频等
字符流:用来处理文本文件,文本文件可以看作是一种特殊的二进制文件,经过编码,便于人们阅读
-
字节流可以处理一切文件,而字符流只能处理文本
-
IO 核心 4 个抽象类:InputStream、OutputStream、Reader、Writer
-
InputStream 和 Reader 是所有输入流的基类
-
OutputStream和 Writer是所有输出流的基类
1.2 核心类
InputStream 类
- 典型实现:FileInputStream (用于读取非文本数据之类的原始字节流)
- 方法:
int read()
:读取数据 - 从输入流中读取数据的下一个字节int read(byte[] b)
:从此输入流中将最多b.length
个字节的数据读入一个byte
数组int read(byte b[], int off, int len)
:从第 off 位置开始读,读取 len 长度的字节,然后放入数组 b 中int available()
:返回可读的字节数void close()
:关闭流,释放资源
OutputStream 类
- 方法:
void write(int b)
: 将指定的字节写入void write(byte[] b)
:将b.length
个字节从指定的byte
数组写入void write(byte b[], int off, int len)
: 将数组 b 中的从 off 位置开始,长度为 len 的字节写入void flush()
: 强制刷新,将缓冲区的数据写入void close()
:关闭流,释放资源
Reader 类
- 读取字符流,需要使用 FileReader
- 方法:
int read()
:读取单个字符int read(char[] ch)
:将字符读入数组 - 如果已经达到流的末尾,将返回 -1int read(char[] ch, int off, int len)
:从第 off 位置开始读,读取 len 长度的字符,然后放入数组 b 中int ready()
:判断是否可以读void close()
:关闭流,释放资源
Writer 类
- 方法:
void write(int c)
: 写入一个字符void write(char[] ch)
: 写入字符数组void write( char cbuf[], int off, int len)
: 将数组 cbuf 中的从 off 位置开始,长度为 len 的字符写入void flush()
: 强制刷新,将缓冲区的数据写入void close()
:关闭流,释放资源
文件 - File 类
-
能新建、删除、重命名文件和目录,但 File 不能访问文件内容本身
-
构造方法:
-
public File(String pathname)
以pathname为路径创建File对象,可以是绝对路径或者相对路径
如果pathname是相对路径,则默认的当前路径在系统属性user.dir中存储
- 绝对路径:是一个固定的路径,从盘符开始
- 相对路径:是相对于某个位置开始
-
public File(String parent, String child)
以parent为父路径,child为子路径创建File对象
-
public File(File parent, String child)
根据一个父File对象和子文件路径创建File对象
-
-
常用方法:
- 获取
- getAbsolutePath() :获取绝对路径
- getPath() :获取路径
- getName() :获取名称
- getParent() :获取上层文件路径地址
- length() :获取文件长度
- lastModified() :获取最后一次修改时间 - 毫秒级
- list() :获取指定文件目录下的所有文件/文件目录的名称数组
- listFiles() :获取指定文件目录下的所有文件/文件目录的File数组
- 重命名:renameTo(File dest):把文件重命名为指定的文件路径
- 判断:
- isDirectory():是否为文件目录
- isFile():是否为文件
- exists():是否存在
- canRead():能否读取
- canWrite():能否写入
- isHidden():是否隐藏
- 创建:
- createNewFile():创建文件
- mkdir():创建文件目录 - 上层文件目录不存在会执行失败,返回 false
- mkdirs():创建文件目录 - 同时创建该文件所在路径的所有缺失的父目录
- 删除:delete():删除文件/文件目录
- 获取
2. 操作对象划分
2.1 简介
-
IO IO,就是输入输出(Input/Output)
-
Input:将外部的数据读入内存,比如说把文件从硬盘读取到内存,从网络读取数据到内存等等
-
Output:将内存中的数据写入到外部,比如说把数据从内存写入到文件,把数据从内存输出到网络等等
-
IO 可以分类为:文件、数组、管道、基本数据类型、缓冲、打印、对象序列化/反序列化,以及转换等
2.2 分类
2.2.1 文件
-
文件流也就是直接操作文件的流,可以细分为字节流(FileInputStream 和 FileOuputStream)和字符流(FileReader 和 FileWriter)
-
FileInputStream 实例
int b; FileInputStream fileIn = new FileInputStream("fileIn.txt"); // 循环读取 while ((b = fileIn.read())!=-1) { System.out.println((char)b); } // 关闭资源 fileIn.close();
-
FileOuputStream 实例
String str = "我是文件输出流"; FileOutputStream fileOut = new FileOutputStream("fileOut.txt"); fileOut.write(str.getBytes()); fileOut.close();
-
FileReader 实例
int b = 0; FileReader fileReader = new FileReader("read.txt"); // 循环读取 while ((b = fileReader.read())!=-1) { // 自动提升类型提升为 int 类型,所以用 char 强转 System.out.println((char)b); } // 关闭流 fileReader.close();
-
FileWriter实例
String str = "我是文件写入流"; FileWriter fileWriter = new FileWriter("writer.txt"); char[] chars = str.toCharArray(); fileWriter.write(chars, 0, chars.length); fileWriter.close();
2.2.2 数组
-
为了提升效率,频繁地读写文件并不是太好,因此就出现了数组流,有时候也称为内存流
-
ByteArrayInputStream
String str = "我是数组输入流"; InputStream is = new BufferedInputStream( new ByteArrayInputStream(str.getBytes(StandardCharsets.UTF_8))); //操作 byte[] flush = new byte[1024]; int len = 0; while(-1 != (len = is.read(flush))) { System.out.println(new String(flush, 0, len)); } //释放资源 is.close();
-
ByteArrayOutputStream
String str = "我是数组输出流"; ByteArrayOutputStream bos = new ByteArrayOutputStream(); byte[] info = str.getBytes(); bos.write(info, 0, info.length); //获取数据 byte[] dest = bos.toByteArray(); //释放资源 bos.close();
2.2.3 管道
-
Java 中的管道和 Unix/Linux 中的管道不同,在 Unix/Linux 中,不同的进程之间可以通过管道来通信,但 Java 中,通信的双方必须在同一个进程中,也就是在同一个 JVM 中,管道为线程之间的通信提供了通信能力
-
一个线程通过 PipedOutputStream 写入的数据可以被另外一个线程通过相关联的 PipedInputStream 读取出来
final PipedOutputStream pipedOutputStream = new PipedOutputStream(); final PipedInputStream pipedInputStream = new PipedInputStream(pipedOutputStream); Thread thread1 = new Thread(new Runnable() { @Override public void run() { try { String str = "我是管道流"; pipedOutputStream.write(str.getBytes(StandardCharsets.UTF_8)); pipedOutputStream.close(); } catch (IOException e) { e.printStackTrace(); } } }); Thread thread2 = new Thread(new Runnable() { @Override public void run() { try { byte[] flush = new byte[1024]; int len =0; while(-1 != (len = pipedInputStream.read(flush))){ System.out.println(new String(flush, 0, len)); } pipedInputStream.close(); } catch (IOException e) { e.printStackTrace(); } } }); thread1.start(); thread2.start();
2.2.4 基本数据类型
-
基本数据类型输入输出流是一个字节流,该流不仅可以读写字节和字符,还可以读写基本数据类型
-
DataInputStream 提供了一系列可以读基本数据类型的方法
DataInputStream dataIn = new DataInputStream(new FileInputStream(“dataIn.txt”)) ; byte b = dataIn.readByte() ; short s = dataIn.readShort() ; int i = dataIn.readInt(); long l = dataIn.readLong() ; float f = dataIn.readFloat() ; double d = dataIn.readDouble() ; boolean bool = dataIn.readBoolean() ; char ch = dataIn.readChar() ;
-
DataOutputStream 提供了一系列可以写基本数据类型的方法
DataOutputStream dataOut = new DataOutputStream(new FileOutputStream(“dataOut.txt”)); dataOut.writeByte(10); dataOut.writeShort(100); dataOut.writeInt(1000); dataOut.writeLong(10000L); dataOut.writeFloat(12.34F); dataOut.writeDouble(12.56); dataOut.writeBoolean(true); dataOut.writeChar('A');
2.2.5 缓冲
-
为了减少程序和硬盘的交互,提升程序的效率,就引入了缓冲流,也就是类名前缀带有 Buffer 的那些,比如说 BufferedInputStream、BufferedOutputStream、BufferedReader、BufferedWriter
-
缓冲流在内存中设置了一个缓冲区,只有缓冲区存储了足够多的带操作的数据后,才会和内存或者硬盘进行交互
简单来说,就是一次多读/写点,少读/写几次,这样程序的性能就会提高
2.2.6 对象序列化/反序列化
-
序列化本质上是将一个 Java 对象转成字节数组,然后可以将其保存到文件中,或者通过网络传输到远程
String str = "我是序列化流"; ByteArrayOutputStream buffer = new ByteArrayOutputStream(); try (ObjectOutputStream output = new ObjectOutputStream(buffer)) { output.writeUTF(str); } System.out.println(Arrays.toString(buffer.toByteArray()));
-
反序列化,将字节数组转成 Java 对象的过程
try (ObjectInputStream input = new ObjectInputStream(new FileInputStream( new File("file.txt")))) { String s = input.readUTF(); }
2.2.7 转换
-
InputStreamReader 是从字节流到字符流的桥连接,它使用指定的字符集读取字节并将它们解码为字符
InputStreamReader insr = new InputStreamReader( new FileInputStream("demo.txt")); char[] ch = new char[1024]; int len = insr.read(cha); System.out.println(new String(ch, 0, len)); isr.close();
-
OutputStreamWriter 将一个字符流的输出对象变为字节流的输出对象,是字符流通向字节流的桥梁
File file = new File("test.txt") ; // 字节流变为字符流 Writer out = new OutputStreamWriter(new FileOutputStream(file)); // 使用字符流输出 out.write("hello world!!"); out.close();
2.2.8 打印
-
System.out
其实返回的就是一个 PrintStream 对象,可以用来打印各式各样的对象System.out.println("我是打印输出流");
-
PrintStream 最终输出的是字节数据,而 PrintWriter 则是扩展了 Writer 接口,所以它的
print()/println()
方法最终输出的是字符数据StringWriter buffer = new StringWriter(); try (PrintWriter pw = new PrintWriter(buffer)) { pw.println("我是打印输出流"); } System.out.println(buffer.toString());
3. AIO、BIO、NIO
3.1 BIO
-
BIO 是一种同步且阻塞的通信模式 - 一个连接一个线程
-
是一个比较传统的通信方式,模式简单,使用方便,但并发处理能力低,通信耗时,依赖网速
-
适用场景:
连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中 ,但程序直观简单易理解 - JDK1.4 以前的唯一选择
-
文件的读取与写入
//初始化实体类 User user = new User(); user.setName("Joker"); user.setAge(23); System.out.println(user); // 将内容写入到指定文件 ObjectOutputStream oos = null; try { oos = new ObjectOutputStream(new FileOutputStream("tempFile.txt")); oos.writeObject(user); } catch (IOException e) { e.printStackTrace(); } finally { IOUtils.closeQuietly(oos); } // 从指定文件读取信息 File file = new File("tempFile.txt"); ObjectInputStream ois = null; try { ois = new ObjectInputStream(new FileInputStream(file)); User1 newUser = (User1) ois.readObject(); System.out.println(newUser); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } finally { IOUtils.closeQuietly(ois); try { FileUtils.forceDelete(file); } catch (IOException e) { e.printStackTrace(); } }
3.2 NIO
-
NIO 是一种非阻塞同步的通信模式,以块的方式处理数据 - 一个请求一个线程
-
按块处理数据比按(流式的)字节处理数据要快得多,但是面向块的 I/O 缺少一些面向流的 I/O 所具有的优雅性和简单性
-
适用场景
连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂 - JDK1.4 开始支持
-
文件的读取和写入
static void readNIO() { String pathname = "E:\SpringNote\mdAndpdf\notebook\Java\visio\TreeMap.png"; FileInputStream fin = null; try { fin = new FileInputStream(new File(pathname)); FileChannel channel = fin.getChannel(); // 字节 int capacity = 100; ByteBuffer bf = ByteBuffer.allocate(capacity); int length = -1; while ((length = channel.read(bf)) != -1) { bf.clear(); byte[] bytes = bf.array(); System.out.write(bytes, 0, length); System.out.println(); } channel.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { if (fin != null) { try { fin.close(); } catch (IOException e) { e.printStackTrace(); } } } } static void writeNIO() { String filename = "out.txt"; FileOutputStream fos = null; try { fos = new FileOutputStream(new File(filename)); FileChannel channel = fos.getChannel(); ByteBuffer src = Charset.forName("utf8").encode("你好你好你好你好你好"); int length = 0; while ((length = channel.write(src)) != 0) { System.out.println("写入长度:" + length); } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { if (fos != null) { try { fos.close(); } catch (IOException e) { e.printStackTrace(); } } } }
3.3 AIO
-
AIO 是一种异步非阻塞的通信模式 - 一个有效请求一个线程
-
在 NIO 的基础上引入了新的异步通道的概念,并提供了异步文件通道和异步套接字通道的实现
-
适用场景
连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用 OS 参与并发操作,编程比较复杂,JDK7 开始支持
-
文件的读取与写入
public class ReadFromFile { public static void main(String[] args) throws Exception { Path file = Paths.get("E:\SpringNote\mdAndpdf\notebook\Java\txt\a.txt"); AsynchronousFileChannel channel = AsynchronousFileChannel.open(file); ByteBuffer buffer = ByteBuffer.allocate(100_000); Future<Integer> result = channel.read(buffer, 0); while (!result.isDone()) { ProfitCalculator.calculateTax(); } Integer bytesRead = result.get(); System.out.println("Bytes read [" + bytesRead + "]"); } } class ProfitCalculator { public ProfitCalculator() { } public static void calculateTax() { } } public class WriteToFile { public static void main(String[] args) throws Exception { AsynchronousFileChannel fileChannel = AsynchronousFileChannel.open( Paths.get("E:\SpringNote\mdAndpdf\notebook\Java\txt\asynchronous.txt"), StandardOpenOption.READ, StandardOpenOption.WRITE, StandardOpenOption.CREATE); CompletionHandler<Integer, Object> handler = new CompletionHandler<Integer, Object>() { @Override public void completed(Integer result, Object attachment) { System.out.println("Attachment: " + attachment + " " + result + " bytes written"); System.out.println("CompletionHandler Thread ID: " + Thread.currentThread().getId()); } @Override public void failed(Throwable e, Object attachment) { System.err.println("Attachment: " + attachment + " failed with:"); e.printStackTrace(); } }; System.out.println("Main Thread ID: " + Thread.currentThread().getId()); fileChannel.write(ByteBuffer.wrap("Sample".getBytes()), 0, "First Write", handler); fileChannel.write(ByteBuffer.wrap("Box".getBytes()), 0, "Second Write", handler); } }