Head First Java 和 AcWing Java课程做的总结7。
7.1 输入&输出
输入
方式1,效率较低,输入规模较小时使用。(10e6分界条件)
import java.util.Scanner;
public class Main{
public static void main(String[] args) throws Exception{
Scanner sc = new Scanner(System.in);
int x = sc.nextInt();// 读入下一个整数
float y = sc.nextFloat();// 读入下一个单精度浮点数
double z = sc.nextDouble();// 读入下一个双精度浮点数
//上述不读入换行符
String line = sc.nextLine();// 读入下一行
//如果前面有上述,则会读入换行
//从第一行开始用则不会
String str = sc.next();// 读入下一个字符串
//不读入空格和换行符
//去掉多空格,可以用next
//多次读入
while(sc.hasNext()){
System.out.println("%s ", sc.next());
}
}
}
方式2,效率较高,输入规模较大时使用,注意需要抛异常。
import java.io.BufferedReader;
import java.io.InputStreamReader;
public class Main{
public static void main(String[] args) throws Exception{
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String str = br.readLine();
System.out.println(str);
}
}
输出
方式1,效率较低,输出规模较小时使用。
public class Main {
public static void main(String[] args) throws Exception {
System.out.println(123); // 输出整数 + 换行
System.out.println("Hello World"); // 输出字符串 + 换行
System.out.print(123); // 输出整数
System.out.print("yxc\n"); // 输出字符串
System.out.printf("%04d %.2f\n", 4, 123.456D); // 格式化输出,float与double都用%f输出
}
}
方式2,效率较高,输出规模较大时使用。注意需要抛异常。
import java.io.BufferedWriter;
import java.io.OutputStreamWriter;
public class Main {
public static void main(String[] args) throws Exception {
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
bw.write("Hello World\n");
bw.flush(); // 需要手动刷新缓冲区
}
}
7.2 串流
串流介绍
数据是在串流中移动的。
-
Java的API中带有连接类型的串流,它代表来源与目的地之间的连接,连接串流将串流与其他串流连接起来。
-
Stream
是连接串流或是链接用的串流;- 连接串流用来表示源或目的地、文件、网络套接字连接;
- 链接用串流用来衔接连接串流。
-
一般来说,串流需要两两连接才能有用——其中一个代表连接,另一个要被调用方法。
- 连接串流很低层
- 比如
FileOutputStream
有可以直接写入字节的方法,但我们通常不直接写字节。而是以对象层次的观点来写入,所以需要高层的连接串流。
-
每个类只要做好一件事,所以不设计单一的串流。这样可以通过不同的组合来达到最大的适应性。
FileOutputStream
把字节写入文件;ObjectOutputStream
把对象转换成可以写入串流的数据;- 调用
ObjectOutputStream
的writeObject
时,对象被打成串流送到FileOutputStream
来写入文件。
写入文本文件
-
//第一版代码 import java.io.*; class WriteAFile{ public static void main(String[] args){ try{ FileWriter writer = new FileWriter("Foo.txt"); writer.write("hello foo!"); writer.close(); }catch(IOException ex){ ex.printStackTrace(); } } }
-
java.io.File
-
File这个类代表磁盘上的文件,但并不是文件中的内容。File对象代表磁盘上的文件或目录的路径名称,但它并不能读取或代表文件中的数据。
-
它提供了一种比使用字符串文件名来表示文件更安全的方式
-
/*用途*/ //创建出代表现存盘文件的File对象 File f = new File("MyCode.txt"); //建立新的目录 File dir = new File("Chapter7"); dir.mkdir(); //列出目录下的内容 if(dir.isDirectory()){ String[] dirContents = dir.list(); for(int i = 0; i < dirContents.length; i++){ System.out.println(dirContents[i]); } } //取得文件或目录的绝对路径 System.out.println(dir.getAbsolutePath()); //删除文件或目录 boolean isDeleted = f.delete();
-
-
缓冲区
- 直接调用
FileWriter
的write
来写文件,每次它都会直接写下去。 - 每趟磁盘操作比内存操作要花费更多的时间。
- 通过
BufferedWriter
和FileWriter
的链接,BufferedWriter
可以暂存一堆数据,到满的时候再实际写入磁盘,这样可以减少对磁盘操作的次数。更有效率。 - 强制缓冲区立即写入,调用
write.flush()
- 直接调用
-
//第二版代码 import java.io.*; class WriteAFile{ public static void main(String[] args){ try{ File myFile = new File("foo.txt"); FileWriter fileWriter = new FileWriter(myFile); BufferedWriter writer = new BufferedWriter(fileWriter); for(int i = 0; i < 3; i++){ writer.write("hello foo!"); } writer.close(); }catch(IOException ex){ ex.printStackTrace(); } } }
读取文本文件
-
//使用File对象,FileReader执行实际读取,并用BufferedReader使读取更有效率 import java.io.*; class ReadAFile{ public static void main(String[] args){ try{ File myFile = new File("MyText.txt"); FileReader fileReader = new FileReader(myFile); BufferedReader reader = new BufferedReader(fileReader); //只有在缓冲区读空的时候才会回头去磁盘读取 /*最常见的读取数据方式*/ String line = null; while((line = reader.readLine()) != null){ System.out.println(line); } reader.close(); }catch(Exception ex){ ex.printStackTrace(); } } }
7.3 对象序列化
序列化(Serialization)
如果需要存储对象状态:
- 对每个对象逐个把每项变量的值写到特定格式的文件中;
- (数据给非Java程序用必选)
- 面向对象的方式——把对象本身“碾平”后“恢复”。
- (只有Java程序会用到)
- 产生的文件,很难阅读,更易恢复且比较安全。
将序列化对象写入文件的方法:
import java.io.*;
//创建出FileOutputStream
FileOutputStream fileStream = new FileOutputStream("MyGame.ser");
//创建出ObjectOutputStream
ObjectOutputStream os = new ObjectOutputStream(fileStream);
//写入对象
os.writeObject(characterOne);
os.writeObject(characterTwo);
os.writeObject(characterThree);
//关闭ObjectOutputStream
os.close();
对象序列化后发生的事:
- 序列化的对象保存了实例变量的值;
- primitive主数据类型时,就保留字节值;
- 对象中有引用变量且引用到其他对象,则被引用的对象也被序列化......操作自动进行
- 还要保存Java虚拟机所需的信息(比如类名称)。
实现序列化的条件:
要让类能够被序列化,就实现Serializable
Serializable
接口又被称为marker或tag类的标记用接口,因为此接口并没有任何方法需要实现。唯一目的就是声明有实现它的类是可以被序列化的。(告诉虚拟机)- 某类可序列化,则其子类自动可以序列化。
os.writeObject(myBox);
的mybox
必须实现序列化。- 序列化是全有或全无的。
- 如果某实例变量不能或不应该被序列化
- 把它标记为
transient
(瞬时)的,序列化程序员会把它跳过; - 不序列化的原因
- 忘记实现
Serializable
- 动态数据只可以在执行时求出而不能或不必存储。
- 忘记实现
- 把它标记为
7.4 对象解序列化
解序列化(Deserialization)
可以在序列化后,不同的虚拟机执行期,甚至不是同一个Java虚拟机,把对象恢复到存储时的状态。
从文件读出序列化后的对象的方法:
import java.io.*;
//创建FileInputStream
FileInputStream fileStream = new FileInputStream("MyGame.ser");
//创建ObjectInputStream
ObjectInputStream os = new ObjectInputStream(fileStream);
//读取对象
Object one = os.readObject();
Object two = os.readObject();
Object three = os.readObject();
//转换对象类型
GameCharacter elf = (GameCharacter) one;
GameCharacter troll = (GameCharacter) two;
GameCharacter magician = (GameCharacter) three;
//关闭ObjectInputStream
os.close();//FileInputStream会跟着关闭
对象解序列化时发生的事:
- Java虚拟机会尝试在堆上创建新的对象,让它维持与被序列化时有相同的状态来恢复对象的原状。
- 对于
transient
的变量,不是null(对引用变量),就是默认值(主数据类型) - 具体步骤:
- 对象从stream中读出来;
- Java虚拟机从存储的信息判断出对象的class类型;
- Java虚拟机尝试寻找和加载对象的类。如果Java虚拟机找不到或无法加载该类,则Java虚拟机抛出异常。
- 新对象会被配置在堆上,但构造函数不会执行。
- 如果对象在继承树上有个不可序列化的祖先类,则该不可序列化类以及在它之上的类的构造函数(就算是可序列化也一样)就会执行。一旦构造函数连锁启动之后将无法停止,也就是说,从第一个不可序列化的父类开始,全部都会重新初始化状态。
- 对象的实例变量会被还原成序列化时点的状态值,
transient
变量会被赋值null的对象引用或主数据类型默认的0、false等值。
- 类不会存储为对象的一部分:
- 序列化有将对象送到网络联机上的用途,如果每个序列化对象都有类,带宽的消耗是个大问题。
- 对于网络传输序列化对象来说,有一种机制:让类使用URL来指定位置,用于RMI。
- 静态变量:
- 不会被序列化
能够被解序列化的条件:
- 要有对象对应的类才能还原和使用该对象。
- 版本控制:
- 序列化后类被修改,该如何处理?
- 会损害解序列化的修改
- 通常不会有事的修改
serialVersionUID
——类的版本识别ID- 当对象被序列化时,该对象不会盖上一个类的版本识别ID;
- 根据类的结构信息计算出来的
- Java尝试还原对象是,它会比对对象与Java虚拟机上的类的
serialVersionUID
。
- 可以把
serialVersionUID
放在class中,让类在演化过程中维持相同的ID。serialver Dog
可以用来取得版本IDstatic final long serialVersionUID = -6849794470754667710L;
- 序列化后类被修改,该如何处理?