Java字节流-InputStream与OutputStream

Java字节流-InputStream与OutputStream1、字节流什么是字节流计算机中都是二进制数据,一个字节是8个2进制位.字节可以表示所有的数据,比如文本,音频,视频.图片,都是作为字节存在的.也

大家好,欢迎来到IT知识分享网。

1、字节流

什么是字节流

计算机中都是二进制数据,一个字节是8个2进制位.字节可以表示所有的数据,比如文本,音频,视频.图片,都是作为字节存在的.也就是说字节流处理的数据非常多。

在文本文件中存储的数据是以我们能读懂的方式表示的。而在二进制文件中存储的数据是用二进制形式表示的。我们是读不懂二进制文件的,因为二进制文件是为了让程序来读取而设计的。例如,Java的源程序(.java源文件)存储在文本文件中,可以使用文本编辑器阅读,但是Java的类(字节码文件)存储在二进制文件中,可以被Java虚拟机阅读。二进制文件的优势在于它的处理效率比文本文件高。

我们已经知道File对象封装的是文件或者路径属性,但是不包含向(从)文件读(写)数据的方法。为了实现对文件的读和写操作需要学会正确的使用Java的IO创建对象。

字节流的抽象基类:

输入流:java.io.InputStream

输出流:java.io.OutputStream

特点:

字节流的抽象基类派生出来的子类名称都是以其父类名作为子类名的后缀。

如:FileInputStream, ByteArrayInputStream等。

说明:

字节流处理的单元是一个字节,用于操作二进制文件(计算机中所有文件都是二进制文件)

1.1 InputStream

案例:读取”c:/a.txt”文件中的所有内容并在控制台显示出来。

注意:事先准备一个a.txt并放到c:/下,不要保存中文。

a, 使用read()方法实现。

b, 使用int read(byte[] b)方法实现。

写代码读取”c:/a.txt”文件中的所有的内容并在控制台显示出来

实现:

查看api文档

InputStream 有read方法,一次读取一个字节,OutputStream的write方法一次写一个int。发现这两个类都是抽象类。意味着不能创建对象,那么需要找到具体的子类来使用。

通过查看api文档,找到了FileInputStream类,该类正是我们体验Io流的一个输入流。

实现;显示指定文件内容。

明确使用流,使用哪一类流?使用输入流,FileInputStream

第一步:

1:打开流(即创建流)

第二步:

2:通过流读取内容

第三步:

3:用完后,关闭流资源

显然流是Java中的一类对象,要打开流其实就是创建具体流的对象,由于是读取硬盘上的文件,应该使用输入流。所以找到了InputStream类,但是InputStream是抽象类,需要使用它的具体实现类来创建对象就是FileInputStream。通过new 调用FileInputStream 的构造方法来创建对象。发现FileInputStream的构造方法需要指定文件的来源。查看构造方法,可以接受字符串也可以接受File对象。我们通过构建File对象指定文件路径。

使用流就像使用水管一样,要打开就要关闭。所以打开流和关闭流的动作是比不可少的。如何关闭流?使用close方法即可,当完成流的读写时,应该通过调用close方法来关闭它,这个方法会释放掉十分有限的操作系统资源.如果一个应用程序打开了过多的流而没有关闭它们,那么系统资源将被耗尽.

如何通过流读取内容?

查找api文档通过read方法,查看该方法,发现有返回值,并且是int类型的,该方法一次读取一个字节(byte)

1.1.1 输入流读取方式1:

read方法()

一次读取一个字节,读到文件末尾返回-1.

仔细查看api文档发现read方法如果读到文件的末尾会返回-1。那么就可以通过read方法的返回值是否是-1来控制我们的循环读取。

/**

* 根据read方法返回值的特性,如果独到文件的末尾返回1,如果不为1就继续向下读。

* */

private static void showContent(String path) throws IOException {

// 打开流

FileInputStream fis = new FileInputStream(path);

int len = fis.read();

while (len != -1) {

System.out.print((char)len);

len = fis.read();

}

// 使用完关闭流

fis.close();

}

我们习惯这样写:

/**

* 根据read方法返回值的特性,如果独到文件的末尾返回1,如果不为1就继续向下读。

* */

private static void showContent(String path) throws IOException {

// 打开流

FileInputStream fis = new FileInputStream(path);

int len;

while ((len = fis.read()) != -1) {

System.out.print((char) len);

}

// 使用完关闭流

fis.close();

}

1.1.2 输入流读取方式2:

使用read(byte[] b) 方法。使用缓冲区(关键是缓冲区大小的确定)

使用read方法的时候,流需要读一次就处理一次,可以将读到的数据装入到字节数组中,一次性的操作数组,可以提高效率。

问题1:缓冲区大小

那么字节数组如何定义?定义多大?

可以尝试初始化长度为5的byte数组。通过read方法,往byte数组中存内容

那么该read方法返回的是往数组中存了多少字节。

/**

* 使用字节数组存储读到的数据

* */

private static void showContent2(String path) throws IOException {

// 打开流

FileInputStream fis = new FileInputStream(path);

// 通过流读取内容

byte[] byt = new byte[5];

int len = fis.read(byt);

for (int i = 0; i < byt.length; i++) {

System.out.print((char) byt[i]);

}

// 使用完关闭流

fis.close();

}

问题一: 缓冲区太小:

数据读取不完.

测试发现问题,由于数组太小,只装了5个字节。而文本的字节大于数组的长度。那么很显然可以将数组的长度定义大一些。例如1024个。

/**

* 使用字节数组存储读到的数据

* */

private static void showContent2(String path) throws IOException {

// 打开流

FileInputStream fis = new FileInputStream(path);

// 通过流读取内容

byte[] byt = new byte[1024];

int len = fis.read(byt);

for (int i = 0; i < byt.length; i++) {

System.out.print(byt[i]);

}

// 使用完关闭流

fis.close();

}

问题二:缓冲区数组有默认值.

测试,打印的效果打印出了很多0,因为数组数组有默认初始化值,所以,我们将数组的数据全部都遍历和出来.现在需要的是取出数组中的部分数据.需要将循环条件修改仔细查看api文档。发现该方法read(byte[] b)返回的是往数组中存入了多少个字节。就是数组实际存储的数据个数。

/**

* 使用字节数组存储读到的数据

* */

private static void showContent2(String path) throws IOException {

// 打开流

FileInputStream fis = new FileInputStream(path);

// 通过流读取内容

byte[] byt = new byte[1024];

int len = fis.read(byt);

for (int i = 0; i <len; i++) {

System.out.print(byt[i]);

}

// 使用完关闭流

fis.close();

}

总结:

问题一:为什么打印的不是字母而是数字,

是ascii码。

如何显示字符,强转为char即可

问题二:注意:回车和换行的问题。

windows的换车和换行是”\r\n” 对应码表是13和10 。

1.1.3 输入流读取方式3:

使用read(byte[] b,int off,int len)

查看api文档,

b显然是一个byte类型数组,当做容器来使用

off,是指定从数组的什么位置开始存字节

len,希望读多少个

其实就是把数组的一部分当做流的容器来使用。告诉容器,从什么地方开始装要装多少。

/**

* 把数组的一部分当做流的容器来使用

* read(byte[] b,int off,int len)

*/

private static void showContent3(String path) throws IOException {

// 打开流

FileInputStream fis = new FileInputStream(path);

// 通过流读取内容

byte[] byt = new byte[1024];

// 从什么地方开始存读到的数据

int start = 5;

// 希望最多读多少个(如果是流的末尾,流中没有足够数据)

int maxLen = 6;

// 实际存放了多少个

int len = fis.read(byt, start, maxLen);

for (int i = start; i < start + maxLen; i++) {

System.out.print((char) byt[i]);

}

// 使用完关闭流

fis.close();

}

案例:测试skip方法

通过Io流,读取”c:/a.txt”文件中的第9个字节到最后所有的内容并在控制台显示出来。

分析:其实就是要跳过文件中的一部分字节,需要查找API文档。可以使用skip方法skip(long n),参数跟的是要跳过的字节数。

我们要从第9个开始读,那么要跳过前8个即可。

