Java语言基础-IO流(输入输出流) 字符流

IO(Input Output)流

Java对数据的操作时通过流的方式;
Java用于操作流的对象都在IO包中;
流按操作数据分为两种:字节流与字符流;
流按流向分为:输入流,输出流。

输入输出流是相对于内存设备而言;
将外设中的数据读取到内存中--输入;
将内存中的数据写入到外设中--输出。

字符流:就是用于读取文字字节数据的字节流与编码表相结合,封装成字符流。
(字节流读取文字字节数据后,不直接操作而是先查指定的编码表,获取对应的文字,再对这个文字进行操作。)

字节流的抽象基类(顶层父类):
InputStream,OutputStream

字符流的抽象基类(顶层父类):
Reader,Writer

这些体系的子类都以父类名作为后缀;
子类名的前缀就是该对象的功能。

需求:将一些文字存储到硬盘的文件中
如果要操作文字数据,建议优先考虑字符流;
而且要将数据从内存写到硬盘上,需要使用字符流中的输出流Writer。
硬盘的数据的基本体现是文件,希望找到一个可以操作文件的Writer。

private static final String LINE_SEPARATOR = System.getProperty("line.separator");//定义行分隔符常量
基本方法示例:

package cn.itcast.p2.io.filewriter;

import java.io.FileWriter;
import java.io.IOException;

public class FileWriterDemo {

    private static final String LINE_SEPARATOR = System.getProperty("line.separator");

    /**
     * @param args
     * @throws IOException
     */
    public static void main(String[] args) throws IOException {

        /*
         * 创建一个可以往文件中写入字符的字符输出流对象 。 往一个文件中写入文字数据,那么在创建对象时,就必须明确该文件
         * 
         * 如果文件不存在,则会自动创建。 如果文件存在,则源文件被覆盖。
         * 
         * 如果构造函数中加入true,可以实现对文件的续写
         */
        FileWriter fw = new FileWriter("demo.txt",true);

        /*
         * 调用Writer对象中的write(String)方法,写入数据。 数据写入到了临时存储缓冲区中。
         */
//        fw.write("abcde"+LINE_SEPARATOR+"hahaha");
        fw.write("xixi");

        /*
         * 进行刷新,将数据直接写入到目的地
         */
        // fw.flush();

        /*
         * 关闭资源。在关闭前,会调用fluse()方法刷新缓冲中的数据到目的地。
         */
        fw.close();
    }

}


FileWriter的IO异常处理示例:

package cn.itcast.p2.io.filewriter;

import java.io.FileWriter;
import java.io.IOException;

public class IOExceptionDemo {

    private static final String LINE_SEPARATOR = System
            .getProperty("line.separator");
    public static void main(String[] args) {

        FileWriter fw = null;// 在try外进行定义,内部进行初始化
        try {
            fw = new FileWriter("k:\\demo.txt");

            fw.write("abcde" + LINE_SEPARATOR + "hahaha");
        } catch (IOException e) {
            System.out.println(e.toString());
        } finally {
            if (fw != null)//如果fw创建文件失败时,关闭时会出现空指针异常
                try {
                    fw.close();
                } catch (IOException e) {
                    throw new RuntimeException("关闭失败");
                }
        }
    }

}

需求:读取一个文本文件,将读取到的字符打印到控制台。
同上,找到了FileReader。
read()方法
public int read() throws IOException
读取单个字符。
返回:作为整数读取的字符,范围在 0 到 65535 之间 (0x00-0xffff),如果已到达流的末尾,则返回 -1。※

package cn.itcast.p3.io.filereader;

import java.io.FileReader;
import java.io.IOException;

public class FileReaderDemo {
    public static void main(String[] args) throws IOException {
        // 创建读取字符数据的流对象
        /*
         * 在创建读取流对象时,必须要明确被读取的文件。一定要确定该文件时存在的。
         * 
         * 用一个读取流关联一个已存在文件
         */
        FileReader fr = new FileReader("demo.txt");

        // 用Reader中的read方法读取字符
        int ch = 0;
        while ((ch = fr.read()) != -1) {
            System.out.println((char) ch);
        }

        fr.close();
    }

}


