字节是文件传输的基本单位,文件内容也是以字节为单位存储的,从文件中把数据读到程序使用输入流,从数据从程序读到文件使用输出流。(以程序为参考对象)
输出流:超类 OutputStream ,对文件操作就用其子类FileOutputStream
输入流:超类 InputStream ,对文件操作就用其子类FileInputStream
输入输出字节流原理:每次只会操作一个字节(从文件中读取或写入),最终调用的是本地方法 native 方法
在UTF-8编码中:一个中文等于三个字节,中文标点占三个字节。 一个英文字符等于一个字节,英文标点占一个字节。
在Unicode编码:一个英文等于两个字节,一个中文(含繁体)等于两个字节。中文标点占两个字节,英文标点。
我设置的是UTF-8,以3个字节读取一次,读取如下面代码:
输出流:
//从程序中写入文件中
public static void out(){
//确定文件对象
File file = new File("1.txt");
//创建文件输出流对象
try {
OutputStream out = new FileOutputStream(file,true);//第二个参数没有就表示覆盖,为true就表示追加
String info = "你好\r\n";
// '\r\n' 表示换行 或者通过以下方式
//获取换行符
String line = System.getProperty("line.separator");
out.write(info.getBytes());
//刷新流
out.flush();
//关闭流
out.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
txt文件:
输入流:
//从文件中写入程序
public static void input(){
//确定文件对象
File file = new File("1.txt");
//创建文件输出流对象
try {
/**
* 通常写法
*/
InputStream input = new FileInputStream(file);
//每次读取的字节按照一定的字节去读
byte[] bytes = new byte[1024];
int len;//表示每次读取的字节长度
//用于读取到字节数组后用于还原为字符串对象
StringBuilder sb = new StringBuilder();
//开始读取---把数据读到数组中并返回读取的字节数,当不等于-1时,表示读取到数据,当等于-1时表示已经读完了
while ((len = input.read(bytes))!=-1){
//根据读取到的字节数组再转换为字符串并添加到StringBuilder中
sb.append(new String(bytes));//这样会导致传参进去后字节读取出错
}
System.out.println(sb);
//关闭流
input.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
以上是以1024个字节为读取单位,这时会出现多读空格而导致的空格乱码:
为什么会出现这样的现象呢?因为UTF8编码中一个中文是3个字节,如果一次读取1024个字节(或其他字节如下图的4个字节),在字节数组中会出现后面索引位置的字节没有被覆盖或填充或如下图的多读一个字节乱码情况:
1.txt:
输入流代码:
控制台输出:
这样会导致多出一个字节乱码,而恰好每次读取3个字节时不会出现乱码,每次都是最后才乱码:原因是StringBuilder在append ‘bytes’数组时,bytes是4个字节读一次,一次性多读了1个字节导致的问题。
主要是最后append时应该追加到读取的长度即可,没必要完全用完字节数组中的所有字节:
最终代码:
import java.io.*;
import java.nio.charset.StandardCharsets;
public class demo_io {
/**
* 字节是文件传输的基本单位,文件内容也是以字节为单位存储的,从文件中把数据读到程序使用输入流,从数据从程序读到文件使用输出流
* @param args
*/
public static void main(String[] args) {
/**
* 字节输入输出流
* 输出流:超类 OutStream 对文件操作就用其子类FileOutStream
* 输入流:超类 InputStream 对文件操作就用其子类FileInputStream
* 输入输出字节流原理:每次只会操作一个字节(从文件中读取或写入),最终调用的是本地方法 native 方法
*
* 在UTF-8编码中:一个中文等于三个字节,中文标点占三个字节。 一个英文字符等于一个字节,英文标点占一个字节。
* Unicode编码:一个英文等于两个字节,一个中文(含繁体)等于两个字节。中文标点占两个字节,英文标点。
*/
// out();
input();
}
//从程序中写入文件中
public static void out(){
//确定文件对象
File file = new File("1.txt");
//创建文件输出流对象
try {
OutputStream out = new FileOutputStream(file,true);//第二个参数没有就表示覆盖,为true就表示追加
String info = "你好\r\n";
// '\r\n' 表示换行 或者通过以下方式
//获取换行符
String line = System.getProperty("line.separator");
out.write(info.getBytes());
//刷新流
out.flush();
//关闭流
out.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
//从文件中写入程序
public static void input(){
//确定文件对象
File file = new File("1.txt");
//创建文件输出流对象
try {
/**
* 通常写法
*/
InputStream input = new FileInputStream(file);
//每次读取的字节按照一定的字节去读
byte[] bytes = new byte[4];
int len;//表示每次读取的字节长度
//用于读取到字节数组后用于还原为字符串对象
StringBuilder sb = new StringBuilder();
//开始读取---把数据读到数组中并返回读取的字节数,当不等于-1时,表示读取到数据,当等于-1时表示已经读完了
while ((len = input.read(bytes))!=-1){
//根据读取到的字节数组再转换为字符串并添加到StringBuilder中
sb.append(new String(bytes,0,len));//这样会导致传参进去后字节读取出错
}
System.out.println(sb);
//关闭流
input.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
简单查看源码:
原来是调用的重载的方法,再次点击进去查看:
大致内容为,显示判断传入的参数是否合法(符合规则),不符合就return,符合就一个一个字节去读。
在点看read()就是抽象方法,我们点开 FileInputStream 可以看到它实现的抽象方法其实是调用了另外一个方法:
再次点开查看是一个本地方法:
OutStream亦是如此!