Java基础——IO流(二)

来源:互联网 发布:慢性牙周炎 知乎 编辑:程序博客网 时间:2024/06/05 19:19

字符流的缓冲区

缓冲区的出现是为了提高流的操作效率而出现的。

所以在创建缓冲区之前,必须要先有流对象。

BufferedWriter

该缓冲区中提供了一个跨平台的换行符。newLine

//字符写入流缓冲区的基本方法演示import java.io.*;public class BufferedWriterDemo {    //为了演示方便,这里先不进行异常的处理    public static void main(String[] args) throws IOException    {        //先创建一个写入流对象        FileWriter fw = new FileWriter("bufw.txt");        //创建一个写入流缓冲区对象,只要将流对象传进来即可        BufferedWriter bufw = new BufferedWriter(fw);        //那么开始在缓冲区中写入数据:        for (int x = 0;x<5 ;x++ )        {            bufw.write("abcde"+x);            bufw.flush();//记住:只要用到缓冲区就记得刷新            bufw.newLine();//该方法是缓冲区中特有的换行的方法,适用于各种平台(系统)        }        bufw.close();//这里缓冲区对象其实引用的是流对象,所以这里只要关闭了缓冲区对象的资源就可以了    }}

运行结果:

缓冲区对象写入数据

BufferedReader

该缓冲区提供了一个可以读取整行的方法readLine,方便与对文本数据的获取。
当返回 为空时,表示已经读到流的末尾。
返回的是回车符之前的数据,不包含回车符。

//读取流缓冲区的基本演示import java.io.*;public class BufferedReaderDemo {    //为了演示方便,先不进行异常的处理    public static void main(String[] args) throws IOException    {        //先创建一个读取流对象,和文件关联        FileReader fr = new FileReader("bufw.txt");        //创建一个字符串缓冲区对象,将流对象作为参数传递给缓冲区对象的构造函数        BufferedReader bufr = new BufferedReader(fr);        //开始读取文件中的数据        //缓冲区对象的readLine方法可以直接获取整行的数据,        //返回包含该行内容的字符串,不包含任何行终止符,如果已到达流末尾,则返回 null         //这就限定了可以用while循环的条件        String line=null;        while ((line = (bufr.readLine()))!=null)        {            System.out.println(line);        }    }}

运行结果为:

缓冲区的newLine方法使用方法

练习1:用缓冲区对象拷贝一个文本文件。

//用缓冲区对象拷贝一个文本文件import java.io.*;public class CopyTextByBuf {    public static void main(String[] args)     {        //先创建一个流对象的引用        BufferedWriter bufw = null;        BufferedReader bufr = null;        try        {            bufw = new BufferedWriter(new FileWriter("textByCopy.txt"));            bufr = new BufferedReader(new FileReader("BufferedWriterDemo.java"));            //用readLine方法读取数据            String line = null;            while ((line = bufr.readLine())!=null)            {                bufw.write(line);                bufw.newLine();//readLine方法只获取数据,没有换行符,所以要自己添加换行符                bufw.flush();//每次存完记得刷新,以免出现意外情况倒置数据的丢失            }        }        catch (IOException e)        {            throw new RuntimeException("读取流对象创建失败");        }        //关闭资源        finally        {            if (bufw!=null)            {                try                {                    bufw.close();                }                catch (IOException e)                {                    throw new RuntimeException("写入流对象关闭失败");                }            }            if (bufr!=null)            {                try                {                    bufr.close();                }                catch (IOException e)                {                    throw new RuntimeException("读取流对象关闭失败");                }            }        }    }}

运行结果为:

用缓冲区对象拷贝一个文件

readLine方法的原理

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

所以根据readLine方法的原理,我们来写一个自己的BufferedReader,实现和BufferedReader同样的方法

