Java基础复习——I/O系统

Java的IO是通过java.io包下的类和接口支持的;在java.io包下包括了输入输出两种IO流;

每种输入输出流分为字节流和字符流;字节流以字节为单位来处理输入输出操作,字符流将以字符为单位来处理输入输出操作;

Java的IO流使用了装饰器设计模式,它将IO流分成底层节点流和上层处理流;节点流用于和底层的物理存储节点直接关联——不同的五里界店获取节点流的方式可能存在一些差异;但程序可以把不同的物理节点流包装成统一的处理流,从而允许程序使用同意的输入输出代码来读取不同的物理存储节点的资源;

File类

File类代表与平台无关的文件和目录,是对文件和目录的描述;
File能新建、删除和重命名文件和目录;但是不能访问文件内容本身,要访问文件的内容需要使用输入输出流;

访问文件和目录

File类用文件路径字符串来创建File实例,该文件路径字符串既可以是绝对路径也可以是相对路径;
默认使用工作路径来解释相对路径;该路径由“user.dir”指定,通常是运行Java虚拟机时所在的路径

  • 访问文件相关的方法:

String getName():放回该File对象所表示的文件名或路径名
String getPath():返回此File对象的路径名
File getAbsoluteFile():返回此File对象的绝对路径
String getAbsolutePath():返回此File对象的绝对路径名
String getParent():返回此File对象的父目录名
boolean renameTo(File newFileName):重命名此File对象的文件或目录

  • 文件检测相关的方法:

boolean exists()
boolean canWrite()
boolean canRead()
boolean isFile()
boolean isDirectory()
boolean isAbsolute()

  • 获取常规信息:

long lastModified():最后修改时间
long length():内容长度

  • 文件操作相关:

boolean createNewFile():当这个File对象所对应的文件不存在时,创建一个新文件;
boolean delete():删除
static File createTempFile(String prefix, String suffix):在默认的临时文件目录中创建一个临时空文件,使用给定前缀、系统生成的随机数和给定后缀作为文件名;

static File createTempFile(String prefix, String suffix, File directory):在directory所指定的目录中创建一个临时的空文件;

void deleteOnExit():Java虚拟机退出时,删除File对象所对应的文件和目录;

  • 目录操作相关:

boolean mkdir():试图创建一个File对象所对应的目录;
String[] list():列出File对象的所有子文件名和路径名
File[] listFiles():列出File对象的所有子文件和目录
static File[] listRoots():列出系统所有的根路径;

文件过滤器

File类的list()方法可以接受一个FilenameFilter参数;通过这个参数,可以只列出符合条件的文件;
eg:

1
2
3
4
5
6
7
8
9
10
11
12
public class FilenameFilterTest{
public static void main(String args[]){
File file = new File(".");
String[] nameList = file.list((dir, name) -> name.endsWith(".java") || new File(name).isDirectory());
for(String name : nameList){
System.out.println(name);
}
}
}

IO

IO流实现输入输出的基础,可以方便的实现数据的输入输出操作,在Java中把不同的输入输出源都抽象的成为“流”;

通过流的方式允许Java程序使用相同的方法来访问不同的输入输出源头;

stream是从起源到接收的有序数据;

流的分类

输入流和输出流

输入流只能从中读取数据,不能向其写入数据;

输出流只能向其写入数据,不能从中读取数据;

字节流和字符流

两者用法几乎一样,区别在于数据单元的不同;字节流操作的数据单元是8位的字节,而字符流操作的是16位的字符;

字节流主要由InputStream和OutputStream作为基类;而字符流由Reader和Writer作为基类

节点流和处理流

可以从/向一个特定的IO设备读写数据的流,成为节点流;节点流可被称为低级流;

处理流则用于对一个已存在的流进行连接或封装,通过封装后的流来实现数据的读写;处理流也被称为高级流;

流的模型

Java的IO流有40多个类(怪不得难记)
Java号称有最容易使用的IO操作,然而大量的类实在……

