概述

java.io.InputStream代表字节输入流,java.io.OutputStream代表输出流。他们都是抽象类,InputStream中有以下方法:

  • int read():读入一个字节,并且转化成unsigned int型整数
  • int read(byte[] b): 从输入流中读取若干字节,保存在b数组中。如果到了输入流的结尾,返回1
  • int read(byte[] b, int off, int len): 如上,这里只是多了在b数组中的开始位置和读取数目
  • void close(): 关闭输入流。如果不关闭的话其他需要这个文件的就无法读取这个文件信息。类似于打开了一个应用程序再打开就说有一个实例正在运行。
  • int available(): 放回从输入流中读取字节数目
  • skip(long n): 从输入流中跳过n个字节
  • boolean markSupported(),void mark(int readLimit), void reset(): 如果想要重复读入数据,就先用markSupported()判断这个流是否支持重复读入数据,如果支持,通过mark在当前位置开始设置readLimit字节的标记,然后用reset()可以使输入流定位到做标记的起始位置,然后通过read()就可以重复读数据了。

OutputStream中方法:

  • void write(): 输出一个字节
  • void write(byte[] b): 同上
  • void write(byte[] b, int off, int len): 同上
  • void close()
  • void flush(): OutputStream本身的flush()不执行操作。但是如果是一些有缓冲区机制的实现类就有用了。在有缓冲区的输出流中,一般都是先把输出存在缓冲区中,等到了一定大小才会输出,这个方法的作用是强制输出缓冲区中数据

设置缓冲区的原因是输出并不是直接输出到屏幕中间要经历一系列的过程,耗时比较长,如果先存到缓冲区然后一次性输出就可以减少时间。

输入流

ByteArrayInputStream,字节数组输入流

这个类从字节数组读取数据,可能会想直接用字符数组不就行了吗,为什么还要弄一个输入流类。ByteArrayInputStream实际上是一个适配器

构造方法:

ByteArrayInputStream(byte[] buf)

ByteArrayInputStream(byte[] buf, int offset, int length)

使用就是用上面的方法

FileInputStream文件输入流

构造方法:

FileInputStream(File file)

FileInputStream(String name):通过name指定路径

例如:

public class FileInputStreamTester
{
public static void main(String[] args)throws IOException
{
FileInputStream in = new FileInputStream("D:\\test.txt");
int data;
while((data=in.read()) != -1)
{
System.out.print(data+" ");
}
in.close();
}
}

如果文件很大,为了提高读取效率,可以利用一个缓冲区。例如:

public class UseBuffer
{
public static void main(String[] args)throws IOException
{
final int SIZE = 1024;
FileInputStream in = new FileInputStream("D:\\test.txt");
FileOutputStream out = new FIleOutputStream("D:\\test.txt");

byte[] buff = new byte[SIZE];
int len = in.read(buff);
while(len != -1)
{
out.write(buff, 0, len);
len = in.read(buff);
}
in.close();
out.close();
}
}

如果要打开的文件和这个类在同一文件夹下,可以用Class类的个体ResourceAsStream()方法,这时可以用相对路径。例如:

InputStream in = UseBuffer.getClass().getResourceAsStream("test.txt");

PipedInputStream管道输入流

管道输入流是从管道输出流中读取数据的。一般是一个线程从管道中输出,然后另外一个接受。使用管道的优点是如果管道中没有数据就会阻塞,有数据才会恢复运行,这样就可以对产生的数据进行处理。

例:

public Sender extends Thread
{
private PipedOnputStream out = new PipedOutputStream();
public PipedOutputStream getPipedOutputStream()
{
return out;
}

public void run()
{
try
{
for(int i=-127; i<=128; i++)
{
out.write(i);
yield();//让步给sender处理
}
out.close();
}
catch(Exception e)
{
throw new RuntimeException(E);
}
}
}
public class Receiver extends Thread
{
private PipedInputStream in;
public Receiver(Sender sender)throw IOException
{
//通过这个构造方法确定了两者的关系
in = new PipedInputStream(sender.getPepedOutputStream);
}
public void run()
{
int data;
while((data=in.read()) != -1)
{
System.out.println(data):
}
in.close();
}
}

SequenceInputStream 顺序输入流

它可以把几个输入流混合到一起输入。

构造方法:

SequenceInputStream(Enumeration e): e是枚举类型,包含若干个输入流