//写一个自己的BufferedReader,要求和BufferedReader的方法功能一样import java.io.*;class MyBufferedReader{    //因为BufferedReader的构造方法有流对象作为参数,这里也可以将参数传进来    private FileReader r;    MyBufferedReader(FileReader r)    {        this.r = r;    }    public String myReadLine() throws IOException    {        //因为BufferedReader底层用的是数组来存储读到的字符,当读到回车符的时候,将数组中的字符转成字符串返回去        //这里为了演示方便就用StringBuilder来代替,效果一样的,        //原理就是,read方法读一个字符,将该字符存储到StringBuilder中,当读到回车符的时候,就将StringBuilder中的字符串返回去。        StringBuilder sb = new StringBuilder();        int ch = 0;        while ((ch = r.read())!=-1)        {            char c = (char)ch;      //将返回的数字转换成字符            if(c=='\r')             //判断是\r的话,继续循环                continue;            if(c=='\n')             //判断是\n的话,遇到了回车符,该行结束,将sb中的字符串返回去                return sb.toString();            else                    //如果没有遇到回车符号,就把读到的字符存储到字符串缓冲区中,                sb.append(c);        }        if(sb.length()!=0)            return sb.toString();   //当某一行没有回车符的时候,这一行会读到,并且存进sb中,但是读不到回车符,                                    //不能将sb中剩下的东西返回去,所以这里要进行一次判断,                                    //如果read方法运行完,sb中还有东西,就返回来。        return null;                //如果read方法返回-1,说明read方法读不到数据了,退出while循环,按照                                    //BufferedReader中的规则,这里应该返回null,    }    //同样的BufferedReader中还有关闭资源的方法    public void myClose() throws IOException    {        r.close();    }}public class MyBufferedReaderDemo {    //这里为了演示方便先不对异常进行处理    public static void main(String[] args) throws IOException     {        //创建流对象        FileReader fr = new FileReader("bufw.txt");        //创建缓冲区对象        MyBufferedReader mybuf = new MyBufferedReader(fr);        //开始读取数据        String line = null;        while ((line = mybuf.myReadLine())!=null)        {            System.out.println(line);        }        //关闭资源        mybuf.myClose();    }}

运行结果为:

自定义BufferedReader进行整行数据的读取

我们把这种像BufferedReader中readLine方法这样的基于read方法的加强功能的方法的设计模式称为装饰设计模式。

装饰设计模式:
当想要对已有的对象进行功能增强时,
可以定义类,将已有的对象传入,基于已有的功能,并提供加强功能
那么自定义的该类称为装饰类。

装饰类通常会通过构造方法接收被装饰类的对象
并基于被装饰的对象的功能,提供更强的功能。

写一个装饰类的例子:

//装饰类的演示//古时候的人们生活条件差,吃饭仅仅是吃饭而已class Person{    public void chifan()    {        System.out.println("吃饭");    }}//改革开放以来,人们的生活水平好了,吃饭功能也增强了class SuperPerson{    private Person p;    SuperPerson(Person p)    {        this.p = p;    }    public void superChifan()    {        System.out.println("开胃酒");        p.chifan();        System.out.println("甜点");        System.out.println("来一根");    }}public class SuperPersonDemo {    public static void main(String[] args)     {        Person p = new Person();        SuperPerson sp = new SuperPerson(p);        sp.superChifan();    }}

运行结果:

装饰设计模式

在以上的示例中,SuperPerson类对Person类的chifan方法的功能增强就是装饰设计模式了。

可是在以上程序中,使用装饰设计模式实现的功能用子类继承父类,然后复写父类的方法也可以实现相同的功能的,那么,装饰设计模式和继承的区别在哪里呢?

举个例子来说

我有自己的读取的一个类MyReader
同时有具体到读取文件类型的类比如:MyTextReader,MyMediaReader
而为了提高读取文件的功能,提供了提高效率的类,MyBufferedTextReder,MyBufferedMediaReader
那么类与类继承的体系应该是这样的

  • MyReader
    • MyTextReader
      • MyBufferedTextReader
    • MyMediaReader
      • MyBufferedMediaReader
    • 如果来了新的读取文件类型,这里还要改代码,不提倡使用这种方法,
      • 而且每一个类都有自己的增强类,每次都这样写很麻烦,而且不利于程序的扩展性。

于是,我们想到了定义一个类,直接将对象传进来,在类中定义增强读取的方法。

class MyBufferedReader{    MyBufferedReader(MyTextReader mtr)    {    }    MyBufferedReader(MyMediaReader mdr)    {    }    .....}

还是不爽,为什么呢?因为每有一个对象创建,都要有一个新的构造函数建立,如果来了新的数据类型,还是要修改代码,于是我们想到了多态,提高其扩展性,只要在构造函数中传入父类对象。

class MyBufferedReader{    MyBufferedReader(MyReader mr)    {    }}

这样,不用每一个数据类型,都写一个构造函数,而且新来的数据类型,只要是MyReader的子类就可以直接传进来对象使用增强方法。

于是体系结构就变成了这样子:

  • MyReader
    • MyTextReader
    • MyMediaReader
    • MyBufferedReader

装饰类避免了继承体系的臃肿模式,降低了类与类之间的关系。

