Java - IO - 字节流

概述

什么是IO?

IO就是输入流输出流的意思。之内存为基准,分为输入input输出output ,即流向内存是输入流,流出内存的输出流。java

  • 输入流:把硬盘中的数据读取到内存中使用
  • 输出流:把内存中的数据写入到硬盘中保存

image-20200329113022378

上方的表格是顶级父类数组

字节流

一切皆字节

一切文件数据(文本、图片、视频等)在存储时,都是以二进制数字的形式保存,都一个一个的字节,那么传输时同样如此。app

因此,字节流能够传输任意文件数据。在操做流的时候,咱们要时刻明确,不管使用什么样的流对象,底层传输的始终为二进制数据。优化

字节输入流(InputStream)

java.io.InputStream ``抽象类是表示字节输入流的全部类的超类,能够读取字节信息到内存中。编码

它定义了字节输入流的基本共性功能方法。操作系统

  • public abstract int read(): 从输入流读取数据的下一个字节。
  • public int read(byte[] b): 从输入流中读取一些字节数,并将它们存储到字节数组 b中 。
  • public void close() :关闭此输入流并释放与此流相关联的任何系统资源。

文件字节输入流(FileInputStream)

java.io.FileInputStream 类是文件输入流,继承InputStream抽象类。publicclass FileInputStream extends InputStream3d

做用是把硬盘文件中的数据,读取到内存中使用。code

读取数据的原理(硬盘->内存):Java程序-->JVM-->操做系统-->调用本地方法-->读取文件内容视频

构造方法
Constructor Description
FileInputStream(File file) * 经过打开与实际文件的链接建立一个 FileInputStream ,该文件由文件系统中的 File对象 file命名。
FileInputStream(String name) 经过打开与实际文件的链接来建立一个 FileInputStream ,该文件由文件系统中的路径名 name命名。
FileInputStream(FileDescriptor fdObj) 建立 FileInputStream经过使用文件描述符 fdObj ,其表示在文件系统中的现有链接到一个实际的文件。

当你建立一个流对象时,必须传入一个文件路径。该路径下,若是没有该文件,会抛出FileNotFoundException对象

经常使用方法
方法 描述
int read() 今后输入流中读取一个数据字节。
int read(byte[] b) 今后输入流中将最多 b.length 个字节的数据读入一个 byte 数组中。
int read(byte[] b, int off, int len) 今后输入流中将最多 len 个字节的数据读入一个 byte 数组中。
void close() 关闭此文件输入流并释放与此流有关的全部系统资源。
long skip(long n) 从输入流中跳过并丢弃 n 个字节的数据。
protected void finalize() 确保在再也不引用文件输入流时调用其 close 方法。
int available() 返回下一次对此输入流调用的方法能够不受阻塞地今后输入流读取(或跳过)的估计剩余字节数。
FileChannel getChannel() 返回与此文件输入流有关的惟一 FileChannel 对象。
FileDescriptor getFD() 返回表示到文件系统中实际文件的链接的 FileDescriptor 对象,该文件系统正被此 FileInputStream 使用。

*在文件字节输入流中,read()底层调用本地方法read0()

其余两个read方法调用本地方法readBytes(byte b[], int off, int len)


使用步骤
  1. 建立FileInputStream对象,构造方法中绑定数据源
  2. 使用FileInputStream对象中的read方法,读取数据
  3. 释放资源
package IO;

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

public class FileInputStream_in {
	public static void main(String args[]) throws IOException{
		
		//read("1.txt");
		//readMore("1.txt");
		readAll("1.txt");
	}
	public static void read(String path) throws IOException{
		FileInputStream fis = new FileInputStream(path);
		int len =0;
		while((len = fis.read())!=-1)
			System.out.print((char)len);
		
		fis.close();
		
	}
	
	public static void readMore(String path) throws IOException{
		FileInputStream fis = new FileInputStream(path);
		int len =0;
		byte[] bytes = new byte[2];
		while((len = fis.read(bytes))!=-1)
			System.out.println(new String(bytes));
		
		fis.close();
		
	}
	//优化上一个方法
	public static void readAll(String path) throws IOException{
		FileInputStream fis = new FileInputStream(path);
		int len =0;
		byte[] bytes = new byte[1024];
		while((len = fis.read(bytes))!=-1)
			System.out.print(new String(bytes,0,len));//只取有效部分
		
		fis.close();
		
	}
}

