您的位置:首页 > 编程语言 > Java开发

深入理解JavaIO流

2015-06-17 19:20 519 查看

自定义
BufferedReader
中的
readLine()
方法

无论是读取一行还是读取多个字符。其实最终都是在硬盘上一个个读取。所以最终使用的还是
read()
方法一次读取一个的方法。

class MyBufferedReader {

private FileReader r;

public MyBufferedReader(FileReader r) {
this.r = r;
}

public String myReadLine() throws IOException {

StringBuilder sb = new StringBuilder(); // 临时容器
int ch = 0;
while ((ch = r.read()) != -1) {
// windows操作系统的换行是\r\n
if (ch == '\r') // 当读取到\r的时候继续向下读取
continue;
if (ch == '\n') // 当读取到\n的时候将临时容器中的内容返回
return sb.toString();
else
sb.append((char) ch);
}
if (sb.length() != 0)
return sb.toString();   // 最后一行中可能没有\n

return null; // 读到行结尾的时候返回空
}

public void myClose() throws IOException {
r.close();
}
}

public class Test {

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

MyBufferedReader mbr = new MyBufferedReader(new FileReader("res/deamon.txt"));
String line = null;
while ((line = mbr.myReadLine()) != null) {
System.out.println(line);
}
mbr.myClose();
}

}


以上的
myReadLine()
其实是对原有的
read()
方法的增强,这实际上是一种装饰者设计模式。

当想要对已有的对象的功能进行增强的时候,可以定义类将已有的类传入,基于已有对象的功能并提供加强功能,自定义的该类就称为装饰类。装饰类通常会通过构造方法接收被装饰的对象,并基于被装饰的对象提供增强的功能。装饰类和被装饰类往往在同一个继承体系中例如下面的例子:

class Pserson{
public void eat(){
System.out.println("吃饭!");
}
}

class SuperPerson{

private Pserson pserson;
public SuperPerson(Pserson pserson) {
this.pserson = pserson;
}

public void superEat(){
System.out.pr
4000
intln("开胃酒。");
pserson.eat();
System.out.println("甜点。");
System.out.println("来一根。");
}
}

public class Test {

public static void main(String[] args){

SuperPerson superPerson = new SuperPerson(new Pserson());
superPerson.superEat();
}

}


装饰与继承的区别

在以上的例子中我们完全可以让
SuperPerson
这个类继承
Person
类,覆写它的
eat()
方法达到我们所需要的效果。装饰模式相对于继承提高了灵活性,避免了继承体系的臃肿,而且降低了类之间的耦合——从继承结构变成了组合结构。

自定义装饰模式的实例:自定义LineNumberReader实现行号

class MyLineNumberReader{
private Reader reader;
private int lineNumber; // 行号

public MyLineNumberReader(Reader reader) {
this.reader = reader;
}

public int getLineNumber() {
return lineNumber;
}

public void setLineNumber(int lineNumber) {
this.lineNumber = lineNumber;
}

public String myReadLine() throws IOException{

lineNumber++;
StringBuilder sb = new StringBuilder();
int ch = 0;
while ((ch = reader.read())!=-1) {
if(ch == '\r')
continue;
if(ch == '\n')
return sb.toString();
else
sb.append((char)ch);
}
if(sb.length()!=0)
return sb.toString();
return null;
}

public void myClose() throws IOException{
reader.close();
}
}

public class Test {

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

MyLineNumberReader mlnr = new MyLineNumberReader(new FileReader("res/deamon.txt"));
String line = null;
mlnr.setLineNumber(100);
while ((line = mlnr.myReadLine())!=null) {
System.out.println(mlnr.getLineNumber() + ":" +line);
}
mlnr.myClose();
}
}


在以上的代码中我们实现了自己的
LineNumberReader
,但是发现以上类中的
myReadLine()
方法和我们自己写的
MyBufferedReader
中的
myReadLine()
方法类似。因此我们可以模仿
LineNumberReader
那样继承
BufferedReader
而使我们自己的类继承
MyBufferedReader
,从而以上的代码简化为如下:

class MyBufferedReader {
private Reader reader;

public MyBufferedReader(Reader reader) {
this.reader = reader;
}

public String myReadLine() throws IOException {
int ch = 0;
StringBuilder sb = new StringBuilder();
while ((ch = reader.read()) != -1) {
if (ch == '\r')
continue;
if (ch == '\n')
return sb.toString();
else
sb.append((char) ch);
}
if (sb.length() != 0)
return sb.toString();
return null;
}

public void myClose() throws IOException {
reader.close();
}
}

class MyLineNumberReader extends MyBufferedReader {

public MyLineNumberReader(Reader reader) {
super(reader);
}

private int lineNumber; // 行号

public int getLineNumber() {
return lineNumber;
}

public void setLineNumber(int lineNumber) {
this.lineNumber = lineNumber;
}

public String myReadLine() throws IOException {

lineNumber++;
return super.myReadLine();
}

public void myClose() throws IOException {
super.myClose();
}
}


自定义字节流缓冲区

class MyBufferedInputStream{

private InputStream is;
private byte[] buf = new byte[1024]; // 缓冲区
private int pos = 0,count = 0;       // 指针和计数器

public MyBufferedInputStream(InputStream is) {
this.is = is;
}

/**
* 一次读取一个字节,从缓冲区(字节数组)中获取
* @throws IOException
*/
public int myRead() throws IOException{
// 通过is读取数据存放到buf中
if(count == 0){
count = is.read(buf);
if(count < 0)
return -1;
pos = 0;    // 指针归零

// 取数据
byte b = buf[pos];
count--;
pos++;
return b & 0xFF;
}else if(count > 0){
// 取数据
byte b = buf[pos];
count--;
pos++;
return b & 0xFF;
}
return -1;
}

public void myClose() throws IOException{
is.close();
}
}

public class Test {

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

MyBufferedInputStream mbis = new MyBufferedInputStream(new FileInputStream("E:/tmp/a.avi"));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("E:/tmp/b.avi"));

int temp = 0;
while ((temp = mbis.myRead()) != -1) {
bos.write(temp);
}
bos.close();

}
}


Q1:为什么读取的时候返回的是int类型而不是byte类型?

A1:因为我们读取二进制数据的时候可能会遇到
1111 1111
这种情况,如果以byte来判断的话会因为是-1而停止读取认为到达了文件末尾,所以需要将byte类型进行提升为int类型(和0xFF进行与运算)。

Q2:既然读取的是int类型,那么我们为什么读取后写入的数据量不是原来数据的4倍?(即:我们每次读取一个byte,但是我们每次写入的却是一个int)

A2:与Q1类似,
write(int b)
方法内部将int强制转化成了byte类型。

读取键盘录入

读取键盘录入,录入一行数据后显示录入的数据的大写形式,当输入over的时候停止录入。

public class Test {

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

BufferedInputStream bis = new BufferedInputStream(System.in);
StringBuilder sb = new StringBuilder();

while (true) {
int ch = bis.read();
if(ch == '\r')
continue;
if(ch == '\n'){
String s = sb.toString();
if("over".equals(s)){
break;
}
System.out.println(s.toUpperCase());
sb.delete(0, sb.length()); // 清空缓冲区
}else {
sb.append((char)ch);
}
}
}
}


发现以上的代码和自定义
BufferedReader
中的
myReadLine()
方法类似。但是现在的问题是
System.in
是字节流重点内容,而
readLine()
方法是
BufferedReader
中的方法,是字符流,所以此时我们需要用到转换流(
InputStreamReader
是Reader的子类,所以需要在构造的时候指定一个字节输入流)。

以下是键盘录入的最常见的写法:

public class Test {

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

BufferedReader br = new BufferedReader(new InputStreamReader(System.in,"utf-8"));
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out,"gbk"));

String line = null;
while ((line = br.readLine())!=null) {
if("over".equals(line))
break;
bw.write(line.toUpperCase());
bw.newLine();
bw.flush();
}
br.close();
bw.close();
}
}


