第四十一讲 I/O流——字节流

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/yerenyuan_pku/article/details/84350667

字节流的基本操作与字符流类同,但它不仅可以操作字符,还可以操作其他媒体文件。这里,我列出比较常用的字节流,如下:

  • FileInputStream
  • FileOutputStream
  • BufferedInputStream
  • BufferedOutputStream

字节输出流——FileOutputStream

查看API帮助文档,FileOutputStream类的构造方法有:
在这里插入图片描述
这样,我们就能知道如何一个创建字节输出流对象了。
通过API帮助文档,我们还能知道字节流写数据的方式:

方法 说明
write(int b) 一次写一个字节
write(byte[] b) 一次写一个字节数组
write(byte[] b, int off, int len) 一次写一个字节数组的一部分

字节流写数据

这里我用一个例子为大家介绍一下字节输出流——FileOutputStream。例,向一个文本文件中写入数据。

package cn.liayun.bytestream;

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

public class FileOutputStreamDemo {

	public static void main(String[] args) throws IOException {
		/*
		 * 将数据写入到文件中。
		 * 使用字节输出流。
		 * FileOutputStream
		 */
		
		File dir = new File("tempfile");
		if (!dir.exists()) {
			dir.mkdir();
		}
		//1,创建字节输出流对象,用于操作文件。在对象初始化时,必须明确数据存储的目的地。
		//输出流所关联的目的地,如果不存在,会自动创建。如果存在,则覆盖。
		FileOutputStream fos = new FileOutputStream("tempfile\\fos.txt");
		
		//2,调用输出流的写功能
//		String str = "abcde";
//		byte[] buf = str.getBytes();
		fos.write("abcde".getBytes());
		
		//3,释放资源。
		fos.close();
	}

}

我们须注意一点——创建字节输出流对象用于操作文件,在对象初始化时必须明确数据存储的目的地,输出流所关联的目的地,如果不存在,则会自动创建;如果存在,则会覆盖。

对IO流异常的处理方式

在使用字节输出流——FileOutputStream向一个文本文件中写入数据时,就不可避免地发生IOException异常,对该异常的正确处理方式究竟是怎样的呢?下面已给出。

package cn.liayun.bytestream;

import java.io.FileOutputStream;
import java.io.IOException;

public class IOExceptionDemo {

    public static void main(String[] args) {

        FileOutputStream fos = null;

        try {
            fos = new FileOutputStream("tempfile\\fos.txt");
            fos.write("hello".getBytes());
        } catch (IOException e) {

            // 自处理.... 但通常将异常信息写入到日志文件中以进行记录。
            System.out.println(e.toString() + "----------------");
        } finally {
            if (fos != null) // 放在try代码块里面亦可
                try {
                    fos.close();
                } catch (IOException e) {
                    // 一般可以throw RuntimeException异常,或者将异常信息写入到日志文件中以进行记录。
                    throw new RuntimeException("关闭失败" + e);
                }
        }


    }

}

注意:最后无论如何都应关闭资源,所以应放在finally代码块中。以后在对IO流的操作中,出现IOException异常,就按照上述代码进行处理。

字节输出流的续写和换行

现在思考这样一个问题:如果想在原有文件上继续加入新的数据,咋整呢?很简单,创建一个FileOutputStream对象时,传递一个true参数,代表不覆盖已有的文件,即在已有文件的末尾处进行数据续写。

package cn.liayun.bytestream;

import java.io.FileOutputStream;
import java.io.IOException;

public class IOExceptionDemo {

	private static final String LINE_SEPARATOR = System.getProperty("line.separator");

	public static void main(String[] args) {
		FileOutputStream fos = null;
		try {
			fos = new FileOutputStream("tempfile\\fos.txt", true);//传入true,实现续写。
			String str = LINE_SEPARATOR + "hello";
			fos.write(str.getBytes());
		} catch (IOException e) {
			//自处理
			System.out.println(e.toString() + "------");
		} finally {
			if (fos != null) {//必须在释放资源前进行判断
				try {
					fos.close();
				} catch (IOException e) {
					
					throw new RuntimeException("程序已失败!" + e);
				}
			}
		}
		
	}

}

注意:\r\n在Windows中表示行终止符,但在其他系统中并不是这样,所以为了更加通用,使用System类的getProperty(“line.separator”)方法得到系统的行终止符。

字节输入流——FileInputStream

查看API帮助文档,FileInputStream类的构造方法有:
在这里插入图片描述
这样,我们就能知道如何一个创建字节输入流对象了。
通过API帮助文档,我们还能知道字节流读取数据的方式:

方法 说明
int read() 一次读取一个字节
int read(byte[] b) 一次读取一个字节数组
int read(byte[] b, int off, int len) 一次读取一个字节数组的一部分

字节流读数据

我们知道如何使用字节输出流——FileOutputStream向一个文本文件中写入数据之后,就要考虑将已有文件的数据读取出来。现在就有这样一个需求:从硬盘的一个文件中读取内容。

分析:既然是读取文件,显然要使用InputStream,而且又因为是要操作文件,所以最终应使用FileInputStream。使用字节输入流——FileInputStream读取文件有三种方式,下面我会详述这三种方式。

首先使用第一种方式来读取文件,给出示例代码如下,以供读者参考。

package cn.liayun.bytestream;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;

public class FileInputStreamDemo {

	public static void main(String[] args) throws IOException {
		/*
		 * 将已有文件的数据读取出来
		 * 既然是读,使用InputStream
		 * 而且是要操作文件,FileInputStream
		 * 
		 */
		//为了确保文件一定在读之前是存在的,将字符串路径封装成File对象。
		File file = new File("tempfile\\fos.txt");
		if (!file.exists()) {
			throw new RuntimeException("要读取的文件不存在");
		}
		
		//创建文件字节读取流对象时,必须明确与之关联的数据源。
		FileInputStream fis = new FileInputStream(file);
		//调用读取流对象的读取方法。read();
		int by = 0;
		while ((by = fis.read()) != -1) {
			System.out.println(by);
		}
		
//		int by1 = fis.read();
//		System.out.println("by1 = " + by1);
//		int by2 = fis.read();
//		System.out.println("by2 = " + by2);
//		int by3 = fis.read();
//		System.out.println("by3 = " + by3);
//		int by4 = fis.read();
//		System.out.println("by4 = " + by4);
//		int by5 = fis.read();
//		System.out.println("by5 = " + by5);
		
		
		//关闭资源
		fis.close();
	}

}

接下来就使用第二种方式来读取文件,同样给出示例代码如下(其中加入了对IOException异常的处理),以供读者参考。

package cn.liayun.bytestream;

import java.io.FileInputStream;
import java.io.IOException;

public class FileInputStreamDemo2 {

	private static final int DEFAULT_SIZE = 1024;

	public static void main(String[] args) {
		//演示第二种读取方式,read(byte[])
		FileInputStream fis = null;
		try {
			fis = new FileInputStream("tempfile\\fos.txt");
			
			//创建一个字节数组
			byte[] buf = new byte[DEFAULT_SIZE];//1024的整数倍
			
			int len = 0;
			while ((len = fis.read(buf)) != -1) {
				System.out.println(new String(buf, 0, len));
			}
			
			//调用read(byte[])方法
//			int len = fis.read(buf);//len记录的是往字节数组里存储的字节个数。
//			System.out.println(len + "...." + new String(buf, 0, len));//将字节数组转成字符串再打印,看一下效果。
//			int len1 = fis.read(buf);//len记录的是往字节数组里存储的字节个数。
//			System.out.println(len1 + "...." + new String(buf, 0, len1));//将字节数组转成字符串再打印,看一下效果。
//			int len2 = fis.read(buf);//len记录的是往字节数组里存储的字节个数。
//			System.out.println(len2 + "...." + new String(buf));//将字节数组转成字符串再打印,看一下效果。
			
			
		} catch (Exception e) {
			// 将异常信息写入到日志文件中,进行记录。
		} finally {
			if (fis != null) {
				try {
					fis.close();
				} catch (IOException e) {
					//一般可以抛出RuntimeException异常,或者将异常信息写入到日志文件中,进行记录。
					e.printStackTrace();
				}
			}
		}
	}

}

从以上程序代码中,可知在创建缓冲区(一个字节数组)时,缓冲区的大小一般设置为1024的整数倍。
最后,使用第三种方式来读取文件,给出示例代码如下,供读者参考。

package cn.liayun.bytestream;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;

public class FileInputStreamDemo3 {

	public static void main(String[] args) throws IOException {
//		File file = new File("tempfile\\fos.txt");
//		System.out.println(file.length());
		
		FileInputStream fis = new FileInputStream("tempfile\\fos.txt");
				
//		System.out.println(fis.available());//可以获取与之关联的字节数。
		byte[] buf = new byte[fis.available()];//创建了一个和文件大小一样的缓冲区,刚刚好。
		fis.read(buf);
		String s = new String(buf);
		System.out.println(s);
		
		fis.close();
	}

}

注意:不建议使用这种方式,因为可能会有内存溢出异常。

复制文本文件

现在有这样一个需求:复制一个文本文件。

分析:复制文本文件说到底就是一个读取源数据,并将数据写到目的地的过程,由于要操作设备上的数据,所以需要用到流,读用到了输入流,写用到了输出流,而且操作的还是文本文件,最终就需要用到字节流中操作文件的流对象了。解决该需求其实有两种方式,下面我会一一详述这两种方式。

首先使用第一种复制方式,即从源数据中读一个字节,就往目的地写一个字节。下面给出示例代码,供读者参考。

package cn.liayun.copy;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class CopyTextTest {

	public static void main(String[] args) throws IOException {
		/*
		 * 复制一个文本文件。
		 * 思路:
		 * 读取源数据,将数据写到目的地中。
		 * 用到了流,操作设备上的数据。
		 * 读用到了输入流,写用到了输出流。
		 * 而且操作的还是文件,需要用到字节流中操作文件的流对象。
		 */
		copyText();
		
	}

	public static void copyText() throws IOException {
		//1,创建一个输入流和源数据相关联
		FileInputStream fis = new FileInputStream("IO流_2.txt");
		//2,创建一个输出流,并通过输出流创建一个目的
		FileOutputStream fos = new FileOutputStream("tempfile\\io_copy.txt");
		//3,读一个写一个字节
		int by = 0;
		while ((by = fis.read()) != -1) {
			fos.write(by);
		}
		
		fos.close();
		fis.close();
	}

}

接着再来看第二种复制方式,同样给出示例代码如下。

package cn.liayun.copy;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class CopyTextByBufTest {

	public static void main(String[] args) {
		copyTextByBuf();
	}

	public static void copyTextByBuf() {
		FileInputStream fis = null;
		FileOutputStream fos = null;
		try {
			fis = new FileInputStream("IO流_2.txt");
			fos = new FileOutputStream("tempfile\\io_buf_copy.txt");
			//创建缓冲区
			byte[] buf = new byte[1024];
			//定义记录个数的变量
			int len = 0;
			//循环读写
			while ((len = fis.read(buf)) != -1) {
				fos.write(buf, 0, len);
			}
		} catch (Exception e) {
			//异常日志。
		} finally {
			if (fos != null) {
				try {
					fos.close();
				} catch (IOException e) {
					//异常日志。
					e.printStackTrace();
				}
			}
			if (fis != null) {
				try {
					fis.close();
				} catch (IOException e) {
					//异常日志。
					e.printStackTrace();
				}
			}
		}
		
	}

}

相比第一种复制方式,此方式的复制效率会更高,所以建议使用第二种复制方式。

复制图片

现在有这样一个需求:复制一个图片。

分析:

  1. 用字节读取流对象和图片关联;
  2. 用字节写入流对象创建一个图片文件,用于存储获取到的图片数据;
  3. 通过循环读写,完成数据的存储;
  4. 关闭资源。

通过以上思路,不难写出如下代码,程序代码与复制一个文本文件非常相似。

import java.io.*;
public class CopyPic {
    public static void main(String[] args) {
        FileOutputStream fos = null;
        FileInputStream fis = null;
        try {
            fos = new FileOutputStream("c:\\2.jpg");
            fis = new FileInputStream("c:\\1.jpg");
            byte[] buf = new byte[1024];
            int len = 0;
            while((len=fis.read(buf)) != -1) {
                fos.write(buf, 0, len);
            }
        } catch(IOException e) {
            throw new RuntimeException("复制文件失败");
        } finally {
            try {
                if(fis != null) 
                    fis.close();
            } catch(IOException e) {
                throw new RuntimeException("读取关闭失败");
            }
            try {
                if(fos != null) 
                    fos.close();
            } catch(IOException e) {
                throw new RuntimeException("写入关闭失败");
            }
        }
    }
}

字节缓冲流

字节流一次读写一个数组的速度明显比一次读写一个字节的速度快很多,这是加入了数组这样的缓冲区效果,Java本身在设计的时候,也考虑到了这样的设计思想(装饰设计模式后面会讲解到),所以提供了字节缓冲区流。

  • 字节缓冲输出流:BufferedOutputStream;
  • 字节缓冲输入流:BufferedInputStream。

复制一个图片的需求解决完之后,接下来就要引出字节流的缓冲区对象了,它们分别是BufferedOutputStream和BufferedInputStream,它俩是怎样使用的呢?下面同样以复制一个图片的案例来看看字节流的缓冲区对象到底是如何应用在代码中的。

package cn.liayun.bytestream.buffer;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class CopyPicByBufferDemo {

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

	public static void copyPicByBuffer() throws IOException {
		//演示缓冲区
		//1,创建具体的流对象
		FileInputStream fis = new FileInputStream("tempfile\\1.jpg");
		//对流进行缓冲
		BufferedInputStream bufis = new BufferedInputStream(fis);
		
		FileOutputStream fos = new FileOutputStream("tempfile\\copy_1.jpg");
		BufferedOutputStream bufos = new BufferedOutputStream(fos);
		
		byte[] buf = new byte[1024];
		int len = 0;
		while ((len = bufis.read(buf)) != -1) {// 缓冲区的读方法,从缓冲区里面读出来的数据
			bufos.write(buf, 0, len);// 缓冲区的写方法,向缓冲区里面写数据
			bufos.flush();// 刷新可有可无,因为缓冲区满了之后会自动刷新
		}
		/*
		int by = 0;
		while ((by = bufis.read()) != -1) {
			bufos.write(by);
		}
		*/
		
		
		fos.close();
		fis.close();
	}

}

猜你喜欢

转载自blog.csdn.net/yerenyuan_pku/article/details/84350667