版权声明:本文为博主原创文章,未经博主允许不得转载。 https://cloud.tencent.com/developer/article/1434901
字符编码就是按照某种格式某种规定将字符存储在计算机中。字符编码有非常多,每个国家都有自己规定的字符编码。常见的字符编码有几种:ASCII, Unicode, UTF-8, GBK(简体繁体融于一库)等
几种不同常用编码模式的特征:
java.io.File 包含了一系列文件和目录路径名的抽象表示方法。
基本操作可在下面例程中展示:
例 1:
遍历一个目录下包括子目录在内的所有 .java 文件打印出来;
public class Demo3 {
public static void main(String[] args) {
printFile3(new File("e:\\coreJava4-26"));
}
public static void printFile3(File file){
File[] files = file.listFiles(new FileFilter() {
public boolean accept(File pathname) {
if(pathname.isFile()){
return pathname.getName().endsWith(".java");
}else if(pathname.isDirectory()){
printFile3(pathname);
}
return false;
}
});
for (File file2 : files) {
System.out.println(file2);
}
}
}
例 2:
删除一个目录(目录为空才能删除)
public class Demo3 {
public static void main(String[] args) {
delDirection(new File("/Users/upcautolong/coreJava4-27"));
}
public static void delDirection(File file){
File[] files = file.listFiles();
// 如果当前路径已经为空,则直接删除当前路径
if(files.length==0){
System.out.println(file);
file.delete();
}
// 当前路径不为空
else {
// 遍历当前路径
for(File file2 : files){
// 当前文件是否为文件
if(file2.isFile()){
System.out.println(file2);
file2.delete();
}
// 当前文件是路径,递归运行
else if(file2.isDirectory()){
delDirection(file2);
}
}
System.out.println(file);
// 将当前路径内的所有文件与路径删除完毕后,最后删除当前路径
file.delete();
}
}
}
由上例可以观察到,文件操作中递归操作很重要。
文件在 Java 中通过流进行操作,基本可以分为读与写两种操作。在 Java 中,可以把文件流分为字节流和字符流两种。由于读和写基本是一对操作,所以将功能完全相反的输入 / 输出流放在一起进行说明。
整体的继承关系如下图所示:
InputStream, OutputStream 是最基本的输入流与输出流,即若干输入流、输出流的抽象父类。InputStream 的 read() 方法,以及 OutputStream 的 write() 方法,是它们功能丰富多彩的子类的基本方法。
InputStream 的 read() 方法如下:
int read();
int read(byte[] bytes, int offset, int length);
- 每次批量读取,往字节数组 bytes 中放,从 offset 位置开始读取,长度为 length 的字符串;
- 返回的是读到的字节的个数;
- 返回 -1 就读到了文件结尾;
OutputStream 的 write() 方法如下:
void write(int);
void write(byte[] bytes, int offset, int length);
- 每次批量写入,往字节数组 bytes 中放,从 offset 位置开始写入,长度为 length 的字符串;
FileInputStream 为文件字节输入流,FileOutputStream 为文件字节输出流,两者都是 InputStream 与 OutputStream 的重要子类。
例 1:用 FileInputStream, FileOutputStream 复制文件:
public class Demo5 {
public static void main(String[] args) {
try {
FileInputStream in =
new FileInputStream("e:\\bootstrap.rar");
FileOutputStream out =
new FileOutputStream("e:\\xx.rar");
int c ;
// 文件缓冲区
byte[] bytes = new byte[1024 * 40];
// 复制文件(先读后写的过程)
while((c = in.read(bytes,0,bytes.length))!=-1){
out.write(bytes,0,c);
}
in.close();
out.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
FilterInputStream 的作用是封装其它的输入流,并为它们提供额外的功能。它的常用的子类有 BufferedInputStream 和 DataInputStream。
FilterOutputStream 的作用是封装其它的输出流,并为它们提供额外的功能。它的常用子类主要包括 BufferedOutputStream, DataOutputStream。
DataInputStream 说明基本如下:
DataOutputStream 基本同理于 DataInputStream:
两者之间也存在一定的联系:应用程序可以使用 DataOutputStream 写入由 DataInputStream 读取的数据。
由上面的字节流可知,大部分功能强大的字节流,都是通过原始的一次读写一个字节的字节流装饰而来,即功能都由最底层的 read(), write() 向上扩展而来。这种扩展的过程中,使用了装饰者模式。
装饰者模式动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。适合使用装饰者模式的情况如下:
- 一种情况是可能有大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长;
- 另一种情况可能是因为类定义被隐藏,或类定义不能用于生成子类。
另外需要注意的还有:
字符流只对文本文件有效。最基本的字符流是 Reader 与 Writer。由于读和写基本是一对操作,所以将功能完全相反的输入 / 输出流放在一起进行说明。
Reader, Writer 对于字符流,就相当于 InputStream, OutputStream 相对于字节流,都是最基本的输入流与输出流。Reader 的 read() 方法,以及 Writer 的 write() 方法,是它们子类的基本方法。
Reader 的 read() 方法如下:
int read();
int read(char[] bytes, int offset, int length);
- 放入到 char 字符数组中,从第 offset 位置开始读,最多读 length 个;
- 返回的是读到的字符的个数;
Writer 的 write() 方法如下:
void write(int): 每次写一个字符
void write(char[] bytes, int offset, int length):
InputStreamReader 与 OutputStreamWriter 最内层的核心,依旧是使用了字节流 InputStream.read(), OutputStream.write()方法,它们才是实现所有读写操作的源头。通过 read(), write() 方法,使 InputStreamReader, OutputStreamWriter 成为了字节流与字符流之间的桥梁,能将字节流输入 / 输出为字符流,并且能为字节流指定字符集,可输入 / 输出一个个的字符。
这里说到了字符流与字节流的关系,字符流实现了自己的方法,然而却与字节流的方法具有相同的方法签名与参数列表。这里使用到的设计模式是适配器模式;
BufferedReader 提供了通用的缓冲方式文本读取,readLine 读取一个文本行,从字符输入流中读取文本,缓冲各个字符,从而提供字符、数组和行的高效读取。
BufferedWriter 提供了缓冲方式文本写入,主要和 Writer 抽象类相同。
对于 BufferedReader, BufferedWriter 的详细解析,可以参照下面的网址:《Java8 I/O源码-BufferedReader与BufferedWriter》
例 2:使用 BufferedReader / BufferedWriter,把一个文本文件的内容写到另给一个文件中。
public class Demo10 {
public static void main(String[] args) {
try {
BufferedReader br =
new BufferedReader(
new InputStreamReader(
new FileInputStream("e:\\Demo1.java")));
BufferedWriter bw =
new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream("e:\\2.txt")));
String str = null;
// 读取一行
while((str = br.readLine()) != null){
// 将一行写入
bw.write(str);
// BufferedWriter 换行
bw.newLine();
// BufferedWrited 刷新缓存
bw.flush();
}
br.close();
bw.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
Java 的序列化是指把 Java 对象转换为二进制字节序列的过程;而 Java 反序列化是指把字节序列恢复为 Java 对象的过程。
为什么需要序列化与反序列化?当两个进程进行远程通信时,可以相互发送各种类型的数据,包括文本、图片、音频、视频等,而这些数据都会以二进制序列的形式在网络上传送。那么当两个 Java 进程进行通信时,能否实现进程间的对象传送呢?答案是可以的。这就需要 Java 序列化与反序列化了。换句话说,一方面,发送方需要把这个 Java 对象转换为字节序列,然后在网络上传送;另一方面,接收方需要从字节序列中恢复出 Java 对象。
当我们明晰了为什么需要 Java 序列化和反序列化后,我们很自然地会想 Java 序列化的好处。
如果要实现序列化,要用到 java.io.ObjectOutputStream 和 java.io.ObjectInputStream。
java.io.ObjectOutputStream,表示对象输出流。它的 writeObject(Object obj) 方法可以对参数指定的 obj 对象进行序列化,把得到的字节序列写到一个目标输出流中。
java.io.ObjectInputStream,表示对象输入流。它的 readObject() 方法从源输入流中读取字节序列,再把它们反序列化成为一个对象,并将其返回。
实现序列化的要求:只有实现了 Serializable 或 Externalizable 接口的类的对象才能被序列化,否则抛出异常。
注:
我们都知道一个对象只要实现了 Serilizable 接口,这个对象就可以被序列化,java 的这种序列化模式为开发者提供了很多便利,我们可以不必关系具体序列化的过程,只要这个类实现了Serilizable 接口,这个类的所有属性和方法都会自动序列化。
但有的时候会遇到一种情况:某个类的有些属性需要序列化,而其他属性不需要被序列化,打个比方,如果一个用户有一些敏感信息(如密码,银行卡号等),为了安全起见,不希望在网络操作(主要涉及到序列化操作,本地序列化缓存也适用)中被传输,这些信息对应的变量就可以加上transient关键字。换句话说,这个字段的生命周期仅存于调用者的内存中而不会写到磁盘里持久化。
java 的 transient 关键字为我们提供了便利,你只需要实现 Serilizable 接口,将不需要序列化的属性前添加关键字 transient,序列化对象的时候,这个属性就不会序列化到指定的目的地中。transient 关键字修饰的成员变量,就不做 jvm 默认的序列化了(但是可以自己写序列化)。
附作业: