前言:为什么要有输入输出流
为什么 Java 要有输入输出流?我们先来看看用现有手段写一个简单的输入输出程序:
package big;
import java.util.Scanner;
public class Simlpe_input_and_output {
public static void main(String[] args) {
Scanner read = new Scanner (System.in);
System.out.println("请输入你想输入的内容:");
String a = read.next();
System.out.println("接下来打印这句话:");
System.out.println(a);
}
}
你看看这多麻烦,要想从键盘上输入还得先导入 util 包里的 Scanner 类,在一开始学 Java 的时候差点因为这个导致厌学(C语言直接 scanf 、printf 不好吗?为啥 Java还得三行代码?) 。 接下来还是看看课本上的介绍吧:Java 语言的输入输出功能是十分强大而灵活的(说实话,我现在都不信🥱🥱🥱),对于数据的输入和输出操作以 “ 流 ”(stream)的方式进行。JDK 提供了各种各样的 “ 流 ” 类,用以获取不同种类的数据,它们都定义在 java.io 包中。程序中通过标准的方法输入或输出数据。
嗯~~那下面我们就来看看这 Java 输入输出流到底咋回事吧,这里主要还是对课本中的示例代码进行复现来学习的,所以课本看不懂的小伙伴可以试试我的这篇学习笔记。
一、Java 的标准输入输出
标准输入输出功能是通过 Java 的 System 系统类来实现的。System 类在 java.lang 包中,是一个最终类,里面的大多数方法都是静态方法,所以在程序中可以直接调用它们(包括标准输入输出)。
什么?不知道 java.lang 是啥?老师上课讲过的,language 嘛,语言的缩写。java.lang包是java语言的核心,它提供了java中的基础类。包括基本Object类、Class类、String类、基本类型的包装类、基本的数学类等等最基本的类。也就是说,一个 Java 程序必备 java.lang 包(默认就存在的吧,我也没见课本示例程序里写啊,是吧?)
标准输入 System.in
System.in 作为 InputStream 类的对象实现标准输入,可以调用它的 read 方法(有以下三种格式)来读取键盘数据:
- public abstract int read()
- public int read (byte[] b)
- public int read (byte[] b, int off, int len)
输入流结束,返回 -1。
标准输出 System.out
System.out 作为 PrintStream 打印流类的对象实现标准输出,可以调用它的 ptint 、println 或 write 方法来输出各种类型的数据。
print 和 println 都不用介绍了,目前用的最多的代码就是写这个的。还是说一下 wtite 方法吧,它用来输出字节数组,注意一点:write 方法在输出单个字节时并不能立即显示出来,还需调用 flush 或 close 方法来强制回调(??不懂)。
程序 exp8_3:简单的输入输出
package big;
import java.io.IOException;
public class exp8_3_simple_inputoutput {
public static void main(String[] args) throws java.io.IOException {
byte buffer[] = new byte[40];
System.out.println("从键盘上输入不超过40个字符,按回车键结束输入:");
int count = System.in.read(buffer);
System.out.println("保存在缓冲区的元素个数为" + count);
System.out.println("输出 buffer 元素值:");
for (int i = 0; i < count; i ++) {
System.out.print(" " + buffer[i]);
}
System.out.println();
System.out.println("输出 buffer 字符元素:");
System.out.write(buffer, 0, buffer.length);
}
}
输出如下: 下面做个小实验,对该程序做几点改变,看看有什么变化:
- 最后一行
write 方法中最后一个参数不应该是 buffer.length-1 吗?结果没有任何改变,看来没什么好说的了,我认为 Java 不严谨,支持我吗,同志们? - 我不想在输出
buffer 元素值时看到的是 ASCII 码,我要看我输入的字母。简单,安排!这只是在 for 循环里面的输入加上一个强制转换符就行了System.out.print(" " + (char)buffer[i]); 。问题是,多了两行空格,这怎么解释?🤔🤔🤔🤨🤨🤨
如此简单一个程序,我以为搞懂了,可是回看一下课本上的说明,还是有问题滴:
- 我们在键盘上输入7个字符,可是控制台却显示保存在缓冲区的元素个数为 9,这是因为还有回车符 “ \r ” 占用了两个元素,且元素值为 ASCII 码值。(会不会那两行空格就是因为这个原因?)
- 对程序中的第一条注释,原来课本里给出了说明:本例用到了
read(byte[] b) 方法,因其会产生输入异常,可以放在 try···catch 块中执行,或令 main 方法将异常上交(即在声明语句中加入 throws java.io.IOException ),这样才能通过编译。 - 课本上说:本例使用了
write 方法直接输出了字节数组的内容,如果使用 println 方法可将字节数组的内容转换为字符串,否则不能正常显示。于是把System.out.write(buffer, 0, buffer.length) 改成了下面这样,但不行耶,老师怎么把字节数组里的内容转换成字符串啊?(我做了什么?最后为什么会有好多空格和 null 这几个字符输出?) :
String str = null;
for (int i = 0; i < count; i ++) {
str += (char)buffer[i];
}
System.out.println(str);
PS:这个问题已得到解决,后面的程序(第三小节输入输出流类的应用里最后一个实验)中会把字节数组转换成字符串,这里写好了就不动了吧,主要是懒,哈哈~~
二、输入输出流框架
课本上哇哇哇说了一大堆,实在没什么可讲的,就直接放图吧:
数据流以输入流的形式被程序获取,再以输出流的形式将数据输出到其它设备。图 1 为输入流模式,图 2 为输出流模式: 输入输出流总框架图: 最后提一下关于输入输出流的分类,了解即可:
数据流是 Java 进行 I/O 操作的对象,它按照不同的标准可以分为不同的类别。
- 按照流的方向主要分为输入流和输出流两大类;
- 数据流按照数据单位的不同分为字节流和字符流;
- 按照功能可以划分为节点流和处理流;
- 依据类的作用,可以分为文件流、缓冲流、数据流、管道流、转换流、打印流、对象流等。
三、输入输出流类的应用
课本程序 exp8.4
例 8.4:创建两个 File 类的对象,分别判断两个文件是否存在;如果不存在,则新建。然后从键盘输入字符数据存入数组 b 里,通过文件输出流,把数组里的字符写入到 hello1.txt 文件,再从 hello1.txt 中读取数据,写到文件 hello2.txt 中。以下是源代码。
package big;
import java.io.*;
public class stream_exp8_4 {
public static void main(String[] args) {
int len;byte b[] = new byte[20];
File file1 = new File("D:\\学校课程学习\\java程序设计\\eclipse\\工作空间\\hello1.txt");
File file2 = new File("D:\\学校课程学习\\java程序设计\\eclipse\\工作空间\\hello2.txt");
FileOutputStream fos = null;
try {
if (!file1.exists())
file1.createNewFile();
if (!file2.exists());
file2.createNewFile();
System.out.println("请输入你想存入file1文件的内容:");
len = System.in.read(b);
fos = new FileOutputStream(file1, true);
fos.write(b, 0, len);
FileReader in = new FileReader(file1);
BufferedReader bin = new BufferedReader(in);
FileWriter out = new FileWriter(file2, true);
String str;
System.out.println("完美复制到file2文件中的内容:");
while ((str = bin.readLine()) != null) {
System.out.println(str);
out.write(str+"\n");
}
out.close();in.close();fos.close();
}
catch(IOException e) {e.printStackTrace();}
}
}
运行结果如下: 看着没问题是吧,再试试重复运行输入一遍。由此我们会发现打印在控制台中的有两个 “ 相信中国!” ,打开文件后发现 hello1.txt 里有两个 “ 相信中国!” ,文件 hello2.txt 里有三个 “ 相信中国!” 。仔细想一想,也就该是这样哈!
改写 exp8.4(1)
OK,接下来,我们再来做些实验,首先有一个问题是装饰类只是加快了读的速度,其实没它照样可以完成任务,我们这样来改写程序:
FileReader in = new FileReader(file1);
FileWriter out = new FileWriter(file2, true);
for (int i = 0; i < len; i ++) {
System.out.print((char)b[i]);
out.write(b[i]);
}
这样一来,运行结果会变成怎样呢?结果是识别不了汉字了(英文倒是可以),另外就是不管运行几遍,控制台打印的都只会是本次运行时键盘输入的内容,文本文件中也只会增加本次输入的内容,大家可以对比着来看看: 不明白,为啥啊???
改写 exp_8.4(2)
再来一个实验:在示例中,我们输出流用的是字节流,输出流用的是字符流,统一一下不好吗?我先来试试统一字节流输入输出好了。 终于,我成功了,就像写高考数学题,写的时候真难,写完了发现好简单😂😂😂
package big;
import java.io.*;
public class exp8_4_zijieliushurushuchu {
public static void main(String[] args) {
int len;byte b[] = new byte[20];
File file1 = new File("D:\\学校课程学习\\java程序设计\\eclipse\\工作空间\\hello1.txt");
File file2 = new File("D:\\学校课程学习\\java程序设计\\eclipse\\工作空间\\hello2.txt");
FileOutputStream fos = null;
try {
if (!file1.exists())
file1.createNewFile();
if (!file2.exists());
file2.createNewFile();
System.out.println("请输入你想存入file1文件的内容:");
len = System.in.read(b);
fos = new FileOutputStream(file1, true);
fos.write(b, 0, len);
FileInputStream in = new FileInputStream(file1);
BufferedInputStream bin = new BufferedInputStream(in);
FileOutputStream out = new FileOutputStream(file2, true);
out.write(b);
String str = new String(b);
System.out.println(str);
out.close();in.close();fos.close();
}
catch(IOException e) {
e.printStackTrace();
}
}
}
就在这个程序里,我把字节数组转换成了字符串,顺利地在控制台(屏幕)上打印出来了(这也是为什么课上有同学问 “ 为什么可以用字节数组输出汉字 ” 问题的答案了,都转换成了字符串,那为什么还不能输出汉语呢,是吧?)。大家看那输入输出流框架图都是一愣一愣的,不过呢,这里有篇文章很好的总结了字节输入输出流的框架和方法,我就是照着这篇文章完成这个实验的,分享一下。
???还有统一字符输入输出流???再说吧,累了。
四、RandomAccessFile 类
课上老师讲:RandomAccessFile 类是一个随机存取文件类。虽然不明白这句话什么意思,但下一句确实醍醐灌顶:这个类可以实现 Java 输入输出流的任意操作。我了个乖乖,相比较框架图里那么多类,这样子只用一个类就方便多了呀!
例 8.5 :通过 RandomAccessFile 类从文件的不同位置读写不同长度的数据,讲字符串数据添加在文件尾部。
package big;
import java.io.*;
public class exp8_5 {
public static void main(String[] args) {
String str[] = {"First lint\n", "second line\n", "Last line\n"};
try {
RandomAccessFile rf = new RandomAccessFile("demo.txt", "rw");
System.out.println("\n光标的位置为:" + rf.getFilePointer());
System.out.println("文件的长度为:" + rf.length());
rf.seek(rf.length());
System.out.println("光标现在的位置为:" + rf.getFilePointer());
for (int i = 0; i < 3; i ++)
rf.writeBytes(str[i]);
rf.seek(0);
System.out.println("\n文件现在的内容:");
String s;
while ((s = rf.readLine()) != null)
System.out.println(s);
rf.close();
}
catch (FileNotFoundException fnoe) {}
catch (IOException ioe) {}
}
}
运行结果和课本里的一样:
以下摘自课本中的说明:
- 从本例看,随机存取文件类的的使用方法,也是先建立文件流通道,打开文件,然后进行读写操作,最后关闭文件通道。只是不用分输入流和输出流。
- 可以用于多线程下载或多个线程同时写数据到文件。
怎么说?其实没什么,我打一遍代码试着运行也是为了看看到底有多方便,也确实比原始输入输出流分开写简单许多。
其实还有一个小节讲的是 “ 对象序列化与对象流类 ” ,课上老师好像也是一笔带过(是不是啊?我没有认真听这点内容,感觉还没讲就过去了,连示例程序都没啥印象)
输入输出流总结
我看课本上的小结讲的是使用输入输出流的参考步骤和流程,但我就 get 不到,没有感觉。看来还是对 Java 不熟悉,对输入输出流不熟悉( c++ 里不也是输入输出流 cin、cout 吗?感觉就像一个在天上,一个在地下。。。)
在最后我还是说一下输入输出和读写的关系吧,我上机的时候就把输入输出流的意思给搞反了。记住一点:输入输出都是以程序为参照物的
- 输入流:用于从键盘或文件(数据源)获得数据传递给程序,只能读不能写;
- 输出流:用于将数据从程序传递给数据接收者(宿点),如内存、显示器屏幕、打印机或文件,只能写不能读。
Java 中一切名词皆为类,一切类都要实例化对象,一切对象都有属性和方法,Java 就是靠着对象的属性和方法来完成编程解决问题的,这就是我学习 Java 以来的深刻体会,这就是面向对象编程!!!
|