Java的IO流由四个抽象类派生:

InputStream/Reader所有输入流的基类

OutputStream/Writer所有输出流的基类

字节流和字符流

InputStream和Reader

是所有输入流的抽象基类;本身不能创建实例来执行输入,但是是所有输入流的模板;

  • InputStream的三个方法:

int read():从输入流中读取单个字节;返回所读取的字节数据;(字节数据可转为int类型)
int read(byte[] b):从输入流中最多读取b.length个字节的数据,存储在字节数组b中,返回字节数;
int read(byte[] b, int off, int len):从输入流中最多读取len个字节的数据,并将其存在b中,在b中从off位置开始存储;返回实际读取的字节数;

  • Reader的三种方法:

int read():从输入流中读取耽搁字符,返回所读取的字符数据(字符数据课转为int类型)
int read(char[] cbuf):从输入流中最多读取cbuf.length个字符的数据,存储在cbuf中,返回实际读取的字符数;
int read(char[] cbuf, int off, int len):从输入流中最多读取len个字符的数据,并将其存在cbuf中,在cbuf中从off位置开始存储;返回实际读取的字节数;

FileInputStream/FileReader是他们读取文件的子类

eg:

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
public calss FileInputStreamTest{
public static void main(String args[]) throws IOException{
FileInputStream fis = new FileInputStream("FileInputStreamTest.java");
byte[] bbuf = byte[1024];
int hasRead = 0;
while((hasRead = fis.read(bbuf)) > 0){
System.out.println(new String(bbuf, 0, hasRead));
}
fis.close();
}
}
//--------------------------------------------------------------
public calss FileReaderTest{
public static void main(String args[]) throws IOException{
try(FileReader fr = new FileReader("FileReaderTest.java")){
char[] cbuf = new char[32];
int hasRead = 0;
while((hasRead = fr.read(cbuf)) > 0){
System.out.println(new String(cbuf, 0, hasRead));
}
}catch(IOException ex){
ex.printStackTrace();
}
}
}
  • InputStream和Reader中移动记录指针的方法:

void mark(int readAheadLimit):在此处记录一个标记
boolean markSupported():判断此输入流是否支持mark操作
void reset():将此流的记录指针重新定位到上一次记录标记的位置
long skip(long n):记录指针向前移动n个字节/字符

OutputStream和Writer

OutputStream和Writer中的常用方法:

void write(int c):
void write(byte[]/char[] buf):

  • Writer中额外增加的方法:
    void write(String str):
    void write(String str, int off, int len):

eg:

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
public class FileOutputStreamTest{
public static void main(String[] args){
try(
FileInputStream fis = new FileInputStream("FileOutputStreamTest.java");
FileOutputStream fos = new FileOutputStream("newFile.txt")){
byte[] bbuf = new byte[32];
int hasRead = 0;
while((hasRead = fis.read(bbuf)) > 0){
fos.write(bbuf, 0, hasRead);
}
}catch(IOException ioe){
ioe.printStackTrace();
}
}
}
//-----------------------------------------------------------
public class FileWriterTest{
public static void main(String[] args){
try(FileWriter fw = new FileWirter("poem.txt")){
fw.write("xxxxxx\r\n");
fw.write("xxxxxxx\r\n");
}catch(IOException ioe){
ioe.printStackTrace();
}
}
}

输入/输出流体系

利用处理流简化以上节点流的用法;

处理流的用法

处理流用于隐藏底层设备上节点流的差异,并对外提供更方便的输入输出方法;让程序员只需关系高级流的操作;

使用处理流时的思路是,使用处理流来包装节点流;程序通过处理流来执行输入/输出的功能,让节点流和底层的IO设备文件交互;

只要流的构造器的参数不是一个五里界店,而是已经存在的流,则该流为处理流;

eg:用PrintStream包装OutputStream;