SequenceInputStream(InputStream s1, InputStream s2):只合并两个

它关闭的时候只需要关闭SequenceInputStream就会一次关闭所有的输入流。

装饰器设计模式

假设有一个类要子类实现三种方法,并且有的子类只需要实现一个,有的要实现多个,那么就要2^3-1个子类,数量过多。我们可以采用装饰器思想减少子类数目。

装饰器也是一个子类。这个子类的特殊之处在于他实现了某一个方法并且它内部封装了父类的实例。它的构造方法是:decorate(Base base)

例如:

B extends A

B b = new B();
decorate1 dec1 = new decorate1(b);
dec1.method1();
decorate2 dec2 = new decorate2(b);
dec2.method2();

FilterInputStream 过滤输入流

过滤输入流其实就是一个装饰器

种类

过滤输入流 描述
DataInputStream 与DataOutputStream搭配使用,按照和平台无关的方式从流中读取基本类型(int,char,long等)
BufferedInputStream 利用缓冲区提高效率
PushbackInputStream 把督导的一个字节压回缓冲区中,编译器用

这些都是FileterInputStream的子类,并且也是装饰器。

DataInputStream类

它的不同方法可以按不同编码读取数据,并且都是以read开头:

  • readByte()
  • readLong()
  • readFloat()
  • readUTF(): 从输入流中读取若干字节,并转化成UTF-8编码字符串

UTF-8如果是ascii就只用一个字节,如果是其他字符就用两个或两个以上字节。

DataInputStream应该和DataOutputStream配套使用。只有配套使用才会保证数据的正确性(因为这里的UTF-8是java本土化的UTF-8,本来的UTF-8好像是稳定3个字节的)。

public class A
{
public static void main(String[] args)
{
FileOutputStream out1 = new FileOutputStream();
BufferedOutputStream out2 = new BufferedOutputStream(out1);
DataOutputStream out = new DataOutputStream(out2);
...
}
}

BufferedInputStream

BufferedInputStream覆盖了读数据的行为,它利用缓冲区提高读取的效率。

构造方法:

  • BufferedInputStream(InputStream in)
  • BufferedInputStream(InputStream in, int size):size指定缓冲区大小

当数据源是文件时,可以用BufferedInputStream装饰数据流,然后再进行其他操作可以提高效率。

PushbackInputStream

它有一个后推缓冲区,用于存放已经读入的字节。

输出流

输出流种类和输入流大致类似,sequenceInputStream对应的没有了,多了ObjectOutputStream。

ByteArrayOutputStream是把信息输出到字节数组中。例如:

public class test
{
public static void main(String[] args)
{
ByteArrayOutputStream out = new ByteArrayOutputStream();
out.write("你好".getBytes("UTF-8");//String的getBytes可以放回制定类型的编码
byte[] buff = out.toByteArray();//获得字符数组的方法
out.close();
}
}

先用write把要输入的数据输入到输出流中,然后通过toByteArray方法输出到字节数组中。

文件输出流

前面大致类似,多了一种构造方法FileOutputStream(String name, boolean append)

其中append是为了确定是不是要在末尾追加数据。

FilterOutputStream 过滤输出流

同样有DataOutputStream和BufferedOutputStream,大致和输入流类似,多了一种PrintStream类

PrintStream

PrintStream和DataOutputStream类似,都可以输出格式化数据。他有如下方法。

  • print(int i): 输出一个int
  • print(float i):
  • print(String i):
  • println(int i): 输出int型数据和换行符
  • println(float f):
  • println(String s):

前面提到过,DataOutputStream和DataInputStream最好匹配使用是因为他们使用了特殊化的UTF-8编码。而PrintStream使用的是普通的UTF-8编码。

PrintStream的print()没有抛出IOException,但是他有checkError()判断写数据是否成功,如果返回true,则代表出现了错误。

PrintStream自带缓冲区。但是这和BufferedInputStream提供的缓冲区还有所不同。后者只有缓冲区满的时候才会输出,前者可以由用户决定数据量多少的时候输出。当然,默认还是满的时候输出。PrintStream还提供了一个自动化的输出方案:

  • PrintStream(OutputStream out, boolean autoFlash)。当满足以下情况就会自动输出
    • 输出一个字节数组
    • 输出一个换行符,即执行print(“\n”)或println()