/**

* skip方法

*

* */

private static void showContent4(String path) throws IOException {

// 打开流

FileInputStream fis = new FileInputStream(path);

// 通过流读取内容

byte[] byt = new byte[1024];

fis.skip(8);

int len = fis.read(byt);

System.out.println(len);

System.out.println(“**********”);

for (int i = 0; i < len; i++) {

System.out.println((char) byt[i]);

}

// 使用完关闭流

fis.close();

}

1.1.4 输入流读取方式4:

使用缓冲(提高效率),并循环读取(读完所有内容).

总结:读完文件的所有内容。很显然可以使用普通的read方法,一次读一个字节直到读到文件末尾。为了提高效率可以使用read(byte[] byt);方法就是所谓的使用缓冲提高效率。我们可以读取大文本数据测试(大于1K的文本文件.)

/**

* 使用字节数组当缓冲

* */

private static void showContent5(String path) throws IOException {

FileInputStream fis = new FileInputStream(path);

byte[] byt = new byte[1024];

int len = fis.read(byt);

System.out.println(len);

String buffer = new String(byt, 0, len);

System.out.print(buffer);

}

注意:如何将字节数组转成字符串? 可以通过创建字符串对象即可。

发现:一旦数据超过1024个字节,数组就存储不下。

如何将文件的剩余内容读完?

我们可以通过通过循环保证文件读取完。

/**

* 使用字节数组当缓冲

* */

private static void showContent7(String path) throws IOException {

FileInputStream fis = new FileInputStream(path);

byte[] byt = new byte[1024];

int len = 0;

while ((len = fis.read(byt)) != -1) {

System.out.println(new String(byt, 0, len));

}

}

1.2 OutputStream

字节输出流

案例:

1,写代码实现把”Hello World!”写到”c:/a.txt”文件中。

a, c:/a.txt不存在时,测试一下。

b, c:/a.txt存在时,也测试一下。

要写两个版本:

a, 使用write(int b) 实现。

b, 使用write(byte[] b) 实现。

2,在已存在的c:/a.txt文本文件中追加内容:“Java IO”。

显然此时需要向指定文件中写入数据。

使用的就是可以操作文件的字节流对象。OutputStream。该类是抽象类,需要使用具体的实现类来创建对象查看API文档,找到了OutputStream的实现类FileOutputStream 创建FileOutputStream 流对象,必须指定数据要存放的目的地。通过构造函数的形式。创建流对象时,调用了系统底层的资源。在指定位置建立了数据存放的目的文件。

流程:

1:打开文件输出流,流的目的地是指定的文件

2:通过流向文件写数据

3: 用完流后关闭流

1.2.1 输出流写出方式1:

使用write(int b)方法,一次写出一个字节.

在C盘下创建a.txt文本文件

import java.io.FileOutputStream;

import java.io.IOException;

public class IoTest2 {

public static void main(String[] args) throws IOException {

String path = “c:\\a.txt”;

writeTxtFile(path);

}

private static void writeTxtFile(String path) throws IOException {

// 1:打开文件输出流,流的目的地是指定的文件

FileOutputStream fos = new FileOutputStream(path);

// 2:通过流向文件写数据

fos.write(‘j’);

fos.write(‘a’);

fos.write(‘v’);

fos.write(‘a’);

// 3:用完流后关闭流

fos.close();

}

}

当c盘下的a.txt不存在会怎么样?

测试:将c盘下的a.txt文件删除,发现当文件不存在时,会自动创建一个。

注意:使用write(int b)方法,虽然接收的是int类型参数,但是write 的常规协定是:向输出流写入一个字节。要写入的字节是参数 b 的八个低位。b 的 24 个高位将被忽略。

1.2.2 输出流写出方式2:

使用write(byte[] b),就是使用缓冲.提高效率.

上述案例中的使用了OutputStram 的write方法,一次只能写一个字节。成功的向文件中写入了内容。但是并不高效,如和提高效率呢?是否应该使用缓冲,根据字节输入流的缓冲原理,是否可以将数据保存中字节数组中。通过操作字节数组来提高效率。查找API文档,在OutputStram类中找到了write(byte[] b)方法,将 b.length 个字节从指定的 byte 数组写入此输出流中。