1
2
3
4
5
6
7
8
9
10
11
12
13
public class PrintStreamTest{
public static void main(String[] args){
try(FileOutputStream fos = new FileOutputStream("test.txt");
PrintStream ps = new PrintStream(fos)){
ps.println("xxxxx");
ps.println(new PrintStreamTest());//直接输出对象
}catch(IOException ios){
ios.printStackTrace();
}
}
}

输入/输出流体系

分类 字节输入流 字节输入流 字符输入流 字符输出流
抽象基类 InputStream OutputStream Reader Writer
访问文件 FileInputStream FileOutputStream FileReader FileWriter
访问数组 ByteArrayInputStream ByteArrayOutputStream CharArrayReader CharArrayWriter
访问管道 PipedInputStream PipedOutputStream PipedReader PipedWriter
访问字符串 StringReader StringWriter
缓冲流 BufferedInputStream BufferedOutputStream BufferedReader BufferedWriter
转换流 InputStreamReader OutputStreamWriter
对象流 ObjectInputStream ObjectOutputStream
抽象基类 FilterInputStream FilterOutputStream FilterReader FilterWriter
打印流 PrintOutputStream PrintWriter
推回输入流 PushbackInputStream PushbackReader
特殊流 DataInputStream DataOutputStream

访问管道的流:实现进程之间的通信功能;

缓冲流,增加缓冲功能提高输入输出效率;需要使用flush()才能将缓冲区的内容写入实际的物理节点

对象流主要用于实现对象的序列化

转换流

用于将字节流转换成字符流;
InputStreamReader将字节输入流转换成字符输入流;
OutputStreamWriter将字节输出流转换成字符输出流;

推回输入流

PushbackInputStream和PushbackReader提供三个方法:

void unread(byte[]/char[] buf):将一个字节/字符数组推回缓冲区里;
void unread(byte[]/char[] b, int off, int len):将一个字节/字符数组从off开始,长度为len字节/字符的内容推回缓冲区中
void unread(int b):将b个字节/字符推回缓冲区中

从而允许重复读取刚刚读取的内容;

重定向标准输入/输出

System类中提供三个重定向标准输入/输出的方法:

static void setErr(PirntStream err)
static void setIn(InputStream in)
static void setOut(OutputStream out)

通过重定向输入流,可以将System.out等输出重定向到文件或其他输出;

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
public class RedirectOut{
public static void main(String[] args){
try(PrintStream ps = new PrintStream(new FileOutputStream("out.txt"))){
System.setOut(ps);
System.out.println("xxx");
System.out.println(new RedirectOut());
}catch(IOException ex){
ex.printStackTrace();
}
}
}
//-----------------------------------------------------
public class RedirectIn{
public static void main(String[] args){
try(FileInputStream fis = new FileInputStream("RedirectIn.java")){
System.setIn(fis);
Scanner sc = new Scanner(System.in);
sc.useDelimiter("\n");
while(sc.hasNext()){
System.out.println("xxxxx" + sc.next());
}
}catch(IOException ex){
ex.printStackTrace();
}
}
}

RandomAccessFile

RandomAccessFile提供了众多的方法来访问文件内容;
既可以读取文件内容,又可以向文件输出数据;
支持“随机访问”,程序可以直接跳转到文件的任意地方读写数据;

RandomAccessFile允许自由定位文件记录指针,RandomAccessFile可以不从开始的地方开始输出;

long getFilePointer():返回文件记录指针的当前位置
void seek(long pos):将文件记录指针定位到pos位置

NIO

JDK1.4开始提供的新的IO(NIO)

NIO的相关包:

java.nio:主要包含各种与Buffer相关的类
java.nio.channels:主要包含与Channel和Selector相关的类
java.nio.charset:主要包含与字符集相关的类
java.nio.channels.spi:主要包含与Channel相关的服务提供者编程接口
java.nio.charset.spi:包含于字符集相关的服务提供者编程接口

  • Channel和Buffer是NIO的亮哥核心;Channel是对传统的输入/输出系统的模拟,在NIO中所有数据都需要通过通道传输;

