IO概述
什么是IO?
Java中I/O的操作主要是靠java.io
包下面的类和接口来实现的,进行输入和输出操作。输入也可以叫做读取数据,输出也可以叫做写入数据。
IO分类
根据数据的流向分为:输入流和输出流
- 输入流:把数据从其他设备上读取到内存当中的流。
- 输出流:把数据从内存当中写入到其他设备上的流。
根据数据类型分为:字节流和字符流
- 字节流:以字节为单位,读取数据的流。
- 字符类:以字符为单位,读写数据的流。
IO流的顶级父类
输入流 | 输出流 | |
---|---|---|
字节流 | 字节输入流:InputStream | 字节输出流:OutputStream |
字符流 | 字符输入流:Reader(读) | 字符输出流:Writer(写) |
字节流
一切皆为字节:一切文件数据(文本文档,图片,视频等)在存储时,都是以二进制数字的形式保存的,都是一个一个的字节,那么数据在进行传输的时候也是如此,所以字节流可以传输任意文件的数据。在操作流的时候,我们要明确,无论使用了什么样的流对象,底层传输的始终为二进制数据。
字节输出流(OutputStream)
java.io.OutputStream
此抽象类是表示输出字节流的所有类的超类,将指定的字节信息写入到目的地。它定义了字节输出流的基本共性的API方法:
返回值 | 方法 | |
---|---|---|
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 static | void | writer(int b) : 将指定的字节输出到此输出流中 |
备注:close()方法,当完成流的操作时,必须调用此方法,释放系统资源。
FileOutputStream类
java.io.FileOutputStream
类是文件字节输出类,用于将数据写入到文件中
构造方法
方法 | |
---|---|
public | FileOutputStream(File file) : 创建一个向指定 File 对象表示的文件中,写入数据的文件输出流(目的地是一个文件) |
public | FileOutputStream(String name) : 创建文件输出流以指定的文件名称,写入文件中。(目的地是一个文件的路径) |
当你创建一个流对象时,必须先传递一个文件路径,该路径下,如果没有这个文件,会创建该文件,如果有这个文件,会清空这个文件当中的数据。
构造方法的作用:
- 创建一个FileOutputStream类对象
- 会根据构造方法中的文件/文件路径(路径上的文件不存在),创建一个空的文件
- 会把FileOutputStream对象指向创建好的文件。
字节输出流的使用步骤:
- 创建一个FileOutputStream类对象,构造方法中传递写入数据的目的地
- 调用FileOutputStream对象的方法write,把数据写入到文件中
- 释放资源
public static void main(String[] args) throws IOException{
//1. 创建一个FileOutputStream类对象,构造方法中传递写入数据的目的地
FileOutputStream fos = new FileOutputStream("day28_IO\\a.txt");//相对路径
//2. 调用FileOutputStream对象的方法write,把数据写入到文件中
fos.write(97);
fos.close();
}
//a
写数据的时候,会把十进制的正数转换成二进制的正数97(1100001),任意的文本编辑器,在打开文件的时候,首先会查询编码表,把字节转换成字符表示,查询ASCII表(0-127) 1100001->97->a,如果是其他值,查询系统的编码表(中文GBK)。
数据的追加续写
如何在保留目标文件数据,还能继续添加新的数据到目标文件中?
方法 | |
---|---|
public | FileOutputStream(File file,boolean append) : 创建文件输出流,以写入有指定的File对象表示的文件中。 |
public | FileOutputStream(String name,boolean append) : 创建文件输出流,已指定的名称写入文件中。 |
这两个构造方法,参数中都需要传入一个boolean类型的值,true表示追加数据,false表示清空原有数据。这样的情况创建输出流对象,就可以指定是否需要在文件末尾追加
代码演示:
public static void main(String[] args) throws IOException {
//1.
FileOutputStream fos = new FileOutputStream("day28_IO\\c.txt", true);
//调用write方法
fos.write("Hello World".getBytes());
//3.关流
fos.close();
}
写入换行
Windows系统里,换行符号是\r\n
。把以指定是否需要追加续写换行。
Linux系统中,换行符号是/n
mac系统里,换行符号是/r
Unix系统里,每行结尾只有换行,即/n
回车符\r
和换行符\n
- 回车符:回到一行的开头
- 换行符:下一行(newLine)。
系统中的换行:
- Windows系统中,每行结尾是
回车+换行
。即\r\n
- Unix系统中,每行的结尾只有
换行
。即/n
- Max系统中,每行的结尾是
回车
。即/r
示例代码:
public static void main(String[] args) throws IOException {
FileOutputStream fos = new FileOutputStream("day28_IO\\c.txt", true);
//调用write方法
fos.write("\r\n".getBytes());
fos.write("java".getBytes());
//3.关流
fos.close();
}
//HelloWorld
//java
字节输入流(InputStream)
java.io.InputStream
此抽象类是表示字节输入流的所有类的超类。可读取字节信息到内存中,它定义了字节输入流的基本共性的API方法:
返回值 | 方法 | |
---|---|---|
public | void | close() : 关闭此输入流并释放与此流相关的其他的任何系统资源 |
public | abstract int | read() : 从输入流中读取数据的下一个字节 |
public | int | read(byte[] b) : 从输入流中读取一些字节数,并将它们存储到字节数组b当中 |
备注:close方法,当完成流的相关操作后,需要调用此方法关闭输入流
FileInputStream类
java.io.FileInputStream
类是文件输入流,从文件中读取字节。
构造方法
方法 | |
---|---|
public | FileInputStream(File file) : 通过打开与实际文件的连接来创建一个FileInputStream,该文件由文件系统中的FIle对象file命名。(File file : 文件) |
public | FileInputStream(String name) : 通过打开与实际文件的连接来创建一个FileInputStream,该文件由文件系统中的路径名name命名。(String name : 文件的路径) |
当你创建一个流对象时,必须传入一个文件路径,该路径下如果没有该文件,会抛出文件找不到异常,FileNotFoundException。
构造方法作用:
- 会创建一个FileInputStream类对象
- 会把FileInputStream类对象指定构造方法要读取的文件
使用步骤:
- 创建一个FileInputStream类对象,构造方法中指定要读取的文件
- 使用FileInputStream类对象中的方法read(),读取文件中的数据(如果文件遍历完,返回-1)
- 释放资源,关闭流对象
代码示例:
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("day28_IO\\c.txt");
int read = fis.read();
System.out.println(read);
int len = 0;
while ((len = fis.read()) != -1){
System.out.println((char)len + " ");
}
fis.close();
}
读取数据的原理
Java–>JVM–>OS–>OS调用系统中读取数据的方法–>读取文件中的数据。
可以使用字节数组类读取数据:read(byte[] b) 从输入流中读取一些字节数,并将它们存储到字节数组b当中。当读取到文件的末尾时,返回一个-1
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("day28_IO\\c.txt");
int len = 0;
byte[] bytes2 = new byte[1024];
while ((len = fis.read(bytes2)) != -1){
System.out.println(new String(bytes2,0,len));
}
}
备注:使用数组读取,每次可以读取到多个字节,减少了系统间的IO操作次数,从而提高了读取的效率,建议使用,
例题:通过字节流实现复制图片,从桌面的复制到D盘目录下
public static void main(String[] args) throws IOException {
//创建字节输入流对象,
FileInputStream fis = new FileInputStream("C:\\Users\\zzzz\\Desktop\\0001.png");
//创建字节输出流对象,
FileOutputStream fos = new FileOutputStream("D:\\codes\\java31\\day28_IO\\0001.png");
int len = 0;
byte[] bytes = new byte[1024];
//使用read方法读取文件
while ((len = fis.read(bytes)) != -1){
fos.write(bytes);
}
fos.close();
fis.close();
}
备注:流的关闭顺序:先开后关,后开先关;
字符流
当使用字节流读取文本文件的时候,可能会引发一些小问题,如果你遇到了中文字符时,可能不会显示完整的字符。那就是因为一个中文字符可能占据多个字节存储。所以Java就提供了一些字符流类,以字符为单位读写数据,专门用来处理文本文档文件。
字符输入流(Reader)
java.io.Reader
抽象类表示用于读取字符流的所有类的超类,可以读取字符信息到内存当中,它定义了字符输入流的基本共性的API方法
返回值 | 方法 | |
---|---|---|
public | void | close() : 关闭此输入流并释放与此流相关的其他的任何系统资源 |
public | int | read() : 从输入流中读取一个字符 |
public | int | read(char[] cbuf) : 从输入流中一次读取多个字符,并将他们存储到字符数组chuf当中 |
FileReader类
java.io.FileReader
类主要是用于读取字符文件的便捷性,构造方法使用默认的编码字符集和默认的字节缓冲区
备注:
- 字符编码:字节与字符的对应的规则,Windows系统中的中文编码字符集默认是GBK,idea中采用UTF-8
- 字节缓冲区:一个字节数组,用来临时存储字节数据
构造方法
方法 | |
---|---|
public | FileReader(File file) : 创建一个新的FileReader对象,指定需要读取的file对象 |
public | FileReader(String Filename) : 创建一个新的FileReader对象,指定需要读取的文件名称 |
当你创建一个流对象是,必须传入一个文件路径。类似于FileInputStream。
使用字符数组读取数据:read(char[] cbuf),每次读取cbuf的长度个字符到数组当中,返回读取到的有效的字符的个数,当它读取到末尾时,返回-1
代码示例
public static void main(String[] args) throws IOException {
FileReader fr = new FileReader("day28_IO\\c.txt");
char[] cbuf = new char[1024];
int len = 0;
while ((len = fr.read(cbuf)) != -1){
//字符数组转换成字符串
//String static valueOf()
System.out.println(String.valueOf(cbuf,0,len));
}
fr.close();
}
字符输出流(Writer)
java.io.Writer
抽象类时表示用于输出字符流的所有类的超类,将指定那个的字符写入到目的地中,它定义了字符输出流的基本的共性API方法:
返回值 | 方法 |
---|---|
void | writer() : 写入单个字符 |
void | writer (char[] cbuf) : 写入字符数组 |
void | writer(char[] cbuf,int off,int len) : 写入char数组的一部分,从char数组的起始索引值off开始,len个写入的字符个数 |
void | writer(String str) : 写入字符串 |
void | writer(String str,int off,int len) : 写入字符串的一部分,从字符串的其实索引off开始,写入len个字符个数 |
void | flush() : 刷新该流的缓冲。 |
void | close() : 关闭此流,但是需要先刷新它。 |
FileWriter类
java.io.FileWriter类是用于写入字符到文件中,构造方法使用系统默认的字符编码和默认的字节缓冲区。
构造方法
FileWriter(File file) : 创建一个新的FileWriter对象,指定需要读取的file对象 |
FileWriter(String filename) : 创建一个新的FileWriter对象,指定需要读取的文件名称 |
字符输出流的使用步骤
- 创建一个FileWriter对象,构造方法中绑定需要写入数据的目的地
- 使用FileWriter对象中的方法writer,把数据写入到内存缓冲区(字符转换为字节的过程)
- 使用FileWriter对象中的方法flush,把内存缓冲区中的数据刷新到文件中
- 释放资源(会先把内存缓冲区的数据刷新到文件中)
public static void main(String[] args) throws IOException {
//1.创建一个FileWriter对象,构造方法中绑定需要写入数据的目的地
FileWriter fw = new FileWriter("day28_IO\\e.txt");
//2.使用FileWriter对象中的方法writer,把数据写入到内存缓冲区(字符转换为字节的过程)
fw.write(122);
//3.使用FileWriter对象中的方法flush,把内存缓冲区中的数据刷新到文件中
//fw.flush();
//4.释放资源(会先把内存缓冲区的数据刷新到文件中)
fw.close();
}
关闭和刷新
因为内置缓冲区的原因,如果不关闭输出流,无法写入字符到文件中。但是关闭流对象,是无法继续写入数据到文件中。如果既想写入数据到文件中,又想继续使用流对象,那么就需要flush方法。
flush和close方法的区别:
flush():刷新缓冲区,流对象可以继续使用
close():先刷新缓冲区,然后会通知系统释放资源,关闭流对象,流对象不可以再使用。
public static void main(String[] args) throws IOException {
//1.创建一个FileWriter对象,构造方法中绑定需要写入数据的目的地
FileWriter fw = new FileWriter("day28_IO\\e.txt");
//2.使用FileWriter对象中的方法writer,把数据写入到内存缓冲区(字符转换为字节的过程)
fw.write(97);//a
//3.刷新
fw.flush();
//刷新之后流可以继续使用
fw.write(98);
//4.关流
fw.close();
//close方法之后,流关闭,已经从内存小时,不能再使用,如果使用会抛出异常 Stream closed
fw.write(99);
}
写入字符数组
:write(char[] cbuf)
和write(char[] cbuf,int off,int len)
,每次可以写入一个字符数组的数据,用法类似于FileOutputStream
public static void main(String[] args) throws IOException {
//new
FileWriter fw = new FileWriter("day28_IO\\g.txt", true);
//write
char[] chars = new char[50];
//fw.write();
for (int i = 0; i < 50; i++) {
chars[i] = (char)(20000+i);
fw.write(chars[i] + "\r\n");
}
//刷新
fw.flush();
//关流
fw.close();
}
备注:字符流只能操作文本文件,不能操作图片,视频等非文本文件。
当我们单纯的就是想操作文本文件,使用字符流,其他情况一律使用字节流
IO异常的处理
JDK1.7之前的处理,使用try…catch…finally 处理流中的异常。
JDK7新特性
在try的后边,可以增加一个(),在括号中定义流对象
那么这个流对象的作用域就在try中有效.
try中的代码执行完毕,会自动把对象释放.不用写finally
JDK7新特性
可以使用try-with-resource语句,该语句确保了每个资源在该语句结束时关闭,所谓的资源(resource)是指在程序完成后,必须要关闭的对象。
格式:
try(定义流对象) {
可能会出现异常的代码
}catch(异常类型 异常变量名) {
异常的处理逻辑
}
public static void main(String[] args) {
try (FileOutputStream fw = new FileOutputStream("day28_IO\\0001.png", true);
FileInputStream fr = new FileInputStream("C:\\Users\\zzzz\\Desktop\\0001.png");
) {
int len = 0;
while ((len = fr.read()) != -1) {
fw.write(len);
}
} catch (IOException e) {
e.printStackTrace();
}
}
-
属性集(重点)
-
缓冲流
-
转换流(字节<–>字符)
-
序列化流(Object & Seriable)
-
打印流(PrintStream)
属性集(Properties)
java.util.Properties
类继承于Hashtable
,用来表示一个持久的属性集,它使用键值结构存储数据,每个键及其对应的值都是一个字符串。
构造方法
方法 | |
---|---|
public | Properties() : 创建一个空的属性集列表。 |
共性的API方法
返回值 | 方法 | |
---|---|---|
public | Object | setProperty(String key,String value) : 保存一对属性 |
public | String | getProperties(String key) : 使用此属性列表中的指定的键搜索对应的值 |
public | set() | stringPropertiesNames() : 获取所有的键的名称并封装到set集合 |
public static void main(String[] args) {
Properties properties = new Properties();
//添加元素
properties.setProperty("name", "abc.txt");
properties.setProperty("size", "12000");
properties.setProperty("destination", "D:\\abc.txt");
properties.put("data", "小孙");
System.out.println(properties);//{destination=D:\abc.txt, name=abc.txt, size=12000, data=小孙}
//通过键来获取值
String data = properties.getProperty("data");//小孙
System.out.println(data);
String size = properties.getProperty("size");//12000
System.out.println(size);
//遍历该属性集
Set<String> set = properties.stringPropertyNames();
for (String key : set) {
//通过key获取value
String value = properties.getProperty(key);
System.out.println(key + "=" + value);
}
}
与流相关的方法
返回值 | 方法 | |
---|---|---|
public | void | load(InputStream input) : 从字节输入流中读取键值对(键和元素对)。 |
public | void | load(Reader reader) : 从字符输入流当中,读取文件中的键值对 |
public | void | store(OutputStream out,String comments) : 把集合当中的数据写入到字节输出流中。 (String comments : 注释:解释说明保存的文件用来做什么的,不能使用中文,会出现乱码) |
public | void | store(Writer writer,String comments) : 把集合当中的数据写入到字符输出流中。 |
参数中使用了字节输入流,通过流对象,可以关联到某个文件上,这样既可以加载文件中的数据。文件中的数据的格式:key=value
例如:
data=小孙;
size=12000;
name=abc.txt
load()方法示例
public static void show01() throws IOException {
//构建一个流对象
FileReader fr = new FileReader("day29_IO\\abc.txt");
//创建Properties集合
Properties properties = new Properties();
//使用Properties集合中的方法load读取保存到输入流当中的数据
properties.load(fr);
//遍历集合
Set<String> set = properties.stringPropertyNames();
for (String key : set) {
//通过key获取value值
String value = properties.getProperty(key);
System.out.println(key + "=" + value);
}
fr.close();
}
注意:
- 在存储键值对的文件中,键与值默认的连接符号是"=",可以使用空格(其他符号)。
- 存储键值对的文件中,可以使用
#
进行注释,被注释的键值对不会被读取到。 - 存储键值对的文件中,键与值默认是字符串,不用额外的添加双引号。
store()方法示例:
可以使用Properties集合当中的方法store(),把集合当中的临时数据,持久化写入到硬盘文件中。
public static void show02() throws IOException {
//1. 创建Properties集合对象,添加数据
Properties properties = new Properties();
properties.setProperty("四大名著1", "红楼梦");
properties.setProperty("四大名著2", "西游记");
properties.setProperty("四大名著3", "水浒传");
properties.setProperty("四大名著4", "三国演义");
//2. 创建字节输出流/字符输出流对象,搞糟方法中绑定需要写入数据的目的地
FileWriter fw = new FileWriter("day29_IO\\abcd.txt",true);
//3. 使用Properties集合中的方法store,把集合当中的临时数据,持久化写入到硬盘当中存储
properties.store(fw,"si da ming zhu");
//4. 释放资源
fw.close();
}
注意事项:注释不能使用中文,会有中文或有乱码,
缓冲流(Buffered)
缓冲流可以理解为对原来的使用数组方式进行数据传输的一种增强
按照类型分为:
- 字节缓冲流:BufferedInputStream,BufferedOutputStream
- 字符缓冲流:BufferedReader,BufferedWriter
缓冲流的基本原理,是在创建流对象的时候,会先创建一个内置的默认大小的缓冲区数组,通过缓冲区来读写数据,减少系统IO操作的次数,较少开销,提高程序读写的效率。
字节缓冲流
构造方法
方法 | |
---|---|
BufferedInputStream(InputStream input) : 创建一个新的缓冲输入流(继承InputStream) | |
public | BufferedInputStream(InputStream input,int size) : 创建一个新的具有指定大小的缓冲输入流 |
public | BufferedOutputStream(OutputStream output) : 创建一个新的缓冲输出流(继承OutputStream) |
public | BufferedOutputStream(OutputStream output,int size) : 创建一个新的具有指定大小的缓冲输出流, |
BufferedOutputStream(OutputStream output)方法示例
public static void main(String[] args) throws IOException {
//1.创建一个FileOutputStream流对象,构造方法中绑定需要写入数据的目的地
FileOutputStream fos = new FileOutputStream("day29_IO\\one.txt");
//2.创建BufferedOutputStream对象,构造方法中传递FileOutputStream流对象
BufferedOutputStream bos = new BufferedOutputStream(fos);
//3.使用BufferedOutputStream对象中的方法write,把数据写入到内部缓冲区中
bos.write("HelloWorld_Java".getBytes());
//4.使用BufferedOutputStream对象中的flush方法,把内存缓冲区的数据刷新到目的地中
bos.flush();
//5.释放资源
bos.close();
}
BufferedInputStream(InputStream input)方法示例
public static void main(String[] args) throws IOException {
//1.创建一个FileInputStream流对象,构造方法中绑定需要写入数据的目的地
final FileInputStream fis = new FileInputStream("day29_IO\\one.txt");
//2.创建BufferedInputStream对象,构造方法中传递FileInputStream流对象
final BufferedInputStream bis = new BufferedInputStream(fis);
//3.使用BufferedInputStream对象中的方法read,把数据写入到内部缓冲区中
byte[] bytes = new byte[1024];
int len = 0;
while ((len = bis.read(bytes)) != -1){
System.out.println(new String(bytes,0,len));
}
//4.释放资源
fis.close();
}
字符缓冲流
构造方法
方法 | |
---|---|
public | BufferedWriter(Writer out) ; 创建一个新的字符缓冲流 |
public | BufferedReader(Reader in) : 创建一个新的字符缓冲输入流 |
特有方法
返回值 | BufferedReader方法 | |
---|---|---|
public | String | readLine() : 读取整行的文本信息 |
返回值 | BufferedWriter方法 | |
public | void | newLine() : 写入一行的行分隔符,由系统属性定义换行符号。 |
BufferedWriter字符缓冲输出流
public static void main(String[] args) throws IOException {
//1.创建一个字符缓冲流对象,构造方法中传递一个字符输出流
final BufferedWriter bw = new BufferedWriter(new FileWriter("day29_IO\\two.txt"));
//2.调用字符缓冲流对象的writer方法,把数据写入到缓冲区中.
bw.write("我今天学习了ps");
bw.newLine();
bw.write("3D软件");
bw.newLine();
bw.write("c");
//3.调用字符缓冲区输出流对象中的flush方法,不内存缓冲区的数据刷新到文件
bw.flush();
//4.释放资源
bw.close();
}
BufferedReader字符缓冲输入流
public static void main(String[] args) throws IOException {
//1.创建一个字符缓冲输入流对象,构造方法中传递一个输入流对象
final BufferedReader br = new BufferedReader(new FileReader("day29_IO\\abc.txt"));
//2.使用字符缓冲输入流中的read方法或readLine方法,读取信息
//循环的结束条件 readLine()返回值为null
String str = null;
while ((str = br.readLine()) != null){
System.out.println(str);
}
//3.释放资源
br.close();
}
示例:文件复制:使用缓冲流和不使用缓冲流,消耗的时间比较
public class Demo05CopyImageFile {
public static void main(String[] args) throws IOException {
//show01();
show02();
}
//不使用缓冲流的操作
public static void show01() throws IOException {
//获取开始时间
long time1 = System.currentTimeMillis();
//构建一个字节输入流对象
FileInputStream fis = new FileInputStream("C:\\Users\\zzzz\\Desktop\\3.gif");
//构建一个字节输出流对象
FileOutputStream fos = new FileOutputStream("day29_IO\\3.gif");
//3.调用字节输入流的read(byte[] b)方法
byte[] bytes = new byte[1024];
int len = 0;//记录读取到的有效字节个数
while ((len = fis.read(bytes)) != -1){
//4.把读取到的字节内容写入到目的地文件中,调用write(byte[] b)方法
fos.write(bytes,0,len);
}
//5.释放资源,先开后关
fos.close();
fis.close();
//获取结束时间
long time2 = System.currentTimeMillis();
System.out.println("文件读取时间" + (time2 - time1) + "ms");
}
//使用缓冲流完成文件复制
public static void show02() throws IOException {
//获取开始时间
long time1 = System.currentTimeMillis();
//构建一个字节输入流对象
final BufferedInputStream bis = new BufferedInputStream(new FileInputStream("C:\\Users\\zzzz\\Desktop\\3.gif"));
//构建一个字节输出流对象
final BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("day29_IO\\3.gif"));
//使用字节缓冲输入流的方法read来读取文件
byte[] bytes = new byte[1024];
int len = 0;
while ((len = bis.read(bytes)) != -1){
//把读取到的字节内容写入到目的地
bos.write(bytes,0,len);
}
//释放资源
bos.close();
bis.close();
//时间
long time2 = System.currentTimeMillis();
System.out.println("文件读取时间" + (time2 - time1) + "ms");
}
}
//不使用缓冲流:文件读取时间454ms
// 使用缓冲流:文件读取时间148ms
转换流(字节流<–>字符流)
字符编码:
按照某种规则,将字符存储到计算机中,成为编码;反之,将存储到计算机中的二进制数按某种规则解析显示出来,称为解码。在进行编码和解码过程中,我们必须采用的是同一种规则,才能数据正常,否则,会导致乱码现象。
- 字符编码:就是一套自然语言中的字符与二进制数之间的对应规则。
字符集:
- 字符集:一个系统可支持的所有字符的集合,包括各国文字,标点符号,图形符号,数字等。也叫编码表。
计算机中要准确存储和识别各种文字的字符符号,需要进行字符编码,一套字符集至少有一套字符编码。
常见的字符编码集由ASCII字符集,GBK字符集,Unicode字符集
ASCII字符集:
- ASCII是基于拉丁字母的一套编码系统。用于显示现代英语。
- 基本的ASCII字符集,使用7位(bit)表示一个字符,共128个字符。ACSII的扩展字符集,使用8位(bit)表示一个字符,共256个字符。
ISO-8859-1字符集:
- 拉丁码表,别名–Lantin-1,用于显示欧洲使用的语言,包括荷兰,丹麦,德语,意大利语,西班牙语等。
- ISO-8859-1使用单字节码表,兼容ASCII编码。
GB字符集:
- GB2312:称为简体中文码表,里面大概含有7000多个简体汉字,此外一些数学符号,罗马,希腊的字母,日本的假名都编进去了,连在ASCII里的原来就有的数字,表点,字母都统统重新用两个字节编写进去了。
- GBK:最常用的中文码表,是在原来的GB2312码表的基础上进行扩展。使用双字节编码。共收录了21000多个汉字,完全兼容GB2312标准,同时支持繁体汉字以继日韩文字。
- GB18030:最新的中文码表,共收录了7万多个汉字,采用多字节编码,每个字可以由1个字节,2个字节或者是4个字节组成,支持国内少数民族的文字,同时也支持繁体字以继日韩汉字等
Unicode字符集
- Unicode编码系统为表达任意语言的任意字符而设计的,是业界的一种标准,也称为统一码表,标准万国码表
- 它最多使用四个字节的数字来表示每个字母,符号,或者文字,由三种常见的编码方案,UTF-8,UTF-16,UTF-32。
- UTF-8编码表,用来表示Unicode标准中的任意字符,编码规则
- 128个US-ASCII字符,使用的是一个字节编码
- 拉丁字的字母,需要两个字节编码
- 大部分常见的汉字,使用的是三个字节编码
- 其他极少数的辅助字符,采用的四个字节编码
编码会引发的问题
由于编码规则不一致,导致引发乱码现象。
那么如何来读取GBK编码的文件?
InputStreamReader类
转换流java.io.InputStreamReader
,是Reader的子类,它是从字节流到字符流的桥梁,它读取字节,并使用指定的字符集将其解码为字符。它的字符集可以由名称指定,或者可以使用平台默认的字符集。
构造方法
方法 | |
---|---|
public | InputStreamReader() : 创建一个使用默认的字符集的字符流 |
public | InputStreamReader(InputStream in,String charsetName) : 创建一个指定字符集的字符流 |
InputStream in : 字节输入流,用来读取文件种保存的字节
String charsetName : 指定编码表名称,UTF-8/utf-8,GBK/gbk,不区分大小写,不指定默认为UTF-8.。
代码演示:
//读取一个使用GBK编码的文件
public static void show01() throws IOException {
//1.创建InputStreamReader对象,构造方法中传递字节输入流和指定的编码表名称
InputStreamReader isr = new InputStreamReader(new FileInputStream("day29_IO\\新建文本文档.txt"), "GBK");
//2.使用InputStreamReader对象中的read方法读取文件中的信息
int len = 0;
while ((len = isr.read()) != -1) {
System.out.print((char) len);
}
//3.释放资源
isr.close();
}
OutputStreamWriter类
转换流java.io.OutputStreamWriter
是Writer的子类,它是字符流到字节流的桥梁。使用指定的编码字符集(charsetName)将字符编码为字节。它的字符集可以手动指定,也可以使用平台默认的字符集。
构造方法
方法 | |
---|---|
public | OutputStreamWriter() : 创建一个使用平台默认的字符集的字符流 |
public | OutputStreamWriter(OutputStream out,String charsetName) : 创建一个指定的字符集的字符流。 |
将GBK编码的文件,转换成UTF-8编码的文件。
- 将指定GBK编码的转换流,读取文本文件。InputStreamReader
- 使用UTF-8编码的转换流,写入到文本文件。OutputStreamReader
public static void main(String[] args) throws IOException {
//1.1将指定GBK编码的转换流,读取文本文件。InputStreamReader
InputStreamReader isr = new InputStreamReader(new FileInputStream("day30_IO\\GBK.txt"), "GBK");
//1.2使用UTF-8编码的转换流,写入到文本文件。OutputStreamReader
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("day30_IO\\UTF-8.txt"), "UTF-8");
//2.使用转换流读取数据文件
char[] chars = new char[1024];
int len = 0;
while ((len = isr.read(chars)) != -1){
//读取出来的数据要写入到目的地文件
osw.write(chars,0,len);
}
osw.close();
isr.close();
}
序列化流
Java提供了一种对象序列化的机制,用一个字节可以表示一个对象,该字节序列包含该对象的数据,对象的类型和对象中存储的属性信息。字节序列写入到文件中后,就想当于在文件中保存类一个对象信息。
反之,给字节序列还可以从文件读取出来,重构对象,对它进行反序列化。对象的类型和对象中存储的数据信息,都可以用来在内存中创建对象。
ObjectOutputStream类
java.io.ObjectOutputStream
类,将Java对象的原始数据类型写入到文件中,实现对象的持久化存储。
构造方法
构造方法 |
---|
ObjectOutputStream(OutputStream out) : 创建一个指定的OutputStream的ObjectOutputStream类对象 |
特有的方法
返回值 | 方法 |
---|---|
void | writerObject() : :将指定的对象写入到对象序列化流 |
public static void main(String[] args) throws IOException {
//1.创建ObjectOutputStream对象,构造方法中传递只当的字节输出流
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("day30_IO\\students.txt"));
//2.使用ObjectOutputStream对象中的writeObject方法,把对象写入到文件中
//2.1先创建一个对象
Student s1 = new Student("小孙",30);
//2.2写对象到文件中
oos.writeObject(s1);
//3.释放资源
oos.close();
}
序列化操作
-
一个对象想要能够序列化和反序列化,必须满足两个条件
-
该类必须实现
java.io.Serializable
接口,Serializable接口,是一个标记型接口,如果该类没有实现Serializable接口,或抛出NotSerializableException。 -
该类的所有属性必须是可以实现序列化或反序列化。如果有一个属性不想让它参与序列化,则该属性必须标明是瞬态的,瞬时的。这个关键字是
transient
。
进行序列化时该属性没有值,系统会赋一个默认值public class Student implements Serializable { private String name; private transient Integer age;//不让age属性参与序列化 }
-
ObjectInputStream类
java.io.ObjectInputStream
类是反序列化流,将之前使用ObjectOutputStream序列化流的原始数据恢复过来。
构造方法
构造方法 |
---|
ObjectInputStream(InputStream in ) : 创建一个指定的InputStream的对象反序列化流对象 |
特有方法
返回值 | 方法 | |
---|---|---|
public final | Object | readObject() : 从反序列化流中读取一个对象。 |
public static void main(String[] args) throws IOException, ClassNotFoundException {
//1.创建一个ObjectInputStream流对象,构造方法中传递一个字节输入流对对象
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("day30_IO\\students.txt"));
//2.使用ObjectInputStream流对象中的方法readObject(),读取保存在文件中的对象数据
Object obj = ois.readObject();
//3.释放资源
ois.close();
//4.查看对象的数据
System.out.println(obj);
if (obj instanceof Student){
Student student = (Student)obj;
System.out.println(student.getAge() + student.getName());
}else {
System.out.println("转换失败");
}
}
对于JVM来说,能够进行反反序列化的对象,前提条件是必须能够找到class文件的类,如果找不到该class文件,则会抛出一个ClassNotFoundException。
另外,当JVM序列化对象时,能够找到class文件,但是class文件在序列化对象时,发生了修改,那么反序列化操作就会抛出一个invalidClassException异常:原因如下:
- 该类的序列化版本号与从流中读取出来描述该类的版本号不一致。
- 该类包含了位置数据类型
- 该类没有可访问的无参构造方法。
Serializable接口给需要序列化的类,提供了一个序列版本号,serialVersionUID该版本号的目的就是在于验证序列化的对象和对应的类的是否是版本一致的。可以在类中手动定义一个版本号,如:private static final long serialVersionUID = 1l;
练习:存储一些对象,实现序列化和反序列化动作
public static void main(String[] args) throws IOException, ClassNotFoundException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("day30_IO\\stu.txt"));
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("day30_IO\\stu.txt"));
Student s1 = new Student("小王",20);
Student s2 = new Student("小李",30);
Student s3 = new Student("小王",18);
ArrayList<Student> list = new ArrayList<>();
list.add(s1);
list.add(s2);
list.add(s3);
//
oos.writeObject(list);
Object obj = ois.readObject();
if (obj instanceof ArrayList) {
ArrayList<Student> list2 = (ArrayList<Student>) obj;
System.out.println(list2.get(1).getAge());
}else {
System.out.println("转型失败");
}
ois.close();
oos.close();
}
打印流
java.io.PrintStream
类继承OutputStream
类,除了常规的IO流的操作,还有很方便的打印各种数据值的方式
- 只负责数据的输出,不负责数据的读取
- 与其他流不同,这个流不会抛出
IOException
特有的方法
方法 | |
---|---|
void | print(任意类型的值) : |
void | println(任意类型的值并且含有换行) |
构造方法
public | PrintStream(File file) : 输出的目的地是一个文件 |
---|---|
public | PrintStream(String filename) : 输出的目的地是一个文件路径 |
public | PrintStream(OutpotStream out) : 输出的目的地是一个字节输出流 |
注意事项:
如果使用来自父类当中的方法write()
写入数据,那么查看数据的时候会查询编码表
如果使用的是自己特有的方法print/println
写入数据,那么写入的数据和输出的数据是一样的。
改变打印流的方向
正常System.out
就是PrintStream
类型的,数据的流动的位置在控制台中,改变数据的流动位置,通过System.setOut(PrintStream print)
来改变方向
PrintStream out = System.out;
out.print(123);//在控制台中输出
//构造方法创建一个打印流对象
PrintStream printStream = new PrintStream("day30_IO\\print.txt");
//改变打印流的方向
System.setOut(printStream);
System.out.println("输出数据的位置");