如何将字节数据保存在字节数组中,以字符串为例,”hello , world” 如何转为字节数组。显然通过字符串的getBytes方法即可。

public class IoTest2 {

public static void main(String[] args) throws IOException {

String path = “c:\\a.txt”;

writeTxtFile(path);

}

private static void writeTxtFile(String path) throws IOException {

// 1:打开文件输出流,流的目的地是指定的文件

FileOutputStream fos = new FileOutputStream(path);

// 2:通过流向文件写数据

byte[] byt = “java”.getBytes();

fos.write(byt);

// 3:用完流后关闭流

fos.close();

}

}

1.1.3 输出流写出方式3:

追加

仔细查看a.txt文本文件发现上述程序每运行一次,老的内容就会被覆盖掉。,那么如何不覆盖已有信息,能够往a.txt里追加信息呢。查看API文档,发现FileOutputStream类中的构造方法中有一个构造可以实现追加的功能FileOutputStream(File file, boolean append) 第二个参数,append – 如果为 true,则将字节写入文件末尾处,而不是写入文件开始处

private static void writeTxtFile(String path) throws IOException {

// 1:打开文件输出流,流的目的地是指定的文件

FileOutputStream fos = new FileOutputStream(path,true);

// 2:通过流向文件写数据

byte[] byt = “java”.getBytes();

fos.write(byt);

// 3:用完流后关闭流

fos.close();

}

1.3 字节流文件拷贝

1.3.1 字节输入输出流综合使用

通过字节输出流向文件中写入一些信息,并使用字节输入流把文件中的信息显示到控制台上。

public class IoTest3 {

public static void main(String[] args) throws IOException {

String path = “c:\\b.txt”;

String content = “hello java”;

writeFile(path, content);

readFile(path);

}

public static void writeFile(String path, String content)

throws IOException {

// 打开文件输出流

FileOutputStream fos = new FileOutputStream(path);

byte[] buffer = content.getBytes();

// 向文件中写入内容

fos.write(buffer);

// 关闭流

fos.close();

}

public static void readFile(String path) throws IOException {

FileInputStream fis = new FileInputStream(path);

byte[] byt = new byte[1024];

int len = 0;

while ((len = fis.read(byt)) != -1) {

System.out.println(new String(byt, 0, len));

}

// 关闭流

fos.close();

}

}

注意输出流的细节:

上述案例中我们将输入流和输出流进行和综合使用,如果尝试将输出流换成文本文件就可以实现文件的拷贝了.

什么是文件拷贝?很显然,先开一个输入流,将文件加载到流中,再开一个输出流,将流中数据写到文件中。就实现了文件的拷贝。

分析:

第一步:需要打开输入流和输出流

第二步:读取数据并写出数据

第三步:关闭流

public class IoTest3 {

public static void main(String[] args) throws IOException {

String srcPath = “c:\\a.txt”;

String destPath = “d:\\a.txt”;

copyFile(srcPath, destPath);

}

public static void copyFile(String srcPath, String destPath)

throws IOException {

}

}

1.3.2 字节流拷贝文件实现1

读一个字节写一个字节read 和write

public class IoTest3 {

public static void main(String[] args) throws IOException {

String srcPath = “c:\\a.txt”;

String destPath = “d:\\a.txt”;

copyFile(srcPath, destPath);

}

public static void copyFile(String srcPath, String destPath)

throws IOException {

// 打开输入流,输出流

FileInputStream fis = new FileInputStream(srcPath);

FileOutputStream fos = new FileOutputStream(destPath);

// 读取和写入信息

int len = 0;

while ((len = fis.read()) != -1) {

fos.write(len);

}

// 关闭流

fis.close();

fos.close();

}

}

文本文件在计算机中是以二进制形式存在的,可以通过io流来拷贝,那么图片能不能拷贝呢?视频呢?音频呢?

