Java IO字节流原理

以前自己对java IO这块一直处于看似懂了,但是每当要用的时候,却总是无法下手。以前总是纠结编码、乱码问题。最近发现自己水平有所提高,所以趁着这个机会重新看了下IO方面的知识,趁着自己还记得,把收获记录在这。这篇文章主要讲通过字节流的方式操作文件。

IO流讲的无非就是将数据从一个地方传输到另一个地方。当我的程序需要某些数据时,就会从缓存或者内存或者硬盘或者网络上读入数据到程序。当我的程序要发送或保存数据时,就会把数据写出到缓存或者内存或者硬盘或者网络上。要实现这些数据传输操作时就要用到Java IO流。

这里有个需要点睛的地方,就是基于字节的读入或写出,指的是你操作的是字节,就是说你读入程序或从程序写出的数据是字节形式。所以说,当你读入数据到程序时,需要用一个字节或者字节数组来存储数据;你写出数据时,需要先把数据转化成字节或者字节数组形式再写出。

当然有人会疑惑,我读入/写出数据不就是为了用数据或者浏览数据吗,这种二进制形式的数据对我来说有什么用?这个问题涉及到编码、解码。大家知道数据存储在电脑的硬盘或内存或缓存都是以二进制的形式存储的,这个二进制都是经过编码后的二进制,使用特定的字符集通过特定的编码方式编码。如果我们要浏览这些数据,那就必须进行解码,解码时必须使用与编码时一样的编码字符集。

因此当你从别处把数据读入到程序后,你得到的是字节(1字节=8位二进制=2位十六进制),如果你需要浏览这些数据,你就需要使用特定的编码字符集进行解码,得到你可以看懂的字符串。读入数据到程序后的解码操作需要你自己编写程序进行解码。当你把数据从程序写出时,写出的是字节流或者说二进制流,写出到目的地后,比如说电脑硬盘上,数据就以二进制形式存储在硬盘上了。当你需要浏览这些数据的时候,只需要打开特定的软件,比如说文本编辑器,解码操作由这个编辑器完成。当然这个编辑器解码时可能会使用错误的编码字符集,因此有时候会出现乱码,所以你需要手动调整编码字符集成与编码时使用的一样。

上面只说了解码,这里再来说说编码。当你读入字节数据时,你得到是编码后的数据。我要解码,我就要知道它编码时使用的是什么编码字符集。这里的编码字符集就是所读入的文件使用的编码字符集。那写出时的字节数据又是使用的什么编码字符集呢,写出时,需要把字符数据转化成字节数据,这个转化过程就是编码过程。你可以指定一个字符集,当你没有指定字符集时,会使用默认的字符集进行编码。

由于IO所涉及的都是对数据的操作,因此,我认为有必要在重复下java的一些数据类型以及二进制和十进制之间的转化等。

int -- 4-byte,二进制补码,范围:-2,147,483,648 ~2,147,483,647
long -- 8-byte,二进制补码,范围:-9,223,372,036,854,775,808~ 9,223,372,036,854,775,807
short -- 2-byte,二进制补码,范围:-32,768~ 32,767
byte -- 8-bit,二进制补码,范围:-128~127

正数的十进制和二进制转化无需再讲,这里讲下负的十进制和二进制的相互转化

将负的二进制转成十进制如下:
十六进制0x80为二进制1000 0000
先反码为:0111 1111
反码在加1:1000 0000

十进制为128,在取负数为-128


将负的十进制转成二进制如下:
十进制-8
先取绝对值8,8的二进制为0000 1000
取反码:1111 0111
发码加1:1111 1000

哎,真累,上面罗里吧嗦一大堆,希望看的人不要抱怨,言归正传。

基于字节流的接口:InputStream(读入),OutputStream(写出)。输入输出流还有许多实现类,不同的实现类具有不同的功能,这里主要使用FileInputStream和FileOutputStream

1、输入流读入数据主要用的方法:

int read()-这个方法读入一个字节,返回的是无符号数,就是说返回的数据在0-255之间

int read(byte[] data)-将数据读入到byte数组类型的方法参数中,返回所读入的字节个数

int read(byte[] data, int offset, int length)-将数据读入到byte数组类型的方法参数中,数据放在数组从offset开始length个字节的位置,返回所读入的字节个数

read方法返回-1时,表示读数据操作已到了文件末,也就是说数据全部读取完。这是一个很重要的信息,当你写代码时,总是要用这个条件判断文件是否读取完成

这里有个小疑问,无参数的read方法返回的是无符号数0-255,而有参数的read读入的却是byte数组,byte为有符号数-128-127之间。没错,你可以将无参数read返回的int值强制转成byte,转化规则为int i = (b >= 0) ? b : 256 + b

2、输出流写出数据主要用的方法:

void write(int b)-将数据b写出到输出流,这个数据b的范围也是0-255

write(byte[]data)-将byte数组数据data写出到输出流

void write(byte[] data, int offset, int length)-将byte数组数据data,从数组的offset偏移量开始写出length个字节