字节输出流(OutputStream)

java.io.OutputStream 抽象类是表示字节输出流的全部类的超类,将指定的字节信息写出到目的地。

它定义了字节输出流的基本共性功能方法。

  • public void close() :关闭此输出流并释放与此流相关联的任何系统资源。
  • public void flush() :刷新此输出流并强制任何缓冲的输出字节被写出。
  • public void write(byte[] b):将 b.length字节从指定的字节数组写入此输出流。
  • public void write(byte[] b, int off, int len) :从指定的字节数组写入 len字节,从偏移量 off开始输出到此输出流。
  • public abstract void write(int b) :将指定的字节输出流。

image-20200329113346675

方法 描述
void close() 关闭此输出流并释放与此流有关的全部系统资源。
void flush() 刷新此输出流并强制写出全部缓冲的输出字节。
void write(byte[] b) 将 b.length 个字节从指定的 byte 数组写入此输出流。
void write(byte[] b, int off, int len) 将指定 byte 数组中从偏移量 off 开始的 len 个字节写入此输出流。
abstract void write(int b) 将指定的字节写入此输出流。

*在字节输出流中,write的重载方法都调用write(int b) 方法

文件字节输出流(FileOutputStream)

java.io.FileInputStream 类是文件输入流,从文件中读取字节。

写入数据的原理(内存->硬盘):Java程序-->JVM-->操做系统-->调用本地方法-->把数据写入文件

构造方法
  • FileInputStream(File file): 经过打开与实际文件的链接来建立一个 FileInputStream ,该文件由文件系统中的 File对象 file命名。
  • FileInputStream(String name): 经过打开与实际文件的链接来建立一个 FileInputStream ,该文件由文件系统中的路径名 name命名。

当你建立一个流对象时,必须传入一个文件路径。该路径下,若是没有该文件,会抛出FileNotFoundException

Constructor Description
FileOutputStream(File file, boolean append) ** 建立文件输出流以写入由指定的 File对象表示的文件。
FileOutputStream(String name, boolean append) 建立文件输出流以指定的名称写入文件。append是追加开关,默认false(关)。
FileOutputStream(String name) 建立文件输出流以指定的名称写入文件。
FileOutputStream(File file) 建立文件输出流以写入由指定的 File对象表示的文件。
FileOutputStream(FileDescriptor fdObj) * 建立文件输出流以写入指定的文件描述符,表示与文件系统中实际文件的现有链接。

其实无论哪一个构造器都转成FileOutputStream(File file, boolean append)形式

经常使用方法
方法 描述
void write(int b) **核心 将指定字节写入此文件输出流。
void write(byte[] b) 将 b.length 个字节从指定 byte 数组写入此文件输出流中。
void write(byte[] b, int off, int len) 将指定 byte 数组中从偏移量 off 开始的 len 个字节写入此文件输出流。
void close() 关闭此文件输出流并释放与此流有关的全部系统资源。
protected void finalize() 清理到文件的链接,并确保在再也不引用此文件输出流时调用此流的 close 方法。
FileChannel getChannel() 返回与此文件输出流有关的惟一FileChannel 对象。
FileDescriptor getFD() 返回与此流有关的文件描述符。

底层调用本地(native)方法write(int b, boolean append)writeBytes(byte b[], int off, int len, boolean append)

在文件字节输出流中,write重载方法调用不一样的本地方法。

实例
//用文件字节输出流向文件写入一个字节
import java.io.*;
public class fileIO {

		public static void main(String args[]) throws IOException{
			FileOutputStream fos = new FileOutputStream("1.txt");
			fos.write(97);//输入一个字节  97的二进制1100001
			fos.close();//打开记事本默认读字符97对应的ASCII表是a
		}
}

image-20200329120329926

package IO;
import java.io.*;
import java.util.Arrays;


public class FileOutputStream_ {