      Reader/Writer

InputStream和OutputStream处理的是字节,但是在很多场合要处理的是字符(java中字符时2字节)。Reader和Writer就是处理这些的。

java中字符时Unicode编码,但是文本文件中不一定是Unidcode编码,还有可能是UTF-8,GBK甚至ascii,因此如何处理不同类型编码就是一个难点。

String的getBytes(String encode)返回特定类型的编码,encode参数指定编码类型。如果不带参数就使用本地操作系统默认编码。

获得本地编码
System.out.println(System.getProperty("file.encoding"));
或:
Charset cs = Charset.defaultCharset();
System.out.println(cs);

Reader类可以把其他类型的编码转换成java所使用的编码。Writer可以把Unicode转换成其他类型的编码。

Reader

Reader和InputStream类的种类大致类似.

类型 描述
CharArrayReader 把字符数组转换成Reader,从字符数组中读取字符
BufferedReader 装饰器,提供缓冲区。同时他的readLine()方法还可以读入一个字符串
LineNumberReader 提供缓冲区。并且可以跟踪字符输入流中中的行号
StringReader 把字符串转成Reader(数据源是字符串,和CharArrayReader类似),从字符串中读字符
PipedReader 连接PipedWriter
FilterReader 扩展其他Reader功能
InputStreamReader 把InputStream转换成Reader,可以指定数据源编码
FileReader

InputStreamReader

构造方法:

  • InputStreamReader(InputStream in): 按照本地的字符编码解读输入流中的字符
  • InputStreamReader(InputStream in, String charsetName): 按照charsetName指定的方式读取输入流中的字符

这里的read读出来的不是一个字节而是一个字符

一些常用的方法:

  • readLine(),一次读入一行
  • readFile(String fileName, String charsetName):从文件中读取字符串,并输出到控制台中
  • copyFile(String from, String charsetFrom, String to, String charsetTo): 把原文件复制到目标文件中,可以指定文件编码

Writer

Writer和OutputStream大致类似,也有PrintWriter,区别是PrintStream只能用系统本地编码,而PrintWriter可以使用任意编码。PrintWriter构造方法:

  • PrintWriter(Writer writer, boolean autoFlush)
  • PrintWriter(OutputStream out, boolean autoFlush)

标准I/O

在System类中,有三个静态变量:

  • System.in: 代表标准输入流。默认输入时键盘
  • System.out: 是PrintStream类型(所以方法和PrintStream一样)。
  • System.err: 代表错误输出流,默认输出时输出到控制台。

对标准输入输出包装

可以利用到前面所学的只是对标准输入输出进行包装。System.in是InputStream类型,可以先用InputStreamReader变成Reader,然后在用BufferedReader装饰。

InputStreamReader reader = new InputStreamReader(System.in);
BufferedReader br = new BufferedReader(reader);
...

重定向

重定向方法:

  • setIn(InputStream, in): 对标准输入重定向
  • setOut(printStream out):
  • setErr(PrintStream out):

这些方法时System的静态方法,所以写的时候是System.setIn()

public static void redirect(InputStream in, PrintStream out, PrintStream err)
{
System.setIn(in);
System.setOut(out);
System.setErr(err);
}

public static void main(String[] args)throws IOException
{
InputStream stdin = System.in;
PrintStream stdout = System.out;
PrintStream stderr = System.err;
InputStream in = new BufferedInputStream("D:\\temp.txt");
PrintStream out = new BufferedOutputStream("D:\\tempout.txt");
redirect(in, out, stderr);
...
}

RandomAccessFile 随机访问文件

随机访问文件就是可以从文件任意位置读写数据,他有如下定位方法:

  • getFilePointer(): 返回当前位置
  • seeek(long pos): 设置位置,与未见开头相距pos
  • skipBytes(int n): 从当前开始跳过n个字节
  • length(): 返回文件包含的字节数

RandomAccessFile实现了DataInput和DataOutput接口,可以读取格式化数据;

  • RandomAccessFile(File file, String mode)
  • RandomAccessFile(String name, String mode):name指定路径

其中mode是访问模式,可以有”r”和”rw”。表示只读和读写,但是”w”是非法的。

新 I/O库

这些类位于java.nio包中,nio是newio。他映入了四个数据类型

