Java进阶之属性集&缓冲流&转换流&序列化流
- 一、IO流的异常处理
- 1.1 jdk7之前的异常处理
- 1.2 jdk7的异常处理
- 二、Properties
- 2.1 Properties的方法
- 2.2 Properties从文件读取键值对
- 三、缓冲流
- 3.1 字节缓冲流
- 3.2 字符缓冲流
- 四、转换流
- 4.1 编码表
- 4.2 字符流读取乱码问题
- 4.3 转换流指定编码读取
- 4.4 转换流指定编码写
- 五、序列化流
- 5.1 序列化概述
- 5.2 序列化流的使用
- 5.3 反序列化的使用
- 5.4 序列化中的static和transient
- 5.5 序列化中的序列号
- 六、打印流
- 七、commons-io
- 7.1 commons-io初体验
一、IO流的异常处理
1.1 jdk7之前的异常处理
/*
JDK7之前IO流的异常处理
*/
public class Demo01Exception {
public static void main(String[] args) {
Writer w = null;
try {
//创建字符输出流对象
w = new FileWriter("iotest\\file01.txt");
//写数据
w.write("你好");
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
//必须流对象创建成功,那么我们才需要释放资源.
//如果流对象创建成功了,那么w的值不是null
if (w != null) {
//释放资源
w.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
1.2 jdk7的异常处理
在JDK7的时候,多了一种try...with...resource
的语句,可以十分方便的处理IO流中的异常。
try...with...resource
就是一种特殊的try..catch
语句。
格式:
try(创建流对象的代码) {
...
} catch(异常类名 变量名) {
...
}
作用: 在try后面小括号中创建的流对象,无论如何都会自动调用close方法。
注意:
- 在try后面小括号中可以创建多个流对象,多个流对象之间使用分号隔开
public class Demo02Exception {
public static void main(String[] args) {
try (Writer w = new FileWriter("iotest\\file02.txt")) {
//写数据
w.write("你好");
} catch (IOException e) {
e.printStackTrace();
}
}
}
二、Properties
Properties是双列集合,叫做属性集。
特点:
- Properties实现Map接口,拥有Map接口中的所有的方法
- Properties没有泛型,里面的键和值都是字符串
- Properties支持和流一起操作,可以从流中加载键值对【从文件中读取键值对】
2.1 Properties的方法
构造方法:
-
Properties()
:创建的是不包含任何元素的Properties集合
常用成员方法:
-
Object setProperty(String key, String value)
:添加键值对元素 -
String getProperty(String key)
:根据键获取值 -
Set<String> stringPropertyNames()
:获取Properties中所有的键并放入到Set集合中返回
public class Demo01Properties {
public static void main(String[] args) {
//创建Properties集合
Properties p = new Properties();
//Object setProperty(String key, String value):添加键值对元素。
p.setProperty("it001", "jack");
p.setProperty("it002", "rose");
p.setProperty("it003", "tony");
//输出结果
System.out.println(p);
//String getProperty(String key):根据键获取值。
System.out.println("it001对应的值:" + p.getProperty("it001"));
System.out.println("=================================");
//Set<String> stringPropertyNames():获取Properties中所有的键并放入到Set集合中返回。
Set<String> set = p.stringPropertyNames();
//遍历Properties集合
for (String s : set) {
System.out.println(s + "-" + p.getProperty(s));
}
}
}
2.2 Properties从文件读取键值对
Properties中和流相关的方法。
-
void load(InputStream inStream)
:参数要传递字节输入流 -
void load(Reader reader)
:参数要传递字符输入流
load方法可以将文件中的键值对读取【加载】到Properties中
Properties集合操作的文件是有要求的:
- 文件一般以.properties结尾【.properties文件一般叫做配置文件】(软性要求)
- 文件中的键值对必须按照下面格式保存(硬性规定)
键=值
键=值
键=值
Properties集合读取文件中键值对的步骤:
- 创建Properties集合
- 创建输入流对象
- 调用load方法,传递输入流对象,将文件中的键值对读取到Properties集合
- 释放资源
public class Demo02Properties {
public static void main(String[] args) throws IOException {
//1. 创建Properties集合。
Properties p = new Properties();
//2. 创建输入流对象。
InputStream is = new FileInputStream("iotest\\config01.properties");
//3. 调用load方法,传递输入流对象,将文件中的键值对读取到Properties集合
p.load(is);
System.out.println(p);
//4. 释放资源。
is.close();
//创建输入流对象
Reader r = new FileReader("iotest\\config01.properties");
//调用load方法,传递输入流对象,将文件中的键值对读取到Properties集合
p.load(r);
System.out.println(p);
//释放资源。
r.close();
}
}
三、缓冲流
使用普通流操作数据存在效率太低的问题。
缓冲流的特点是效率高,缓冲流可以起到加速的作用。
缓冲流本身并不具备读或者写的功能,缓冲流的主要作用是对其他流进行加速,因为缓冲流内部有一个数组作为了缓冲区,所以可以加速。
字节缓冲流
- 字节输入缓冲流:
BufferedInputStream
- 字节输出缓冲流:
BufferedOutputStream
字符缓冲流
- 字符输入缓冲流:
BufferedReader
- 字符输出缓冲流:
BufferedWriter
3.1 字节缓冲流
字节缓冲流的构造方法:
-
BufferedInputStream(InputStream in)
: 参数要传递字节输入流 -
BufferedOutputStream(OutputStream out)
:参数要传递字节输出流
字节缓冲流中读写的方法:
- 字节缓冲流是属于字节流的,所以里面读写的方法和之前字节流读写的方法一模一样
字节缓冲流的使用步骤:
- 创建字节缓冲流
- 读或写
- 释放资源
public class Demo02BufferedStream {
public static void main(String[] args) throws IOException {
//创建字节缓冲输入流
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("iotest\\aa.jpg"));
//创建字节缓冲输出流
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("iotest\\bb.jpg"));
//记录时间
long start = System.currentTimeMillis();
//定义变量,用来接收读取到的字节
int i;
//开始循环
while ((i = bis.read()) != -1) {
bos.write(i);
}
//记录时间
long end = System.currentTimeMillis();
System.out.println(end - start);//42
//记录时间
start = System.currentTimeMillis();
//定义数组,用来保存读取到的数据
byte[] bArr = new byte[1024];
//定义变量表示读取到的字节个数
int len;
while((len = bis.read(bArr) != -1) {
bos.write(bArr,0,len);
}
//记录时间
end = System.currentTimeMillis();
System.out.println(end - start);//0
//释放资源
bos.close();
bis.close();
}
}
3.2 字符缓冲流
字符缓冲流构造方法:
-
BufferedReader(Reader in)
:参数要传递字符输入流 -
BufferedWriter(Writer out)
:参数要传递字符输出流
字符缓冲流读写的方法:
- 字符缓冲流是字符流,里面读写的方法和之前字符流的读写方法一模一样
- BufferedWriter中有一个方法可以实现跨平台的换行
void newLine()
:一个跨平台的换行符 - BufferedReader中有一个方法可以读取一行数据:
String readLine()
:读取一行数据并返回,如果已经读取结束了,返回null值。
【注意:readLine不会读取换行符,只能读取换行符之前的内容】
字符缓冲流的使用步骤:
- 创建流
- 读或写
- 关闭流
- 【如果是字符输出流,需要刷新】
public class Demo04BufferedStream {
public static void main(String[] args) throws IOException {
method2();
}
/*
String readLine():读取一行数据并返回,如果已经读取结束了,返回null值。
*/
public static void method2() throws IOException {
//创建字符输入缓冲流
BufferedReader br = new BufferedReader(new FileReader("day11\\file03.txt"));
//使用循环读取
//定义变量,用来保存每次读取到的一行数据
String line;
//开始循环
/*
条件位置做了哪些事情
1. 通过字符输入缓冲流调用readLine方法读取一行数据。
2. 将读取到的这行数据赋值给了变量line
3. 判断line是否不等于null,如果line不是null,表示读取到了数据,就进行处理。
*/
while ((line = br.readLine()) != null) {
System.out.println(line);
}
//释放资源
br.close();
}
/*
void newLine():一个跨平台的换行符
*/
public static void method() throws IOException {
//创建流对象
BufferedWriter bw = new BufferedWriter(new FileWriter("day11\\file03.txt"));
//写数据
bw.write("窗含西岭千秋雪");
//void newLine():一个跨平台的换行符
bw.newLine();
bw.write("门泊东吴万里船");
//关流
bw.close();
}
}
四、转换流
编码和解码:
- 编码:字符 -> 字节
- 解码;字节 -> 字符
4.1 编码表
编码表(字符集):字节和字符的对应关系表
编码表 | 每个字符占的字节数 | 内容 | 备注 |
ACSII码表 | 1 | 有128个英文字母数字以及标点符号 | |
ISO8859-1(Latin)码表 | 1 | 有256个内容,包含了ASCII码表以及拉丁文 | |
GB2312(1980)码表 | 1/2 | 支持六千多个汉字或者字符,包含了ASCII码表 | 汉字占2个字节 |
BIG5码表 | 1/2 | 支持繁体字,包含了ASCII码表 | 汉字占2个字节 |
GBK码表 | 1/2 | 支持两万多汉字或者字符,包含了ASCII码表 | 汉字占2个字节 |
Unicode国际标准码表 | 2 | 保存任何国家的任何语言 | 在unicode8.0版本,可以保存emoji |
UTF-32 | 4 | 万国码 | |
UTF-16 | 2/3/4 | 万国码 | |
UTF-8 | 1/2/3/4 | 万国码 | 汉字占3个字节 |
在IDEA中,使用的默认编码是UTF-8
在windows中, 使用的默认编码是UTF-8
(最近win10更新,新版本的win10默认编码已经变成utf-8,如果没有更新,默认是GBK
)
4.2 字符流读取乱码问题
当我们使用FileReader读取采用GBK编码的文件中的数据到idea中,会出现乱码现象。
这是因为文件采用的是GBK
编码,而FileReader会采用idea默认的编码UTF-8
进行读取,此时两个编码不一致,就引发了乱码问题。
如果想要读取GBK编码的文件,那么可以指定编码读取,如果要指定编码,需要使用转换流。
4.3 转换流指定编码读取
InputStreamReader
是转换流,用来读取,可以【按照指定编码】将文件中的数据读取Java程序中。
InputStreamReader
是字符流,可以以字符为单位进行读取。
InputStreamReader的构造方法:
-
InputStreamReader(InputStream in)
:参数要传递字节输入流,使用这个构造方法创建的流将来会采用idea默认编码进行读取 -
InputStreamReader(InputStream in, String charsetName)
:第一个参数是字节输入流,第二个参数是指定的编码方式,会采用指定的编码方式读取
InputStreamReader的读取方法:
-
InputStreamReader
是字符流,所以里面读取方法的方法和字符流读取的方法是一模一样的
InputStreamReader使用步骤:
- 创建流
- 读数据
- 关闭流
注意:
使用转换流指定编码时,如果指定的编码不存在,那么会报错。
public class Demo02InputStreamReader {
public static void main(String[] args) throws IOException {
//readGBK();
readUTF8();
}
/*
读取UTF-8文件的数据
*/
public static void readUTF8() throws IOException {
//创建流
InputStreamReader isr = new InputStreamReader(new FileInputStream("d:\\file02-utf8.txt"), "utf-8");
//读取
int i;
while ((i = isr.read()) != -1) {
System.out.print((char) i);
}
//释放资源
isr.close();
}
/*
读取GBK文件的数据
*/
public static void readGBK() throws IOException {
//1. 创建流,指定编码
InputStreamReader isr = new InputStreamReader(new FileInputStream("d:\\file01-gbk.txt"), "gbk");
//2. 读取数据
int i;
while ((i = isr.read()) != -1) {
System.out.print((char) i);
}
//3. 释放资源
isr.close();
}
}
4.4 转换流指定编码写
OutputStreamWriter
是转换流,用来写,可以【指定编码】将Java程序中的数据写到文件中。
OutputStreamWriter
属于字符流,会以字符为单位写数据。
OutputStreamWriter构造方法:
-
OutputStreamWriter(OutputStream out)
:参数要传递字节输出流,使用该构造方法创建的转换流将来会以idea默认编码去写数据 -
OutputStreamWriter(OutputStream out, String charsetName)
:第一个参数是字节输出流;第二个参数是指定的编码方式,会按照指定编码写数据
OutputStreamWrite写数据方法:
-
OutputStreamWriter
属于字符流,里面写数据的方法和之前的字符流写数据的方法一模一样
OutputStreamWriter使用步骤:
- 创建转换流,指定编码
- 调用write方法写数据
- 刷新
- 关流
public class Demo03OutputStreamWriter {
public static void main(String[] args) throws IOException {
// writeGBK();
writeUTF8();
}
//向文件中写UTF-8编码的数据
public static void writeUTF8() throws IOException {
//创建OutputStreamWriter对象,并指定编码
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("d:\\file04-utf8.txt"), "utf-8");
//写数据
osw.write("你好");
//刷新
osw.flush();
//释放资源
osw.close();
}
//向文件中写入GBK编码的数据
public static void writeGBK() throws IOException {
//创建OutputStreamWriter对象,并指定编码
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("d:\\file03-gbk.txt"), "gbk");
//写数据
osw.write("你好");
//刷新
osw.flush();
//释放资源
osw.close();
}
}
五、序列化流
5.1 序列化概述
5.2 序列化流的使用
ObjectOutputStream
是序列化流,可以将Java程序中的对象写到文件中。
ObjectOutputStream构造方法:
-
ObjectOutputStream(OutputStream out)
:参数要传递一个字节输出流
ObjectOutputStream写对象的方法【特有方法】:
-
void writeObject(Object obj)
:将对象写到文件
ObjectOutputStream的使用步骤:
- 创建流
- 写对象
- 释放资源
向文件中写的对象必须要实现Serializable
接口,Serializable
里面什么都没有,仅仅起到标记作用。
public class Demo01ObjectOutputStream {
public static void main(String[] args) throws IOException {
//1. 创建流
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("iotest\\file04.txt"));
//2. 写Person对象
Person p = new Person("张三丰", 100);
oos.writeObject(p);
//3. 关闭流
oos.close();
}
}
5.3 反序列化的使用
ObjectInputStream
是反序列化流,可以将文件中的对象读取到Java程序中。
ObjectInputStream构造方法:
-
ObjectInputStream(InputStream in)
:参数要传递字节输入流
ObjectInputStream读取对象的方法【特有方法】:
-
Object readObject()
:从文件中读取对象
ObjectInputStream使用步骤:
- 创建反序列化流
- 读取对象
- 释放资源
注意事项:如果使用反序列化流读取对象时,对象所属的类不存在,那么会报错。
public class Demo02ObjectInputStream {
public static void main(String[] args) throws IOException, ClassNotFoundException {
//1. 创建反序列化流
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("iotest\\file04.txt"));
//2. 读取对象
Object obj = ois.readObject();
System.out.println(obj);
//Person p = (Person) obj;
//System.out.println(p.getName() + "-" + p.getAge());
//3. 释放资源
ois.close();
}
}
5.4 序列化中的static和transient
- 被
static
修饰的属性不能被序列化。被static
修饰的属性属于类,不属于对象,而序列化操作写的是对象。 - 如果我们不希望某个属性被序列化,同时不希望使用
static
关键字,那么可以使用transient
。
5.5 序列化中的序列号
Serializable这个接口中没有任何的方法。
这个接口只是起到一个标记作用,必须实现这个接口后,该类的对象才具备序列化的功能,才可以写到文件中。
对象中的版本号这个属性值是通过类中的内容计算得出的,如果类发生改变,版本号就会改变。
当从文件中读取对象时,会对比文件中保存的版本号和class文件中的版本号是否一致,如果不一致,就会报错。
如果要解决版本号冲突问题,我们可以给类固定一个版本号,不管该类怎么修改,版本号都不变。这样就解决了问题。
我们可以在类中提供一个常量,该常量表示类的版本号。
要求:
- private static final 修饰
- long类型的
- 必须叫做serialVersionUID
public class Person implements Serializable{
private String name;
private /*static*/ /*transient*/ int age;
//给类固定一个版本号,不管这个类怎么修改,版本号(序列号)永远是1
private static final long serialVersionUID = 1L;
get...set...构造方法...toString
}
六、打印流
打印流的特点:
- 只有输出(写),没有输入(读)
- 写数据十分的方便
PrintStream构造方法:
-
PrintStream(String fileName)
:参数要传递字符串的文件路径 -
PrintStream(File file)
:参数要传递File对象 -
PrintStream(OutputStream out)
:参数要传递字节输出流
PrintStream特有的写数据的方法:
-
void print(任何类型)
: 写任何类型的数据 -
void println(任何类型)
: 写任何类型的数据并自动换行
PrintStream的使用步骤:
- 创建打印流对象
- 调用方法写数据。
- 释放资源
public class Demo01PrintStream {
public static void main(String[] args) throws FileNotFoundException {
//1. 创建打印流对象
PrintStream ps = new PrintStream("iotest\\file06.txt");
//2. 调用方法写数据。
//void print(任何类型): 写任何类型的数据。
//ps.print("你好");
//ps.print("我好");
//void println(任何类型): 写任何类型的数据并自动换行。
ps.println("你好");
ps.println("我好");
//3. 释放资源
ps.close();
}
}
七、commons-io
commons-io是由第三方(Apache)提供的IO流操作的工具包。
如果我们要使用第三方的工具包,一般要导入jar包。
jar包其实就是java的压缩包,里面保存了很多class文件。
导入jar包后,可以直接使用里面的内容。
导入jar包的步骤:
- 新建一个文件夹叫做lib
- 将jar包复制到lib文件夹下
- 点lib文件夹右键选择 Add as Library
7.1 commons-io初体验
IOUtils中的方法:
-
static int copy(InputStream input, OutputStream output)
:复制文件,该方法适用于2G以下的文件 -
static long copyLarge(InputStream input, OutputStream output)
复制文件,该方法适用于2G以上的文件
FileUtils中的方法:
-
static void copyFileToDirectory(File srcFile, File destDir)
:将文件(srcFile)复制到一个文件夹(destDir)中 -
static void copyDirectoryToDirectory(File srcDir, File destDir)
:将一个文件夹(srcDir)复制到另一个文件夹(destDir)中 -
static void writeStringToFile(File file, String data)
:向文件中写字符串数据 -
static String readFileToString(File file)
:从文件中读取数据,并返回读取到的内容