Java的 IO 流_解惑篇

这篇博客详细介绍了Java的IO流,包括输入流和输出流的概念,字节流与字符流的区别,以及节点流与处理流的分类。文章通过实例展示了文件复制操作,讲解了缓冲流提高效率的作用,并特别提到了转换流InputStreamReader和OutputStreamWriter在字符编码转换中的应用。同时,博主解答了关于流操作中的一些常见疑惑,例如关于len的值和文件末尾的判断。
摘要由CSDN通过智能技术生成
流的分类
1.输入流和输出流

按照流的流向来分,可分为输入流和输出流

  • 输入流: 只能从中读取数据,而不能向其写入数据。
  • 输出流: 只能向其写入数据,而不能从中读取数据。

关于输入、输出涉及到一个方向问题,数据从内存到硬盘,通常称为输出流
——也就是说,这里的输入,输出都是从程序运行所在的内存的角度来划分的。


2.字节流和字符流
  • 字节流和字符流的用法几乎完全一样,区别在于字节流和字符流所操作的数据单元不同——字节流操作的数据单元是 8 位的字节,而字符流的数据单元是 16 位的字符(2字节)。

  • 字节流主要由 InputStream 和 OutputStream 作为基类,而字符流则主要由 Reader 和 Writer 作为基类。


3.节点流和处理流

按照流的角色来分,可分为节点流处理流

  • 节点流: 可以从 / 向一个特定的 IO 设备(如磁盘、网络) 读 / 写 数据的流,节点流也被称为 低级流
    ~当使用节点流进行输入 / 输出时,程序直接连接到实际的数据源,和实际的输入 / 输出节点连接。

  • 处理流: 则用于一个已存在的流进行连接 或 封装,通过封装后的流来实现数据读 / 写功能。处理流也被称为高级流.
    ~当使用处理流进行输如、输出时,程序并不会直接连接到实际的数据源,没有和实际的输入、输出节点连接。
    ~使用处理流的一个明显好处就是,只要使用相同的处理流,程序就可以采用完全相同的输入 / 输出 代码来访问不同的数据源,随着处理流所包装节点流的变化,程序实际所访问的数据源也相应的发生变化

  • 识别处理流非常简单,只要流的构造器参数不是一个物理节点,而是已经存在的流,那么这个流一定是处理流;而所有节点流都是直接以物理 IO 节点作为构造器参数的。

输入输出流体系

一些流的案例
  1. 简单的文件复制案例

原理;读取一个已有的数据,并将这些读到的数据写入到另一个文件中。
淘宝
下列案例可以升级使用缓冲流 提高效率

public class CopyFileByBufferTest {

	public static void main(String[] args) throws IOException {
		File srcFile = new File("c:\\YesDir\test.JPG");
		File destFile = new File("copyTest.JPG");
		// 明确字节流 输入流和源相关联,输出流和目的关联。
		FileInputStream fis = new FileInputStream(srcFile);
		FileOutputStream fos = new FileOutputStream(destFile);
		//定义一个缓冲区。提高效率
		byte[] buf = new byte[1024];
		int len = 0;
		while ((len = fis.read(buf)) != -1) {
			fos.write(buf, 0, len);// 将数组中的指定长度的数据写入到输出流中。
		}
		// 关闭资源。
		fos.close();
		fis.close();
	}
}
  • 上例中 fis.read(buf) 方法,实现了一次性最多读取 buf 数组长度的字节,读取的字节都先存到这个数组中,

  • fos.write(buf, 0, len)方法, 一次性写入 buf 数组,元素下标0-len 长度的数据;此操作可以提高开发效率。

  • 关于 fis.read(buf) 此方法的返回值为 读取的字节长度

  • 此方法有些小疑惑,不是读取到文件末尾就返回 -1嘛?的确没有错,不过第一次读取到文件末尾时,此方法返回的还是读取的字节长度,已经读取到文件末尾后再次调用此方法就会返回 - 1;有兴趣的可以去查阅源代码.
    详解参考下列的一些小疑惑


//关于 RandomAccessFile 该类对文件的操作非常实用,该类不属于流,但可以读写,指定位置
2. ###### 实用的处理流操作,可借此实现一个统计文件行数的工具