装饰类因为增强已有对象,具备的功能和已有的是相同的,只不过提供了更强功能。
所以装饰类和被装饰类通常都是 属于一个体系的。

于是我们就可以自己定义包装类了。
还是上边自定义readLine方法的例子

//自定义的装饰类,要求在Read的体系中import java.io.*;class MyBufferedReader extends Reader//在人家的体系中当然是先继承人家了{    //因为BufferedReader的构造方法有流对象作为参数,这里也可以将参数传进来    private Reader r;    MyBufferedReader(Reader r)    {        this.r = r;    }    public String myReadLine() throws IOException    {        StringBuilder sb = new StringBuilder();        int ch = 0;        while ((ch = r.read())!=-1)        {            char c = (char)ch;                  if(c=='\r')                             continue;            if(c=='\n')                             return sb.toString();            else                                    sb.append(c);        }        if(sb.length()!=0)            return sb.toString();           return null;                    }    //因为Read类中有抽象方法read和close,所以在这里要进行覆盖    public void close() throws IOException    {        r.close();    }    public int read(char[] cbuf, int off, int len) throws IOException    {        return r.read(cbuf,off,len);    }    public void myClose() throws IOException    {        r.close();    }}public class MyBufferedReaderDemo {    public static void main(String[] args) throws IOException     {        //创建流对象        FileReader fr = new FileReader("bufw.txt");        //创建缓冲区对象        MyBufferedReader mybuf = new MyBufferedReader(fr);        //开始读取数据        String line = null;        while ((line = mybuf.myReadLine())!=null)        {            System.out.println(line);        }        //关闭资源        mybuf.myClose();    }}

LineNumberReader
获取行号的缓冲区,继承自BufferedReader

这里写图片描述

获取行号的方法

那么就来演示一下

//获取行号的演示import java.io.*;public class LineNumberReaderDemo {    public static void main(String[] args)     {        FileReader fr =null;        try        {            fr = new FileReader("SuperPersonDemo.java");            //创建一个读取行号的对象,            LineNumberReader lnr = new LineNumberReader(fr);            //也有整行读取的方法            String line =null;            while ((line = lnr.readLine())!=null)            {                //先写行号再写内容                System.out.println(lnr.getLineNumber()+" "+line);            }        }        catch (IOException e)        {            throw new RuntimeException("读取失败");        }        //关闭资源        finally        {            try            {                if(fr!=null)                fr.close();            }            catch (IOException e)            {                throw new RuntimeException("关闭资源失败");            }        }    }}

运行结果:
获取行号的对象使用实例

练习:定义一个自己的获取行号的方法。

//写一个自己的获取行号的对象import java.io.*;class MyLineNumberReader{    private FileReader fr;    MyLineNumberReader(FileReader fr)    {        this.fr = fr;    }    private int count = 0;//这里定义一个行号,起始值为0    public String myReadLine() throws IOException    {        StringBuilder sb = new StringBuilder();        int num = 0;        while ((num = fr.read())!=-1)        {            char ch = (char)num;            if(ch=='\r')                continue;            if(ch=='\n')            {                //当读到回车符的时候,count自增一次                count++;                return sb.toString();            }            else                sb.append(ch);        }        if((sb.length())!=0)            return sb.toString();        return null;    }    public void mySetLineNumber(int count)//设置count的值    {        this.count  = count;    }    public int myGetLineNumber()//获取count值    {        return count;    }}public class MyLineNumberReaderDemo {    public static void main(String[] args)     {        FileReader fr =null;        try        {            fr = new FileReader("SuperPersonDemo.java");            //创建一个读取行号的对象,            MyLineNumberReader mylnr = new MyLineNumberReader(fr);            //也有整行读取的方法            String line =null;            while ((line = mylnr.myReadLine())!=null)            {                //先写行号再写内容                System.out.println(mylnr.myGetLineNumber()+" "+line);            }        }        catch (IOException e)        {            throw new RuntimeException("读取失败");        }        //关闭资源        finally        {            try            {                if(fr!=null)                fr.close();            }            catch (IOException e)            {                throw new RuntimeException("关闭资源失败");            }        }    }}

运行结果:

自定义获取行号的类

发现,这里的代码和MyBufferedReader里边的代码只有几点不一样的地方,于是,我们可以让该获取行号的类继承MyBufferedReader类,将获取行号的类的代码进行优化。

class MyLineNumberReader extends MyBufferedReader{    MyLineNumberReader(FileReader fr)    {        super(fr);    }    private int count = 0;//这里定义一个行号,起始值为0    public String myReadLine() throws IOException    {        count++;        return super.myReadLine();    }    public void mySetLineNumber(int count)//设置count的值    {        this.count  = count;    }    public int myGetLineNumber()//获取count值    {        return count;    }}

其主要原理也就是在某些地方调用了父类(MyBufferedReader)的方法

字节流

InputStream读取字节流
OutputStream写入字节流
API中发现,和字符流中的方法差不多。。

直接演示

//字节流写入方法演示import java.io.*;public class FileStream {    //为了演示方便,先不进行异常的处理    public static void main(String[] args) 2 IOException    {        writeFile();    }    public static void writeFile() throws IOException    {        //创建一个字节流对象,并且和文件关联        FileOutputStream fos = new FileOutputStream("fos.txt");        //字节流,当然是要写入字节了。        fos.write("abcdef".getBytes());    }}

运行结果:
字节流写文件

发现没有刷新动作,也没有关流的动作,但是文件确确实实写进去了。这是为什么呢?

这是因为,字符流底层用的也是字节的缓冲区,
因为编码表中一个字符对应的两个字节,所以在字符流中,当读取到一个字节的时候,会将字节都放在一个缓冲区中,
然后再去查表,找到字节相应的字符进行操作,

而在字节流中,底层数据类型是字节,不涉及到查表,所以可以实现读一个写一个的动作,

那么写完文件了,,我们试试读取文件

//字节流写入方法演示import java.io.*;public class FileStream {    //为了演示方便,先不进行异常的处理    public static void main(String[] args) throws IOException    {        readText_1();    }    //读取文件    public static void readText_1()throws IOException    {        //创建字节流读取对象,并与文件关关联        FileInputStream fis = new FileInputStream("fos.txt");        //read方法一样会返回int类型的值        int num = 0;        while((num = fis.read())!=-1)        {            System.out.println((char)num);        }        fis.close();    }}

运行结果为:

字节流读取数据

但是这种方法一个字节一个字节的读取,效率超慢的,所以我们用数组缓冲区来提高效率。

//字节流写入方法演示import java.io.*;public class FileStream {    //为了演示方便,先不进行异常的处理    public static void main(String[] args) throws IOException    {        readText_2();    }    //读取文件    public static void readText_2()throws IOException    {        //创建字节流读取对象,并与文件关关联        FileInputStream fis = new FileInputStream("fos.txt");        int len = 0;        //和字符流一样的方法过程,只是注意数据类型变成了字节        byte[] buf = new byte[1024];        while ((len = fis.read(buf))!=-1)        {            System.out.println(new String(buf,0,len));        }        fis.close();    }}

运行结果:
字节流读取数据2

这种方法效率稍高,
发现InputStream类中有一个available方法,返回int类型的值,
是流对象获取到的所有的字节数(包括\r\n这样的字符)
这样的话,我们就可以直接定义数组缓冲区的大小了。就不用再while循环了。

//字节流写入方法演示import java.io.*;public class FileStream {    //为了演示方便,先不进行异常的处理    public static void main(String[] args) throws IOException    {        readText_3();    }    //读取文件    public static void readText_3()throws IOException    {        //创建字节流读取对象,并与文件关关联        FileInputStream fis = new FileInputStream("fos.txt");        //和字符流一样的方法过程,只是注意数据类型变成了字节        byte[] buf = new byte[fis.available()];//这里定义一个和文件字符数量一样大小的数组。        //将读取到数据存到数组中        fis.read(buf);        //将数组直接转换成字符串打印出来        System.out.println(new String(buf));        fis.close();    }}

运行结果:

用一个刚好的字节数组来存放读取到的数据

这种方法只适用于操作比较小的数据,因为jvm分配的内存一共64M,万一文件过大了,容易造成内存溢出。所以在操作比较大的数据的时候还是以创建一个1024字节的数组来循环比较靠谱。

练习:拷贝一个图片:

//用字节流复制一张图片import java.io.*;public class CopyPic {    public static void main(String[] args)     {        //在最外边创建两个引用        FileInputStream fis = null;        FileOutputStream fos = null;        try        {            //开始创建读取流和写入流,并和文件关联            fis = new FileInputStream("11.jpg");            fos = new FileOutputStream("22.jpg");            //开始读写操作            int len = 0;            byte[] buf = new byte[1024];            while ((len = fis.read(buf))!=-1)            {                fos.write(buf,0,len);            }        }        catch (IOException e)        {            throw new RuntimeException("读取失败");        }        //关闭资源        finally        {            if (fis!=null)            {                try                {                    fis.close();                }                catch (IOException e)                {                    throw new RuntimeException("读取流关闭失败");                }            }            if (fos!=null)            {                try                {                    fos.close();                }                catch (IOException e)                {                    throw new RuntimeException("写入流关闭失败");                }            }        }    }}

运行结果:

拷贝图片

练习二:拷贝一个MP3文件,用缓冲区

//拷贝MP3用流缓冲区import java.io.*;public class CopyMp3 {    public static void main(String[] args)     {        //在最外边建立一个引用        BufferedInputStream bufis = null;        BufferedOutputStream bufos = null;        try        {            //创建流对象            FileInputStream fis = new FileInputStream("1.mp3");            FileOutputStream fos = new FileOutputStream("2.mp3");            bufis = new BufferedInputStream(fis);            bufos = new BufferedOutputStream(fos);            //读取返回的字节            int by = 0;            while ((by = bufis.read())!=-1)            {                bufos.write(by);//写入字节            }        }        catch (IOException e)        {            throw new RuntimeException("读取失败");        }        //关闭资源        finally        {            if(bufis!=null)            {                try                {                    bufis.close();                }                catch (IOException e)                {                    throw new RuntimeException("读取流关闭失败");                }            }            if(bufos!=null)            {                try                {                    bufos.close();                }                catch (IOException e)                {                    throw new RuntimeException("写入流关闭失败");                }            }        }    }}

实验结果:

拷贝MP3

其实字节流缓冲区中也是有一个数组的,加上缓冲区是为了增强read方法功能。
缓冲区读取数据时过程是这样的。
调用FileInputStream的read方法,从硬盘上读取1024个字节的数据,放在定义的长度为1024的数组中
然后再用BufferedInputStream中的read方法从缓冲区中获取数组中的元素。然后写入到硬盘上来。

具体过程图例:
这里写图片描述

那么了解了字节流缓冲区的原理呢,我们就自己定义一个字节流缓冲区。

//子定义一个字节流缓冲区import java.io.*;class MyBufferedInputStream{    private InputStream is;    //因为底层是用的数组,所以这里需要定义指针,和计数器    //思路是:    //先拿一堆数据过来,myRead方法通过指针获取数组中的值,然后count用来计算数组中的数据是否全部取出    //如果count为0了,再去取数据,如果不为0,再从缓冲区中取数据    private int pos = 0;    private int count = 0;    MyBufferedInputStream(InputStream is)    {        this.is = is;    }    //read方法一次只取一个字节    public int myRead() throws IOException    {        byte[] buf = new byte[1024];        //如果count为0,抓一把数据进来,读第一位数,返回去        if (count==0)        {            count = is.read(buf);//这个时候count的值就不是0了,如果读到末尾,发现count的值返回为-1 了,            pos = 0;                //说明数据已经读取完毕了,直接返回-1,读到末尾            if (count==-1)            {                return -1;            }            byte b = buf[pos];            pos++;            count--;            return b & 255;//把数组中的元素返回去,注意:这里返回去的是byte类型的,但是上边函数上声明的是int类型的                        //所以会进行一次自动提升类型操作                        //read方法一次读取一个字节,如果MP3文件的前8位都是1呢?那么myRead方法返回的数直接就是-1了                        //这样的话在下边调用myRead方法的时候,遇到这样的情况,就会造成一种数据已经读完的假象                        //造成数据复制的损失,怎么解决呢?                        //假如遇到了全是1的情况,b的值为-1,二进制类型为1111-1111                        //这里return b会进行一次,提升,提升为int类型的,完了之后b的值还是-1,其二进制形式为                        //11111111-11111111-11111111-11111111                        //但是我们可以在int类型的前24位补0,只留下最后的8位,这样就可以解决这种情况了                        //只获取最后的八位,只要用这个数和255进行与运算就可以了。原理是:                        // 11111111-11111111-11111111-11111111    -1                        //&00000000-00000000-00000000-11111111    255                        //=00000000-00000000-00000000-11111111    255                        //所以这里进行的一次之获取后八位的运算方法就是&255        }        else if(count>0)//第二次进来的时候count就不为0了,所以不用抓数据,直接取就可以了        {            byte b = buf[pos];            pos++;            count--;            return b &255;//把数组中的元素返回去,        }        return -1;    }    public void myClose()throws IOException    {        is.close();    }}class MyBufferedInputStreamDemo{    public static void main(String[] args)    {        //在最外边建立一个引用        MyBufferedInputStream bufis = null;        BufferedOutputStream bufos = null;        try        {            //创建流对象            FileInputStream fis = new FileInputStream("1.mp3");            FileOutputStream fos = new FileOutputStream("2.mp3");            bufis = new MyBufferedInputStream(fis);            bufos = new BufferedOutputStream(fos);            //读取返回的字节            int by = 0;            while ((by = bufis.myRead())!=-1)            {                bufos.write(by);//写入字节            }        }        catch (IOException e)        {            throw new RuntimeException("读取失败");        }        //关闭资源        finally        {            if(bufis!=null)            {                try                {                    bufis.myClose();                }                catch (IOException e)                {                    throw new RuntimeException("读取流关闭失败");                }            }            if(bufos!=null)            {                try                {                    bufos.close();                }                catch (IOException e)                {                    throw new RuntimeException("写入流关闭失败");                }            }        }    }}

运行结果:

自定义字节流缓冲区拷贝MP3文件

获取键盘录入

读取键盘上录入的数据实际上也是读取流在操作:

简单演示一下获取键盘录入的例子

//读取键盘录入import java.io.*;public class SystemInDemo {    public static void main(String[] args) throws IOException    {        //获取一个读取流对象,与键盘录入相关联        InputStream is = System.in;        int ch = 0;        while((ch = is.read())!=-1)        {            System.out.println(ch);//打印所有读到的内容        }    }}

运行结果为:

键盘录入简单演示

发现,我只输入了一个a,却打印了三个值,
这是因为我还敲了一次回车,虚拟机把回车键的ASCII值也打印出来了。

那么需求变了,我要让键盘上敲的字母打印出来全部大写的,而且一直敲到over,程序就结束。

//读取键盘录入import java.io.*;public class SystemInDemo {    public static void main(String[] args)     {        //获取一个读取流对象,与键盘录入相关联        InputStream is = null;        try        {            is = System.in;            int num = 0;            //穿件一个字符串缓冲区,将读取到的字符先存到缓冲区中,            StringBuilder sb = new StringBuilder();            while((num = is.read())!=-1)            {                char ch = (char)num;                if(ch=='\r')                    continue;                if(ch=='\n')                {                    //碰到回车符,先进行判断是否为over,如果不是的话打印字符串,如果是的话,跳出循环                    String s = sb.toString();                    if(s.equals("over"))                        break;                    System.out.println(s.toUpperCase());                    //每打印完一次,sb就要清空一次,要不然前边的东西会一直占着                    sb.delete(0,sb.length());                }                else                    sb.append(ch);            }        }        catch (IOException e)        {            throw new RuntimeException("流创建失败");        }    }}

运行结果:

键盘录入字符,

转换流对象

发现上边的有一部分代码和在自定义readLine方法的时候写的代码有点相似。
那么就想到,用键盘录入的时候,能不能直接用readLine方法,直接读取一整行呢?
可是,readLine方法是在BufferedReader中的,属于字符流对象的,
而键盘录入对象是属于字节流的,在InputStream中的。

InputStreamReader:将字节流转换为字符流对象,可以使用readLine方法。

代码实例:

//转换流对象演示import java.io.*;public class TransStreamDemo {    //为了演示方便,先不进行异常的处理    public static void main(String[] args) throws IOException    {        //获取一个键盘录入对象        InputStream in = System.in;        //将字节流装换为字符流对象        InputStreamReader isr = new InputStreamReader(in);        //为了提高效率,用到字节流缓冲区对象        BufferedReader bufr = new BufferedReader(isr);        String line = null;        while ((line = bufr.readLine())!=null)        {            if("over".equals(line))                break;            System.out.println(line.toUpperCase());        }        bufr.close();    }}

运行结果为:

用readLine方法获取键盘录入

OutputStreamWriter

将字符流转换为字节流

比如,在上边的例子中,键盘录入的是字符,但是存在硬盘的时候用的就是字节,所以需要将字符流转换为字节流。

示例:

//转换流对象演示import java.io.*;public class TransStreamDemo {    //为了演示方便,先不进行异常的处理    public static void main(String[] args) throws IOException    {        //获取一个键盘录入对象        InputStream in = System.in;        //将字节流装换为字符流对象        InputStreamReader isr = new InputStreamReader(in);        //为了提高效率,用到字节流缓冲区对象,这里边才有readLine方法        BufferedReader bufr = new BufferedReader(isr);        OutputStream out = System.out;        OutputStreamWriter osw = new OutputStreamWriter(out);        BufferedWriter bufw = new BufferedWriter(osw);//一样的,这里边才有通用的换行newLine方法        String line = null;        while ((line = bufr.readLine())!=null)        {            if("over".equals(line))                break;            bufw.write(line);            bufw.newLine();//在录入数据的时候不带的回车符,所以需要自己添加            bufw.flush();//每次写完要记得刷新一下,要不然出不来数据        }        bufr.close();        bufw.close();    }}

运行结果

字符流转换为字节流

流操作的基本规律

通过两个明确来完成

  1. 明确源和目的
    1. 源:输入流。InputStream ;Reader
    2. 目的:输出流:OutputStream Writer
  2. 操作的数据是否是存文本
    1. 是:字符流
    2. 不是:字节流
  3. 当体系明确后,再明确要使用哪个具体的对象
    1. 通过设备来进行区分
      1. 源设备:内存,硬盘,键盘
      2. 目的设备:内存,硬盘,控制台。

练习:将一个图片文件存储到另一个文件中,复制文件,要求按照以上格式完成三个明确

//复制图片,要求完成三个明确import java.io.*;public class CopyPicTest {    public static void main(String[] args)     {        /*        源:输入流:Reader,InputStream        目的:输出流:Writer,OutputStream        操作的数据是否是纯文本?        不是:InputStream,和OutputStream        设备:        源:硬盘中的文件   FileInputStream        母的,硬盘中的文件 FileOutputStream        需要提高效率吗?是的,BufferedInputStream,BufferedOutputStream        */        //开始按照分析思路创建对象        BufferedInputStream bufis =null;         BufferedOutputStream bufos =null;         try        {            //开始读写操作            bufis = new BufferedInputStream(new FileInputStream("11.jpg"));            bufos = new BufferedOutputStream(new FileOutputStream("22.jpg"));            int by = 0;            while ((by = bufis.read())!=-1)            {                bufos.write(by);            }        }        catch (IOException e)        {            throw new RuntimeException("读取写入失败");        }        //关闭资源        finally        {            if(bufos!=null)            {                try                {                    bufos.close();                }                catch (IOException e)                {                    throw new RuntimeException("写入流关闭失败");                }            }            if(bufis!=null)            {                try                {                    bufis.close();                }                catch (IOException e)                {                    throw new RuntimeException("读取流关闭失败");                }            }        }    }}

练习二:将键盘录入的数据保存到一个文件中

//将键盘录入的数据保存到一个文件中/*源:InputStream   Reader是不是纯文本?是!Reader设备:键盘。对应的对象是System.in不是选择Reader吗?System.in对应的不是字节流吗?为了操作键盘的文字数据方便,转换成字符流按照字符串操作是最方便的。所以既然明确了Reader那么就将System.in转换为Reader用了Reader中的转换流,InputStreamReader需要提高效率吗?需要,BufferedReader目的:OutputStream   Writer是否是存文本。是!Writer设备:硬盘,一个文件,使用FileWriter需要提高效率吗?需要BufferedWriter bufw = new BufferedWriter()*/import java.io.*;public class Test2 {    public static void main(String[] args)     {        BufferedReader bufr = null;         BufferedWriter bufw = null;         try        {            bufr = new BufferedReader(new InputStreamReader(System.in));            bufw = new BufferedWriter(new FileWriter("systemin.txt"));            String line = null;            while ((line = bufr.readLine())!=null)            {                if("over".equals(line))                    break;                bufw.write(line);                bufw.newLine();                bufw.flush();            }        }        catch (IOException e)        {            throw new RuntimeException("读写发生异常啦");        }        finally        {            if (bufr!=null)            {                try                {                    bufr.close();                }                catch (IOException e)                {                    throw new RuntimeException("读取流发生异常");                }            }            if (bufw!=null)            {                try                {                    bufw.close();                }                catch (IOException e)                {                    throw new RuntimeException("写入流发生异常");                }            }        }    }}

写数据

运行结果

扩展一下:
想要把录入的数据按照指定的编码表(UTF-8),将数据存到文件中。

目的:OutputStream,Writer
是否是存文本?是,Writer
设备,硬盘,一个文件,使用FileWriter
但是FileWriter是使用默认的编码表GBK

但是存储时,需要加入指定 编码表utf-8,而指定的编码表只有转换流可以指定
所以要使用的对象是OutputStreamWriter
而该转换流对象要接收一个字节输出流,而且还可以操作的文件的字节输出流FileOutputStream

OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(“d.txt”),”utf-8”);