public int read(char[] cbuf) throws IOException
将字符读入数组。
参数:cbuf - 目标缓冲区
返回:读取的字符数,如果已到达流的末尾,则返回 -1 ※

package cn.itcast.p3.io.filereader;

import java.io.FileReader;
import java.io.IOException;

public class FileReaderDemo2 {
    public static void main(String[] args) throws IOException {
        FileReader fr = new FileReader("demo.txt");

        /*
         * 使用read(char())读取文本文件数据 先创建字符数组
         */
        char[] buf = new char[1024];//长度最好为1024的整数倍
        int len=0;
        while((len=fr.read(buf))!=-1){
            System.out.println(new String(buf,0,len));
        }

        /*int num = fr.read(buf);// 将读到的字符存储到数组中
        System.out.println(num + ":" + new String(buf));
        int num1 = fr.read(buf);
        System.out.println(num1 + ":" + new String(buf));
        int num2 = fr.read(buf);
        System.out.println(num2 + ":" + new String(buf));*/
        /*
         * abcde# 
         * 结果: 
         * 3:abc 
         * 2:dec 
         * -1:dec 
         * 第一次读:a b c (从a处开始,读取到3个字符,返回3) 
         * 第二次读:d e c (c是此一次读取的c,从d处开始,读取到2个字符,返回2) 
         * 第三次读:d e c (c是此一次读取的c,开始处即是流的末尾,未读取到字符,返回-1)
         */
        fr.close();
    }

}


将C盘的一个文本文件复制到D盘
分析:
复制的原理:
读取C盘文件中的数据,将这些数据写入到D盘中。
读&写。

方法一:

package cn.itcast.p3.io.charstream.test;

import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

/*
 * 需求:将C盘的一个文本文件复制到D盘
 * 思路:
 * 1.需要读取源;
 * 2.将读到的源数据写入到目的地
 * 3.操作文本数据,使用字符流
 */
public class CopyTextTest {

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

        //1.读取一个已有的文本文件,用字符读取流和文件相关联
        FileReader fr=new FileReader("IO流_2.txt");
        //2.创建一个目的,用于存储读到的数据
        FileWriter fw=new FileWriter("copytest_1.txt");
        //3.频繁的读写操作
        int ch=0;
        while((ch=fr.read())!=-1){
            fw.write((char)ch);
        }
        //4.关闭流资源
        fw.close();
        fr.close();
        
    }
}

方法二(效率较高):

package cn.itcast.p3.io.charstream.test;

import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

public class CopyTextTest_2 {

    private static final int BUFFER_SIZE = 1024;

    /**
     * @param args
     */
    public static void main(String[] args) {
        FileReader fr = null;
        FileWriter fw = null;

        try {
            fr = new FileReader("IO流_2.txt");
            fw = new FileWriter("copytest_2.txt");
            // 创建一个临时容器,用于缓存读取到的字符
            char[] buf = new char[BUFFER_SIZE];//自定义缓冲区

            // 定义一个变量,记录读取到的字符数(其实就是网数组里装的字符数)
            int len = 0;
            while ((len = fr.read(buf)) != -1) {//循环次数少,效率较高
                fw.write(buf, 0, len);
            }
        } catch (Exception e) {

        } finally {
            if (fw != null)
                try {
                    fw.close();
                } catch (IOException e) {
                    // System.out.println("读写失败");
                    throw new RuntimeException("读写失败");
                }
            if (fr != null)
                try {
                    fr.close();
                } catch (IOException e) {
                    throw new RuntimeException("读写失败");
                }
        }
    }

}

字符流的缓冲区(提高性能)
public class BufferedReader extends Reader
从字符输入流中读取文本,缓冲各个字符,从而实现字符、数组和行的高效读取。

public class BufferedWriter extends Writer
将文本写入字符输出流,缓冲各个字符,从而提供单个字符、数组和字符串的高效写入。

BufferedWriter示例:

package cn.itcast.p3.io.charstream.buffer;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;

public class BufferedWriterDemo {