public class ReadRow {

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

		// 节点流 创建一个新的 FileReader ,给定要读取的文件的名称。
		FileReader fin = new FileReader("ReadRow.java");

		// 处理流来包装这个节点流
		BufferedReader fr = new BufferedReader(fin);
		
		int count = 0; // 统计文件有多少行
		String s = "";
		
		// fr.readLine() 读取一行 ,如果已达到流的末尾,则为null
		while ((s = fr.readLine()) != null) {
			System.out.println(s);
			count++;
		}

		fr.close();// 关于处理流,在关闭流的时候,只需要关闭最外层的那个流便可。
		System.out.println("读取到的行数"+count);
	}

}
  • 上例中,先创建一个节点流(直接连接实际数据源的),之后用 BufferedReader(处理流)包装节点流。

  • 处理流创建好后,就可以通过处理流来 处理包装的节点流所连接的实际数据源。

  • 此处理流中有一个实用方法,readLine() 可以直接读取一行数据

  • 利用此处理流就可以方便统计文件的行数了。


介绍一些常用流
  • 转换流

下面介绍的2个装换流用于实现将字节流装换成字符流。

  1. InputStreamReader类
    InputStreamReader:
    |–FileReader;

InputStreamReader 是字节流通向字符流的桥梁:它使用指定的字符编码表读取字节并将其解码为字符。它使用的字符集可以由名称指定或显式给定,或者可以接受平台默认的字符集。

public class InputStreamReaderDemo {
	public static void main(String[] args) throws IOException {
		//演示字节转字符流的转换流
		readCN();
	}
	public static void readCN() throws IOException{
		//创建读取文件的字节流对象
		InputStream in = new FileInputStream("c:\\cn8.txt");
		//创建转换流对象 
				
		//InputStreamReader isr = new InputStreamReader(in);
		//这样创建对象,会用本地默认码表读取,将会发生错误解码的错误
		
        InputStreamReader isr = new InputStreamReader(in,"utf-8");
		//使用转换流去读字节流中的字节
		int ch = 0;
		while((ch = isr.read())!=-1){
			System.out.println((char)ch);
		}
		//关闭流
		isr.close();
	}
}

注意:在读取指定的编码的文件时,一定要指定编码格式,否则就会发生解码错误,而发生乱码现象。

  1. OutputStreamWriter类
    OutputStreamWriter:
    |–FileWriter:
    OutputStreamWriter 是字符流通向字节流的桥梁:可使用指定的字符编码表,将要写入流中的字符编码成字节。它的作用的就是,将字符串按照指定的编码表转成字节,在使用字节流将这些字节写出去。
	public static void writeCN() throws Exception {
		//创建与文件关联的字节输出流对象
		FileOutputStream fos = new FileOutputStream("c:\\cn8.txt");
		//创建可以把字符转成字节的转换流对象,并指定编码
		OutputStreamWriter osw = new OutputStreamWriter(fos,"utf-8");
		//调用转换流,把文字写出去,其实是写到转换流的缓冲区中
		osw.write("你好");//写入缓冲区。
		osw.close();
	}

OutputStreamWriter流对象,它到底如何把字符转成字节输出的呢?
其实在OutputStreamWriter流中维护自己的缓冲区,当我们调用OutputStreamWriter对象的write方法时,会拿着字符到指定的码表中进行查询,把查到的字符编码值转成字节数存放到OutputStreamWriter缓冲区中。然后再调用刷新功能,或者关闭流,或者缓冲区存满后会把缓冲区中的字节数据使用字节流写到指定的文件中。


  • 父类和子类的功能有什么区别呢?
    • OutputStreamWriter和InputStreamReader是字符和字节的桥梁:也可以称之为字符转换流。字符转换流原理:字节流+编码表。
    • FileWriter和FileReader:作为子类,仅作为操作字符文件的便捷类存在。当操作的字符文件,使用的是默认编码表时可以不用父类,而直接用子类就完成操作了,简化了代码。
    • InputStreamReader isr = new InputStreamReader(new FileInputStream(“a.txt”));//默认字符集。
    • InputStreamReader isr = new InputStreamReader(new FileInputStream(“a.txt”),“GBK”);//指定GBK字符集。
    • FileReader fr = new FileReader(“a.txt”);
      这三句代码的功能是一样的,其中第三句最为便捷。

