Copyright©stonee
种一棵树最好的时间是十年前,其次是现在。
- 使用前要导包,使用时进行IO处理,使用后释放资源
- 流按照流向分为输入流和输出流
- 操作分为字节流(InputStream+OutputStream)和字符流(Reader+Writer)
- 建议打开相关API文档
流库和I/O流的区别
流库
- 是Java8中的新特性,对集合对象功能的增强它专注于对集合对象进行各种非常便利、高效的聚合操作,或者大批量数据操作(关于集合,可以参考我这篇博客)。
- Stream 不是集合元素,它不是数据结构并不保存数据,它是有关算法和计算的,它更像一个高级版本的Iterator。原始版本的 Iterator,用户只能显式地一个一个遍历元素并对其执行某些操作;高级版本的 Stream,用户只要给出需要对其包含的元素执行什么操作,比如 “过滤掉长度大于 10 的字符串”、“获取每个字符串的首字母”等,Stream 会隐式地在内部进行遍历,做出相应的数据转换。
- 流操作类似于SQL,注重于结果而不是过程
- 存在于
java.util.stream
包下 - 在成神之路专栏进行详细讲解流库
I/O流
- I/O流可以理解为一个内存到硬盘的管道,Java程序通过调用操作系统内部的API来实现对数据的输入输出
- I/O流和流库没有任何关系
- 从硬盘读入一个字节序列叫做输入流,写入一个字节序列叫做输出流
- 字节序列的来源和目的地可以是文件,也可以是网络连接,甚至是内存块
- 存在于
java.io
包下
先导知识
资源泄露
- 当程序已经和目的地或者来源通过操作系统建立了I/O流的连接,但是程序并没有指向这个连接,我们称之为内存泄露,在I/O流中,忘记使用
close
关闭通道容易产生资源泄露
数据类型
- 1 byte(字节) = 8 bits(位)
- Char 由2个byte组成,描述了一个UTF-16的代码单元
- 16位称为一个代码单元
- 某个字符对应的码表称为码点,一个码点可能包含多个代码单元
- String类中的length返回的是代码单元,不是码点
- ASCII:单字节编码,美国标准编码 ,一共规定了128个字符
- ISO8859-1:编码属于单字节编码,最多只能表示0-255的字符范围,主要用于西欧语言
- GBK/GB2312:中文的国际编码,专门用来表示汉字,是双字节编码
- Unicode和UTF区别
- Unicode:Java中就是使用此编码方式,也是最标准的一种编码,是使用16进制表示的编码。囊括了基本上所有的字符。但此编码不兼容ISO8859-1标准
- UTF-8 将Unicode码点编码为1到4个字节的序列
- UTF-16将Unicode码点编码为1个或者2个代码单元
- Unicode是一个字符集,UTF是一种编码规则
- 一个链接
为什么要分字节流和字符流
- 因为面向字节的流不便于处理以Unicode形式储存信息,所以抽象类
Reader
和Writer
继承了一个专门处理Unicode字符的单独类层次结构 - 字节流是基于byte的,字符流是基于char的
- 字节流的主要操作类是OutputStream、InputStream的子类;不用缓冲区,直接对文件本身操作。
- 字符流的操作字符类型数据,主要操作类是Reader、Writer的子类;使用缓冲区缓冲字符,不关闭流就不会输出任何内容。
- 字符流需要缓冲区的原因是它需要缓冲区中的字节进行编码
输入流和输入流
- 输入流(
Reader
&InputStream
)指将硬盘写入内存 - 输出流(
Writer
&OutputStream
)指将内存中数据写入硬盘
相对路径和绝对路径
I/O流框架
-
字节流框架
-
字符流框架
- 接口继承框架
字节流
通过I/O框架可以看出,字节流中输入输出流全部继承的抽象类
InputStream
&OutputStream
,我们用的更多的是他们的派生类
派生类
-
FileInputStream
&FileOutputStream
他们可以提供一个附着在磁盘文件上的输入流和输出流,只需要提供路径名就可。这两个类主要用于从文件中读取数据。我们只能读取字节和字节数组,解决这个问题的办法是用DataInputStream类
byte b = (byte) fin.read();
- 一个基本的程序:
public static void main(String[] args) throws IOException { FileInputStream fileInputStream = new FileInputStream("E://Images//666.jpg"); FileOutputStream fileOutputStream = new FileOutputStream("copy.jpg"); //这样子拷贝大文件效率特别低 int a; while ((a = fileInputStream.read()) != -1){ fileOutputStream.write(a); } fileInputStream.close(); fileOutputStream.close(); }
-
SequenceInputStream
将多个输入流合成一个输入流
public static void main(String[] args) throws IOException { FileInputStream fileInputStream = new FileInputStream("stonee.txt"); FileInputStream fileInputStream1 = new FileInputStream("stonee.txt"); FileInputStream fileInputStream2 = new FileInputStream("stonee.txt"); //创建集合对象 Vector<FileInputStream> vector = new Vector<>(); //将流对象存储到集合中 vector.add(fileInputStream); vector.add(fileInputStream1); vector.add(fileInputStream2); //将枚举中的输入流整合成一个 Enumeration<FileInputStream> enumeration = vector.elements(); SequenceInputStream sequenceInputStream = new SequenceInputStream(enumeration); FileOutputStream fileOutputStream = new FileOutputStream("copy.txt"); int a; while ((a = sequenceInputStream.read()) != -1){ fileOutputStream.write(a); } sequenceInputStream.close(); fileOutputStream.close(); }
-
ByteArrayInputStream
&ByteArrayOutputStream
ByteArrayInputStream将一个字节数组当作流输入的来源,而ByteArrayOutputStream则可以将一个字节数组当作流输出目的地
FileInputStream fileInputStream = new FileInputStream("stonee.txt"); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); int a; while ((a = fileInputStream.read()) != -1){ //将读到的数据写入内存缓冲数组中 byteArrayOutputStream.write(a); } //获取缓冲区中的值,可以指定不同码表 //byte[] bytes = byteArrayOutputStream.toByteArray(); //System.out.println(new String(bytes)); //这样只能用平台默认码表 System.out.println(byteArrayOutputStream); //关了也没y用 //byteArrayOutputStream.close(); fileInputStream.close();
-
ObjectInputStream
&ObjectOutputStream
通过对象序列化可以储存多态集合,前提是类需要实现Serializable接口
public class Demo4ObjectOutputStream { public static void main(String[] args) throws IOException, ClassNotFoundException { Person person = new Person("java",23); Person person1 = new Person("python",3); Person person2 = new Person("C#",8); //通过集合一次性调用 ArrayList<Person> arrayList = new ArrayList<>(); arrayList.add(person); arrayList.add(person1); arrayList.add(person2); ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("object.txt")); objectOutputStream.writeObject(arrayList); objectOutputStream.close(); input(); } public static void input() throws IOException, ClassNotFoundException { ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("object.txt")); ArrayList<Person> arrayList = (ArrayList<Person>)objectInputStream.readObject(); for (Person e: arrayList) { System.out.println(e); } objectInputStream.close(); } }
-
FilterInputStram
&FilterOutputStream
过滤器以及其子类这些文件的子类用于向处理字节的输入输出添加额外功能
上面的程序栗子中的拷贝效率特别低,是因为每次只传输一个字节,降低了传输效率。解决办法是我们可以加一个缓冲区来:
byte [] arr = new byte[1024*8]; //将file中的字节读入arr中,返回值是个数 int len; while ((len = fileInputStream.read(arr)) != -1 ){ //这种不会导致重复写 fileOutputStream.write(arr,0,len);
-
BufferedInputStream
&BufferedOutputStream
子类中提供了一个缓冲区的类,,用来解决缓冲区的问题
和ByteArrayOutStream的区别:
- 前者是输入输出时创建一个缓冲区,有助于更高效率地利用传输。当缓冲区满了以后就会被写入硬盘,即缓冲区中不能存储所有数据
- 后者在内存中创建了一个动态变化的数组,可以把数据暂存于这个数组中
-
DataOutputStream
&DataInputStream
可以将字节数组装到更有用的数据类型中
DataInputStream din = new DataInputStream( new BufferedInputStream( new FileInputStream("stonee.txt"))); //解决了InputStream类不能读取出字节之外的其他数据结构的问题 double x = din.readDouble();
-
注意
read()
和write()
方法在执行时都将阻塞,意味着如果流不能被立即访问,那么当前线程就将会被阻塞。available()
将会检查可读入的字节数量,可以防止阻塞:
if(in.available > 0){
byte[] data = new byte[in.availabe];
in.read(data)
}
close()
关闭时会冲刷缓冲区中的数据,所以不关闭文件不仅可以产生内存泄露,最后一个包也有可能得不到传递。也可以通过flush()
来人工冲刷缓冲区
-
使用
write()
方法的时候,一定要转换为byte:fileOutputStream.write("天下武功,唯快不破".getBytes());
-
System.in
是InputStream
类型,意味着我们不仅可从控制台读入信息,也可以从文件中读入信息
字符流
字符流和字节流大同小异,这里我们讨论一下派生类的功能
-
FileReader
&FileWriter
-
InputStreamReader
&OutputStreamWriter
- 前者将包含字节(用某种方式表示的字符)输入流转换为可以产生Unicode码元的读入器
- 后者使用选定的字符编码方式,将Unicode码元的输出流转换为字节流
Reader in = InputStreamReader(System.in, StandardCharsets.UTF_8);
-
PrintWriter
此类拥有以文本格式打印字符串和数字的方法。可以调用print等文本输出方法
PrintWriter out = new PrintWrite("stonee.txt","UTF-8"); //将harry写入stone.txt文件中 out.print("harry"); //将harry写入控制台上 System.out.print("harry"); //System.out属于PrintStream类中
PrintStream
在OutputStream
包下PrintWriter
在Writer
包下
-
BufferedReader
&BufferedWriter
用法和字节流中的一样,BufferedReader用于文本输入
String line; //字符串中没有-1,故返回null while ((line = bufferedReader.readLine()) != null){ bufferedWriter.write(line); //输出换行,和/r/n区别,newLine跨平台 bufferedWriter.newLine(); }
也可以用Scanner:
Scanner in = new Scanner( new FileInputStream("stonee.txt"), "UTF-8"); in.nextInt();
注意
readline()
返回null值时说明读完,read()
返回-1说明读完
读写二进制数据
虽然文本格式是可阅读的,但是二进制数据在传递的时候会更高效
DataInput
&DataOutput
接口
writeChars();//将char字符以二进制形式写入
readChars();//将二进制以char字符读出
writexxx();
readxxx();
-
DataInputStream
&DataOutputStream
实现了上述接口 -
RandomAccessFile
也实现了上述接口磁盘文件都是随机访问的,但是网络套接字的输入输出流却不是
这个类一次性实现两个功能
RandomAccessFile randomAccessFile = new RandomAccessFile("copy.txt", "rw"); int x = randomAccessFile.read(); System.out.println(x); randomAccessFile.seek(5); //将文件指针移到5 randomAccessFile.write(97); randomAccessFile.close();
其他
-
文件不关闭可能导致异常,关于带资源的trycatch语句请参考Java中的异常入门
-
显示的时候用字符流,复制的时候用字节流
-
本文涉及到的文件Files类和序列化没有详细说明
-
参考
- Java核心卷1,2