IO第十一回:处理流之四:标准输入流 & 标准输出流

IO第十一回:处理流之四:标准输入流 & 标准输出流

标签: IO流


在IO中,标准输入流是System.in,标准输出流是System.out。我们经常使用它们,我们今天要学一学他们的原理

System.out

我们初学java的第一个程序是”hello world”

public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("hello world");
    }
}

上面程序到底是怎么在屏幕上输出“hello world”的呢?
这就是下面要讲解的内容,即System.out.println(“hello world”)的原理。

out的定义

我们先看看System.java中out的定义,源码如下:

public final class System {
    ...

    public final static PrintStream out = null;

    ...
}

从中,我们发现out是一个PrintStream类型的静态成员变量:

  • out是System类的静态变量,因此我们在使用时,可以以类名.静态变量名的形式来使用。

  • out是PrintStream对象,PrintStream打印类中有许多重载的println()方法。

接下来,看它是如何被初始化的,它是怎么和屏幕输出关联的?

我们还是一步步来分析,首先看看System类中的initializeSystemClass()方法。

initializeSystemClass()的源码如下:

private static void initializeSystemClass() {
    ......

    FileInputStream fdIn = new FileInputStream(FileDescriptor.in);
    FileOutputStream fdOut = new FileOutputStream(FileDescriptor.out);
    FileOutputStream fdErr = new FileOutputStream(FileDescriptor.err);
    setIn0(new BufferedInputStream(fdIn));
    setOut0(new PrintStream(new BufferedOutputStream(fdOut, 128), true));
    setErr0(new PrintStream(new BufferedOutputStream(fdErr, 128), true));

    ......
}

我们只关注out相关的代码,可以细分为一下几步:

  • 第1步 FileDescriptor fd = FileDescriptor.out;
    获取FileDescriptor.java中的静态成员out,out是一个FileDescriptor对象,它实际上是“标准输出(屏幕)”的标识符。

  • 第2步 FileOutputStream fdOut = new FileOutputStream(fd);
    创建“标准输出(屏幕)”对应的“文件输出流”。

  • 第3步 BufferedOutputStream bufOut = new BufferedOutputStream(fdOut, 128);
    创建“文件输出流”对应的“缓冲输出流”。目的是为“文件输出流”添加“缓冲”功能。

  • 第4步 PrintStream ps = new PrintStream(bufout, true);
    创建“缓冲输出流”对应的“打印输出流”。目的是为“缓冲输出流”提供方便的打印接口,如print(), println(), printf();使其能方便快捷的进行打印输出。

  • 第5步 setOut0(ps);
    执行setOut0(ps);

其中,第5步的setOut0(ps)的声明,如下:

private static native void setOut0(PrintStream out);

从中,我们发现setOut0()是一个native本地方法。其作用就是将ps设置为System.java的out静态变量。

FileDescriptor.out就是机器的“标准输出(屏幕)”的文件标识符。我们可以通俗的将文件标识符就理解为,FileDescriptor.out就是代表的“标准输出”。

因此,在initializeSystemClass()中,上面的5步就是将“FileDescriptor.out”封装了起来。封装后的System.out既有缓冲功能;又有便利的操作接口,如print(), println(), printf()。

记录日志文件

标准输出流对象会将数据打印到控制台上。查阅API可知有如下方法,

static void setOut(PrintStream out) //重新分配“标准”输出流。

可以重新指定输出流对象,即将System.out.println();的输出内容打印到我们想打印到的地方。

File file = new File("F:\\a.txt");
PrintStream printStream = new PrintStream(file);
System.setOut(printStream);
System.out.println("打印到F:\\a.txt中");

这时候内容回写入到文件a.txt中去,而不是打印在控制台中。

我们可以利用这个方法来记录日志文件。

假设有代码:

try{
   int n = 5/0;
}catch(Exception e){
   e.printStackTrace();
}

执行结果会抛出我们想要的错误日志信息。

java.lang.ArithmeticException: / by zero
    at log.DemoLog.main(DemoLog.java:26)

这时候想将日志信息保存起来怎么办呢?

  • void printStackTrace()
    将此throwable和其追溯打印到标准错误流。
  • void printStackTrace(PrintStream s)
    将此throwable和其追溯打印到指定的打印流。
  • void printStackTrace(PrintWriter s)
    将此throwable和其追溯打印到指定的打印作者。