    private static final String LINE_SEPEARTOR = System
            .getProperty("line.separator");

    /**
     * @param args
     * @throws IOException
     */
    public static void main(String[] args) throws IOException {
        FileWriter fw = new FileWriter("buf.txt");

        // 为了提高写入的效率,使用了字符流的缓冲区
        // 创建了一个字符写入流的缓冲对象,并和指定要被换种的对象相关联
        BufferedWriter bufw = new BufferedWriter(fw);

        // 使用缓冲区的写入方法,将数据写入到缓冲区中
        // bufw.write("abcde"+LINE_SEPEARTOR+"haha");

        // bufw.write("abcde");
        // bufw.newLine();
        // bufw.write("heheh");

        for (int i = 1; i <= 4; i++) {
            bufw.write("abcde" + i);
            bufw.newLine();//BufferedReader特有的方法newLine()
            bufw.flush();
        }

        // 使用缓冲区的刷新方法将数据刷入目的地中
        bufw.flush();

        bufw.close();// 缓冲区关闭时,底层关闭的是缓冲的流对象
    }

}

BufferedReader使用示例:

package cn.itcast.p3.io.charstream.buffer;

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;

public class BufferedReaderDemo {

    /**
     * @param args
     * @throws IOException
     */
    public static void main(String[] args) throws IOException {
        // demo();

        FileReader fr = new FileReader("buf.txt");

        BufferedReader bufr = new BufferedReader(fr);

        String line = null;
        while ((line = bufr.readLine()) != null) {//BufferedReader特有方法readLine()
            System.out.println(line);
        }
    }

    /**
     * @throws FileNotFoundException
     * @throws IOException
     */
    private static void demo() throws FileNotFoundException, IOException {
        FileReader fr = new FileReader("buf.txt");

        char[] buf = new char[1024];

        int len = 0;
        while ((len = fr.read(buf)) != -1) {
            System.out.println(new String(buf, 0, len));
        }

        fr.close();
    }

}

※底层流对象的read方法读的是硬盘中的数据,缓冲区流对象的read读的是缓冲区中的数据。
readLine()方法的临时容器中存储的是一行的数据(这个容器可以使StringBuilder,以为最终返回的是字符串),不包含换行符。返回该行内容的字符串。
readLine()原理:使用了读取缓冲区的read方法,将读取到的字符进行缓冲并判断换行标记,将标记前的缓冲数据变成字符串返回。

缓冲区-复制文本文件示例:

package cn.itcast.p3.io.charstream.buffer.test;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

public class CopyTextBufferTest {

    /**
     * @param args
     * @throws IOException 
     */
    public static void main(String[] args) throws IOException {
        FileReader fr=new FileReader("buf.txt");
        BufferedReader bufr=new BufferedReader(fr);
        
        FileWriter fw=new FileWriter("buf_copy.txt");
        BufferedWriter bufw=new BufferedWriter(fw);
        
        String line=null;
        while((line=bufr.readLine())!=null){
            bufw.write(line);
            bufw.newLine();//不要忘记换行
        }
        bufw.close();
        bufr.close();
        
        /*
        int ch=0;
        while((ch=bufr.read())!=-1){
            bufw.write(ch);
        }
        bufw.close();
        bufr.close();
        */
    }

}


自定义MyBufferedReader

package cn.itcast.p4.io.charstream.mybuffer;

import java.io.FileReader;
import java.io.IOException;

/**
 * 自定义的读取缓冲区,其实就是模拟一个BufferedReader
 * 
 * 分析: 
 * 缓冲区中无非就是封装了一个数组,并对外提供了更多的方法对数组进行访问。
 * 其实这些方法最终操作的都是数组的指针。
 * 
 * 缓冲的原理:
 * 从源中获取一批数据装入缓冲区中,然后从缓冲区中不断取出一个一个的数据。
 * 当此次取完后,再从源中继续取一批数据进入缓冲区。
 * 当源中的数据取光时,用-1作为结束标记。
 * @author chenchong
 * 
 */
public class MyBufferedReader {
    private FileReader r;