当然这里也有个小疑问,这里int型数据规定范围是0-255,但是参数类型是int,如果传入一个超过这个范围的数据不会有问题吗?不会,如果传入的数据超过这个范围的话,将会使用256为模对参数求余,从而保证参数在0-255之间。比如参数为21586,求余21586 % 256 = 82;如果为负数-21586,同理求余-21586 % 256 = -82,再通过int i = (b >= 0) ? b : 256 + b规则转化则-82 + 256 = 174

这里有一点需要提醒下,无论是输入流还是输出流,都要记得关闭!

3、实例演示

对字符数据编码,对字节数据解码可以使用Charset类

package com.io;

import java.nio.ByteBuffer;
import java.nio.charset.Charset;

/**
 * 编码
 */
public class EncodeTest {
	public static void main(String[] args) {
		String str = "中国";
		// 使用编码名称创建一个Charset类型实例
		Charset cst = Charset.forName("GBK");
		// 对字符数据编码,得到一个字节缓存流
		ByteBuffer bf = cst.encode(str);
		// 将字节缓存转成字节数组
		byte[] bfarr = bf.array();

		System.out.print("十六进制:");
		for (int i = 0; i < bfarr.length; i++) {
			System.out.print(Integer.toHexString(bfarr[i]) + " ");
		}
		System.out.println();

		System.out.print("十进制:");
		for (int i = 0; i < bfarr.length; i++) {
			System.out.print(bfarr[i] + " ");
		}
	}
}

输出结果:

十六进制:ffffffd6 ffffffd0 ffffffb9 fffffffa 
十进制:-42 -48 -71 -6 

package com.io;

import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;

/**
 * 解码
 */
public class DecodeTest {
	public static void main(String[] args) {
		
		// 使用编码名称创建Charset类型实例
		Charset cst = Charset.forName("GBK");
		
		// 这个字节数组元素存的是十进制数
		byte[] bsInte = new byte[] { -42, -48, -71, -6 };
		// 将字节数组包装成字节缓存
		ByteBuffer bfInte = ByteBuffer.wrap(bsInte);
		// 解码,获得字符缓存
		CharBuffer cbInte = cst.decode(bfInte);
		// 将字符缓存转化成字符串
		String resultInte = cbInte.toString();
		System.out.println(resultInte);
		
		// 下面这段逻辑与上面一样,只是这里的字节数组元素是字节
		byte[] bsHex = new byte[] { (byte) 0xd6, (byte) 0xd0, (byte) 0xb9, (byte) 0xfa };
		ByteBuffer bfHex = ByteBuffer.wrap(bsHex);
		CharBuffer cbHex = cst.decode(bfHex);
		String resultHex = cbHex.toString();
		System.out.println(resultHex);
	}
}

输出结果:

中国

中国

package com.io;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

/**
 * 输出流
 */
public class OutputTest {
	public static void main(String[] args) {
		
		// 这段代码只是获取系统可用的字符编码集,只是想把这个作为写出的数据而已
		Map csMap = Charset.availableCharsets();
		Set> csMapEntrySet = csMap.entrySet();
		Iterator> csMapEntryIt = csMapEntrySet.iterator();
		StringBuffer csBuf = new StringBuffer();
		while (csMapEntryIt.hasNext()) {
			Entry csMapEntry = csMapEntryIt.next();
			csBuf.append(csMapEntry.getKey()).append("\n");
		}
		csBuf.append("中国");
		File file = new File("C:\\Users\\Administrator\\Desktop\\charset.txt");
		try {
			// 这个file不一定要在系统中存在,如果不存在会尝试创建一个文件,如果无法创建文件会报错
			OutputStream os = new FileOutputStream(file);
			// 编码时,可以使用String的byte[] getBytes()方法,也可以使用上面的编码例子
			// String的getBytes()方法可以传入字符集名称作为参数,如果没传入参数时将使用默认字符集编码
			os.write(csBuf.toString().getBytes("GBK"));
			// 一定要记得关闭输出流
			os.close();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}
package com.io;

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

/**
 * 输入流
 */
public class InputTest {
	public static void main(String[] args) {
		
		File file = new File("C:\\Users\\Administrator\\Desktop\\charset.txt");
		// 这里定义的字节数组用来保存读入的数据,可以根据文件大小一次性定义字节数组
		// 如:byte[] inBytes = new byte[(int) file.length()];
		byte[] inBytes = new byte[256];
		StringBuffer strBuf = new StringBuffer();
		
		try {
			// 这个file在系统中一定要存在,否则会抛出FileNotFoundException错误
			InputStream is = new FileInputStream(file);
			while (is.read(inBytes) != -1) {
				// 可以使用String的构造函数来解码将字节数组转化成字符串,也可以使用上面解码的例子
				strBuf.append(new String(inBytes, "GBK"));
			}
			
			// 一定记得要关闭输入流
			is.close();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		System.out.println(strBuf);
	}
}

你可能感兴趣的:(Java IO字节流原理)