需要高效吗?需要
BufferedWriter bufw = nwe BufferedWriter(osw);

所以记住,转换流什么时候使用,字符和字节之间的桥梁,通常,涉及到字符编码转换时,需要用到转换流

指定编码表的键盘录入写入文件的程序:

//将键盘录入的数据保存到一个文件中,指定utf-8码表import java.io.*;public class Test2 {    public static void main(String[] args)     {        BufferedReader bufr = null;         BufferedWriter bufw = null;         try        {            bufr = new BufferedReader(new InputStreamReader(System.in));            //指定编码表,只能用转换流指定。            bufw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("d2.txt"),"utf-8"));            String line = null;            while ((line = bufr.readLine())!=null)            {                if("over".equals(line))                    break;                bufw.write(line);                bufw.newLine();                bufw.flush();            }        }        catch (IOException e)        {            throw new RuntimeException("读写发生异常啦");        }        finally        {            if (bufr!=null)            {                try                {                    bufr.close();                }                catch (IOException e)                {                    throw new RuntimeException("读取流发生异常");                }            }            if (bufw!=null)            {                try                {                    bufw.close();                }                catch (IOException e)                {                    throw new RuntimeException("写入流发生异常");                }            }        }    }}

运行结果,指定GBK和utf-8的写入同样的字,你好,两个文件的占内存大小不一样。

