黑马程序员-- 十、IO

来源:互联网 发布:西工大编程 编辑:程序博客网 时间:2024/05/21 08:49
---------------------- ASP.Net+Android+IOS开发、.Net培训、期待与您交流! ----------------------

一、 概述

Java对数据的操作是通过流的方式,IO流用来处理设备之间的数据传输。

IO流按操作数据类型可分为:字节流和字符流。

按流向可分为:输入流和输出流。

按流的角色分为:节点流和处理流。可以从一个特定的IO设备读写的数据流,称为节点流,节点流也被称为低级流;处理流则用于对一个已经存在的流进行封装或链接,通过封装后的流来实现数据读写功能。处理流也被称为高级流。

字节流的抽象基类有:InputStreamOutputStream

字符流的抽象基类有:ReaderWriter

Tips

Java使用处理流来包装节点流是一种典型的装饰者设计模式,通过使用处理流来包装不同的节点流,可以消除不同节点流的实现差异,也可以提高更方便的方法来完成输入输出功能。因此处理流也被称为包装流。

处理流的功能主要体现在以下两个方面:

——性能的提高:主要以增加缓冲的方式来提高输入输出效率。

——操作的便捷:处理流可能提供了一系列便捷的方法来一次输入\输出大批量的内容,而不是每次只操作一点点内容。

二、字节流和字符流

字节流和字符流的操作方式几乎完全一样,区别只是操作的数据单元不同而已——字节流操作的数据单元是字节,字符流操作的是字符。

1InputStreamReader

InputStream里有如下三个方法:

——int read():从输入流中读取单个字节,返回读取的字节数据(字节数据可直接转换为int类型)。

——int read(byte[] b):从输入流中最多读取b.length个字节数据,并将其存储在字节数组b中,返回实际读取的字节数。

——int read(byte[] b, int off,int len):从输入流中读取最多len个字节数,并将其存储在数组b中,放入数组b时,从off位置开始放,返回实际读取的字节数。

InputStream对应,Reader里包含如下三个方法:

int read()int read(char[] cbuf)int read(char[] cbuf, int off, int len),它们的使用方式与InputStream一样,只是操作的数据是以字符为单位。

InputStreamReader都是抽象类,它们各有一个用于读取文件的实现类:FileInputStreamFileReader,他们都是节点流,会直接和指定文件关联。

下面的程序示范了如何使用FileInputStream来读取自身的效果:

import java.io.*;

 

public class FileInputStreamTest {

public static void main(String[] args) throws IOException

{

//第一步:关联源文件

FileInputStream fis = new FileInputStream

("H:\\WorkSpace\\StudyNote\\src\\io\\FileInputStreamTest.java");

//第二步:创建一个字节数组,用来读取数据

byte[] bbuf = new byte[1024];

//定义一个int型变量,用来存储实际读取到的字节

int hashread;

//第三步:读取文件数据

while ((hashread = fis.read(bbuf)) > 0)

{

System.out.println(new String(bbuf, 0, hashread));

}

//第四步:关闭流对象,放在finally块中更安全

fis.close();

}

}

Tips

1、 上面创建了一个长度为1024的字节数组来读取该文件,实际Java源文件的长度不足1024字节,也就是说,程序只需执行一次read方法即可读取全部内容。但如果创建了较小的字节数组,程序在输出中文时就可能显示乱码——这是因为本文件保存时采用的是GBK编码方式,每个字符占两个字节,如果read()方法读取时只读取到了半个字节,将导致乱码。

2、 Java7以前,程序需手动关闭流对象,释放系统资源,Java7改写了所有IO资源类,它们都实现了AutoCloseable接口,因此都可通过自动关闭资源的try语句来关闭这些IO流。

3、 java7实现的AutoCloseable接口关闭流对象,好像不会在关闭之前刷新流。——待验证

下面程序使用FileReader来读取文件本身:

import java.io.*;

 

public class FileReaderTest {

public static void main(String[] args) throws IOException

{

try{

//第一步:关联源文件

FileReader fr = new FileReader

("H:\\WorkSpace\\StudyNote\\src\\io\\FileInputStreamTest.java");

//第二步:创建一个字符数组,用来读取数据

char[] cbuf = new char[32];

//定义一个int型变量,用来存储实际读取到的字符

int hashread;

//第三步:读取文件数据

while ((hashread = fr.read(cbuf)) > 0)

{

System.out.println(new String(cbuf, 0, hashread));

}

}

catch(IOException e)

{

e.printStackTrace();

}

}

}

除此之外,InputStreamReader还支持如下几个方法来移动记录指针:

——void mark(int readAheadLimit)在记录指针当前位置记录一个标记(mark

——boolean markSupported()判断此输入流是否支持mark操作。

——void reset()将此流的记录指针位置重新定位到上一次记录标记(mark)位置。

——long skip(long n)记录指针向前移动n个字节/字符。

2OutputStreamWriter

这两个流都提供了如下三种方法:

——void write(int c)将指定的字节/字符输出到输出流中,其中c既可以为字节又可以为字符。

——void write(byte[]/char[] buf)将指定字节数组/字符数组中的数据输出到指定输出流中。

——void write(byte[]/char[] buf, int off, int len)将字节数组/字符数组中从off位置开始,长度为len的字节/字符输出到输出流中。

此外,字符流直接以字符作为操作单位,所以Writer可以用字符串来代替字符数组,即以String对象作为参数。Writer里还包含了如下两个方法:

——void write(String str):将str字符串里包含的字符串输出到输出流中。

——void write(String str, int off, int len):将str字符串里从off位置开始,长度为len的字符串输出到输出流中。

下面程序使用FileInputStream来执行输入,使用FileOutputStream来执行输出,用以实现文件复制功能:

import java.io.*;

 

public class FileOutputStreamTest {

public static void main(String[] args)

{

try{

//创建字节输入流

FileInputStream fis = new FileInputStream("H:\\WorkSpace\\StudyNote\\src\\io\\FileOutputStreamTest.java");

//创建字节输出流

FileOutputStream fos = new FileOutputStream("C:\\Users\\MyPC\\Desktop\\FileOutputStreamTest.java");

byte[] bbuf = new byte[32];

int hasread = 0;

//从输入流中读取数据

while((hasread = fis.read(bbuf)) > 0)

{

//每读取一次,即写入文件输出流

fos.write(bbuf, 0, hasread);

}

}

catch (IOException e)

{

e.printStackTrace();

}

}

}

Tips

关闭输出流有两个作用:

1、 释放物理资源

2、 将输出流缓冲区的数据flush到物理节点里。

如果希望直接输出字符串内容,则使用Writer会有更好的效果:

import java.io.*;

 

public class FileWriterTest {

public static void main(String[] args)

{

try {

FileWriter fw = new FileWriter("C:\\Users\\MyPC\\Desktop\\锦瑟.txt");

fw.write("锦瑟 李商隐\r\n");

fw.write("锦瑟无端五十弦,一弦一柱思华年。\r\n");

fw.write("庄生晓梦迷蝴蝶,望帝春心托杜鹃。\r\n");

fw.write("沧海月明珠有泪,蓝田日暖玉生烟。\r\n");

fw.write("此情可待成追忆,只是当时已惘然。\r\n");

fw.flush();

} catch (IOException e) {

// TODO 自动生成的 catch 

e.printStackTrace();

}

}

}

Tips

Java7添加AutoCloseable接口,并让所有IO类实现此接口,使用try语句会自动关闭流对象,无需手动调用close方法,但自动关闭流之前不会调用flush方法,需手动调用。

三、IO流体系

1、处理流的用法

Tips

识别处理流十分简单,只要流的构造函数参数不是一个物理节点,而是已经存在的流,那么这种流就一定是处理流;所有节点流都是以物理IO节点作为构造函数参数的。

下面程序使用PrintStream处理流来包装OutputStream,使用处理流后的输出流在输出是将更加方便:

import java.io.*;

 

public class PrintStreamTest {

public static void main (String[] args)

{

try {

FileOutputStream fos = new FileOutputStream("test.txt");

PrintStream ps = new PrintStream(fos);

//使用PrintStream执行输出

ps.println("测试字符串");

//使用PrintStream打印对象

ps.println(new PrintStreamTest());

} catch (FileNotFoundException e) {

// TODO 自动生成的 catch 

e.printStackTrace();

}

}

}

2、输入输出流体系

Java的输入/输出流体系提供了近40个类,下表显示了Java输入输出流体系中常见的流分类:

分类

字节输入流

字节输出流

字符输入流

字符输出流

抽象基类

InputStream

OutputStream

Reader

Writer

访问文件

FileInputStream

FileOutputStream

FileReader

FileWriter

访问数组

ByteArrayInputStream

ByteArrayOutputStream

CharArrayReader

CharArrayWriter

访问管道

PipedInputStream

PipedOutputStream

PipedReader

PipedWriter

访问字符串

 

 

StringReader

StringWriter

缓冲流

BufferedInputStream

BufferedOutputStream

BufferedReader

BufferWriter

转换流

 

 

InputStreamReader

OutputStreamWriter

对象流

ObjectInputStream

ObjectOutputStream

 

 

抽象基类

FilterInputStream

FilterOutputStream

FilterReader

FilterWriter

打印流

PrintInputStream

PrintOutoutStream

PrintReader

PrintWriter

推回输入流

PushbackInputStream

 

PushbackReader

 

特殊流

DataInputStream

DataOutputStream

 

 

Tips:粗体标出的类代表节点流,八个抽象基类无法创建对象

3、转换流

转换流:InputStreamReaderOutputStreamWriterReader Writer的实现类,用于将字节输入/输出流转换为字符输入/输出流。例如下面程序:

import java.io.*;

 

public class KeyinTest {

public static void main(String[] args)

{

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

String buffer = null;

try {

while((buffer = br.readLine()) != null)

{

//如果读取的字符串为“exit”,则程序退出

if(buffer.equals("exit"))

System.exit(1);

//打印读取的内容

System.out.println("输入内容为:"+ buffer);

}

} catch (IOException e) {

// TODO 自动生成的 catch 

e.printStackTrace();

}

}

}

Tips

转换流除了可将字节流转换为字符流外,更便捷的一个功能是可以指定编码表。例:new OutputStreamWriter(OutputStream out, String charsetName)。即创建了一个使用指定字符集(charsetName,可以是GBKUTF-8等字符集名称)的输出转换流对象。

4、推回输入流

推回输入流:PushbackInputStreamPushbackReader,他们有如下三个方法:

——void unread(byte[]/char[] buf)将一个字节/字符数组内容推回到推回缓冲区里,从而允许重复读取刚刚读取过的内容。

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

——void unread(int b)将一个字节/字符推回到推回缓冲区里。

Tips

当程序调用这两个推回输入流的read()方法时总是先从推回缓冲区读取,只有完全读取了推回缓冲区的内容后,但还没装满read方法所需的数组时才会从原输入流中读取。

5、重定向标准输入/输出

System类提供了如下三个方法重定向标准输入输出的方法:

——static void setErr(PrintStream err):重定向标准错误输出流。

——static void setIn(InputStream in):重定向标准输入流。

——static void setOut(OutputStream):重定向标准输出流。

下面的例子通过重定向标准输入输出流到指定文件,实现了文件的复制:

import java.io.*;

 

public class RedirectTest {

public static void main(String[] args)

{

try {

//创建输入流

FileInputStream fis = new FileInputStream(".\\src\\io\\RedirectTest.java");

//重定向标准输入流为fis流

System.setIn(fis);

PrintStream ps = new PrintStream(new FileOutputStream("副本.java"));

System.setOut(ps);

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

BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));

String line = null;

while((line = br.readLine()) != null)

{

bw.write(line);

bw.newLine();

bw.flush();

}

} catch (Exception e) {

// TODO 自动生成的 catch 

e.printStackTrace();

}

}

}