public class IoTest3 {

public static void main(String[] args) throws IOException {

String srcPath = “c:\\秋.jpg”;

String destPath = “d:\\秋.jpg”;

copyFile(srcPath, destPath);

}

public static void copyFile(String srcPath, String destPath)

throws IOException {

// 打开输入流,输出流

FileInputStream fis = new FileInputStream(srcPath);

FileOutputStream fos = new FileOutputStream(destPath);

// 读取和写入信息

int len = 0;

while ((len = fis.read()) != -1) {

fos.write(len);

}

// 关闭流

fis.close();

fos.close();

}

}

测试统统通过,所以字节流可以操作所有的文件。只是发现程序很慢,需要很长时间。特别是拷贝音频和视频文件时。

为什么?因为每次读一个字节再写一个字节效率很低。很显然这样效率低下的操作不是我们想要的。有没有更快更好的方法呢,是否可以使用缓冲区来提高程序的效率呢。

1.3.3 字节流拷贝文件实现2

使用字节数组作为缓冲区

public static void copyFile2(String srcPath, String destPath)

throws IOException {

// 打开输入流,输出流

FileInputStream fis = new FileInputStream(srcPath);

FileOutputStream fos = new FileOutputStream(destPath);

// 读取和写入信息

int len = 0;

// 使用字节数组,当做缓冲区

byte[] byt = new byte[1024];

while ((len = fis.read(byt)) != -1) {

fos.write(byt);

}

// 关闭流

fis.close();

fos.close();

}

问题1: 使用缓冲(字节数组)拷贝数据,拷贝后的文件大于源文件.

测试该方法,拷贝文本文件,仔细观察发现和源文件不太一致。

打开文件发现拷贝后的文件和拷贝前的源文件不同,拷贝后的文件要比源文件多一些内容问题就在于我们使用的容器,这个容器我们是重复使用的,新的数据会覆盖掉老的数据,显然最后一次读文件的时候,容器并没有装满,出现了新老数据并存的情况。

所以最后一次把容器中数据写入到文件中就出现了问题。

如何避免?使用FileOutputStream 的write(byte[] b, int off, int len)

b 是容器,off是从数组的什么位置开始,len是获取的个数,容器用了多少就写出多少。

public static void copyFile2(String srcPath, String destPath)

throws IOException {

// 打开输入流,输出流

FileInputStream fis = new FileInputStream(srcPath);

FileOutputStream fos = new FileOutputStream(destPath);

// 读取和写入信息

int len = 0;

// 使用字节数组,当做缓冲区

byte[] byt = new byte[1024];

while ((len = fis.read(byt)) != -1) {

fos.write(byt, 0, len);

}

// 关闭流

fis.close();

fos.close();

}

使用缓冲拷贝视频,可以根据拷贝的需求调整数组的大小,一般是1024的整数倍。发现使用缓冲后效率大大提高。

1.4 字节流的异常处理

上述案例中所有的异常都只是进行了抛出处理,这样是不合理的。所以上述代码并不完善,因为异常没有处理。

当我们打开流,读和写,关闭流的时候都会出现异常,异常出现后,后面的代码都不会执行了。假设打开和关闭流出现了异常,那么显然close方法就不会再执行。那么会对程序有什么影响?

案例:

public class IoTest4 {

public static void main(String[] args) throws IOException,

InterruptedException {

String path = “c:\\b.txt”;

readFile(path);

}

private static void readFile(String path) throws IOException,

InterruptedException {

FileInputStream fis = new FileInputStream(path);

byte[] byt = new byte[1024];

int len = fis.read(byt);

System.out.println(new String(byt, 0, len));

// 让程序睡眠,无法执行到close方法。

Thread.sleep(1000 * 10);

fis.close();

}

}

在执行该程序的同时我们尝试去删除b.txt文件。如果在该程序没有睡醒的话,我们是无法删除b.txt 文件的。因为b.txt还被该程序占用着,这是很严重的问题,所以一定要关闭流。

目前我们是抛出处理,一旦出现了异常,close就没有执行,也就没有释放资源。那么为了保证close的执行该如何处理呢。

那么就需要使用try{} catch(){}finally{}语句。try中放入可能出现异常的语句,catch是捕获异常对象,fianlly是一定要执行的代码