		public static void main(String args[]) throws IOException{
			fileOutOne("1.txt",97);//向1文件写入1个字节(8位)01100001
			
			byte[] t = "你好".getBytes();
			System.out.println(Arrays.toString(t)); //gdk中英都占两个字节。 utf-8 英文一字节,中文三字节
			fileOutMore("2.txt",t);//向2文件写入你好字符串转化的字节数组。
			
			byte[] b = {97,98,49,48,48};
			fileOutMore1("3.txt",b);//向3文件写入一个规定字节数组。
			
		}
		
		//使用字节输出流向文件写入一个字节
		public static void fileOutOne(String path,int num) throws IOException{
			FileOutputStream fos = new FileOutputStream(path);//指向路径的文件
			fos.write(num);//输入一个字节  97的二进制1100001
			fos.close();//打开记事本默认读字符97对应的ASCII表是a
		}
		
		//使用字节输出流向文件写入多个字节
		public static void fileOutMore(String path,byte[] bytes) throws IOException{
			File f = new File(path);
			FileOutputStream fos = new FileOutputStream(f);//指向具体文件
			
			fos.write(bytes);//写入一个字节数组
		
			fos.close();
		}
		
		//使用字节输出流向文件写入偏移的多个字节
		public static void fileOutMore1(String path,byte[] bytes) throws IOException{
			File f = new File(path);
			FileOutputStream fos = new FileOutputStream(f);//指向具体文件
			
			//byte[] b = {97,98,49,48,48}; 本应该是 ab100 偏移两位,写入三位得100
			fos.write(bytes,2,3);//输入一个字节数组
		
			fos.close();
		}
}
追加 和 换行
package IO;

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

public class FileOutputStrean_append {

	public static void main(String args[]) throws IOException{
		fileOutAppend("1.txt","append".getBytes());//向1文件追加换行和写入一个单词
	}
	
	public static void fileOutAppend(String path,byte[] bytes) throws IOException{
		FileOutputStream fos = new FileOutputStream(path,true);//true 标记追加
		
		fos.write("\r\n".getBytes());//window系统下文件的换行符号
		
		fos.write(bytes);//写入一个字节数组
	
		fos.close();
	}
}

复制文件

步骤

  1. 建立一个字节输入流对象,构造方法中绑定要读取的数据源
  2. 建立一个字节输出流对象,构造方法中绑定要写入的目的地
  3. 使用字节输入流对象的read方法读取数据
  4. 使用直接输出流对象的write方法将读取到的数据写入目的地文件
  5. 释放资源(栈顺序,先进后出)
package bytestream;

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

public class CopyFile {
    public static void main(String[] args) throws IOException {
        // 1. 建立一个字节输入流对象,构造方法中绑定要读取的数据源
        FileInputStream fis = new FileInputStream("test.txt");
        // 2. 建立一个字节输出流对象,构造方法中绑定要写入的目的地
        FileOutputStream fos = new FileOutputStream("test2.txt");
        // 3. 使用字节输入流对象的read方法读取数据
        byte[] bytes = new byte[1024];
        int len = 0;
        while ((len = fis.read(bytes)) != -1) {

            // 4. 使用直接输出流对象的write方法将读取到的数据写入目的地文件
            fos.write(bytes, 0, len);
        }
        // 5. 释放资源(栈顺序,先进后出)
        fos.close();
        fis.close();
    }
}

问题

中文字符的编码

  • GBK 占两字节
  • UTF-8 占三个字节
package bytestream;

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

public class ByteStreamTest {
    public static void main(String[] args) throws IOException {
        // 当使用字节流读取字符时,存在一些问题
        FileInputStream fis = new FileInputStream("test.txt");

        int len = 0;
        while ((len = fis.read()) != -1) {
            System.out.println((char)len);
        }
        fis.close();
    }
}

test.txt

123789456你好

运行结果

1
2
3
7
8
9
4
5
6
ä
½
 
å
¥
½

使用字节流读取字符数据出现乱码!

字节流存在的问题:当使用字节流读取文本文件时,可能会有一个小问题。就是遇到中文字符时,可能不会显示完整的字符,

那是由于一个中文字符可能占用多个字节存储。因此Java提供一些字符流类,以字符为单位读写数据,专门用于处理文本文件。