6RandomAcessFile

RandomAcessFileJava输入输出流体系中功能最丰富的文件内容访问类,它既可以读取文件内容,又可以向文件输出数据。与普通输入输出流不同的是,它支持随机访问的方式,程序可以跳到文件的任意地方来读写数据。

RandomAcessFile对象包含一个记录指针,用以表示当前读写处的位置,当程序新创建了一个RondomAcessFile对象时,该对象的记录指针位于文件头,当读写了n个字节后,文件记录指针将向后移动n个字节。除此之外,RandomAcessFile可以自由移动记录指针,提供了如下两个方法来操作文件记录指针:

——long getFilePointer():返回记录指针的当前位置。

——void seek(long pos):将文件记录指针定位到pod位置。

RondomAcessFile既包含了类似于InoutStream的三个read()方法,也包含了类似于OutputStream的三个write()方法。此外,它还包含了一系列的readXxx()writeXxx()方法来完成输入输出。

Tips

创建RondomAcessFile对象时还需指定一个mode参数,该参数指定RandomAcessFile的访问模式,该参数有四个值:r(只读),rw(读写),rws(相对rw模式,要求每个更新都同步写入底层设备),rwd(相对rw模式,要求每个更新都同步写入底层设备)。

7、流操作规律

如何选择操作流对象:

1、明确源和目的

——源:即输入流,有InputStreamReader的实现类。

——目的:即输出流,有OutputStreamWriter的实现类。

2、操作的数据是否是纯文本

——是:选字符流

——否:选字节流

3、当体系明确后,再明确使用哪个具体对象

可以通过设备来区分:

——源设备:内存,硬盘,键盘

——目的设备:内存,键盘,控制台

Tips

——标准的键盘输入:System.in。最常见写法:BufferReader bufr = new BufferReader(new InputStreatReader(System.in));

——标准的屏幕输出:System.out。最常见写法:BufferWriter bufw = new BufferWriter(new OutputStreamWriter(System.out));

 

---------------------- ASP.Net+Android+IOS开发、.Net培训、期待与您交流! ----------------------
0 0
原创粉丝点击