一、 IO流的概述及分类:Java.io包
IO流概述
IO流用来处理设备之间的数据传输, Java对数据的操作是通过流的方式, Java用于操作流的对象都在IO包中。
IO流的分类:
- 按照数据流向:
- 输入流:只能从硬盘或文件读取数据,不能写入数据。
- 输出流:只能向文件硬盘写入数据,不能读取数据。
- 按照数据单元:
- 字节流:操作的数据单元是8位的字节。
- InputStream (抽象方法)
- FileInputStream
- BufferedInputStream(高效率)
- OutputStream(抽象方法)
- FileOutputStream
- BufferedOutputStream(高效率)
- InputStream (抽象方法)
- 字符流:操作的数据单元是8位的字节。
- Reader (抽象方法)
- FileReader
- BufferedReader(高效率)
- Writer(抽象方法)
- FileWriter
- BufferedWriter(高效率)
- Reader (抽象方法)
- 字节流:操作的数据单元是8位的字节。
- 按照流的角色:
- 节点流(低级流):从/向一个特定的IO设备读/写数据的流。
- 处理流(高级流):对一个已经存在的流进行连接或者封装,通过封装后的流来实现数据读写功能。
选取说明:字节流是万能流,而字符流一般用来读取带有中文字符的文件,字符流的底层实现 = 字节流+编码表。
- 流的概念模型:水滴
- 水滴模型概述:
- 水滴是最小的输入\输出单位,对字节流来说,一个水滴就是一个字节;对字符流来说,一个水滴就是一个字符。
- 处理流主要功能:
- 性能的提高:主要以增加缓冲的方式来提高输入/输出的效率。
- 操作的便捷:处理流可能提供了一系列便捷的方法来一次输出输入大批的内容,而不是输入/输出一个或者多个水滴。
- 水滴模型概述:
二、 各种流的用法说明:
1. 字节流:
FileOutputStream:写入数据 配合使用 FileInputStream:读出数据。
应用:复制文本文件:
- 操作步骤:
- 创建字节输入流对象和字节输出流对象。
- throws 和 try…catch异常处理。
- 频繁的读写操作。
- 两组常用读写方法:
- 读取:
- int read( ):一次性读取一个字节,返回读取到的字节的个数,如果没有数据,就返回 -1
- int read(byte[] b):一次读取一个字节数组,返回读取到的字节的个数,如果没有数据了就返回-1
- 写入:
- public void write(int b):写一个字节。
- public void write(byte[] b,int off,int len):写一个字节数组,off表示起始位置,len表示长度。
- 读取:
- 两组常用读写方法:
- 释放资源。
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
/**
* @author Prince、
* @date 2020/2/15
*/
//方法一:一次读取一个字节
public class Copy {
public static void main(String[] args) throws IOException {
FileInputStream fileInputStream = new FileInputStream("a.txt");
FileOutputStream fileOutputStream = new FileOutputStream("copya.txt");
int len = 0;
while ((len = fileInputStream.read())!=-1){
fileOutputStream.write(len);
fileOutputStream.flush();
}
fileInputStream.close();
fileOutputStream.close();
}
}
//方法二:一次读取一个字节数组
public class Copy {
public static void main(String[] args) throws IOException {
FileInputStream fileInputStream = new FileInputStream("a.txt");
FileOutputStream fileOutputStream = new FileOutputStream("copya.txt");
int len = 0;
byte[] bytes = new byte[1024];
while ((len = fileInputStream.read(bytes))!= -1){
fileOutputStream.write(bytes,0,len);
fileOutputStream.flush();
}
fileInputStream.close();
fileOutputStream.close();
}
}
2. 字节字符缓冲区流
简述:字节流一次读写一个数组的速度明显比一次读写一个字节的速度快很多,这是加入了数组这样的缓冲区效果,java本身在设计的时候,也考虑到了这样的设计思想,所以提供了字节缓冲区流。
字节缓冲区流:从字符/字符输入/输出流中读取/写文本,缓冲各个字节或者字符,从而实现字节、字符、数组和行的高效读取/写。
可以指定缓冲区的大小,或者可使用默认的大小。大多数情况下,采取默认值。
中文 | 名称 |
---|---|
字节缓冲输出流 | BufferedOutputStream |
字节缓冲输入流 | BufferedInputStream |
字符缓冲输出流 | BufferedWriter |
字符缓冲输入流 | BufferedReader |
字节缓冲输出流 BufferedOutputStream 和 字节缓冲输入流 BufferedInputStream
public class CopyDemo2 {
public static void main(String[] args) throws IOException {
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream("acopy.txt"));
bufferedOutputStream.write("雨纷纷旧故里草木深".getBytes());
bufferedOutputStream.write("\n我听闻你始终一个人\r".getBytes());
bufferedOutputStream.write(100);
byte[] bytes = new byte[]{'a','b','c'};
bufferedOutputStream.write(bytes);
bufferedOutputStream.close();
BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream("a.txt"));
byte[] bytes1 = new byte[50];
int num = bufferedInputStream.read(bytes1);
System.out.println(num);
bufferedInputStream.close();
}
}
字符缓冲输出流 BufferedWriter 和 字符缓冲输入流 BufferedReader
public class CopyDemo2 {
public static void main(String[] args) throws IOException {
BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter("a.txt"));
bufferedWriter.write(100);
bufferedWriter.write("菊花残,满地伤");
bufferedWriter.write("\r你的笑容也泛黄\n");
bufferedWriter.write(new char['我']);
bufferedWriter.close();
BufferedReader bufferedReader = new BufferedReader(new FileReader("a.txt"));
String s = null;
while ((s = bufferedReader.readLine())!=null){
System.out.println(s);
}
bufferedReader.close();
}
}
3.转换流(了解)
简述:字节流使用广泛,字符流操作方便。在对文本文件进行操作时,我们习惯性使用更加方便的字符流,所以java提供了字节流到字符流的转换。
字符流: 字符流 = 字节流 + 编码表
编码:把看得懂的变成看不懂的: String – byte[]
解码:把看不懂的变成看得懂的: byte[] – String
- OutputStreamWriter:字节输出流转字符输出流
默认编码(GBK)
构造方法可以指定编码 - InoutStreamReader:字节输入流转字符输入流
默认的编码(GBK)
构造方法可以指定编码
转换流的名字比较长,而我们常见的操作都是按照本地默认编码实现的,所以,为了简化我们的书写,转换流提供了对应的子类。
转换流 | 便捷类 |
---|---|
OutputStreamWriter | FileWriter |
InputStreamReader | FileReader |
4.字符流
因为一个中文汉字占两个字节,如果使用字节流操作,我们就需要对这个汉字进行拆分和合并,比较麻烦,java为了方便的操作中文就给我们提供了一个字符流,但是计算机现在操作的最小单位时字节,那么操作数据,本质还是对最小单位的操作,于是这个字符流底层还是使用的字节流,那么要使用字符流,就需要对这个中文汉字进行拆分和合并,那么按照的就是编码表进行拆分合并。
字符流: 字符流 = 字节流 + 编码表
- 编码表:
- ASCLL
- GB2312
- GBK:简体中文字符集
- BIG5:繁体中文字符集
- UNICODE(usc-2,usc-4)
- UTF-8:8为UCS转换格式
字符流特殊方法:
方法 | 功能 |
---|---|
BufferedWriter: public void newLine() | 根据系统来决定换行符 具有系统兼容性的换行符 |
BufferedReader: public String readLine() | 一次读取一行数据 是以换行符为标记的 读到换行符就换行 没读到数据返回null包含该行内容的字符串,不包含任何行终止符,如果已到达流末尾,则返回 null |
三、其他流及其用法:
数据输入输出流
- 数据输入流: DataInputStream
- 数据输出流: DataOutputStream
- 特点: 可以写基本数据类型,可以读取基本数据类型
内存操作流
- 内存输出流:ByteArrayOutputStream
- 内存输入流:ByteArrayInputStream
- 特点:此流关闭无效,所以无需关闭。
- 操作字符数组
- CharArrayWrite
- CharArrayReader
- 操作字符串
- StringWriter
- StringReader
- 操作字符数组
打印流
- 分类:
- PrintStream
- PrintWriter
- 特点:
- 打印流只能操作目的地,不能操作数据源(不能进行读取数据)
- 可以操作任意数据类型的数据 调用print() 方法可以写任意数据类型
- 如果我们启用自动刷新,构造中选择true。那么在调用println、printf 或 format 方法中的一个方法的时候,会完成自动刷新
序列化流
- 序列化流: ObjectOutputStream
- 所谓的序列化:就是把 对象 通过流的方式存储到文件中。注意:此对象 要重写Serializable 接口才能被序列化。
- 反序列化流: ObjectInputStream
- 反序列化:就是把文件中存储的对象以流的方式还原成对象。
一个对象可以被序列化的前提是这个对象对应的类必须实现Serializable接口。
public class Student implements Serializable {
//生成序列化ID
private static final long serialVersionUID = 320594411320224722L;
private String name;
// transient 关键字修饰后,使用transient关键字声明不需要序列化的成员变量 按照初值
//transient private int age;
private int age;
注意事项:
- transient 关键字修饰后,使用transient关键字声明不需要序列化的成员变量 按照初值。
- 我们的一个类可以被序列化的前提是需要这个类实现Serializable接口,就需要给这个类添加一个标记。
- 在完成序列化以后,序列化文件中还存在一个标记,然后在进行反序列化的时候,会验证这个标记和序列化前的标记是否一致。
- 如果一致就正常进行反序列化
- 如果不一致就报错了。 而当我们把操作对象类做了修改后(比如全局变量权限修改),将相当于更改了标记,而导致这两个标记不一致,就报错了。黄色警告线错误!
自动生成对象的序列化ID的方法:
Setting——Editor——inspections——Serializable class without SerialVersion UID 选定打钩
随机访问流
- RandomAccessFile 能读能写。支持对随机访问文件的读取和写入
- RandomAccessFile类不属于流,是Object类的子类。但它融合了InputStream和OutputStream的功能。
- RandomAccessFile的父类是Object , 这个流对象可以用来读取数据也可以用来写数据.可以操作任意数据类型的数据.
- 创建随机访问流对象时候,注意设置访问权限。 r-读;w-写
- RandomAccessFile F = new RandomAccessFile(“a.txt” , “rw”);
- 我们可以通过getFilePointer方法获取文件指针,并且可以通过seek方法设置文件指针位置。
计算机识别中文和字符的区别:
当遇到的是字符,读取的字节将会是一个整数。
当遇到的是中文(一个中文被看成两个字节),这时候读取的两个字节,第一个字节必是负数,第二个可能是正数,可能是负数。
Java 1.7后,中文按照3个字节存储,每次遇到中文字符,会先读取两个字节,之后每个汉字按照3个字节计算。验证请参考下图指针位置。
private static void WriteData() throws IOException {
RandomAccessFile w = new RandomAccessFile("c.txt","rw");
w.writeInt(100);
w.writeByte(12);
w.writeBoolean(true);
w.writeUTF("爱你"); //遇到中文,先读取2个字节(6+2),再按照每个汉字3字节计算 (6+2)+3+3
w.close();
}
文件复制增强版:断点复制
说明:可自行设置断点,控制复制过程中断,下次重新运行程序,seek方法自动定位上次复制的指针位置,继续复制。
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
/**
* @author Prince、
* @date 2020/2/14
*/
public class IOcopyDemo2 {
public static void main(String[] args) throws IOException {
// 断点复制 复制文件
RandomAccessFile r = new RandomAccessFile("稻香.mp3","rw");
RandomAccessFile w = new RandomAccessFile("稻香copy.mp3","rw");
File file = new File("稻香copy.mp3");
if (file.exists()) {
long length = file.length();
//定位字节位置
r.seek(length);
w.seek(length);
} else {
r.seek(0);
w.seek(0);
}
Copy(r, w);
}
private static void Copy(RandomAccessFile r, RandomAccessFile w) throws IOException {
int len = 0;
byte[] bytes = new byte[10];
while ((len = r.read(bytes))!=-1){
long filePointer = r.getFilePointer();
System.out.println(filePointer);
w.write(bytes,0,len);
}
r.close();
w.close();
}
}
Properties
-
Properties 可保存在流中或从流中加载。
-
属性列表中每个键及其对应值都是一个字符串。
-
Properties父类是Hashtable。属于双列集合,这个集合中的键和值都是字符串,因此Properties不能指定泛型。
-
Properties和IO流进行配合使用:
方法 | 功能 |
---|---|
public void load(Reader reader) | 读取键值对数据把数据存储到Properties中 配置文件中数据的键值要用"="拼接 Key=Value |
public void store(Writer writer, String comments) | 把Properties集合中的键值对数据写入到文件中, comments注释,一般默认。 |
public class IODemo2 {
public static void main(String[] args) throws IOException {
Properties properties = new Properties();
properties.load(new FileReader("userproperties.txt"));
boolean b = properties.containsKey("Lisa"); //原值 Lisa=666666
if(true){
properties.put("Lisa","123456"); //通过键值,覆盖原来的值
//把集合中的数据 写回配置文件 覆盖掉配置文件
properties.store(new FileWriter("userproperties.txt"),null);
}
String str = properties.getProperty("Lisa");
System.out.println(str); 覆盖后的值 Lisa=123456
}
}