深入理解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(); } }
相关文章推荐
- Spring MVC中Session的正确用法之我见(转)
- Struts 页面返回空白原因
- java8新特性 (λ、stream 与 默认接口方法)
- Understanding Spring MVC Model and Session Attributes
- SpringMVC AOP 里面在invoke方法里面获取request参数
- 关于eclipse里可以连接sqlserver2008,在浏览器里却连接不了问题
- day06 Java面向对象
- Eclipse中使用SVN
- Struts2他们拦截器实例定义—登陆权限验证
- day06 Java基础
- 分解和合并:Java 也擅长轻松的并行编程! 作者:Julien Ponge
- Akka学习笔记-简介与API简单操作
- java Socket使用详细解释
- Java并发编程-20-在执行器中取消任务和控制任务的完成
- Hadoop伪分布模式安装以及在Eclipse中运行第一个MapReduce项目
- Struts2(二)局部与全局类型转换器
- Java图形化界面设计——容器(JFrame)
- 贪吃蛇(修改Ⅰ版)
- Struts2(一)接收参数
- 简单说一下java中计时器,实际需要,没有详解