内存操作流&打印流&输入流(Scanner)

内存操作流

定义:除了文件之外,IO操作也可以发生在内存中,发生在内存中的操作流称为内存流。

文件流的操作里面一定会产生一个文件数据(不管后这个文件数据是否被保留)。
如果现在需求是:需要进行IO处理,但是又不希望产生文件,这种情况下就可以使用内存作为操作终端。

内存流也分为两类:

  1. 字节内存流:ByteArrayInputStreamByteArrayOutputStream
  2. 字符内存流:CharArrayReaderCharArrayWriter

首先来观察ByteArrayInputStream和ByteArrayOutputStream的构造方法:

public
class ByteArrayInputStream extends InputStream {

    /**
     * Creates a <code>ByteArrayInputStream</code>
     * so that it  uses <code>buf</code> as its
     * buffer array.
     * The buffer array is not copied.
     * The initial value of <code>pos</code>
     * is <code>0</code> and the initial value
     * of  <code>count</code> is the length of
     * <code>buf</code>.
     *
     * @param   buf   the input buffer.
     */
    public ByteArrayInputStream(byte buf[]) {
        this.buf = buf;
        this.pos = 0;
        this.count = buf.length;
    }

public class ByteArrayOutputStream extends OutputStream {

    /**
     * Creates a new byte array output stream. The buffer capacity is
     * initially 32 bytes, though its size increases if necessary.
     */
    public ByteArrayOutputStream() {
        this(32);
    }

在这里插入图片描述

范例:通过内存流实现大小写转换

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;

public class TestFileStream{
    public static void main(String[] args) throws Exception{
        String msg = "hello world";
        ByteArrayInputStream inputStream = new ByteArrayInputStream(msg.getBytes());
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        int len = 0;
        while ((len = inputStream.read()) != -1){
            outputStream.write(Character.toUpperCase(len));
        }
        System.out.println(outputStream);
        inputStream.close();
        outputStream.close();
    }
}

打印流

打印流解决的就是OutputStream的设计缺陷,属于OutputStream功能的加强版。
如果操作的不是二进制数据, 只是想通过程序向终端目标输出信息的话,OutputStream不是很方便,其缺点有两个:

  1. 所有的数据必须转换为字节数组。
  2. 如果要输出的是int、double等类型就不方便了。

打印流概念

打印流设计的主要目的是为了解决OutputStream的设计问题,其本质不会脱离OutputStream。

范例:设计一个简单打印流

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;

class PrintUtil{
    private OutputStream outputStream;