看到Exception类中的这3个重载方法,我们不难得知,只要给他指定一个打印输出流对象当中,即可将日志信息保存到我们想要的地方。

File file = new File("F:\\a.log");
        PrintStream printStream = new PrintStream(file);
        try{
            int n = 5/0;//除数为零异常
        }catch(Exception e){
            e.printStackTrace(printStream);
        }

但是我们这有一个缺陷,每次出现异常,新的异常的打印都会替换掉原来的异常。

打印流PringStream接收的参数,可以是File类型的,可以是OutputStream类型的,也可以是String类型的文件名。

当接受OutputStream类型的对象时,我们可以在构造方法时添加append参数:
FileOutputStream(File file, boolean append)
创建一个向指定 File 对象表示的文件中写入数据的文件输出流。如果第二个参数append为 true,则将字节写入文件末尾处,而不是写入文件开始处。

因此

File file = new File("F:\\a.log");
        PrintStream printStream = new PrintStream(new FileOutputStream(file,true),true);
        try{
            int n = 5/0;//除数为零异常
        }catch(Exception e){
            e.printStackTrace(printStream);
        }

实现了日志的打印保存。

System.in

public static final InputStream in
“标准”输入流。 该流已经打开,准备提供输入数据。通常,该流对应于键盘输入或由主机环境或用户指定的另一个输入源。

System.in原理

 public final static InputStream in = null;

initializeSystemClass()

FileInputStream fdIn = new FileInputStream(FileDescriptor.in);
FileOutputStream fdOut = new FileOutputStream(FileDescriptor.out);
FileOutputStream fdErr = new FileOutputStream(FileDescriptor.err);
setIn0(new BufferedInputStream(fdIn));

首先System.in指的是一个输入流(InputStream),它的功能是创建一个由键盘输入的流。

再说说System.in的工作原理:

我们知道一个输入流一定要有一个输入源为其提供数据,那System.in这个输入流的源头在哪里呢?

System.in比较特殊,在使用这个输入流的时候,数据源会更新,也就是说它的源头是新开辟出来的空间,用来储存键盘上输入的内容。

当我们调用System.in语句的时候实际上我们做了两个动作:
1. 创建了一个字节流;
2. 创建了一个类似于文件的储存空间(用完即被消除)。
试分析下面语句:

BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
while((s=in.readLine)!=null){
        //循环主体
}

这种从键盘上向一个流中输入数据的方法称为阻塞式方法,当你不在键盘上输入数据或者没有按enter键输入的时候,循环是不会进行的,流会暂停流动。

简单标准输入

System.in作为字节输入流类InputStream的对象实现标准输入,通过read()方法从键盘接受数据。

int read() 
// 返回输入数值的ASCII码,,该值为0到255范围内的int字节值。若返回值为-1,说明没有读取到任何字节读取工作结束。

int read(byte b[]) 
// 读入多个字节到缓冲区b中,返回值是读入的字节数

int read(byte b[],int offset,int len)
//读入多个字节到缓冲区b中,指定起始位置和长度

我们针对三种方法进行实践:

   /**
     * System.in.read()返回值为输入数值的ASCII码,该值为0到 255范围内的int字节值
     * 如果因为已经到达流末尾而没有可用的字节,则返回值 -1。
     */
public static void testRead1() throws IOException {
        int a = 0;
        System.out.println("请输入a:");
        a = System.in.read();     //返回int类型的ASCII值
        System.out.println("a=" + a);
        System.out.println("(char)a=" + (char)a);
    }

上面的代码只能返回一个字符

请输入aa
a=97
(char)a=a
   /**
     * 将要输入的数据存进字节数组中
     * 
     */