    //定义一个数组作为缓冲区
    private char[] buf=new char[1024];
    //定义一个指针,用于操作数组中的元素,当操作到最后一个元素后,指针归零
    private int pos=0;
    
    //定义一个计数器,用于记录缓冲区中的数据个数,当该数据减到0,
    //就从源中继续获取数据到缓冲区中
    private int count=0;
    
    public MyBufferedReader(FileReader r) {
        this.r = r;
    }

    /**
     * 该方法从缓冲区中一次取一个字符
     * @return
     * @throws IOException
     */
    public int myRead() throws IOException {

        if(count==0){
            count=r.read(buf);//从源中获取数据
            pos=0;
        }
        if(count<0)
            return -1;
        
        char ch=buf[pos++];
        
        count--;
        return ch;
        
        /*//1.从源中取出一批数据到缓冲区中,要先做判断,
        //只有计数器为0时才 需要从源中获取数据
        if(count==0){
            count=r.read(buf);
            
            if(count<0)
                return -1;
            
            //每次获取数据到缓冲区后,角标归零
            pos=0;
            char ch=buf[pos];
            
            pos++;
            count--;
            return ch;
        }else if(count>0){
            char ch=buf[pos];
            
            pos++;
            count--;
            return ch;
        }*/
        
    }

    public String myReadLine() throws IOException {

        StringBuilder sb=new StringBuilder();
        int ch=0;
        while((ch=myRead())!=-1){
            if(ch=='\r')
                continue;
            if(ch=='\n')
                return sb.toString();
            
            //将从缓冲区中读到的字符存储到缓存行数据的缓冲区中
            sb.append((char)ch);
        }
        if(sb.length()!=0)
            return sb.toString();
        return null;
    }

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

}

装饰设计模式
    对一组对象的功能进行增强时,就可以使用该模式进行问题的解决。装饰和继承都能实现一样的特点:进行功能的扩展增强
区别:
举例:
所有线有一个继承体系,
Writer
    |--TextWriter        用于操作文本
    |--MediaWriter    用于操作媒体

想要对操作的动作进行效率的提高,
按照面向对象,可以通过继承对具体对象进行功能的扩展。
效率提高需要加入缓冲技术
Writer
    |--TextWriter        用于操作文本
        |--BufferTextWriter        加入了缓冲技术的操作文本的对象
    |--MediaWriter    用于操作媒体
        |--BufferMediaWriter        加入了缓冲技术的操作媒体的对象

但是这样做并不理想。
如果这个体系再次进行功能扩展,又多了流对象,这个流要提高效率,也要产生子类。
这时,就会发现只为提高功能进行的继承,导致继承体系越来越臃肿,不够灵活。

重新思考这个问题。
既然加入的都是同一种技术——缓冲,
前一种是让缓冲和具体的对象相结合。
考虑将缓冲进行单独的封装:哪个对象需要缓冲,就将哪个对象和缓冲关联。

class Buffer extends Writer{
    Buffer(TestWriter w)
    {}
    Buffer(MediaWriter m)
    {}
}
                                    ↓
class BufferWriter extends Writer{
    BufferWriter(Writer w)
    {}
}

Writer
    |--TextWriter        用于操作文本
    |--MediaWriter    用于操作媒体
    |--BufferWriter    用于提高效率
发现装饰比继承更为灵活。
特点:装饰类和被装饰类都必须所属于同一个接口或父类。

LineNumberReader示例:

package cn.itcast.p6.io.charstream.linenumber;

import java.io.FileReader;
import java.io.IOException;
import java.io.LineNumberReader;

public class LineNumberReaderDemo {

    /**
     * @param args
     * @throws IOException 
     */
    public static void main(String[] args) throws IOException {
        
        FileReader fr=new FileReader("IO流_2.txt");
        LineNumberReader lnr=new LineNumberReader(fr);
        
        String line=null;
        lnr.setLineNumber(100);//设置行号从100开始
        while((line=lnr.readLine())!=null){
            System.out.println(line+":"+lnr.getLineNumber());
        }
        lnr.close();
    }

}

posted on 2012-08-10 07:34  foolchen  阅读(607)  评论(0编辑  收藏  举报

导航