    public PrintUtil(OutputStream outputStream) {
        this.outputStream = outputStream;
    }
    public void print(String str){
        try {
            outputStream.write(str.getBytes());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    public void println(String str){
        this.print(str+"\r\n");
    }
    public void print(int num){
        this.print(num);
    }
    public void println(int num){
        this.print(num+"\r\n");
    }
    public void print(double data){
        this.print(data);
    }
    public void println(double data){
        this.print(data+"\r\n");
    }
}
public class TestFileStream {
    public static void main(String[] args)throws Exception {
        PrintUtil printUtil = new PrintUtil(new FileOutputStream
                (new File("C:" + File.separator + "Users" + File.separator
                        + "DELL" + File.separator + "Desktop" + File.separator + "Test.txt")));
        printUtil.print("姓名:");
        printUtil.println("刘德华") ;
        printUtil.print("年龄:") ;
        printUtil.println(50) ;
        printUtil.print("工资:") ;
        printUtil.println(1000.123456789);
    }
}

经过简单处理之后,让OutputStream的功能变的更加强大了,其实本质就只是对OutputStream的功能做了一个 封装而已。java提供有专门的打印流处理类:PrintStreamPrintWriter

使用系统提供的打印流

打印流分为字节打印流:PrintStream、字符打印流:PrintWriter
首先来观察这两 个类的继承结构与构造方法:

在这里插入图片描述
此时看上图继承关系我们会发现,有点像之前讲过的代理设计模式,但是代理设计模式有如下特点:

  1. 代理是以接口为使用原则的设计模式。
  2. 最终用户可以调用的方法一定是接口定义的方法。

打印流优缺点:

  1. 优点:扩展功能特别方便,需要不同的功能时只需要更换装饰类即可。
  2. 缺点:类结构复杂。

打印流应用的是装饰设计模式(基于抽象类的设计模式):核心依然是某个类(OutputStream)的功能(writer()),但是为了得到更好的操作效果,让其支持的功能更多。

范例:使用打印流

public class TestFileStream {
    public static void main(String[] args)throws Exception {
        PrintStream printUtil = new PrintStream(new FileOutputStream
                (new File("C:" + File.separator + "Users" + File.separator
                        + "DELL" + File.separator + "Desktop" + File.separator + "Test.txt")));
        printUtil.print("姓名:");
        printUtil.println("刘德华") ;
        printUtil.print("年龄:") ;
        printUtil.println(50) ;
        printUtil.print("工资:") ;
        printUtil.println(1000.123456789);
    }
}

格式化输出

C语言有一个printf()函数,这个函数在输出的时候可以使用一些占位符,例如:字符串(%s)、数字(%d)、小数 (%m.nf)、字符(%c)等。从JDK1.5开始,PrintStream类中也追加了此种操作。

  • 格式化输出:
    /**
     * A convenience method to write a formatted string to this output stream
     * using the specified format string and arguments.
     *
     * <p> An invocation of this method of the form <tt>out.printf(format,
     * args)</tt> behaves in exactly the same way as the invocation
     *
     * <pre>
     *     out.format(format, args) </pre>
     *
     * @param  format
     *         A format string as described in <a
     *         href="../util/Formatter.html#syntax">Format string syntax</a>
     *
     * @param  args
     *         Arguments referenced by the format specifiers in the format
     *         string.  If there are more arguments than format specifiers, the
     *         extra arguments are ignored.  The number of arguments is
     *         variable and may be zero.  The maximum number of arguments is
     *         limited by the maximum dimension of a Java array as defined by
     *         <cite>The Java&trade; Virtual Machine Specification</cite>.
     *         The behaviour on a
     *         <tt>null</tt> argument depends on the <a
     *         href="../util/Formatter.html#syntax">conversion</a>.
     *
     * @throws  java.util.IllegalFormatException
     *          If a format string contains an illegal syntax, a format
     *          specifier that is incompatible with the given arguments,
     *          insufficient arguments given the format string, or other
     *          illegal conditions.  For specification of all possible
     *          formatting errors, see the <a
     *          href="../util/Formatter.html#detail">Details</a> section of the
     *          formatter class specification.
     *
     * @throws  NullPointerException
     *          If the <tt>format</tt> is <tt>null</tt>
     *
     * @return  This output stream
     *
     * @since  1.5
     */
    public PrintStream printf(String format, Object ... args) {
        return format(format, args);
    }
  • 格式化字符串:
    /**
     * Writes a formatted string to this output stream using the specified
     * format string and arguments.
     *
     * <p> The locale always used is the one returned by {@link
     * java.util.Locale#getDefault() Locale.getDefault()}, regardless of any
     * previous invocations of other formatting methods on this object.
     *
     * @param  format
     *         A format string as described in <a
     *         href="../util/Formatter.html#syntax">Format string syntax</a>
     *
     * @param  args
     *         Arguments referenced by the format specifiers in the format
     *         string.  If there are more arguments than format specifiers, the
     *         extra arguments are ignored.  The number of arguments is
     *         variable and may be zero.  The maximum number of arguments is
     *         limited by the maximum dimension of a Java array as defined by
     *         <cite>The Java&trade; Virtual Machine Specification</cite>.
     *         The behaviour on a
     *         <tt>null</tt> argument depends on the <a
     *         href="../util/Formatter.html#syntax">conversion</a>.
     *
     * @throws  java.util.IllegalFormatException
     *          If a format string contains an illegal syntax, a format
     *          specifier that is incompatible with the given arguments,
     *          insufficient arguments given the format string, or other
     *          illegal conditions.  For specification of all possible
     *          formatting errors, see the <a
     *          href="../util/Formatter.html#detail">Details</a> section of the
     *          formatter class specification.
     *
     * @throws  NullPointerException
     *          If the <tt>format</tt> is <tt>null</tt>
     *
     * @return  This output stream
     *
     * @since  1.5
     */
    public PrintStream format(String format, Object ... args) {
        try {
            synchronized (this) {
                ensureOpen();
                if ((formatter == null)
                    || (formatter.locale() != Locale.getDefault()))
                    formatter = new Formatter((Appendable) this);
                formatter.format(Locale.getDefault(), format, args);
            }
        } catch (InterruptedIOException x) {
            Thread.currentThread().interrupt();
        } catch (IOException x) {
            trouble = true;
        }
        return this;
    }

System类对IO的支持

  • 输出(均是打印流PrintStream的对象)
  1. 标准输出(显示器):System.out
  2. 错误输出:System.err
  • 输入(输入流InputStream的对象)

    标准输入(键盘):System.in

系统输出

系统输出一共有两个常量:out、err,并且这两个常量表示的都是PrintStream类的对象。

  1. out输出的是希望用户能看到的内容
  2. err输出的是不希望用户看到的内容

这两种输出在实际的开发之中都没用了,取而代之的是"日志。

System.err只是作为一个保留的属性而存在,现在几乎用不到。唯一可能用到的就是System.out
由于System.out是PrintStream的实例化对象,而PrintStream又是OutputStream的子类,所以可以直接使用 System.out直接为OutputStream实例化,这个时候的OutputStream输出的位置将变为屏幕

范例:使用System.out为OutputStream实例化

import java.io.OutputStream;

public class TestFileStream {
    public static void main(String[] args)throws Exception {
        OutputStream outputStream = System.out;
        outputStream.write("你好,中国".getBytes());
        outputStream.close();
    }
}

系统输入

System.in对应的类型是InputStream,而这种输入流指的是由用户通过键盘进行输入(用户输入)。java本身并没有直接的用户输入处理,如果要想实现这种操作,必须使用java.io的模式来完成

范例:利用InputStream实现数据输入

import java.io.InputStream;

public class TestFileStream {
    public static void main(String[] args)throws Exception {
        InputStream inputStream = System.in;
        byte[] data = new byte[1024];
        System.out.println("请输入内容:");
        int temp = inputStream.read(data);
        System.out.println("输入内容为:" + new String(data,0,temp));
        inputStream.close();
    }
}

现在发现当用户输入数据的时候程序需要暂停执行,也就是程序进入了阻塞状态。直到用户输入完成(按下回车), 程序才能继续向下执行。

以上的程序本身有一个致命的问题,核心点在于:开辟的字节数组长度固定,如果现在输入的长度超过了字节数组长度,那么只能够接收部分数据。这个时候是由于一次读取不完所造成的问题,所以此时好的做法是引入内存操作流来进行控制,这些数据先保存在内存流中而后一次取出。

范例:引入内存流

import java.io.ByteArrayOutputStream;
import java.io.InputStream;

public class TestFileStream {
    public static void main(String[] args)throws Exception {
        InputStream inputStream = System.in;
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        byte[] data = new byte[1024];
        System.out.println("请输入内容:");
        int temp = 0;
        while ((temp = inputStream.read(data)) != -1){
            byteArrayOutputStream.write(data,0,temp);
            if (temp < data.length){
                break;
            }
        }
        inputStream.close();
        byteArrayOutputStream.close();
        System.out.println("输出内容为:" + new String(byteArrayOutputStream.toByteArray()z));
    }
}

现在虽然实现了键盘输入数据的功能,但是整体的实现逻辑过于混乱了,即java提供的System.in并不好用,还要结 合内存流来完成,复杂度很高。

如果要想在IO中进行中文的处理,最好的做法是将所有输入的数据保存在一起再处理,这样才可以保证不出现乱码

两种输入流(原生InputStream的进化版)

BufferedReader类

BufferedReader类属于一个缓冲的输入流,而且是一个字符流的操作对象。
在java中对于缓冲流也分为两类:字节缓冲流(BufferedInputStream)、字符缓冲流(BufferedReader)。
之所以选择BufferedReader类操作是因为在此类中提供有如下方法(读取一行数据):

    /**
     * Reads a line of text.  A line is considered to be terminated by any one
     * of a line feed ('\n'), a carriage return ('\r'), or a carriage return
     * followed immediately by a linefeed.
     *
     * @return     A String containing the contents of the line, not including
     *             any line-termination characters, or null if the end of the
     *             stream has been reached
     *
     * @exception  IOException  If an I/O error occurs
     *
     * @see java.nio.file.Files#readAllLines
     */
    public String readLine() throws IOException {
        return readLine(false);
    }

readLine()这个方法可以直接读取一行数据(以回车为换行符)
但是这个时候有一个非常重要的问题要解决,来看BufferedReader类的定义与构造方法:

public class BufferedReader extends Reader {

    /**
     * Creates a buffering character-input stream that uses an input buffer of
     * the specified size.
     *
     * @param  in   A Reader
     * @param  sz   Input-buffer size
     *
     * @exception  IllegalArgumentException  If {@code sz <= 0}
     */
    public BufferedReader(Reader in, int sz) {
        super(in);
        if (sz <= 0)
            throw new IllegalArgumentException("Buffer size <= 0");
        this.in = in;
        cb = new char[sz];
        nextChar = nChars = 0;
    }

    /**
     * Creates a buffering character-input stream that uses a default-sized
     * input buffer.
     *
     * @param  in   A Reader
     */
    public BufferedReader(Reader in) {
        this(in, defaultCharBufferSize);
    }

而System.in是InputStream类的子类,这个时候与Reader没有关系,要建立起联系就要用到InputStreamReader 类。如下:
在这里插入图片描述

范例:利用BufferedReader实现键盘输入

import java.io.BufferedReader;
import java.io.InputStreamReader;

public class TestFileStream {
    public static void main(String[] args)throws Exception {
        BufferedReader bufferedReader = new BufferedReader(
                new InputStreamReader(System.in));
        System.out.println("请输入信息:");
        String str = bufferedReader.readLine();
        System.out.println("输入信息为:" + str);
    }
}

以上形式实现的键盘输入有一个大特点:由于接收的数据类型为String,可以使用String类的各种操作进行数据处理并且可以变为各种常见数据类型

java.util.Scanner类

打印流解决的是OutputStream类的缺陷,BufferedReader解决的是InputStream类的缺陷。而Scanner解决的是 BufferedReader类的缺陷(替换了BufferedReader类) 。Scanner是一个专门进行输入流处理的程序类,利用这个类可以方便处理各种数据类型,同时也可以直接结合正则表达式进行各项处理。

在这个类中主要关注以下方法:

  • 判断是否有指定类型输入
public boolean hasNextXXX()
  • 取得指定类型数据
public 数据类型 nextXXX()
  • 自定义分隔符
public Scanner useDelimiter(Pattern pattern)
  • 构造方法
public Scanner(InputStream source) 

范例:使用Scanner实现数据输入

import java.util.Scanner;

public class TestFileStream {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入信息:");
        if (scanner.hasNext()){
            System.out.println("输出内容为:" + scanner.next());
        }
        scanner.close();
    }
}

使用Scanner还可以接收各种数据类型,并且帮助用户减少转型处理

范例:接收其他类型数据

import java.util.Scanner;

public class TestFileStream {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入年龄:");
        if (scanner.hasNextInt()){
            System.out.println("年龄为:" + scanner.next());
        }else {
            System.out.println("输入的不是数字!"); ;
        }
        scanner.close();
    }
}

最为重要的是,Scanner可以对接收的数据类型使用正则表达式判断

范例:利用正则表达式进行判断

import java.util.Scanner;

public class TestFileStream {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入出生日期:");
        if (scanner.hasNext("\\d{4}-\\d{2}-\\d{2}")){
            System.out.println("生日为:" + scanner.next());
        }else {
            System.out.println("输入的格式非法,不是生日");
        }
        scanner.close();
    }
}

范例:使用Scanner操作文件

import java.io.File;
import java.util.Scanner;

public class TestFileStream {
    public static void main(String[] args) throws Exception{
        Scanner scanner = new Scanner(new File("C:" + File.separator + "Users" + File.separator
                + "DELL" + File.separator + "Desktop" + File.separator + "Test.txt"));
        scanner.useDelimiter("\n") ; // 自定义分隔符
        while (scanner.hasNext()){
            System.out.println(scanner.next());
        }
        scanner.close();
    }
}

总结:以后除了二进制文件拷贝的处理之外,那么只要是针对程序的信息输出都是用打印流(PrintStreamPrintWriter),信息输出使用Scanner

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章