以上程序中的目的可以使用
PrintWriter pw = new PrintWriter(System.out)
来代替。 <
11195
br>
System.in
System.out
可以使用
setIn()
setOut
进行重定向。例如:

System.setIn(new FileInputStream("res/deamon.txt"));    // 重定向System.in和System.out
System.setOut(new PrintStream("res/out.txt"));

BufferedReader br = new BufferedReader(new InputStreamReader(System.in,"utf-8"));
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out,"utf-8"));

String line = null;
while ((line = br.readLine())!=null) {
if("over".equals(line))
break;
bw.write(line.toUpperCase());
bw.newLine();
bw.flush();
}
br.close();
bw.close();


基于以上的特点我们可以自定义自己的出错日志文件(一般上使用的是log4j):

String s = null;
try {
System.out.println(s.length());
} catch (Exception e) {
PrintStream pw = new PrintStream(new FileOutputStream("res/exception.log"),true);
pw.println(new SimpleDateFormat("yyyy年MM月dd HH:mm:ss.SSS").format(new Date()));
e.printStackTrace(pw);
}


我们还可以将系统信息保存到文件中。

Properties props = System.getProperties();
props.list(new PrintStream("res/sysinfo.txt"));


属性文件和配置文件

Properties
文件是
HashTable
的子类。

// 设置和获取属性
Properties prop = new Properties();

prop.setProperty("zhangsan", "30");
prop.setProperty("lisi", "20");
prop.setProperty("王五", 10 + "");
prop.setProperty("王五", 40 + ""); // 上面的属性被覆盖了

System.out.println(prop);                       // 打印属性文件中的全部内容
prop.list(System.out);                          // 列出属性列表到指定输出流
System.out.println(prop.getProperty("王五")); // 取得特定键对应的值

for (String key : prop.stringPropertyNames()) {
System.out.println(key + "--->" + prop.getProperty(key));
}
System.out.println("-------------------------------------");

// 将文件data.properties中的内容加载到内存
prop = new Properties();
prop.load(new FileReader("res/data.properties"));
prop.list(System.out);

// 将属性文件中的内容持久化到文件(属性文件和xml两种格式)
prop.store(new FileOutputStream("info.properties"), "Student information");
prop.storeToXML(new FileOutputStream("info.xml"), "学生信息", "utf-8");


基于属性文件,我们可以设计如下的程序记录某个程序的运行次数,如果使用次数已到给出注册提示。

思考:肯定需要一个程序计数器,该计数器必须存放在外存,因为程序一旦运行结束计数器就消失了。所以需要一种机制:当程序启动的时候从持久层获取计数器,程序启动后对计数器进行加1操作,重新将计数器持久化。

public class Test {

public static final int COUNT_OF_PROGRAM = 5;
public static final String REG_INFO = "使用次数已到,请注册!\n请输入注册码:";
public static final String REG_SUCCESS = "注册成功。";
public static final String REG_FAILURE = "注册码错误!";
public static final String REG_CODE = "admin";

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

Properties prop = new Properties();

File file = new File("res/count.properties");
if(!file.exists())
file.createNewFile();           // 首次运行程序的时候产生配置文件

int count = 0;
prop.load(new FileReader(file));    // 加载配置文件
String value = prop.getProperty("time", 0 + "");
count = Integer.parseInt(value) + 1;
if(count >= COUNT_OF_PROGRAM){
System.out.println(REG_INFO);
Scanner scanner = new Scanner(System.in);
while (scanner.hasNextLine()) {
String key = scanner.nextLine();
if(key.equalsIgnoreCase(REG_CODE)){
System.out.println(REG_SUCCESS);
break;
}else {
System.out.println(REG_FAILURE);
scanner = new Scanner(System.in);
}
}
}
prop.setProperty("time", count + "");
prop.store(new FileWriter(file), "程序计数器,使用次数到达的时候提示注册");
}
}


文件的合并和分解

合并流
SequenceInputStream
用于将多个源合并成一个源。例如有以下的3个文件1.txt、2.txt、3.txt三个文件,3个文件中存放的分别的若干个1、若干个2、若干个3。现在将1.txt、2.txt、3.txt三个文件合并成一个文件out.txt。

