一、 概览
Java 的 I/O 大概可以分成以下几类:
- 磁盘操作:
File
- 字节操作:
InputStream
和 OutputStream
- 字符操作:
Reader
和 Writer
- 对象操作:
Serializable
- 网络操作:
Socket
- 新的输入/输出:
NIO
流分类
按照方向:一切以程序为中心
按照功能划分:
- 节点流: 可以直接从数据源或者目的地读写数据===> 直接操作数据源
关系:节点流是在IO的第一线,所有操作必须同节点流。处理流是对节点流的性能进行提升。
通常很少使用单个流对象,而是将一系列的流以包装的形式链接起来处理数据。
包装可以在不改变被包装流的前提下,获得更强的流处理功能 。
按照数据分类:
字节流: 按照字节读取数据(InputStream
、 OutputStream
)
字符流: 按照字符读取数据(Reader
、 Writer
)
编码的不同,从而有了对字符进行高效操作的字符流对象。
原理: 底层还是基于字节流,自动搜索了指定的码表(UTF-8、GBK、Unicode等)
典型的字符输入流/输出流的链接如下:
二、 磁盘操作File
File 类可以用于表示文件和目录的信息,但是它不表示文件的内容。 File代表的是一个抽象的表示形式,用于连接java程序与磁盘的桥梁,java只能跟OS交流。
File类的构造方法
File(File parent, String child) 从父抽象路径名和子路径名字符串创建新的 File 实例。 |
---|
File(String pathname) 通过将给定的路径名字符串转换为抽象路径名来创建新的 File 实例。 |
File(String parent, String child) 从父路径名字符串和子路径名字符串创建新的 File 实例。 |
File(URI uri) 通过将给定的 file: URI转换为抽象路径名来创建新的 File 实例。 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| import java.io.File;
public class Demo1 { public static void main(String[] args) { String path = "F:/java/base/io/io.png"; File file = new File(path); System.out.println(file.length());
File file1 = new File("F:/java/base/io", "io.png"); System.out.println(file1.length());
File file2 = new File(new File("F:/java/base"), "/io/io.png"); System.out.println(file1.length());
System.out.println(file.getAbsoluteFile()); System.out.println(System.getProperty("user.dir")); File src = new File("kkk/ooo.png"); System.out.println(src);
} }
|
416160
416160
416160
F:\java\base\io\io.png
F:\java\base
kkk\ooo.png
从 Java7 开始,可以使用 Paths 和 Files 代替 File。
1 2
| Path p = Paths.get("F:/java/base","io/io.png"); System.out.println(p);
|
查看文件的基本信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| import java.io.File;
public class FileDemo01 { public static void main(String[] args) { String path = "F:/java/base/io/io.png"; File file = new File(path); System.out.println("名称:" + file.getName()); System.out.println("路径: "+ file.getPath()); System.out.println("绝对路径: "+ file.getAbsolutePath()); System.out.println("父路径:" + file.getParent()); System.out.println(file.getParentFile().getName()); } }
|
名称:io.png
路径: F:\java\base\io\io.png
绝对路径: F:\java\base\io\io.png
父路径:F:\java\base\io
io
查看文件的状态
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| import java.awt.*; import java.io.File;
public class FileDemo02 { public static void main(String[] args) { File src = new File("io/io.png"); System.out.println(src.length()); System.out.println(src.getPath()); System.out.println(src.getAbsoluteFile()); System.out.println("是否存在: "+ src.exists()); System.out.println("是否是文件: "+ src.isFile()); System.out.println("是否是文件夹: "+ src.isDirectory()); src = new File("kkk.jpg"); if(!src.exists()){ System.out.println("文件不存在"); }else { if(src.isFile()){ System.out.println("文件操作"); }else{ System.out.println("文件夹操作"); } }
} }
|
416160
io\io.png
F:\java\base\io\io.png
是否存在: true
是否是文件: true
是否是文件夹: false
文件不存在
操作文件夹
boolean | mkdir() 创建由此抽象路径名命名的目录。 |
---|
boolean | mkdirs() 创建由此抽象路径名命名的目录,包括任何必需但不存在的父目录。 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| import java.io.File;
public class DirDemo1 {
public static void main(String[] args) { File file = new File("./io/dir/test"); boolean flag = file.mkdir(); System.out.println(flag); flag = file.mkdirs(); System.out.println(flag); } }
|
false
true
list文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| import java.io.File;
public class DirDemo1 {
public static void main(String[] args) { File dir = new File("./io"); String[] subNames = dir.list(); for(String name: subNames){ System.out.println(name); } System.out.println("-----------------------------------"); File[] subFiles = dir.listFiles(); for(File s: subFiles){ System.out.println(s.getName()); }
System.out.println("-----------------------------------");
File[] roots = dir.listRoots(); for(File f : roots){ System.out.println(f.getAbsolutePath()); }
} }
|
dir
io.iml
io.png
src
dir
io.iml
io.png
src
C:
D:
F:
H:\
递归列出一个目录下的所有文件
1 2 3 4 5 6 7 8 9 10 11 12
| public static void listAllFiles(File dir) { if (dir == null || !dir.exists()) { return; } if (dir.isFile()) { System.out.println(dir.getName()); return; } for (File file : dir.listFiles()) { listAllFiles(file); } }
|
计算文件夹的大小:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| import java.io.File;
public class countFiles {
private static long length = 0;
public static void main(String[] args) { File file = new File("./io"); count(file); System.out.println(length); }
public static void count(File src){ if(src == null || !src.exists()){ return; } if(src.isFile()){ length += src.length(); }else{ for(File f : src.listFiles()){ count(f); } } }
}
|
421864
进阶一下:使用面向对象的思维对文件夹进行统计大小。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| import java.io.File;
public class DirCount { private long length; private File src; private String path; public DirCount(String path) { this.path = path; src = new File(path); this.count(src); } public long getLength() { return length; } private void count(File src){ if(src == null || !src.exists()){ return; } if(src.isFile()){ length += src.length(); }else{ for(File f : src.listFiles()){ count(f); } } }
public static void main(String[] args) { DirCount dir = new DirCount("./io"); System.out.println(dir.getLength()); } }
|
422905
三、 字符编码
计算机只能识别二进制数据,早期由来是电信号。为了方便应用计算机,让它可以识别各个国家的文字。就将各个国家的文字用数字来表示,并一一对应,形成一张表。这就是编码表。
编码就是把字符转换为字节,而解码是把字节重新组合成字符。
如果编码和解码过程使用不同的编码方式那么就出现了乱码。
- GBK 编码中,中文字符占 2 个字节,英文字符占 1 个字节;
- UTF-8 编码中,中文字符占 3 个字节,英文字符占 1 个字节;
- UTF-16be 编码中,中文字符和英文字符都占 2 个字节。
UTF-16be 中的 be 指的是 Big Endian
,也就是大端。相应地也有 UTF-16le,le 指的是 Little Endian
,也就是小端。
Java 的内存编码使用双字节编码 UTF-16be
,这不是指 Java 只支持这一种编码方式,而是说 char
这种类型使用 UTF-16be
进行编码。char
类型占 16 位,也就是两个字节,Java 使用这种双字节编码是为了让一个中文或者一个英文都能使用一个 char
来存储。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| import java.io.UnsupportedEncodingException;
public class ContentEncode { public static void main(String[] args) throws UnsupportedEncodingException { String msg = "性命生命使命"; byte[] datas = msg.getBytes(); System.out.println(datas.length);
try { datas = msg.getBytes("UTF-16LE"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } System.out.println(datas.length);
msg = new String(datas, 0, datas.length, "UTF-16LE"); System.out.println(msg);
msg = new String(datas,0, datas.length - 1, "UTF-16LE"); System.out.println(msg);
msg = new String(datas,0, datas.length , "gbk"); System.out.println(msg);
} }
|
18
12
性命生命使命
性命生命使�
‘`}Tu}TO}T
String的编码方式
String 可以看成一个字符序列,可以指定一个编码方式将它编码为字节序列,也可以指定一个编码方式将一个字节序列解码为 String。
1 2 3 4 5
| String str1 = "中文"; byte[] bytes = str1.getBytes("UTF-8"); String str2 = new String(bytes, "UTF-8"); System.out.println(str2);
|
在调用无参数 getBytes()
方法时,默认的编码方式不是 UTF-16be。双字节编码的好处是可以使用一个 char
存储中文和英文,而将 String 转为 bytes[] 字节数组就不再需要这个好处,因此也就不再需要双字节编码。getBytes()
的默认编码方式与平台有关,一般为 UTF-8。
1
| byte[] bytes = str1.getBytes();
|
四、 四大抽象类
抽象基类 | 字节流 | 字符流 | 常用方法 |
---|
输入流 | InputStream 字节输入流 | Reader 字符输入流 | int read()、void close() |
输出流 | OutputStream 字节输出流 | Writer 字符输出流 | void write(int)、void flush()、 void close() |
Java的IO流共涉及40多个类,实际上非常规则,都是从如下4个抽象基类派生的。
由这四个类派生出来的子类名称都是以其父类名作为子类名后缀。
标准步骤
想象成搬家的程序:
①. 创建源 ②. 选择流 ③. 操作(读、写) ④. 释放资源
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| import java.io.*;
public class IOTest01 { public static void main(String[] args){ File file = new File("F:\\java\\base\\io\\a.txt"); InputStream in = null; try { in = new FileInputStream(file); int temp; while ((temp = in.read()) != -1){ System.out.println((char)temp); }
} catch (IOException e) { e.printStackTrace(); }finally { try { if(null != in) in.close(); } catch (IOException e) { e.printStackTrace(); } } } }
|
H
e
l
l
o
W
o
r
l
d
!
选择流就相当于选择搬家公司,read()方法就是一个字节一个字节地读取,就相当于一件物品的去搬, 而read(bytr[] a) 相当于用卡车来搬!更加快速。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| import java.io.*;
public class IOTest01 { public static void main(String[] args){ File file = new File("F:\\java\\base\\io\\a.txt"); InputStream in = null; try { in = new FileInputStream(file); byte[] flush = new byte[1024]; int len = -1; while ((len = in.read(flush)) != -1){ String str = new String(car, 0, len); System.out.println(str); }
} catch (IOException e) { e.printStackTrace(); }finally { try { if(null != in) in.close(); } catch (IOException e) { e.printStackTrace(); } }
} }
|
Hello World! zhu hong liang
文件字节流输出流FileOutputStream
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException;
public class IOTes02 { public static void main(String[] args) { File file = new File("./io/dext.txt"); FileOutputStream os = null; try { os = new FileOutputStream(file, true); String msg = "Hello , welcome to BeiJing !"; byte[] buff = msg.getBytes(); os.write(buff,0, buff.length); os.flush(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } }
|
实现文件拷贝
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| import java.io.*;
public class CopyFile { public static void main(String[] args) { File src = new File("./io/io.png"); FileInputStream in = null; FileOutputStream os = null; try { in = new FileInputStream(src); os = new FileOutputStream("./io/io_cpoy.png"); byte[] buff = new byte[1024 * 20]; int cnt; while ((cnt = in.read(buff, 0, buff.length)) != -1) { os.write(buff, 0, cnt); } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }finally { try{ if(os != null){ os.close(); } if(in != null){ in.close(); } }catch (IOException e){ e.printStackTrace(); }
}
} }
|
思考:进阶–> 实现文件夹的拷贝
Reader 与 Writer
不管是磁盘还是网络传输,最小的存储单元都是字节,而不是字符。但是在程序中操作的通常是字符形式的数据,因此需要提供对字符进行操作的方法。
- InputStreamReader 实现从字节流解码成字符流;
- OutputStreamWriter 实现字符流编码成为字节流。
实现逐行输出文本文件的内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public static void readFileContent(String filePath) throws IOException {
FileReader fileReader = new FileReader(filePath); BufferedReader bufferedReader = new BufferedReader(fileReader);
String line; while ((line = bufferedReader.readLine()) != null) { System.out.println(line); }
bufferedReader.close(); }
|
上面的FileInputStream中源都是一个文件,文件是存储在磁盘中的,Java程序无法直接访问到,需要通过OS来连接,这里的字节数组流ByteArrayInputStream 的源是内存中的一个字节数组,JVM是可以直接访问的,与操作系统无关了,并且字节数组流不需要close资源,因为JVM有GC来管理,会由JVM来释放,不需要自己手动关闭。
字符串、一切数据,所有的都可以转换成字节数组,方便网络的传输,在底层使用的较多。
ByteArrayInputStream中的构造函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| import java.io.*;
public class IOTest03 { public static void main(String[] args){ byte[] src = "talk is cheap show me the code".getBytes(); InputStream in = null; try { in = new ByteArrayInputStream(src); byte[] flush = new byte[5]; int len = -1; while ((len = in.read(flush)) != -1){ String str = new String(flush, 0, len); System.out.println(str); }
} catch (IOException e) { e.printStackTrace(); }finally { try { if(null != in) in.close(); } catch (IOException e) { e.printStackTrace(); } }
} }
|
ByteArrayOutputStream
该类实现了将数据写入字节数组的输出流。当数据写入缓冲区时,缓冲区会自动增长。数据可以使用toByteArray()
和toString()
。不需要在构造方法中传入目的数组。
关闭ByteArrayOutputStream
没有任何效果。 该流中的方法可以在流关闭后调用,而不生成IOException
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| import java.io.*;
public class IOTes04 { public static void main(String[] args) { byte[] dest = null; ByteArrayOutputStream os = null; try { os = new ByteArrayOutputStream(); String msg = "Hello , welcome to BeiJing !"; byte[] buff = msg.getBytes(); os.write(buff,0, buff.length); os.flush(); dest = os.toByteArray(); System.out.println(dest.length+"===>"+new String(dest,0, os.size())); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } }
|
综合-对接流
例:将一张图片读取到一个字节数组中,先使用文件输入流,通过程序做一个中转,程序再写出到字节数组中。然后字节数组通过字节数组输入流到程序中,再使用文件输出流将字节数组写回到文件中(图片)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
| import java.io.*;
public class picTest { public static void main(String[] args) { byte[] datas = FileToByteArray("./io/io.png"); System.out.println(datas.length); ByteArrayToFile(datas, "./io/ppp_img.png"); }
public static byte[] FileToByteArray(String filepath){ File file = new File(filepath); byte[] dext = null;
InputStream in = null; ByteArrayOutputStream bos = new ByteArrayOutputStream(); try { in = new FileInputStream(file); byte[] flush = new byte[1024]; int len = -1; while ((len = in.read(flush)) != -1){ bos.write(flush, 0, len); } bos.flush(); return bos.toByteArray();
} catch (IOException e) { e.printStackTrace(); }finally { try { if(null != in) in.close(); } catch (IOException e) { e.printStackTrace(); } } return null; }
public static void ByteArrayToFile(byte[] datas, String filePath){ File dest = new File(filePath); InputStream in = null; OutputStream os = null; try { in = new ByteArrayInputStream(datas); os = new FileOutputStream(dest); byte[] flush = new byte[1024 * 10]; int len = -1; while ((len = in.read(flush)) != -1) { os.write(flush, 0, len); } os.flush();
}catch (IOException e){ e.printStackTrace(); }finally { if(null != os){ try { os.close(); } catch (IOException e) { e.printStackTrace(); } } } }
}
|
封装成工具类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83
| import java.io.*;
public class FileUtils { public static void main(String[] args) { try { InputStream is = new FileInputStream("./io/a.txt"); OutputStream os = new FileOutputStream("./io/a_copy.txt"); copy(is, os); } catch (FileNotFoundException e) { e.printStackTrace(); }
byte[] datas = null; try { InputStream is = new FileInputStream("./io/io.png"); ByteArrayOutputStream os = new ByteArrayOutputStream(); copy(is, os); datas = os.toByteArray(); System.out.println(datas.length); } catch (FileNotFoundException e) { e.printStackTrace(); }
try { InputStream is = new ByteArrayInputStream(datas); OutputStream os = new FileOutputStream("./io/io_copyppppp.png"); copy(is, os); } catch (FileNotFoundException e) { e.printStackTrace(); }
}
public static void copy(InputStream in, OutputStream os){ try { byte[] buff = new byte[10]; int cnt; while ((cnt = in.read(buff, 0, buff.length)) != -1) { os.write(buff, 0, cnt); } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }finally { close(); } }
public static void close(Closeable... ios){ for(Closeable io: ios){ try{ if(io != null){ io.close(); } }catch (IOException e){ e.printStackTrace(); } } }
}
|
装饰者设计模式
Java I/O 使用了装饰者模式来实现。以 InputStream
为例,
- InputStream 是抽象组件;
- FileInputStream 是 InputStream 的子类,属于具体组件,提供了字节流的输入操作;
- FilterInputStream 属于抽象装饰者,装饰者用于装饰组件,为组件提供额外的功能。例如 BufferedInputStream 为 FileInputStream 提供缓存的功能。
实例化一个具有缓存功能的字节流对象时,只需要在 FileInputStream
对象上再套一层 BufferedInputStream
对象即可。
1 2
| FileInputStream fileInputStream = new FileInputStream(filePath); BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream);
|
DataInputStream
装饰者提供了对更多数据类型进行输入的操作,比如 int
、double
等基本类型。
装饰者模式有四个对象:
1、抽象组件: 需要装饰的抽象对象(接口或者抽象父类)
2、 具体组件: 需要装饰的对象(如上例的Person类)
3、 抽象装饰类: 包含了对抽象组件的引用以及装饰者共有的方法(写到构造器里面)
4、 具体装饰类: 被装饰的对象
装饰者与被装饰者拥有共同的超类,继承的目的是继承类型,而不是行为
模拟对人的声音放大
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
|
public class DecorateTest01 { public static void main(String[] args) { Person p = new Person(10); p.say(); Amplifier am = new Amplifier(p); am.say(); } }
interface Say{ void say(); } class Person implements Say{ private int vioce = 10;
public Person(int vioce) { this.vioce = vioce; } public int getVioce() { return vioce; }
@Override public void say() { System.out.println("人的声音为:"+ this.getVioce()); } }
class Amplifier implements Say{ private Person p; Amplifier(Person p){ this.p = p; } @Override public void say() { System.out.println("人的声音为:"+ p.getVioce() * 100); System.out.println("噪音........."); } }
|
人的声音为:10
人的声音为:1000
噪音………
模拟咖啡
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103
|
public class DecorateTest02 { public static void main(String[] args) { Drink coffe = new Coffe(); System.out.println(coffe.info() +" ===>"+ coffe.cost()); Drink suger = new Sugar(coffe); System.out.println(suger.info() +" ===>"+ suger.cost()); Drink milk = new Milk(coffe); System.out.println(milk.info() +" ===>"+ milk.cost()); suger = new Sugar(milk); System.out.println(suger.info() +" ===>"+ suger.cost()); } }
interface Drink{ double cost(); String info(); }
class Coffe implements Drink{ String name = "原味咖啡"; public String getName() { return name; } @Override public double cost() { return 10; }
@Override public String info() { return this.name; } }
class Decorator implements Drink{ Drink drink; Decorator(Drink drink){ this.drink = drink; }
@Override public double cost() { return this.drink.cost(); }
@Override public String info() { return this.drink.info(); } }
class Milk extends Decorator{ Milk(Drink drink) { super(drink); }
@Override public double cost() { return this.drink.cost() * 4; }
@Override public String info() { return this.drink.info() + "加了牛奶" ; }
}
class Sugar extends Decorator{ Sugar(Drink drink) { super(drink); }
@Override public double cost() { return this.drink.cost() * 2; }
@Override public String info() { return this.drink.info() + "加了蔗糖" ; }
}
|
原味咖啡 = >10.0
原味咖啡加了蔗糖 =>20.0
原味咖啡加了牛奶 =>40.0
原味咖啡加了牛奶加了蔗糖 ===>80.0
五、 IO-缓冲流
缓冲流可以提高性能,一开始的是字节流的read()方法,可以比喻成蚂蚁搬家,一个字节一个字节地读取,使用read(byte[] buff), 自己维护了一个字节数组,相当于叫了一个搬家公司用卡车搬;而这里的缓冲流是在内部维护了一个缓冲区,默认 8k,将字节流打包,放入缓冲流中,相当于使用了一个更大的卡车。可以提高性能,避免频繁去读写。
这里的缓冲流成为处理流,任何一个处理流,不管怎么嵌套,最底层都是一个节点流,没有节点流就没有处理流。
随着流越来越多,释放资源可以直接释放处理流,处理流内部会自动释放节点流。如果需要手动释放,释放的原则是:从里到外,一依次释放。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public class CopyFile { public static void main(String[] args) { File src = new File("./io/io.png"); try(InputStream in = new BufferedInputStream(new FileInputStream(src)); OutputStream os = new BufferedOutputStream(new FileOutputStream("./io/io_copy.png"))) { byte[] buff = new byte[10]; int cnt; while ((cnt = in.read(buff, 0, buff.length)) != -1) { os.write(buff, 0, cnt); } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }
} }
|
字符缓冲流BufferedReader、BufferedWriter
这两个流有许多新增方法,注意不要使用多态。
BufferedReader中的String readLine()
读一行文字。
BufferedWriter中的void
newLine()
写一行行分隔符。
使用缓冲流实现纯文本的拷贝:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| import java.io.*;
public class CopyTxt { public static void main(String[] args) { File src = new File("./io/dext.txt"); File dest = new File("./io/copy_dext.txt"); try (BufferedReader br = new BufferedReader(new FileReader(src)); BufferedWriter bw = new BufferedWriter(new FileWriter(dest))){ String line = null; while ((line = br.readLine()) != null){ bw.write(line); bw.newLine(); bw.flush(); } }catch (IOException e){ e.printStackTrace(); }
} }
|
1.将字节流转换成字符流。字节流可以处理一切内容,文本、图片、音频、视频。所以在很多框架和系统中底层返回的是一个字节流。但是里面是纯文本的时候,就需要进行转换。
如System.in
和 System.out
都是属于字节流。
2.在底层中,如果是一个纯文本的话,涉及到一个字符集,将字节转换成字符叫做解码,如果工程中的字符集或者系统的字符集与源的字符集不一致的话,就会出现乱码问题。这时候我们需要自己指定字符集。
String getEncoding()
: 返回此流使用的字符编码的名称。
OutputStreamWriter编码
OutputStreamWriter
是字符流到字节流的桥梁:使用指定的字符编码charset
将指定的字符编码成字节 。 它使用的字符集可以由名称指定,也可以被明确指定,或者可以接受平台的默认字符集。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| import java.io.*;
public class ConvertTest { public static void main(String[] args) { try(BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)) ; BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(System.out))){ String msg = ""; while (!(msg = reader.readLine()).equals("exit")){ writer.write(msg); writer.newLine(); writer.flush(); } }catch (IOException e){ System.out.println("操作异常"); } }
}
|
六、 其他流(数据流、对象流、打印流)
1. 数据流
方便我们处理基本数据类型和字符串,不但保留了数据,还保存了数据类型。
构造方法: DataInputStream(InputStream in)
方法:
DataOutputStream
用法与DataInputStream相似
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| import java.io.*;
public class DataTest {
public static void main(String[] args) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); DataOutputStream dos = new DataOutputStream(new BufferedOutputStream(baos)); dos.writeUTF("良辰美景奈何天"); dos.writeInt(19); dos.writeChar('a'); dos.writeBoolean(false); dos.flush(); byte[] datas = baos.toByteArray(); System.out.println(datas.length); DataInputStream dis = new DataInputStream(new BufferedInputStream(new ByteArrayInputStream(datas))); String msg = dis.readUTF(); System.out.println("字符串的大小: "+msg.getBytes().length); int a = dis.readInt(); char c = dis.readChar(); boolean flag = dis.readBoolean(); System.out.println(msg); System.out.println(flag); } }
|
30
字符串的大小: 21
良辰美景奈何天
false
2. 打印流
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| import java.io.*;
public class PrintTest { public static void main(String[] args) throws FileNotFoundException { PrintStream ps = System.out; ps.println("Hello"); ps.println(true); ps.flush(); ps = new PrintStream(new BufferedOutputStream(new FileOutputStream("./io/print.txt",true)), true); ps.println("这是打印流"); ps.println(true); System.setOut(ps); System.out.println("我已经变了,不是输出到控制台了!");
System.setOut(new PrintStream(new BufferedOutputStream(new FileOutputStream(FileDescriptor.out)), true)); System.out.println("我回来啦!!!!"); ps.close(); } }
|
Hello
true
我回来啦!!!!
还有PrintWriter
。
七、 对象操作
1. 序列化
不是所有的对象都是可以序列化。序列化就是将一个对象转换成字节序列,方便存储和传输。
- 序列化:
ObjectOutputStream.writeObject()
- 反序列化:
ObjectInputStream.readObject()
不会对静态变量进行序列化,因为序列化只是保存对象的状态,静态变量属于类的状态。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83
| import java.io.*;
public class ObjectTest { public static void main(String[] args) throws IOException, ClassNotFoundException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(new BufferedOutputStream(baos)); oos.writeUTF("良辰美景奈何天"); oos.writeInt(18); oos.writeChar('a'); oos.writeBoolean(true); oos.writeObject("花谢花飞花满天"); Employee e = new Employee("马云", 5000.0); oos.writeObject(e); oos.flush(); byte[] datas = baos.toByteArray(); System.out.println(datas.length); ObjectInputStream dis = new ObjectInputStream(new BufferedInputStream(new ByteArrayInputStream(datas))); String msg = dis.readUTF(); System.out.println("字符串的大小: "+msg.getBytes().length); int a = dis.readInt(); char c = dis.readChar(); boolean flag = dis.readBoolean(); System.out.println(msg); System.out.println(flag); Object str = dis.readObject(); Object employee = dis.readObject();
if( str instanceof String){ String ss = (String)str; System.out.println(ss); } if(employee instanceof Employee){ Employee eee = (Employee)employee; System.out.println(eee.toString()); } } }
class Employee implements java.io.Serializable{ private String name; private double money;
public void setName(String name) { this.name = name; }
public void setMoney(double money) { this.money = money; }
public String getName() { return name; }
public double getMoney() { return money; }
public Employee(String name, double money) { this.name = name; this.money = money; }
@Override public String toString() { return this.name +"===>"+ this.money; } }
|
2. Serializable
序列化的类需要实现 Serializable
接口,它只是一个标准,没有任何方法需要实现,但是如果不去实现它的话而进行序列化,会抛出异常。
3. transient
transient
关键字可以使一些属性不会被序列化。
ArrayList
中存储数据的数组 elementData
是用 transient
修饰的,因为这个数组是动态扩展的,并不是所有的空间都被使用,因此就不需要所有的内容都被序列化。通过重写序列化和反序列化方法,使得可以只序列化数组中有内容的那部分数据。
1
| private transient Object[] elementData;
|
八、 commons-io组件
下载
http://commons.apache.org/proper/commons-io/download_io.cgi
环境搭建
环境准备:
1.在idea中导入以下两个jar包
2.打开idea的project struct:
3,进入Dependencies
操作文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| import org.apache.commons.io.FileUtils; import org.apache.commons.io.filefilter.DirectoryFileFilter; import org.apache.commons.io.filefilter.EmptyFileFilter; import org.apache.commons.io.filefilter.FileFilterUtils; import org.apache.commons.io.filefilter.SuffixFileFilter;
import java.io.File; import java.util.Collection;
public class CIO_test { public static void main(String[] args) { long len = FileUtils.sizeOf(new File("./io/io.png")); System.out.println(len); len = FileUtils.sizeOfDirectory(new File("./io")); System.out.println(len);
Collection<File> files = FileUtils.listFiles(new File("./io"), EmptyFileFilter.NOT_EMPTY, null); for (File file: files){ System.out.println(file.getName()); }
System.out.println("----------------------------------"); files = FileUtils.listFiles(new File("./io"), EmptyFileFilter.NOT_EMPTY, DirectoryFileFilter.INSTANCE); for (File file: files){ System.out.println(file.getAbsolutePath()); } System.out.println("----------------------------------"); files = FileUtils.listFiles(new File("./io"), FileFilterUtils.or(new SuffixFileFilter("java"), new SuffixFileFilter("txt")), DirectoryFileFilter.INSTANCE); for (File file: files){ System.out.println(file.getAbsolutePath()); }
}
}
|
416160
1696597
a.txt
a_copy.txt
copy_dext.txt
dext.txt
io.iml
io.png
io_copy.png
io_copyppppp.png
ppp_img.png
print.txt
F:\java\base.\io\a.txt
F:\java\base.\io\a_copy.txt
F:\java\base.\io\copy_dext.txt
F:\java\base.\io\dext.txt
F:\java\base.\io\io.iml
F:\java\base.\io\io.png
F:\java\base.\io\io_copy.png
F:\java\base.\io\io_copyppppp.png
F:\java\base.\io\ppp_img.png
F:\java\base.\io\print.txt
F:\java\base.\io\src\CIOTest.java
F:\java\base.\io\src\CIO_test.java
F:\java\base.\io\src\ContentEncode.java
F:\java\base.\io\src\ConvertTest.java
F:\java\base.\io\src\CopyFile.java
F:\java\base.\io\src\CopyTxt.java
F:\java\base.\io\src\countFiles.java
F:\java\base.\io\src\DataTest.java
F:\java\base.\io\src\DecorateTest01.java
F:\java\base.\io\src\DecorateTest02.java
F:\java\base.\io\src\Demo1.java
F:\java\base.\io\src\DirCount.java
F:\java\base.\io\src\DirDemo1.java
F:\java\base.\io\src\FileDemo01.java
F:\java\base.\io\src\FileDemo02.java
F:\java\base.\io\src\FileUtils.java
F:\java\base.\io\src\IOTes02.java
F:\java\base.\io\src\IOTes04.java
F:\java\base.\io\src\IOTest01.java
F:\java\base.\io\src\IOTest03.java
F:\java\base.\io\src\listAllFiles.java
F:\java\base.\io\src\ObjectTest.java
F:\java\base.\io\src\Path.java
F:\java\base.\io\src\picTest.java
F:\java\base.\io\src\PrintTest.java
F:\java\base.\io\a.txt
F:\java\base.\io\a_copy.txt
F:\java\base.\io\copy_dext.txt
F:\java\base.\io\dext.txt
F:\java\base.\io\print.txt
F:\java\base.\io\src\CIOTest.java
F:\java\base.\io\src\CIO_test.java
F:\java\base.\io\src\ContentEncode.java
F:\java\base.\io\src\ConvertTest.java
F:\java\base.\io\src\CopyFile.java
F:\java\base.\io\src\CopyTxt.java
F:\java\base.\io\src\countFiles.java
F:\java\base.\io\src\DataTest.java
F:\java\base.\io\src\DecorateTest01.java
F:\java\base.\io\src\DecorateTest02.java
F:\java\base.\io\src\Demo1.java
F:\java\base.\io\src\DirCount.java
F:\java\base.\io\src\DirDemo1.java
F:\java\base.\io\src\FileDemo01.java
F:\java\base.\io\src\FileDemo02.java
F:\java\base.\io\src\FileUtils.java
F:\java\base.\io\src\IOTes02.java
F:\java\base.\io\src\IOTes04.java
F:\java\base.\io\src\IOTest01.java
F:\java\base.\io\src\IOTest03.java
F:\java\base.\io\src\listAllFiles.java
F:\java\base.\io\src\ObjectTest.java
F:\java\base.\io\src\Path.java
F:\java\base.\io\src\picTest.java
F:\java\base.\io\src\PrintTest.java
读取内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| import org.apache.commons.io.FileUtils; import org.apache.commons.io.LineIterator; import java.io.File; import java.io.IOException; import java.util.List;
public class CIOTest { public static void main(String[] args) throws IOException { String msg = FileUtils.readFileToString(new File("./io/dext.txt"), "UTF-8"); System.out.println(msg); byte[] datas = FileUtils.readFileToByteArray(new File("./io/dext.txt")); System.out.println(datas.length); List<String> msgs = FileUtils.readLines(new File("./io/dext.txt"),"UTF-8"); for(String m: msgs){ System.out.println(m); }
LineIterator it = FileUtils.lineIterator(new File("./io/dext.txt")); while (it.hasNext()){ System.out.println(it.nextLine()); }
} }
|
写出内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| import org.apache.commons.io.FileUtils; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.List;
public class CIOtestWrite {
public static void main(String[] args) throws IOException { FileUtils.write(new File("./io/happy.txt"), "今天天气真好!\r\n", "UTF-8"); FileUtils.writeStringToFile(new File("./io/happy.txt"), "河山大好,出去走走吧!", "UTF-8",true); FileUtils.writeByteArrayToFile(new File("./io/happy.txt"), "河山大好,出去走走吧!".getBytes("UTF-8"),true);
List <String> datas = new ArrayList<>(); datas.add("马云"); datas.add("马化腾"); datas.add("李嘉诚");
FileUtils.writeLines(new File("./io/happy.txt"), datas, "...", true); } }
|
1
| 今天天气真好!河山大好,出去走走吧!河山大好,出去走走吧!马云...马化腾...李嘉诚...
|
复制文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| import org.apache.commons.io.FileUtils; import java.io.File; import java.io.IOException; import java.net.URL;
public class CIOCopy { public static void main(String[] args) throws IOException { FileUtils.copyFile(new File("./io/io.png"), new File("./io/cio.png")); FileUtils.copyFileToDirectory(new File("./io/io.png"), new File("./io/test")); String url = "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1582094003889&di=8d3bc6164079cb45c6ba073ff143b591&imgtype=0&src=http%3A%2F%2Fwww.bbra.cn%2FUploadFiles%2Fimgs%2F2015%2F11%2F02%2Fmm3%2F005.jpg"; FileUtils.copyURLToFile(new URL(url), new File("./io/girl.jpg"));
} }
|
九、 NIO
新的输入/输出 (NIO) 库是在 JDK 1.4 中引入的,弥补了原来的 I/O 的不足,提供了高速的、面向块的 I/O。
1. 流与块
I/O 与 NIO 最重要的区别是数据打包和传输的方式,I/O 以流的方式处理数据,而 NIO 以块的方式处理数据。
面向流的 I/O 一次处理一个字节数据:一个输入流产生一个字节数据,一个输出流消费一个字节数据。为流式数据创建过滤器非常容易,链接几个过滤器,以便每个过滤器只负责复杂处理机制的一部分。不利的一面是,面向流的 I/O 通常相当慢。
面向块的 I/O 一次处理一个数据块,按块处理数据比按流处理数据要快得多。但是面向块的 I/O 缺少一些面向流的 I/O 所具有的优雅性和简单性。
I/O 包和 NIO 已经很好地集成了,java.io.*
已经以 NIO 为基础重新实现了,所以现在它可以利用 NIO 的一些特性。例如,java.io.*
包中的一些类包含以块的形式读写数据的方法,这使得即使在面向流的系统中,处理速度也会更快。
2. 通道与缓冲区
通道
通道 Channel 是对原 I/O 包中的流的模拟,可以通过它读取和写入数据。
通道与流的不同之处在于,流只能在一个方向上移动(一个流必须是 InputStream
或者 OutputStream
的子类),而通道是双向的,可以用于读、写或者同时用于读写。
通道包括以下类型:
FileChannel
:从文件中读写数据;DatagramChannel
:通过 UDP 读写网络中数据;SocketChannel
:通过 TCP 读写网络中数据;ServerSocketChannel
:可以监听新进来的 TCP 连接,对每一个新进来的连接都会创建一个 SocketChannel
。
缓冲区
发送给一个通道的所有数据都必须首先放到缓冲区中,同样地,从通道中读取的任何数据都要先读到缓冲区中。也就是说,不会直接对通道进行读写数据,而是要先经过缓冲区。
缓冲区实质上是一个数组,但它不仅仅是一个数组。缓冲区提供了对数据的结构化访问,而且还可以跟踪系统的读/写进程。
缓冲区包括以下类型:
ByteBuffer
CharBuffer
ShortBuffer
IntBuffer
LongBuffer
FloatBuffer
DoubleBuffer
2. 缓冲区状态变量
- capacity:最大容量;
- position:当前已经读写的字节数;
- limit:还可以读写的字节数。
状态变量的改变过程举例:
① 新建一个大小为 8 个字节的缓冲区,此时 position 为 0,而 limit = capacity = 8。capacity 变量不会改变,下面的讨论会忽略它。
② 从输入通道中读取 5 个字节数据写入缓冲区中,此时 position 为 5,limit 保持不变。
③ 在将缓冲区的数据写到输出通道之前,需要先调用 flip() 方法,这个方法将 limit 设置为当前 position,并将 position 设置为 0。
④ 从缓冲区中取 4 个字节到输出缓冲中,此时 position 设为 4。
⑤ 最后需要调用 clear() 方法来清空缓冲区,此时 position 和 limit 都被设置为最初位置
3. 文件NIO实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| public static void fastCopy(String src, String dist) throws IOException {
FileInputStream fin = new FileInputStream(src);
FileChannel fcin = fin.getChannel();
FileOutputStream fout = new FileOutputStream(dist);
FileChannel fcout = fout.getChannel();
ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
while (true) {
int r = fcin.read(buffer);
if (r == -1) { break; }
buffer.flip();
fcout.write(buffer);
buffer.clear(); } }
|
4. 选择器
NIO 常常被叫做非阻塞 IO,主要是因为 NIO 在网络通信中的非阻塞特性被广泛使用。
NIO 实现了 IO 多路复用中的 Reactor 模型,一个线程 Thread 使用一个选择器 Selector 通过轮询的方式去监听多个通道 Channel 上的事件,从而让一个线程就可以处理多个事件。
通过配置监听的通道 Channel 为非阻塞,那么当 Channel 上的 IO 事件还未到达时,就不会进入阻塞状态一直等待,而是继续轮询其它 Channel,找到 IO 事件已经到达的 Channel 执行。
因为创建和切换线程的开销很大,因此使用一个线程来处理多个事件而不是一个线程处理一个事件,对于 IO 密集型的应用具有很好地性能。
应该注意的是,只有套接字 Channel 才能配置为非阻塞,而 FileChannel 不能,为 FileChannel 配置非阻塞也没有意义。
创建选择器
1
| Selector selector = Selector.open();
|
将通道注册到选择器上
1 2 3
| ServerSocketChannel ssChannel = ServerSocketChannel.open(); ssChannel.configureBlocking(false); ssChannel.register(selector, SelectionKey.OP_ACCEPT);
|
通道必须配置为非阻塞模式,否则使用选择器就没有任何意义了,因为如果通道在某个事件上被阻塞,那么服务器就不能响应其它事件,必须等待这个事件处理完毕才能去处理其它事件,显然这和选择器的作用背道而驰。
在将通道注册到选择器上时,还需要指定要注册的具体事件,主要有以下几类:
SelectionKey.OP_CONNECT
SelectionKey.OP_ACCEPT
SelectionKey.OP_READ
SelectionKey.OP_WRITE
它们在 SelectionKey
的定义如下:
1 2 3 4
| public static final int OP_READ = 1 << 0; public static final int OP_WRITE = 1 << 2; public static final int OP_CONNECT = 1 << 3; public static final int OP_ACCEPT = 1 << 4;
|
可以看出每个事件可以被当成一个位域,从而组成事件集整数。例如:
1
| int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;
|
监听事情
1
| int num = selector.select();
|
使用 select() 来监听到达的事件,它会一直阻塞直到有至少一个事件到达。
获取到达的事件
1 2 3 4 5 6 7 8 9 10 11
| Set<SelectionKey> keys = selector.selectedKeys(); Iterator<SelectionKey> keyIterator = keys.iterator(); while (keyIterator.hasNext()) { SelectionKey key = keyIterator.next(); if (key.isAcceptable()) { } else if (key.isReadable()) { } keyIterator.remove(); }
|
事件循环
因为一次 select()
调用不能处理完所有的事件,并且服务器端有可能需要一直监听事件,因此服务器端处理事件的代码一般会放在一个死循环内。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| while (true) { int num = selector.select(); Set<SelectionKey> keys = selector.selectedKeys(); Iterator<SelectionKey> keyIterator = keys.iterator(); while (keyIterator.hasNext()) { SelectionKey key = keyIterator.next(); if (key.isAcceptable()) { } else if (key.isReadable()) { } keyIterator.remove(); } }
|
5. 套接字NIO实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
| public class NIOServer {
public static void main(String[] args) throws IOException {
Selector selector = Selector.open();
ServerSocketChannel ssChannel = ServerSocketChannel.open(); ssChannel.configureBlocking(false); ssChannel.register(selector, SelectionKey.OP_ACCEPT);
ServerSocket serverSocket = ssChannel.socket(); InetSocketAddress address = new InetSocketAddress("127.0.0.1", 8888); serverSocket.bind(address);
while (true) {
selector.select(); Set<SelectionKey> keys = selector.selectedKeys(); Iterator<SelectionKey> keyIterator = keys.iterator();
while (keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
if (key.isAcceptable()) {
ServerSocketChannel ssChannel1 = (ServerSocketChannel) key.channel();
SocketChannel sChannel = ssChannel1.accept(); sChannel.configureBlocking(false);
sChannel.register(selector, SelectionKey.OP_READ);
} else if (key.isReadable()) {
SocketChannel sChannel = (SocketChannel) key.channel(); System.out.println(readDataFromSocketChannel(sChannel)); sChannel.close(); }
keyIterator.remove(); } } }
private static String readDataFromSocketChannel(SocketChannel sChannel) throws IOException {
ByteBuffer buffer = ByteBuffer.allocate(1024); StringBuilder data = new StringBuilder();
while (true) {
buffer.clear(); int n = sChannel.read(buffer); if (n == -1) { break; } buffer.flip(); int limit = buffer.limit(); char[] dst = new char[limit]; for (int i = 0; i < limit; i++) { dst[i] = (char) buffer.get(i); } data.append(dst); buffer.clear(); } return data.toString(); } }
|
1 2 3 4 5 6 7 8 9 10
| public class NIOClient {
public static void main(String[] args) throws IOException { Socket socket = new Socket("127.0.0.1", 8888); OutputStream out = socket.getOutputStream(); String s = "hello world"; out.write(s.getBytes()); out.close(); } }
|
6. 内存映射文件
内存映射文件 I/O 是一种读和写文件数据的方法,它可以比常规的基于流或者基于通道的 I/O 快得多。
向内存映射文件写入可能是危险的,只是改变数组的单个元素这样的简单操作,就可能会直接修改磁盘上的文件。修改数据与将数据保存到磁盘是没有分开的。
下面代码行将文件的前 1024 个字节映射到内存中,map() 方法返回一个 MappedByteBuffer,它是 ByteBuffer 的子类。因此,可以像使用其他任何 ByteBuffer 一样使用新映射的缓冲区,操作系统会在需要时负责执行映射。
1
| MappedByteBuffer mbb = fc.map(FileChannel.MapMode.READ_WRITE, 0, 1024);
|
7. 对比
NIO 与普通 I/O 的区别主要有以下两点:
- NIO 是非阻塞的;
- NIO 面向块,I/O 面向流。
参考
- https://cyc2018.github.io/CS-Notes
- https://www.runoob.com/design-pattern/design-pattern-tutorial.html