Channel提供了一个map()方法,该方法可以直接将一块数据映射到内存中

NIO是面向块的处理

  • Buffer是一个容器,本质为一个数组;发送到Channel中的所有对象都必须先放在Buffer中,从Channel中读取的数据也要先放在Buffer中;

Channel可以直接将文件的某个数据应射程Buffer

  • Charset类用于将Unicode字符串应射程字节序列以及逆映射

  • Selector类支持非阻塞式输入/输出

Buffer

ByteBuffer、CharBuffer、ShortBuffer、IntBuffer、LongBuffer、FloatBuffer、DoubleBuffer;

以上Buffer类,除了ByteBuffer外,都采用相同或类似的方法来管理数据;

  • 获取Buffer对象的方法:

static XxxBuffer allocate(int capacity):创建一个容量为capacity的XxxBuffer对象;

ByteBuffer的子类:MappedByteBuffer:表示Channel将磁盘文件的部分或全部映射到内存后得到的结果,通常由Channel的map()方法返回;

  • 三个重要概念:capacity、limit、position

capacity:缓冲区的容量;表示该Buffer的最大数据容量;
limit:第一个不应该被读出或写入的缓冲区位置索引;
position:下一个可以被读出或写入的缓冲区位置索引;

Buffer的主要作用就是装入数据,然后输出数据,开始时,position为0,limit为capacity,程序可通过put()方法向Buffer放入一些数据(或从Channel中获取一些数据),没放入一些数据,Buffer的position相应地向后移动一些位置;

在Buffer装入数据结束后,调用Buffer的flip()方法,该方法将limit设置为position所在的位置,并将position设为0,这就使得Buffer的读写指针又移到了开始位置。也就是说,Buffer调用flip()方法之后,Buffer为输出数据做好准备;当Buffer输出数据结束后,Buffer调用clear()方法,clear()方法不是清空Buffer的数据,仅仅将position置为0,将limit置为capacity,为再次向Buffer中装入数据做好准备;

  • Buffer的一些方法:

int capacity():返回Buffer的capacity大小
boolean hasRemaining():判断当前位置和界限之间是否还有元素可供处理
int limit():返回Buffer的limit
Buffer limit(int newLt):重新设置一个limit值,并返回一个具有新的limit值的Buffer对象
Buffer mark():设置Buffer的mark位置
int position():返回Buffer的position值
Buffer position(int newLt):设置Buffer的position,并返回修改后的Buffer对象
int remaining():返回当前位置和limit之间的元素个数
Buffer reset():将position转到mark所在的位置
Buffer rewind():将position设置为0

eg:

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
public class BufferTest{
public static void main(String[] args){
CharBuffer buff = CharBuffer.allocate(8);
System.out.println(buff.capacity()); //8
System.out.println(buff.limit()); //8
System.out.println(buff.position()); //0
buff.put('a');
buff.put('b');
buff.put('c');
System.out.println(buff.position()); //3
buff.flip();
System.out.println(buff.limit()); //3
System.out.println(buff.position()); //0
System.out.println(buff.get()); //a
System.out.println(buff.position()); //1
buff.clear();
System.out.println(buff.limit()); //8
System.out.println(buff.position()); //0
System.out.println(buff.get(2)); //c
System.out.println(buff.position()); //0
}
}

Channel

类似于传荣的流对象;

区别:

Channel可以直接将制定文件的部分或全部直接映射成Buffer;
程序不能直接访问Channel中的数据,包括读写都不行,Channel只能与Buffer进行交互;

要从Channel中取得数据,必须先用Buffer从Channel中去除一些数据,然后让程序从Buffer中却出这些数据;相反,要将程序中的数据写入Channel,要先让程序将数据放入Buffer中,程序再将Buffer里的数据写入Channel中;

**Java为Channel接口提供了DatagramChannel、FileChannel、PipeSinkChannel、PipeSourceChannel、SelectableChannel、ServerSocketChannel、SocketChannel等实现类;