public static void testRead2() throws  IOException {
        byte[] bytes = new byte[10];
        System.in.read(bytes);
        for (int i = 0; i < bytes.length; i++) {
            if (bytes[i] != 0) {
                System.out.print((char)bytes[i] );
            }
        }

下面的程序通过while循环,可以输入一行数组,直到输入停止

 public static void testRead3() throws IOException {
        int b;
        System.out.println("请输入:");
        while ((b = System.in.read()) != -1) {
            System.out.print((char) b);
        }
    }

Scanner类对象

public final class Scanner extends Object implements Iterator<String> Closeable

一个简单的文本扫描器,可以使用正则表达式解析原始类型和字符串。

构造方法

我们可以从Scanner类的构造方法中发现其用法:

//文本扫描器的数据源是一个File类型的文件对象
Scanner(File source) 
Scanner(File source, String charsetName) 
//文本扫描器的数据源是一个InputStream输入流的对象
Scanner(InputStream source) 
Scanner(InputStream source, String charsetName) 
//文本扫描器直接将String字符串当做数据源
Scanner(String source) 
//文本扫描器的数据源是一个路径
Scanner(Path source) 
Scanner(Path source, String charsetName) 
//文本扫描器的数据源是一个Readable字符源
Scanner(Readable source) 
构造一个新的 Scanner ,产生从指定源扫描的值。  
/文本扫描器的数据源是一字符通道
Scanner(ReadableByteChannel source) 
构造一个新的 Scanner ,产生从指定通道扫描的值。  
Scanner(ReadableByteChannel source, String charsetName) 
构造一个新的 Scanner ,产生从指定通道扫描的值。  
重要方法
  • void close()
    关闭此扫描仪。

  • Pattern delimiter()
    返回 Pattern这个 Scanner正在使用来匹配分隔符。

  • boolean hasNext()
    如果此扫描仪在其输入中有另一个令牌,则返回true。

  • boolean hasNextLine()
    如果扫描仪的输入中有另一行,则返回true。

  • String next()
    查找并返回此扫描仪的下一个完整令牌。

  • int nextInt()
    将输入的下一个标记扫描为 int 。

  • String nextLine()
    将此扫描仪推进到当前行并返回跳过的输入。

从hasNext(),next()繁衍了大量的同名不同参方法,这里不一一列出,感兴趣的,可以查看API

代码演示

当通过new Scanner(System.in)创建一个Scanner,控制台会一直等待输入,直到敲回车键结束,把所输入的内容传给Scanner,作为扫描对象。如果要获取输入的内容,则只需要调用Scanner的nextLine()方法即可。

//逐行扫描文件,并逐行输出 
    public static void fileScanner() throws IOException{
        File file = new File("PrintStream.txt");
        FileInputStream fis = new FileInputStream(file);
        Scanner scanner = new Scanner(fis);
        //如果在此扫描器的输入中存在另一行,则返回 true
        while (scanner.hasNextLine()) { 
            //此扫描器执行当前行,并返回跳过的输入信息。
            System.out.println(scanner.nextLine()); 
        }
        fis.close();
        scanner.close();
    }
//从键盘输入,以空格为分隔,以回车为结束
    public static void keyboardScanner1() {
        System.out.println("请输入:");
        Scanner scanner = new Scanner(System.in);
        //判断扫描器中当前扫描位置后是否还存在下一段
        while (scanner.hasNext()) { 
            //查找并返回来自此扫描器的下一个完整标记,返回的String字符
            System.out.println(scanner.next());
        }
    }

    public static void keyboardScanner2() {
        System.out.println("请输入:");
        Scanner scanner = new Scanner(System.in);
        while (true) {
            String line = scanner.nextLine();
            if (line.equals("exit")) break;
            System.out.println(">>>" + line);
        }
    }
//hasNext和next方法的衍生方法
    public static void keyboardScanner2() {
        Scanner in =  new Scanner(System.in);
        System.out.println("请输入一个整数");
        while(in.hasNextInt()){
            int num = in.nextInt();
            System.out.println("数字"+num);//输入999 32 只读到999
            System.out.println("请输入一个字符串");
            String str = in.next();//输入 adcd ef 只读adcd

            System.out.println("字符串"+str);
        }
    }
//Scanner useDelimiter(String pattern) 
//将此扫描器的分隔模式设置为从指定的构造的模式 String 。  
    public static void testUseDelimiter() {
        Scanner s = new Scanner("123 asda bf 12 123 nh l,sf.fl ...adafafa    lda");
        s.useDelimiter(" |, |\\.");  //用“ ”或“,”或“.”来分隔
        while (s.hasNext()) {
            System.out.println(s.next());
        }
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值