改变标准输入输出设备

System.setIn(InputStream is)//将标准输入流从键盘录入改为一个字节读取流
System.setOut(PrintStream ps);//将标准的输出流从控制台改为一个打印流(参数为一个文件名称)

演示一下:

//转换流对象演示import java.io.*;public class TransStreamDemo2 {    //为了演示方便,先不进行异常的处理    public static void main(String[] args) throws IOException    {        //把默认的输入流改变为文件读取流        System.setIn(new FileInputStream("CopyPic.java"));        //获取一个标准录入对象        InputStream in = System.in;        //将字节流装换为字符流对象        InputStreamReader isr = new InputStreamReader(in);        //为了提高效率,用到字节流缓冲区对象,这里边才有readLine方法        BufferedReader bufr = new BufferedReader(isr);        //改变标准输出流改变为一个文件        System.setOut(new PrintStream("zz.txt"));        OutputStream out = System.out;        OutputStreamWriter osw = new OutputStreamWriter(out);        BufferedWriter bufw = new BufferedWriter(osw);//一样的,这里边才有通用的换行newLine方法        String line = null;        while ((line = bufr.readLine())!=null)        {            if("over".equals(line))                break;            bufw.write(line);            bufw.newLine();//在录入数据的时候不带的回车符,所以需要自己添加            bufw.flush();//每次写完要记得刷新一下,要不然出不来数据        }        bufr.close();        bufw.close();    }}

