第十六章 输入输出

今天我们来学习一下Java输入(Input)输出(Output),也就是IO。从大的角度出发,输入和输出都是针对设备而言的,比如从键盘输入信息,向显示器输出信息等等。从数据角度出发,输入和输出可以针对文件,网络接口而言的,比如从文件读取信息,或者向文件中写入信息等等。输入和输出是以“流(Stream)”的形式进行传输的。java.io包下提供了各种“流”类的接口,用以获取不同种类的数据,并通过标准的方法输入或输出数据。在整个Java.io包中最重要的就是5个类和一个接口。5个类指的是File、OutputStream、InputStream、Writer、Reader;一个接口指的是Serializable序列化。

我们可以把传输的数据比作“水流”,它具有方向性,从一端流向另一端。在Java语言中,输出输入的方向是按照内存的角度进行区分的,如果数据流向内存,那就是输入,如果数据从内存流出,那就是输出。今天我们所讲的输入输出主要是针对文件而言。也就是说,我们的输入就是向文件中写入数据,输出就是从文件中读取数据。我们知道,文件是保存在硬盘中的某个目录下。我们读取文件内容,就是将数据从硬盘中传输到内存中,这就是输入。如果是将内存中的数据写入到硬盘文件中,这就是输出。因此,根据流的方向性做分类的话,Java IO可以分为输入流(InputStream)和输出流(OutputStream),也就是对应了读取流(Reader)和写入流(Writer)。

我们在介绍一下字节和字符。计算机中的数据本质是二进制,这个毋庸置疑。这个二进制数据的最小剂量单位就是字节Byte,任何形式的数据(文字,图片,音视频等等)都可以按照字节的形式进行读取操作。但在我们日常编程中,除了多媒体数据之外,我们处理最多的即使文字数据,也就是字符数据。因此,根据数据内容做分类的话,Java IO可以分为字节流和字符流。其中,InputStream 和OutputStream读写均为字节数据;Reader 和 Writer读写均为字符数据。字符流其实就是将字节数据按照“编码格式”,将其转换成为了字符文字。

因此,如果我们的数据是一些图片或者音视频的话,那肯定要使用字节流了。如果数据就是文本数据的话,使用字符流是最好的。当然,我们也可以使用字节流,然后手动转化为字符数据。Java中字符是采用Unicode标准,一个字符是16位(也就是16个二进制0或1组成),即一个字符使用两个Byte字节来表示。所以,我们需要将两个字节的数据转化为一个字符。另外,如果输出流是带有缓冲功能的话,我们使用flush方法就可以讲数据写入流中,输入流也可以带有缓冲功能,缓冲的使用提高了流的读写效率!

我们上面也说到,本章节的Java IO主要是对文件的操作。因此,我们先看看Java中对文件操作提供的一些API吧。首先就是File类,该类位于java.io包中,用来表示一个文件。File类保存文件或目录的各种基础数据信息,包括文件名、文件长度、最后修改时间、是否可读等等,其内部的方法提供了创建,删除,获取当前路径,判断是否存在、获得当前目录中的文件列表等等功能。 File类共提供了三个不同的构造函数:

1)File (String pathname)                    参数就是文件名,可以是绝对路径文件名

2)File (String path, String name)       参数为文件的路径和文件名

3)File (File path, String name)           参数为文件的路径对象和文件名

说白了,实例化一个文件,就需要该文件的路径和文件名信息,该信息可以分开也可以合并。接下来,我们看看File类的一些方法:

1)public boolean exists( )               判断文件或目录是否存在

2)public boolean isFile( )                判断是否文件

3)public boolean isDirectory( )       判断是否目录

4)public String getName( )               返回文件名或目录名

5)public String getPath( )               返回文件或目录的路径。

6)public long length( )                    获取文件的长度,单位是字节

7)public String[ ] list ( )                    将目录中所有文件名保存在字符串数组中返回。

8)public boolean renameTo(File newFile)    重命名文件

9)public Boolean createNewFile()                 创建文件

10)public void delete( )                                    删除文件

11)public boolean mkdir( )                              创建目录

接下来,我们使用代码示例来创建一个文本文件,如下:

// 实例化一个文件类,文件名为 test.txt
// 文件的路径与当前Hello工程目录下
File file = new File("test.txt");

// 如果文件存在,则删除
if (file.exists()) file.delete();

// 创建文件 
file.createNewFile();
if(file.exists()) {
	System.out.println("文件创建成功");
}else {
	System.out.println("文件创建失败");
}

在上面的代码中,我们需要注意的是,我们实例化File的参数是”test.txt”,这种情况下,这个文件被创建的话,就是我们当前Hello这个工程的根目录,也就是“F:\workspace\Hello”。如果我们想把文件创建到其他目录下的话,可以写一个绝对路径,例如“F:\text.txt”。这样的话,文本文件text.txt就会被创建到了F盘下啦。我们去Hello工程目录下看一看,

可以看到,我们的text.txt文件创建成功了,因为是空的,所以大小是0字节。

接下来我们来介绍输出字节流OutputStream,它是所有的输出字节流的父类,它是一个抽象类,因此我们不能直接实例化它,而是要使用它的子类。在这些子类中,ByteArrayOutputStream和FileOutputStream分别向Byte数组和文件中写入数据。PipedOutputStream是向线程管道中写入数据。这些子类都继承并实现了父类OutputStream中的write写方法,用于向流中写入数据。另外两种重点方法是:flush方法用来刷新输出流,强制输出缓冲区中的字节数据。Close方法关闭输出流。由于我们是对文件进行读写操作,因此我们这里使用FileOutputStream类来做IO说明,先看看它的构造方法:

FileOutputStream(File file)   指定File对象,也就是写入数据到File对象文件中

FileOutputStream(File file, boolean append)       同上,append表示追加还是覆盖文件内数据

FileOutputStream(String name)    指定文件名称来直接创建对象

FileOutputStream(String name, boolean append) 同上,append表示追加还是覆盖文件内数据

// 创建字节输出流对象
OutputStream os = new FileOutputStream(file);

// 写出数据,参数为字节数组
os.write("hello".getBytes());

// 关闭流
os.close();

// 内容追加写入
os = new FileOutputStream(file, true);

// 写入换行符,linux下是"\n"
os.write("\r\n".getBytes());

// 追加内容
os.write("java".getBytes());

// 关闭流
os.close();

在上面的代码中,需要大家注意的是,流对象创建成功后,就可以使用了,使用完毕后必须关闭流。另外就是boolean append参数的设置,默认是覆盖之前的数据,也就是说,当我们使用write方法写入数据的时候,就会覆盖掉之前的数据。因此,一般情况下,我们都会设置boolean append参数为true,也就是追加数据。我们运行代码后,在去“F:\workspace\Hello”目录下查看这个“test.txt”文件,发现数据被写入进去啦,下图:

 

我们发现文件“test.txt”变成了11字节,打开这个文件,发现写入成功啦。

接下来,我们介绍字节输入流InputStream,同样它也是一个抽象类,我们需要使用它的子类。ByteArrayInputStream、StringBufferInputStream、FileInputStream分别从Byte数组、StringBuffer、和文件中读取数据。PipedInputStream是从线程管道中读取数据。他们的子类都继承和实现了父类InputStream中的read读方法。方法read()的返回值为-1时,表示读取数据结束。其他几个重点的方法,大家也要了解:

long skip(long n):在输入流中跳过n个字节,并返回实际跳过的字节数。

int available() :返回在不发生阻塞的情况下,可读取的字节数。

void close() :关闭输入流。

由于我们操作的是文件,因此我们使用FileInputStream类来说明IO操作,先看构造方法:

FileInputStream(File file)                      通过一个File对象构建输入流

FileInputStream(String name)             通过一个文件名来构建输入流

// 创建字节输入流对象
InputStream is = new FileInputStream(file);

// 实例化一个很大的字节数组
byte[] data = new byte[1024];
// 读取文件内容,返回读取的长度len
int len = is.read(data);

// 按照实际读取长度len来实例化字符串
String str = new String(data,0,len);
System.out.println(str);

// 关闭流
is.close();

因为我们不知道文件内容的大小,因此我们直接定义了一个大小为1024的字节数组。然后使用输入流将文件内容读取到了这个字节数组中,read方法返回真正读取字节的长度len。这样,我们在实例化字符串的时候,只使用字节数组中read读取到的真正长度len即可。否则如果直接实例化data字节数组的话,len长度后面的data字节数组元素被强制转化成字符串的话,会出现乱码。在我们日常编程中,我们也比较使用BufferedOutputStreamBufferedInputStream这两个流对象,因为它们具备缓冲功能,提高了读写效率。我们直接代码示例演示如下:

// 这里我们创建一个新文件
File bfile = new File("test2.txt");

// 创建缓冲输出流对象
OutputStream bos = new BufferedOutputStream(new FileOutputStream(bfile, true));

// 输出内容到文件
bos.write("Hello BufferedOutputStream".getBytes());

// 刷新缓冲输出流
bos.flush();

// 关闭流
bos.close();

// 创建缓冲输入流对象
InputStream bis = new BufferedInputStream(new FileInputStream(bfile));

// 创建字符串构造器
StringBuilder builder = new StringBuilder();

// 每次读取两个字节(一个字符)
int len = 0;
byte[] chr = new byte[2];
while ((len = bis.read(chr)) != -1)
builder.append(new String(chr));

// 输出字符串
System.out.println(builder.toString());

// 关闭流
bis.close();

在上述代码中,需要注意的就是关于StringBuilder类的使用,以及每次循环读取两个字节,因为在Java中两个字节构成一个字符,我们把字符追加到字符串构造器,最后在整体输出。另外,我们在说说其他常用的类。ObjectInputStream 和ObjectOutputStream类允许读取或写入自定义的类,但该类必须实现Serializable序列号接口,其实该接口并没有什么方法,可能相当于一个标记而已。有时没有必要存储整个对象的信息,而只是要存储一个对象的成员数据,就可以使用DataInputStream、DataOutputStream来写入或读出数据。而PrintStream 就是System.out这个对象。关于这些类,我们就不再详细介绍了。

接下来,我们介绍字符输入输出流Writer和Reader。对于文件的读取操作的话,我们使用最多的就是FileWriter和FileReader这两个类。另外BufferedWriter和BufferedReader也常用。请注意,我们介绍的这些都是字符流,也就是对应的文字。谈到中文的话,就必须指明编码格式了,比如说GBK,或者UTF-8等等。编码和解码必须使用同一种编码格式哦。我们之前讲过,Eclipse默认使用GBK编码,源码中的中文使用GBK编码,本案例代码也是GBK编码。

// 这里我们创建一个新文件
File cfile = new File("test3.txt");

// 创建FileWriter对象
Writer fw = new FileWriter(cfile, true);

// 写入两行文本
fw.write("你好");
fw.write("\r\n");
fw.write("Java");

// 刷新并关闭流
fw.flush();
fw.close();

// 创建FileReader对象
FileReader fr = new FileReader(cfile);

// 获取编码格式
String code = fr.getEncoding();
System.out.println("编码格式:" + code); // 编码格式:GBK

// 创建字符串构造器
StringBuilder sber = new StringBuilder();

// 循环一个字符一个字符读取
int ch = 0;
while((ch = fr.read()) != -1) 
sber.append((char) ch);

// 输出字符串
System.out.println(sber.toString());

// 关闭流
fr.close();

上述代码中,我们需要重点看的就是编码格式。如果必须指定其他编码格式的话,我们在实例化OutputStreamWriter和InputStreamReader对象的时候,可以指定编码格式。这里我们不再详细介绍了。最后我们来介绍另外两个比较常用的字节输入输出流,BufferedWriter和BufferedReader。他们使用了缓冲,提高了读写效率。接下来,我们直接使用代码示例演示:

// 这里我们创建一个新文件
File cbfile = new File("test4.txt");

// 构建BufferedWriter对象,我们借助了FileWriter
BufferedWriter bw = new BufferedWriter(new FileWriter(cbfile));

// 写入字符串文本
bw.write("hello");

// 写入换行
bw.newLine();

// 写入数字
bw.write("123");

// 刷新并关闭流
bw.flush();
bw.close();

// 创建BufferedReader对象,借助FileReader类
BufferedReader br = new BufferedReader(new FileReader(cbfile));

// 创建字符串构造器
StringBuilder sbuider = new StringBuilder();

// 循环一行一行读取字符文本
String line = null;
while((line=br.readLine())!=null)
sbuider.append(line);

// 输出字符串
System.out.println(sbuider.toString());

// 关闭流
br.close();

上面的代码中,我们可以看到,BufferedWriter 类提供了newLine 方法用写入换行符。同时,BufferedReader类提供了readLine方法用来读取一行字符文本。这些对我们操作字符文本来讲,是非常有用的。

本课程涉及的代码可以免费下载:
https://download.csdn.net/download/richieandndsc/85645935

今天的内容就讲的这里,我们来总结一下。今天我们主要讲了Java的输入输出。Java的输入和输出的方向是根据内存而定的,向内存(变量)写入数据就是输入,把内存(变量)输出到其他地方(硬盘上的文件)就是输出。根据数据类型,可以分为字节流和字符流。字节流我们经常使用带有缓冲功能的BufferedOutputStream和BufferedInputStream两个类。如果是针对文件进行操作的话,我们经常使用FileOutputStream和FileInputStream两个类。字符流的话,我们经常使用带有缓冲功能的BufferedWriter和BufferedReader两个类了。另外,对于字符流的操作,我们还需要注意编码格式的问题。好的,谢谢大家的收看,欢迎大家在下方留言,我也会及时回复大家的留言的。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值