  • Buffer: 缓冲区
  • Charset: 把Unicode和其他类型相互转换
  • Channel: 数据传送通道,把Buffer内容输出或读入到Buffer
  • Selector: 支持异步I/O操作,也叫非阻塞I/O操作

Buffer

缓冲区有两个作用:

  • 减少读取次数
  • 高速缓存有关,这一段内存一直被重用。

层次:

他有以下属性:

  • 容量
  • 极限: 表示当前所使用缓冲区大小.极限可以修改
  • 位置: 表示下一个读写单元位置

他有如下设置属性方法:

  • clear(): 把极限设置成容量,并且把位置变成0
  • flip(): 把极限设置成位置,然后把位置变成0
  • rewind(): 不改变极限,把位置变成0

Buffer类是一个抽象类,他有8个具体类。最基本的是ByteBuffer类,他没有公开构造方法,但是有静态工厂。

  • allocate(int capacity):
  • directAllocate(int capacity): 返回一个直接缓冲区。直接缓冲区速度较快,但是分配所需时间较多,所以一般只在所需空间较大并且长期使用的情况下才会用它。

除了boolean类型之外,其他类型都有缓冲区(感觉和c的allocate有点类似),例如LongBuffer。此外,还有一种MappedByteBuffer,这是ByteBuffer的子类。它可以把缓冲区和文件某个区域直接映射(输出)。

共用方法:

  • get(): 从当前位置读一个单元,然后位置加*ex位置读一个单元
  • put(): 向当前位置写入一个数据,然后位置加1
  • put(int index):

Channel

Channel用来连接缓冲区和数据源。它是一个接口,有两个方法:

  • close(): 关闭通道
  • isOpen(): 判断通道是否打开

通道会在创建时被打开,一旦被关闭就不能再次打开。

子接口ReadableByteChannel声明了read(ByteBuffer dst),把数据源数据读入到缓冲区中。WritableByteChannel声明了write(ByteBuffer src),这个把src缓冲区中的数据输出。

ByteChannel扩展了上面说的两个接口,可以同时读写

ScatteringByteChannel扩展了ReadableByteChannel,可以分散读取数据。分散读取是指可以一次把数据放到多个缓冲区中。

GatheringByteChannel接口扩展了WritableByteChannel,可以把多个缓冲区中的数据一次性输出。他的wirte(ByteBuffer[] srcs)用来输出数据。

这些方法都是缓冲区没满就继续读入

FileChannel是Channel的实现类,他实现了ByteChannel,ScatteringByteChannel,GatheringByteChannel接口。支持上面所有操作。但是他没有公开构造方法,但是FileInputStream,FileOutputStream,RandomAccessFile类中提供了getChannel()方法,返回相应的FileChannel对象。

Charset

Charset类每个实例代表特定的字符编码类型。他有以下用于编码转换的方法:

  • ByteBuffer encode(String str): 把str转换成当前编码
  • ByteBuffer encode(CharBuffer cb): 把cb指定的字符缓冲区变成当前编码
  • CharBuffer decode(ByteBuffer bb): 把bb指定的ByteBuffer变成Unicode编码

Charset有一个defaultCharset(): 返回代表本地平台编码的Charset对象。

FileChannel读写文件

public static void main(String[] args)
{
final int BSIZE = 1024;
FileChannel fc = new FileOutputStream("D:\\temp.txt").getChannel();
fc.write(ByteBuffer.wrap("你好".getBytes());
fc.close();
//向文件末尾添加
fc = new RandomAccessFile("D:\\temp.txt").getChannel();
fc.position(fc.size());//定位到文件末尾
fc.write(ByteBuffer.wrap("朋友".getBytes());
fc.close();

fc = new FileInputStream("D:\\temp.txt").getChannel();
ByteBuffer buff = ByteBuffer.allocate(BSIZE);
fc.read(buff);
buff.flip();
Charset cs = Charset.defaultCharset();
System.out.println(cs.decode(buff));
fc.close();
}

应用

控制缓冲区

前面说的clear(),flip,rewind看似没用,但是它可以方便我们读入输出数据

ByteBuffer buff = ByteBuffer.allocate(BSIZE);
while(in.read(buff) != -1)
{
buff.flip();
out.write(buff);
buff.clear();
}

这段代码前面的flip是为了保证只操作当前数据,而clear()是为了保证接受尽可能多的数据。

字符编码转换

CharBuffer存放的数据单元室Unicode字符,ByteBuffer中的asCharBuffer()可以把Byte中数据转换成Unicode字符,并且存放在CharBuffer中。

缓冲区视图

ByteBuffer类提供了asCharBuffer(),asIntBuffer(),和asFloatBuffer()来生成视图。通过视图,可以读取或写入各种类型的数据。

public static void main(String[] args)throws IOException
{
ByteBuffer bb = ByteBuffer.allocate(4);
while(bb.hasRemaining())
{
System.out.println(bb.get());
}
bb.rewind();
CharBuffer cb = bb.asCharBuffer();
cb.put("你好");
whlie(bb.hasremaining())
{
System.out.println(bb.get());
}
}

MappedByteBuffer

用于创建和修改那些因为太大而不能放入内存的文件。

FIleChannel类提供了获得MappedByteBuffer的map方法:

  • MappedByteBuffer map(FIleChannel.MapMode mode, long position, long size)

position是文件映射起始位置,size是映射区域大小,mode是模式,有三种:

  • MapMode.READ_ONLY
  • READ_WRITE
  • MapMode.PRIVATE: 对MappedByteBuffer的修改不会保存到文件中,且其他程序不可见。

例:

public static void main(String[] args)
{
int capacity = 0x8000000;
MappedByteBuffer mb = new RandomAccessFile("D:\\test.txt","rw").getChannel()
.map(FileChannel.MapMode.READ_WRITE, 0, capacity);
mb.flip();
System.out.println(Charset.forName("GBK").decode(mb));
}

文件加锁

他允许程序同步访问作为共享资源的文件,但是可能发生同一时间多个线程同时访问的情况,甚至还有可能要和其他进程竞争。所以java中的文件锁是其他线程可见的。

FileChannel的tryLock()或Lock()用于锁定文件,如果成功放回FileLock对象,如果不成功就立刻返回null。lock()是阻塞式的,如果没有获得线程就会进入阻塞状态。

也可以部分加锁

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

上面的shared如果为true表示共享锁,如果是false是排他锁。

  • 共享锁,如果一个线程获得了共享锁,那么其他线程还可以获得共享锁,但是不能获得排他锁
  • 排他锁,如果一个县城获得了排他锁,那么其他线程不可以获得共享锁或排他锁。

可以用FileLock的isShared()判断锁的类型,如果是true,则是共享锁。release()用于释放文件锁。

自动释放资源

因为和c++中的delete一样,经常会忘了close(),所以从JDK7开始,绝大多数I/O类都实现了AutoCloseable接口。他会在一定条件下自动关闭:

  • 定义在try块中,退出try块时会自动调用close()(无论是正常出去还是非正常退出)。

用File来查看,创建,删除文件目录

File表示真实系统中的一个文件,他有如下构造方法:

  • File(String pathname):
  • File(String parent, String child):parent表示根路径,child表示子路径。
  • File(File parent, String child)

一般来说,如果只要处理一个文件,那么使用第一种构造方法,否则就用后面几种。

还提供管理文件方法:

  • boolean canRead().测试程序是否能对进行读操作
  • boolean canWrite()
  • boolean delete():删除文件,如果删除的是目录并且目录中有东西就不能删除
  • boolean exists():看这个文件时候存在
  • String getAbsolutePath():获取文件绝对目录
  • String getChanonicalPath(): 获取真正的路径,没有...
  • String getName()
  • String getParent()
  • String getPath(): 相对目录
  • String[] list(): 返回当前目录下所有文件列表
  • File[] listFiles().返回目录下的所有文件和目录的File对象
  • boolean mkdir(): 创建目录
  • boolean createNewFile(): 如果FIle表示文件且在当前目录下不存在,就创建

操作目录树

Files类: 有移动文件的move(),复制文件的copy(),搜索目录树的find().此外newDirectoryStream()回创建一个目录流,程序可以通过这个目录流遍历整个目录,用walkFileTree()遍历。

Path接口: 表示一个路径。

Paths类: 提供创建Path的静态方法,他的get(String first, String… more)返回一个Path对象,这个对象以first为根路径,以more为子路径.例如:Paths.get("/root", "dir1", "dir2")返回路径/root/dir1/dir2.

查看zip

可以通过FileSystems的newFileSystem()创建表示zip文件的FileSystem对象。然后可以用walkFileTree()遍历zip中所有文件。

其中walkFileTree可以查看官方文档。