Channel通过InputStream、OutputStream的getChannel()方法来返回对应的Channel;

不同的节点流获得的Channel不一样;

Channel最常用的方法是:map()、read()、write()

map()方法用于将Channel对应的部分或全部数据映射成ByteBuffer;
read()和write()方法从Buffer中读取数据或向Buffer中写入数据;

MappedByteBuffer map(FileChannel.MapMode mode, long position, long size)mode为映射时的模式,分别有只读、读写等

eg:以FileChannel为例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class FileChannelTest{
public static void main(String[] args){
File f = new File("FileChannelTest.java");
try(
FileChannel inChannel = new FileInputStream(f).getChannel();
FileChannel outChannel = new FileOutputStream("a.txt").getChannel()){
MappedByteBuffer buffer = inChannel.map(FileChannel.MapMode.READ_ONLY, 0, f.length());
Charset charset = Charset.forName("GBK");
outChannel.write(buffer);
buffer.clear();
CharsetDecoder decoder = charset.newDecoder();
CharBuffer charBuffer = decoder.decode(buffer);
System.out.println(charBuffer);
}catch(IOException ex){
ex.printStackTrace();
}
}
}

eg:以RandomAccessFile为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class RandomFileChannelTest{
public static void main(String[] args){
File f = new File("a.txt");
try(
RandomAccessFile raf = new RandomAccessFile(f, "rw");
FileChannel randomChannel = raf.getChannel()){
ByteBuffer buffer = randomChannel.map(File.Channel.MapMode.READ_ONLY, 0, f.length());
randomChannel.position(f.length());
randomChannel.write(buffer);
}catch(IOException ex){
ex.printStackTrace();
}
}
}

eg:多次读取:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class ReadFile{
public static void main(String[] args) throws IOException{
try(
FileInputStream fis = new FileInputStream("ReadFile.java");
FileChannel fcin = fis.getChannel()){
ByteBuffer bbuff = ByteBuffer.allocate(256);
while(fcin.read(bbuff) != -1){
bbuf.filp();
Charset charset = Charset.forName("GBK");
CharsetDecoder decoder = charset.newDecoder();
CharBuffer cbuff = decoder.decode(bbuff);
System.out.println(cbuff);
bbuff.clear()
}
}
}
}

字符集和Charset

Encode和Decode;
编码和解码;

Java默认使用Unicode字符集;

JDK1.4提供的Charset类提供了处理字节序列和字符序列的转换关系;

Charset的availableCharsets()静态方法用于获取当前JDK支持的所有字符集;

eg:

1
2
3
4
5
6
7
8
9
10
public class CharsetTest{
public static void main(String[] args){
SortedMap<String, Charset> map = Charset.availableCharsets();
for(String alias : map.keySet()){
System.out.println(alias + "------>" + map.get(alias));
}
}
}

获得Charset对象之后,就可以通过该对象的newDecoder()、newEncoder()方法返回CharsetDecoder和CharsetEncoder对象,代表Charset的解码器和编码器;
调用CharsetDecoder的decode()方法可以将ByteBuffer转换成CharBuffer;调用CharsetEncoder的encode()可以将CharBuffer或String转换成ByteBuffer;

1
2
Charset cs = Charset.forName("ISO-8859-1");
Charset csCn = Charset.forName("GBK");

eg:

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
public class CharsetTransform{
public static void main(String[] args){
Charset cn = Charset.forName("GBK");
CharsetEncoder cnEncoder = cn.newEncoder();
CharsetDecoder cnDecoder = cn.newDecoder();
CharBuffer cbuff = CharBuffer.allocate(8);
cbuff.put("孙");
cbuff.put("孙");
cbuff.put("孙");
cbuff.flip();
ByteBuffer bbuff = cnEncoder.encoder(cbuff);
for(int i = ; i < bbuff.capacity(); i++){
System.out.println(bbuff.get(i) + " ");
}
System.out.println(cnDecoder.decode(bbuff));
}
}