**注意:一旦要指定其他编码时,绝对不能用子类,必须使用字符转换流。**什么时候用子类呢?
条件:
1、操作的是文件。2、使用默认编码。
总结:
字节—>字符 : 看不懂的—>看的懂的。 需要读。输入流。 InputStreamReader
字符—>字节 : 看的懂的—>看不懂的。 需要写。输出流。 OutputStreamWriter


  • 缓冲流

Java中提高了一套缓冲流,它的存在,可提高IO流的读写速度
缓冲流,根据流的分类,分类字节缓冲流与字符缓冲流。此处介绍字节流

字节缓冲输出流BufferedOutputStream

通过字节缓冲流,进行文件的读写操作 写数据到文件的操作

  • 构造方法
    public BufferedOutputStream(OutputStream out)创建一个新的缓冲输出流,以将数据写入指定的底层输出流。
public class BufferedOutputStreamDemo01 {
	public static void main(String[] args) throws IOException {
		
		//写数据到文件的方法
		write();
	}

	/*
	 * 写数据到文件的方法
	 * 1,创建流
	 * 2,写数据
	 * 3,关闭流
	 */
	private static void write() throws IOException {
		//创建基本的字节输出流
		FileOutputStream fileOut = new FileOutputStream("abc.txt");
		//使用高效的流,把基本的流进行封装,实现速度的提升
		BufferedOutputStream out = new BufferedOutputStream(fileOut);
		//2,写数据
		out.write("hello".getBytes());
		//3,关闭流
		out.close();
	}
}
字节缓冲输入流 BufferedInputStream

刚刚我们学习了输出流实现了向文件中写数据的操作,那么,现在我们完成读取文件中数据的操作

  • 构造方法
    public BufferedInputStream(InputStream in)
/*
	 * 从文件中读取数据
	 * 1,创建缓冲流对象
	 * 2,读数据,打印
	 * 3,关闭
	 */
	private static void read() throws IOException {
		//1,创建缓冲流对象
		FileInputStream fileIn = new FileInputStream("abc.txt");
		//把基本的流包装成高效的流
		BufferedInputStream in = new BufferedInputStream(fileIn);
		//2,读数据
		int ch = -1;
		while ( (ch = in.read()) != -1 ) {
			//打印
			System.out.print((char)ch);
		}
		//3,关闭
		in.close();
	}

一些小疑惑
byte[] bytes = new byte[3];		
int len = in.read(bytes);

关于len 如果文件里面有数据,len始终是字节数组长度

  • 可是有大家有疑问不是返回 -1才证明这个文件达到了末尾嘛?
    如果字节数组里面一次性可以存完文件数据,返回的始终是读取的字节数

  • 关于这一操作的源代码

public int read(byte b[], int off, int len) throws IOException {
        if (b == null) {
            throw new NullPointerException();
        } else if (off < 0 || len < 0 || len > b.length - off) {
            throw new IndexOutOfBoundsException();
        } else if (len == 0) {
            return 0;
        }

        int c = read();
        if (c == -1) {
            return -1;
        }
        b[off] = (byte)c;

        int i = 1;
        try {
            for (; i < len ; i++) {
                c = read();
                if (c == -1) {
                    break;
                }
                b[off + i] = (byte)c;
            }
        } catch (IOException ee) {
        }
        return i;
    }

从源代码中可以知道 程序只有再一开始的时候会判断 是否有文件

		while( (len = in.read(bytes)) != -1){
			
		}

关于这种情况,实际只要读取到文件就会一直执行代码块里语句,
假设读取到文件末尾

		   if (c == -1) {
                 break;
          }

也就会执行这句话,所以最终在while里 len 还是读取的长度,
因为满足条件,所以再执行一次循环,这时候一开始就会执行

  		  int c = read();
           if (c == -1) {
             return -1;
         }

如果一开始就没有读取就到文件末尾就返回-1,这时才返回-1退出循环


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值