运行结果为:

修改标准的输入输出流

将异常的信息打印到异常日志文件中,带上时间。

//将异常信息导入到异常日志文件中,要带上时间import java.io.*;import java.util.*;import java.text.*;public class ExceptionLogDemo {    public static void main(String[] args)     {        //发现异常        try        {            int[] arr = new int[2];            System.out.println(arr[4]);//创建一个数据角标越界的异常        }        //进行处理        catch (Exception e)        {            //加上时间,获取能够看得懂的时间信息            Date d = new Date();            SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 kk:mm:ss");            String time = sdf.format(d);            //将标准输出流换成文件打印流,并且与日志文件相关联            try            {                PrintStream ps = new PrintStream("exception.log");                System.setOut(ps);            }            catch (IOException ex)            {                throw new RuntimeException("异常日志文件创建失败");            }            System.out.println(time);            e.printStackTrace(System.out);//将异常打印在指定的输出流中        }    }}

运行结果:

将异常信息打印在异常日志文件中

想要将系统信息,输出到指定的文件夹

发现系统信息对象Properties有自己的指定输出流的方法list
这里写图片描述

于是,我们就可以直接进行调用:

//将系统信息输出到指定的文件中import java.io.*;import java.util.*;public class SystemInfo {    public static void main(String[] args)     {        //获取系统信息        Properties prop = System.getProperties();        //Properties中的list方法,可以将系统信息输出到指定的输出流        try        {            //输出流和文件相关联            prop.list(new PrintStream("systeminfo.txt"));        }        catch (IOException e)        {            e.printStackTrace();        }    }}

运行结果:

将系统信息打印在文件中

0 0
原创粉丝点击