Java基础-I/O流
参考文献
- IO 多路复用
- Java语言设计-基础篇(第8版)
- Java核心技术 卷2 高级特性
- Java IO Tutorials
I/O
流
InputStream/Reader
: 所有的输入流的基类,前者是字节输入流,后者是字符输入流.OutputStream/Writer
: 所有的输出流的基类,前者是字节输出流,后者是字符输出流.
File
类
File
对象封装了文件或路径的属性,但是它既不包括创建文件,也不包括从(向)文件读(写)数据的方法.
文件输入和输出
PrintWriter
-
java.io.PrintWriter
类可以用来创建一个文件并向文本文件写入数据.首先必须为一个文本文件创建一个PrintWriter
对象.1
2
3final PrintWriter printWriter = new PrintWriter(new File(""));
printWriter.println("");
printWriter.close();
Scanner
-
java.util.Scanner
类用来从控制台读取字符串和基本类型数值.Scanner
可以将输入分为由空白字符串分隔的有用信息.1
2
3
4
5final Scanner scanner = new Scanner(System.in);
while (scanner.hasNext()) {
final String next = scanner.next();
}
scanner.close();
FileInputStream
类和FileOutputStream
类
-
FileInputStream
类和FileOutputStream
类是为了从(向)文件读取(写入)字节.它们的所有方法都是从InputStream
类和OutputStream
类继承的.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/**
* 文件的拷贝
* @param 源文件路径
* @param 目录文件路径
* @throws FileNotFoundException,IOException
* @return
*/
public static void copyFile(String srcPath,String destPath) throws FileNotFoundException,IOException {
//1、建立联系 源(存在且为文件) +目的地(文件可以不存在)
File src =new File(srcPath);
File dest =new File(destPath);
if(! src.isFile()){ //不是文件或者为null
System.out.println("只能拷贝文件");
throw new IOException("只能拷贝文件");
}
//2、选择流
InputStream is =new BufferedInputStream(new FileInputStream(src));
OutputStream os =new BufferedOutputStream( new FileOutputStream(dest));
//3、文件拷贝 循环+读取+写出
byte[] flush =new byte[1024];
int len =0;
//读取
while(-1!=(len=is.read(flush))){
//写出
os.write(flush, 0, len);
}
os.flush(); //强制刷出
//关闭流
os.close();
is.close();
}
FilterInputStream
类和FilterOutputStream
类
- 过滤器数据流(filter stream)是为某种目的的过滤字节的数据流.基本字节输入流提供的读取方法
read
只能用来读取字节.如果要读取整数值,双精度值或字符串,那就需要一个过滤类器类来包装字节输入流.使用过滤器类就可以读取整数值,双精度值和字符串,而不是字节或字符. FilterInputStream
类和FilterOutputStream
类是过滤数据的基类,需要处理基本数据值类型时,就使用DataInputStream
类和DataOutputStream
类来过滤字节.
DataInputStream
类和DataOutputStream
类
DataInputStream
从数据流读取字节,并且将他们转换为正确的基本类型值或字符串.
DataOutputStream
将基本类型的值或字符串转换为字节,并且将字节输出到数据流.
1 | DataOutputStream dataOutputStream = null; |
BufferedInputStream
类和BufferedOutputStream
类
BufferedInputStream
类和BufferedOutputStream
类可以通过减少读写次数来提高输入和输出的速度.BufferedInputStream
类和BufferedOutputStream
类没有包含新的方法.所有方法都是从InputStream
类和OutputStream
类继承而来.BufferedInputStream
类和BufferedOutputStream
类为存储字节在流中添加一个缓冲区,以提高处理效率.- 如果没有指定缓冲区大小,默认为512个字节.缓冲区输入流会在每次读取调用中尽可能多地将数据读入缓冲区.相反地,只有当缓冲区已满或调用
flush()
方法时,缓冲输出流才会调用写入方法.
1 | public static void readByBufferedInputStream() throws IOException { |
1 | import java.io.BufferedOutputStream; |
DataInputStream
类和DataOutpuStream
类
DataInputStream
类和DataOutpuStream
类可以实现基本数据类型与字符串的输入和输出,还可以实现对象的输入输出.
随机访问文件-RandomAccessFile
-
RandomAccessFile
类允许在文件内的随机位置上进行读写.RandomAccessFile
类实现了DataInput
和DataOutput
接口. -
当创建一个
RandomAccessFile
数据流时,可以指定两种模式(r
或rw
)之一.1
final RandomAccessFile raf = new RandomAccessFile("filename.txt", "rw");
- 如果文件已存在,则创建raf访问这个文件;如果文件不存在,则创建一个名为’'filename.txt"的新文件,再创建raf访问这个文件
-
随机访问文件是由字节序列组成的.一个称为文件指针(
file poniter
)的特殊标记定位这些字节中的某个字节的位置.文件读写操作就是在文件指针的位置进行的.打开文件时,文件指针置于文件的起始位置.在文件中进行读写数据后,文件指针就会向前移到下一个数据项.
ZIP文档
-
ZIP文档通常以压缩格式存储了一个或多个文件,每个ZIP文档都有一个头,包含诸如每个文件名和所使用的压缩方法等信息.
-
在Java中可以使用
ZipInputStream
来读入ZIP文档.若需要浏览文档中每个单独的项,getNextEntry
方法就可以返回一个描述这些项的ZipEntry
类型的对象. -
ZipInputStream
的read方法被修改为在碰到当前项的结尾是返回-1(而不是碰到ZIP文件的末尾),然后必须调用closeEntry
来读入下一项.1
2
3
4
5
6
7
8final ZipInputStream zipInputStream = new ZipInputStream(new FileInputStream(""));
ZipEntry zipEntry;
while ((zipEntry=zipInputStream.getNextEntry())!=null){
// analyze entry
// read the content of zin
zipInputStream.closeEntry();
}
zipInputStream.close();- 注意: 在读入单个ZIP项后,不要关闭ZIP输入流,也不要将其传递给可能会关闭它的方法,否则就不能再读入后续的项了.
-
写出到ZIP文件,可以使用
ZipOutputStream
,若希望放入到ZIP文件中每一项,都应该创建一个ZipEntry
对象,并将文件名传递给ZipEntity
的构造器,它将设置其他诸如文件日期和解压缩方法等参数.然后需要调用ZipOutputStream
的putNextEntry
方法来开始写出新文件,并将文件数据发送到ZIP流中,最后完成时,需要调用closeEntry
.1
2
3
4
5
6
7
8
9final ZipOutputStream zipOutputStream = new ZipOutputStream(new FileOutputStream("test.zip"));
// deal all files
{
ZipEntry ze=new ZipEntry("filename");
zipOutputStream.putNextEntry(ze);
// send data to zipOutputStream
zipOutputStream.closeEntry();
}
zipOutputStream.close();- JAR文件只是带有一个特殊项的ZIP文件,这个项被称为清单.可以使用
JarInputStream
和JarOutputStream
来处理.
- JAR文件只是带有一个特殊项的ZIP文件,这个项被称为清单.可以使用
操作文件
Path
- Path表示的是一个目录名序列,其后还可以跟着一个文件名.路径中的第一个部件可以是根部件,如
/
或D:\
.而允许访问的根部件取决于文件系统. - 以根部件开始的路径是绝对路径;否则,就是相对路径.
- 静态的
Paths.get
方法接收一个或多个字符串,并将它们用默认文件系统的路径分隔符连接起来.然后解析连接起来的结果,如果其表示不是给定文件系统中的合法路径,那么就抛出InvalidPathException
异常.
组合和解析
-
p.resolve(q)
将按照下列规则返回一个路径:- 如果q是绝对路径,则结果就是q;
- 否则,根据文件系统的规则,将"p后面跟着q"作为结果;
-
resolve
方法是一种快捷方式,它接收一个字符串而不是路径:1
Path workPath=basePath.resolve("work");
-
resolveSibling
它通过解析指定路径的父路径产生其兄弟路径.- 例如,若workPath是"/opt/myapp/work"那么下面的调用将创建"/opt/myapp/temp"
1
Path tempPath=workPath.resolveSibling("temp");
-
resolve
的对立是relative
即调用p.relative(r)
将产生路径q,对q进行解析会产生r.- 例如"/home/cay"为目标对"/home/fred/myprog"进行相对化操作,会产生"…/fred/myprog",其中假设"…"为文件系统的父目录.
-
normalize
方法将移除所有冗余的".“和”…“部件(或者文件系统认为冗余的所有部件).例如规范化”/home/cay/…/fred/./myprog"将产生"/home/cay/fred/myprog"
Files
Files
类可以使得普通文件操作变得快捷.
1 | // 读取文件 |
- 若目标路径已存在,那么复制或移动将失败,如果想要覆盖已有的目标路径,可以使用
REPLACE_EXISTING
选项,如果想要复制所有文件属性,可以使用COPY_ATTRIBUTES
,也可以同时选择两个选项.
创建文件和目录
1 | // 创建目录 |
- 若文件已存在了,这个创建文件的操作就会抛出异常.检查文件是否存在和创建文件是原子的.
迭代目录中的文件
1 | try (DirectoryStream<Path> entries = Files.newDirectoryStream(Paths.get(""))) { |
- 若想要访问某个目录的所有子孙成员可以使用
walkFileTree
方法,并想起传递一个FileVisitor
类型的对象,这个对象会得到下列通知:- 在遇到一个文件或目录时:
FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
- 在一个目录被处理前:
FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs)
- 在一个目录被处理后:
FileVisitResult postVisitDirectory(Path dir, IOException exc)
- 在试图访问文件或目录时发生错误,例如没有权限打开目录:
FileVisitResult visitFileFailed(Path file, IOException exc)
- 对于上述每种情况,都可以指定是否希望执行下面的操作:
- 继续访问下一个文件:
FileVisitResult.CONTINUE
- 继续访问,但不再访问这个目录下的任何项:
FileVisitResult.SKIP_SUBTREE
- 继续访问,但不再访问这个文件的兄弟文件(和该文件在同一个目录下的文件):
FileVisitResult.
SKIP_SIBLINGS - 终止访问:
FileVisitResult.TERMINATE
- 继续访问下一个文件:
- 在遇到一个文件或目录时:
1 | Files.walkFileTree(Paths.get(""),new SimpleFileVisitor<Path>(){ |
内存文件映射
-
大多数操作系统都可以利用虚拟内存实现来将一个文件或文件的一部分"映射"到内存中.然后,这个文件就可以当做是内存数组一样地访问,这比传统文件操作要快得多.
-
在
java.nio
包使内存映射变得十分简单-
首先从文件中获得一个通道(channel),通道是用于磁盘文件的一种抽象,它使我们可以访问注入内存映射,文件加锁机制以及文件快速数据传递等操作系统特性.
1
final FileChannel fileChannel = FileChannel.open(Paths.get(""), StandardOpenOption.READ);
-
然后通过调用
FileChannel
类的map
方法从这个通道中获得一个ByteBuffer
.可以指定想要映射的文件区域与映射模式,支持的模式有三种:java.nio.channels.FileChannel.MapMode#READ_ONLY
: 所产生的缓冲区是只读的,任何对该缓冲区写入的尝试都会导致ReadOnlyBufferException
异常;java.nio.channels.FileChannel.MapMode#READ_WRITE
:所产生的缓冲区是可写的,任何修改都会在某个时刻写会到文件中.注意,其他映射同一个文件的程序不能立即看到这些修改,多个程序同时进行文件映射的确切行为是依赖操作系统的;java.nio.channels.FileChannel.MapMode#PRIVATE
: 所产生的缓存区是可写的,但是任何修改对这个缓冲区来说都是私有的,不会传播到文件中.
-
一但有了缓冲区,就可以使用
ByteBuffer
类和Buffer
超类的方法读写数据了.
-
-
缓冲区支持顺序和随机数据访问,它有一个可以通过
get
和put
操作类移动的位置.1
2
3
4
5
6
7while (mappedByteBuffer.hasRemaining()) {
final byte b = mappedByteBuffer.get();
}
for (int i = 0; i < mappedByteBuffer.limit(); i++) {
final byte b = mappedByteBuffer.get(i);
}
缓冲区数据结构
-
Buffer
类是一个抽象类,它有很多具体子类,包括ByteBuffer
,CharBuffer
,DoubleBuffer
,IntBuffer
,LongBuffer
和ShortBuffer
.StringBuffer
类与这些缓冲区没有关系.
-
最常用的是
ByteBuffer
和CharBuffer
,每个缓冲区都具有:-
一个容量,它永远不能改变.
-
一个读写位置,下一个值将在此进行读写.
-
一个界限,超过它进行读写是没有意义的.
-
一个可选的标记,用于重复一个读入或写出操作.
-
这些值满足下面的条件
1
0<=标记<=位置<=界限<=容量
-
-
使用缓冲区的主要目的是执行**“写,然后读入”**循环.假设有一个缓冲区,在一开始,它的位置是0,界限等于容量.不断地调用
put
将值添加到这个缓冲区中,当耗尽所有的数据或者写出的数据量达到容量大小时,就应该切换到读入操作了. -
此时调用
filp
方法将界限设置到当前位置,并把位置复位到0.现在在remaining
方法返回正数时(它返回的值是"界限-位置"),不断地调用get
.在将所有缓冲区的内容读入之后,调用clear
使缓冲区为下一次写循环做准备.clear
方法将位置复位到0,并将界限复位到容量.
RandomAccessFile
-
读取文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14public static void randomAccessFileRead() throws IOException {
// 创建一个RandomAccessFile对象
RandomAccessFile file = new RandomAccessFile( "d:/test.txt", "rw");
// 通过seek方法来移动读写位置的指针
file.seek(10);
// 获取当前指针
long pointerBegin = file.getFilePointer();
// 从当前指针开始读
byte[] contents = new byte[1024];
file.read( contents);
long pointerEnd = file.getFilePointer();
System. out.println( "pointerBegin:" + pointerBegin + "\n" + "pointerEnd:" + pointerEnd + "\n" + new String(contents));
file.close();
} -
随机写入文件
1
2
3
4
5
6
7
8
9
10
11
12
13public static void randomAccessFileWrite() throws IOException {
// 创建一个RandomAccessFile对象
RandomAccessFile file = new RandomAccessFile( "d:/test.txt", "rw");
// 通过seek方法来移动读写位置的指针
file.seek(10);
// 获取当前指针
long pointerBegin = file.getFilePointer();
// 从当前指针位置开始写
file.write( "HELLO WORD".getBytes());
long pointerEnd = file.getFilePointer();
System. out.println( "pointerBegin:" + pointerBegin + "\n" + "pointerEnd:" + pointerEnd + "\n" );
file.close();
}
常用方法
byteArray ==> File
1 | // bytearray ==> File |
byteArray ==> InputStream
1 | public static InputStream byteToInputStream(byte[] bytes) { |
InputStream ==> bytearray
1 | // InputStream ==> bytearray |
File ==> byteArray
1 | public static byte[] fileToByteArray(File file) throws IOException { |