public class IoTest4 {

public static void main(String[] args) throws IOException,

InterruptedException {

String path = “c:\\b.txt”;

readFile(path);

}

private static void readFile(String path) {

FileInputStream fis = null;

try {

fis = new FileInputStream(path);

byte[] byt = new byte[1024];

int len = fis.read(byt);

System.out.println(new String(byt, 0, len));

} catch (IOException e) {

// 抛出运行时异常

throw new RuntimeException(e);

} finally {

// 把close方法放入finally中保证一定会执行

// 先判断是否空指针

if (fis != null) {

try {

fis.close();

} catch (Exception e) {

throw new RuntimeException(e);

}

}

}

}

}

文件拷贝的异常处理:

public static void copyFile(String srcPath, String destPath) {

FileInputStream fis = null;

FileOutputStream fos = null;

try {

fis = new FileInputStream(srcPath);

fos = new FileOutputStream(destPath);

byte[] byt = new byte[1024 * 1024];

int len = 0;

while ((len = fis.read(byt)) != -1) {

fos.write(byt, 0, len);

}

} catch (IOException e) {

throw new RuntimeException(e);

} finally {

if (fis != null) {

try {

fis.close();

} catch (IOException e) {

throw new RuntimeException(e);

}

}

if (fos != null) {

try {

fos.close();

} catch (IOException e) {

throw new RuntimeException(e);

}

}

}

}

注意:

在最后的close代码中可能会有问题,两个close,如果第一个close方法出现了异常,并抛出了运行时异常,那么程序还是停止了。下面的close方法就没有执行到。

那么为了保证close的执行,将第二个放到fianlly中即可。

public static void copyFile(String srcPath, String destPath) {

FileInputStream fis = null;

FileOutputStream fos = null;

try {

fis = new FileInputStream(srcPath);

fos = new FileOutputStream(destPath);

byte[] byt = new byte[1024 * 1024];

int len = 0;

while ((len = fis.read(byt)) != -1) {

fos.write(byt, 0, len);

}

} catch (IOException e) {

throw new RuntimeException(e);

} finally {

try {

if (fis != null) {

fis.close();

}

} catch (IOException e) {

throw new RuntimeException(e);

} finally {

if (fos != null) {

try {

fos.close();

} catch (IOException e) {

throw new RuntimeException(e);

}

}

}

}

}

1.5 字节缓冲流

1.5.1 缓冲流

上述程序中我们为了提高流的使用效率,自定义了字节数组,作为缓冲区.Java其实提供了专门的字节流缓冲来提高效率.

BufferedInputStream和BufferedOutputStream

BufferedOutputStream和BufferedOutputStream类可以通过减少读写次数来提高输入和输出的速度。它们内部有一个缓冲区,用来提高处理效率。查看API文档,发现可以指定缓冲区的大小。其实内部也是封装了字节数组。没有指定缓冲区大小,默认的字节是8192。

显然缓冲区输入流和缓冲区输出流要配合使用。首先缓冲区输入流会将读取到的数据读入缓冲区,当缓冲区满时,或者调用flush方法,缓冲输出流会将数据写出。

注意:当然使用缓冲流来进行提高效率时,对于小文件可能看不到性能的提升。但是文件稍微大一些的话,就可以看到实质的性能提升了。

public class IoTest5 {

public static void main(String[] args) throws IOException {

String srcPath = “c:\\a.mp3”;

String destPath = “d:\\copy.mp3”;

copyFile(srcPath, destPath);

}

public static void copyFile(String srcPath, String destPath)

throws IOException {

// 打开输入流,输出流

FileInputStream fis = new FileInputStream(srcPath);

FileOutputStream fos = new FileOutputStream(destPath);

// 使用缓冲流

BufferedInputStream bis = new BufferedInputStream(fis);

BufferedOutputStream bos = new BufferedOutputStream(fos);

// 读取和写入信息

int len = 0;

while ((len = bis.read()) != -1) {

bos.write(len);

}

// 关闭流

bis.close();

bos.close();

}

}

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://yundeesoft.com/78224.html

(0)
上一篇 2024-08-12 11:33
下一篇 2024-08-12 12:45

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注微信