CharBuffer decode(ByteBuffer bb)
ByteBuffer encode(CharBuffer cb)
ByteBuffer encode(String str)

文件锁

在多个运行程序需要并发修改同个文件时,程序之间使用文件锁可以有效的阻止多个进程并发修改同一个文件;

JDK1.4的NIO开始支持文件锁;

在FileChannel中提供lock()/tryLock()方法可以获得文件锁FileLock对象,从而锁定文件

  • lock()/tryLock()的区别:lock()试图锁定某个文件时,如果无法获得文件锁,程序将陷入阻塞;tryLock()若无法锁定文件,将返回null;

lock(long position, long size, boolean shared)
tryLock(long position, long size, boolean shared)

eg:

1
2
3
4
5
6
7
8
9
10
11
public class FileLockTest{
public static void main(String[] args){
try(FileChannel channel = new FileOutputStream("a.txt").getChannel()){
FileLock lock = channel.tryLock();
Thread.sleep(10000);
lock.release();
}
}
}

Java7的NIO2

提供了全面的文件IO和文件系统访问支持
基于异步Channel的IO

Path、Paths、Files

Path代表一个平台无关的铭泰路径;
Files包含了大量静态工具方法来操作文件;
Paths包含了两个返回Path的静态方法

eg:Paths

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class PathTest{
public static void main(String[] args){
Path path = Paths.get(".");
System.out.println(path.getNameCount());
Path absolutePath = path.toAbsolutePath();
System.out.println(absolutePath.getRoot());
System.out.pritnln(absolutePath.getNameCount());
System.out.println(absolutePath.getName(3));
Path path2 = Paths.get("g:" , "publish", "coder");
}
}

eg:Files

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
public class FilesTest{
public static void main(String[] args) throws Exception{
Files.copy(Paths.get("FilesTest.java"), new FileOutputStream("a.txt"));
System.out.println(Files.isHidden(Paths.get(FilesTest.java)));
List<String> lines = Files.readAllLines(Paths.get("FilesTest.java"), Charset.forName("gbk"));
System.out.println(lines);
System.out.println(Files.size(Paths.get("FilesTest.java")));
List<String> poem = new ArrayList<>();
poem.add("xxxxx");
poem.add("aaaaa");
Filse.write(Paths.get("poem.txt"), poem, Charset.forName("gbk"));
Flies.lines(Paths.get("FilesTest.java"), Charset.forName("gbk")).forEach(line -> System.out.println(line));
FileStore cStore = Files.getFileStore(Paths.get("C:"));
cStore.getTotalSpace();
cStore.getUsableSpsace();
}
}

使用FileVisitor遍历文件和目录

Files提供亮哥方法遍历文件和子目录:

walkFileTree(Path start, FileVisitor<? super Path> visitor):遍历start路径下的所有文件和子目录
walkFileTree(Path start, Set options, int maxDepth, FileVisitor<? super Path> visitor):最多遍历maxDepth深度的文件;


FileVistitor是一个文件访问器;

FileVisitor定义了四个方法:

FileVisitResult postVisitDirectory(T dir, IOException exc):访问子目录后触发
FileVisitResult preVisitDirectory(T dir, BasicFileAttributes attrs):访问子目录前触发
FileVisitResult visitFile(T file, BasicFileAttributes attrs):访问file文件时触发
FileVisitResult postVisitDirectory(T file, IOException exc):访问file文件失败时触发

FileVisitResult的后续行为:

CONTINUE代表“继续访问”的后续行为
SKIP_SIBLINGS代表“继续访问”的后续行为,但不访问该文件或目录的兄弟文件或目录
SKIP_SUBTREE代表“继续访问”的后续行为,但不访问该文件或目录的子目录树
TERMINATE代表“终止访问”的后续行为;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class FileVisitor{
public static void main(String[] args){
Files.walkFileTree(Paths.get("g:", "publsh", "codes", "15")
, new SimpleFileVisitor<Path>(){
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException{
...
}
...
});
}
}