Vector<FileInputStream> vector = new Vector<FileInputStream>(); // 通过Vector获取Enumeration
vector.add(new FileInputStream("res/1.txt"));
vector.add(new FileInputStream("res/2.txt"));
vector.add(new FileInputStream("res/3.txt"));

SequenceInputStream sis = new SequenceInputStream(vector.elements()); // 将多个源合并成一个源

FileOutputStream fos = new FileOutputStream("res/out.txt");
byte[] buf = new byte[1024];
int len = 0;
while ((len = sis.read(buf))!=-1) {
fos.write(buf, 0, len);
}

fos.close();
sis.close();


文件的切割和合并

// 切割文件
FileInputStream fis = new FileInputStream("res/a.avi");
FileOutputStream fos = null;

byte[] buf = new byte[1024*1024*4]; // 创建4M的缓冲区

int len = 0;
int count = 0;
while ((len = fis.read(buf))!=-1) {
fos = new FileOutputStream("res/a.avi.part" + (++count));
fos.write(buf, 0, len);
fos.close();
}
fis.close();

// 合并文件
ArrayList<FileInputStream> fiss = new ArrayList<FileInputStream>();
for(int i = 1; i <= 5;i++){
fiss.add(new FileInputStream("res/a.avi.part" + i));
}
// 将List转化成Enumeration需要通过List的迭代器
final Iterator<FileInputStream> iterator = fiss.iterator(); // 匿名内部类访问局部变量需要final修饰
Enumeration<FileInputStream> enumeration = new Enumeration<FileInputStream>() {

@Override
public boolean hasMoreElements() {
return iterator.hasNext();
}

@Override
public FileInputStream nextElement() {
return iterator.next();
}
};

SequenceInputStream sis = new SequenceInputStream(enumeration);
fos = new FileOutputStream("res/b.avi");
buf = new byte[1024];
len = 0;
while ((len = sis.read(buf))!=-1) {
fos.write(buf, 0, len);
}
fos.close();
sis.close();


管道流

输入和输出可以直接进行连接,通常通过线程结合使用。



在读取键盘录入的时候我们知道它是一个阻塞式方法——我们可以理解为另一个线程处于等待状态,当读取到数据的时候另一个线程就被唤醒了。注意:在使用管道流的时候两个管道必须进行连接。

/**
*
* @ClassName: Read
* @Description: 在构造的时候传入一个管道输入流,从管道中读取数据
*
*/
class Read implements Runnable{

private BufferedInputStream bis;
public Read(PipedInputStream pis) {
bis = new BufferedInputStream(pis);
}

@Override
public void run() {

byte[] buf = new byte[1024];
int len = 0;

try {
System.out.println("读取前,没有数据.\t【阻塞中】");
while ((len = bis.read(buf)) != -1) {
System.out.println(new String(buf, 0, len));
}
System.out.println("数据读取完成.\t【阻塞解除】");
bis.close();
} catch (Exception e) {
e.printStackTrace();
}
}

}

/**
*
* @ClassName: Write
* @Description: 构造时传入一个管道输入流,向管道中不断写入随机数
*
*/
class Write implements Runnable{

private BufferedOutputStream bos;
private static Random random = new Random();

public Write(PipedOutputStream pos) {
bos = new BufferedOutputStream(pos);
}

@Override
public void run() {

try {

while (true) {
System.out.println("2s后开始写入数据.\t【LOADING TO WRITE】");
Thread.sleep(2000);
bos.write((random.nextLong()+"").getBytes());
System.out.println("数据写入完成.\t【WRITE DONE】");
bos.flush();
}

} catch (Exception e) {
e.printStackTrace();
}
}

}

public class Test {

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

PipedInputStream pis = new PipedInputStream();
PipedOutputStream pos = new PipedOutputStream();
pis.connect(pos);                               // 两个管道进行连接

new Thread(new Read(pis)).start();
new Thread(new Write(pos)).start();

}
}


内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: