输入输出——下
- File类
- 处理压缩文件
- GZIP
- ZIP
- 对象序列化
- Serializable 接口
- Externalizable 接口
- 随机读写
- 构造方法
File类
File 可以帮助操作磁盘文件,其中定义了一些与平台无关的方法来操作文件。包括
- 创建、删除文件
- 重命名文件
- 判断文件读写权限
- 判断文件是否存在
- 设置和查询文件最近修改时间
以下方法实现了创建一个名为 hello.txt 文件的功能,首先判断有无这个文件,有则删除这个文件,否则创建一个同名的空文件。其中 Sepatator 属性相当于文件路径的分隔符 “\”。
void createOrDeleteFile(){
File f = new File("doc"+File.separator+"hello.txt");
if(f.exists() && f.isFile()) f.delete();
else{
try{
f.createNewFile();
}catch (Exception e){
System.out.println(e.getMessage());
}
}
}
File 类可以使用 exists() 判断文件是否已经存在,也可以使用 isFile() 判断是否是文件或者目录。以下是一个复制文件的例子。
void copyBytes(String[] args){
DataInputStream in;
DataOutputStream out;
if(args.length != 2){
System.out.println("Please Enter file names");
return ;
}
File inFile = new File(args[0]);
File outFile = new File(args[1]);
if(outFile.exists() && outFile.isFile()){
System.out.println(args[1]+" already exists");
return;
}
if(!inFile.exists() && outFile.isFile()){
System.out.println(args[0]+" does not exist");
}
try{
in = new DataInputStream(new BufferedInputStream(
new FileInputStream(inFile)));
out = new DataOutputStream(new BufferedOutputStream(
new FileOutputStream(outFile)));
try{
int data;
while(true){
data = in.readUnsignedByte();
out.writeByte(data);
}
}catch (EOFException eof){
out.close();
in.close();
return;
}
}catch (FileNotFoundException fe){
System.out.println("Problem opening files");
}catch (IOException ioe){
System.out.println("IO Problems");
}
}
处理压缩文件
这里介绍一下如何压缩和解压gzip文件和zip文件。java.util.zip 包中提供了一些可以用于在压缩格式对流进行读写的流。这些流都继承自 OutputStream 和 InputStream。
- 压缩流类 GZIPOutputStream 和 ZipOutputStream 可以将数据压缩成 GZIP 格式和 ZIP 格式
- GZIPInputStream 和 ZipInputStream 可以将 GZIP 和 ZIP 格式的数据解压缩回复原状
GZIP
- GZIPOutputStream 的父类是 DeflaterOutputStream,可以把数据压缩成 GZIP 格式
- GZIPInputStream 的父类是 InflaterInputStream,可以将 GZIP 格式文件解压缩
以下演示以下如何将 GZIP 格式文件压缩和解压缩
void gzipStreamTest() throws IOException{
// 压缩
FileInputStream in = new FileInputStream("./doc/hello.txt");
GZIPOutputStream out = new GZIPOutputStream(new FileOutputStream("./doc/hello.gz"));
System.out.println("Compressing files from doc/hello.txt to doc/test.gz");
int c;
while((c = in.read()) != -1) out.write(c); //读写低位一个字节
in.close();
out.close();
// 读取
System.out.println("Reading file from doc/hello.gz to minitor");
BufferedReader in2 = new BufferedReader(new InputStreamReader(
new GZIPInputStream(new FileInputStream("doc/hello.gz"))));
String s;
while((s=in2.readLine())!=null) System.out.println(s);
in2.close();
// 解压缩
System.out.println("Writing decompression to doc/newHello.txt");
GZIPInputStream in3 = new GZIPInputStream(new FileInputStream("doc/hello.gz"));
FileOutputStream out2 = new FileOutputStream("doc/newHello.txt");
while((c=in3.read())!=-1) out2.write(c);
in3.close();
out2.close();
}
- read() 方法每次读取一个字节,遇到文件结尾返回 -1,否则将其转化为 [0, 255] 之间的一个整数,返回一个 int 类型的数值。
- write() 方法将一个 int 类型的数值的低 8 位写入文件,忽略高 24 位,等于写入一个字节。read() 和 write() 配合可以完成任何文件的复制。
ZIP
ZIP 可以由多个文件压缩而成,我们称这些文件的路径称为 ZIP 文件的入口 ( Entry ) ,我们压缩的时候不仅需要完成文件的压缩,而且需要记录入口。同样,解压缩的时候要读取入口,恢复最初的路径。
- Zip 文件的每个文件对应一个入口,入口记录了其路径信息,解压缩的时候需要入口信息完成路径的恢复。每个入口使用一个 ZipEntry 对象表示,对象的 getName() 方法返回文件的路径名称。
- ZipOutputStream,父类是 DeflaterOutputStream,可以把数据压缩成 ZIP 格式。
- ZipInputStream,父类是 InflaterInputStream,可以将 ZIP 格式的文件解压缩。
ZIP 文件的压缩与解压缩举例
void zipStreamTest(String[] args) throws IOException{
// 压缩
ZipOutputStream out = new ZipOutputStream(new BufferedOutputStream(
new FileOutputStream("./doc/test.zip")));
for(int i=0; i<args.length; i++){
System.out.println("Writing file e" + args[i]);
BufferedInputStream in = new BufferedInputStream(
new FileInputStream(args[i]));
out.putNextEntry(new ZipEntry(args[i])); // putNextEntry() 可以记住路径信息,从而可以还原路径关系信息
int c;
while((c = in.read()) != -1) out.write(c);
in.close();
}
out.close();
// 读取
System.out.println("Reading file");
ZipInputStream in2 = new ZipInputStream(new BufferedInputStream(
new FileInputStream("./doc/test.zip")));
ZipEntry ze;
while((ze=in2.getNextEntry())!=null){
System.out.println("Reading file "+ze.getName());
int x;
while((x=in2.read())!=-1) System.out.write(x);
System.out.println();
}
in2.close();
}
解压缩 ZIP 文件并回复原来的路径。
class Unzip{
byte doc[] = null;
String Filename = null; // 压缩文件路径
String UnZipPath = null; // 解压缩路径
public Unzip(String filename, String unZipPath){
this.Filename = filename;
this.UnZipPath = unZipPath;
this.setUnZipPath(this.UnZipPath);
}
// 如果只传入压缩文件路径,默认解压缩到当前文件夹下面。
public Unzip(String filename){
this.Filename = new String(filename);
this.UnZipPath = null;
this.setUnZipPath(this.UnZipPath);
}
// 统一解压缩路径的格式,以文件分割符结尾
private void setUnZipPath(String unZipPath){
if(unZipPath==null || !unZipPath.endsWith("\\"))
this.UnZipPath = new String(unZipPath+"\\");
else
this.UnZipPath = new String(unZipPath);
}
public void doUnZip(){
try{
ZipInputStream zipin = new ZipInputStream(new FileInputStream(Filename));
ZipEntry fEntry = null;
while((fEntry=zipin.getNextEntry())!=null){ // getNextEntry() 方法获得下一个入口的路径,是一个相对路径
if(fEntry.isDirectory())
checkFilePath(UnZipPath+fEntry.getName()); // 加上解压缩的目录地址获得完整的解压缩路径
else{
String fname = new String(UnZipPath + fEntry.getName());
try{
FileOutputStream out = new FileOutputStream(fname);
doc = new byte[512];
int n;
while((n=zipin.read(doc, 0, 512)) != -1) // 一次读取 512 个字节, read()返回读取到的实际字节数,实现文件结尾的读取
out.write(doc, 0, n);
out.close();
out=null;
doc=null;
}catch (Exception ex) {ex.printStackTrace();}
}
}
zipin.close();
}catch (IOException ioe){
System.out.println(ioe);
ioe.printStackTrace();
}
}
// 检查是否是目录文件,如果是且没有这个目录,就创建这个目录
private void checkFilePath(String dirName) throws IOException {
File dir = new File(dirName);
if(!dir.exists()) dir.mkdirs(); // 可以多级创建目录
}
}
public class Exp9 {
public static void main(String[] args) {
String zipFile = args[0];
String unZipPath = args[1];
Unzip myZip = new Unzip(zipFile, unZipPath);
myZip.doUnZip();
}
}
对象序列化
我们可以将多个对象存储到一个二进制文件中,需要的时候再从文件夹中恢复。存储对象实际上是存储对象属性的值,而且我们可以决定哪些属性存,哪些不必存。所以我们可以在类中定义一个密码,将其定义为不被存储的类型,然后其它属性序列化之前使用其加密,然后恢复的时候用其解密,这样就可以实现对象的加密。
Serializable 接口
对象序列化的时候不保存被 transient 和 static 关键字修饰的变量。对于需要序列化的对象,其类必须实现 Serializable 接口,实际上 Serializable 接口是一个空接口,只需要声明实现改接口,但是不要求实现任何的方法,只是帮助让大家了解到这个类要有这个特性。实际上,所有的 Java 中定义的类都实现了 Serializable 接口,所以可以直接将它们序列化,而我们自己定义的类需要主动声明实现该接口。
对象序列化的输入输出流为 ObjectInputStream 和 ObjectOutputStream 类,可以将对象从磁盘上读取出来或者将对象存储到磁盘上。
以下是一个对象存盘的例子
void writeObject() throws Exception{
FileOutputStream out = new FileOutputStream("./doc/theTime");
ObjectOutputStream s = new ObjectOutputStream(out);
s.writeObject("Today");
s.writeObject(new Date(2019, 12, 25));
s.flush();
s.close();
}
以下是一个读取对象的例子
void readObject() throws Exception{
FileInputStream in = new FileInputStream("./doc/theTime");
ObjectInputStream s = new ObjectInputStream(in);
String today = (String)s.readObject();
Date date = (Date)s.readObject();
System.out.println(today);
System.out.println(date);
s.close();
}
创建一个 Book 对象并存盘和读取
class Book implements Serializable{
int id;
String name;
String author;
float price;
public Book(int id, String name, String author, float price){
this.id = id;
this.name = name;
this.author = author;
this.price = price;
}
@Override
public String toString() {
return "Book{" +
"id=" + id +
", name='" + name + '\'' +
", author='" + author + '\'' +
", price=" + price +
'}';
}
}
class Test{
void testBook() throws Exception{
Book book = new Book(100032, "Java Language", "Wang Sir", 30);
ObjectOutputStream oout = new ObjectOutputStream(new FileOutputStream("./doc/book.dat"));
oout.writeObject(book);
oout.close();
book = null;
ObjectInputStream oin = new ObjectInputStream(new FileInputStream("./doc/book.dat"));
book = (Book)oin.readObject();
oin.close();
System.out.println(book);
}
}
public class Exp10 {
public static void main(String[] args) throws Exception{
Test t = new Test();
t.testBook();
}
}
结果为:
Book{id=100032, name=‘Java Language’, author=‘Wang Sir’, price=30.0}
Externalizable 接口
Externalizable 接口继承自 Serializable 接口,但是多了两个方法,writeExternal() 和 readExternal() 方法需要实现,当实现了 Externalizable 接口的类被序列化的时候,在写入和读取时会自动调用 writeExternal() 和 readExternal() 。从而我们可以在序列化之前和序列化之后完成一些操作。
class User implements Externalizable {
private String name;
private transient String password;
// 必须要有无参构造器
public User() {
System.out.println("constructor");
}
// 序列化User对象, 可以在这里使用密钥加密
public void writeExternal(ObjectOutput out) throws IOException {
System.out.println("writeExternal");
out.writeObject(name);
out.writeObject(password);
}
// 反序列化User对象, 可以在这里用密钥解密
public void readExternal(ObjectInput in) throws IOException,
ClassNotFoundException {
System.out.println("readExternal");
name = (String) in.readObject();
password = (String) in.readObject();
}
// 不会被执行
private void writeObject(ObjectOutputStream out) throws IOException {
System.out.println("writeObject");
}
// 不会被执行
private void readObject(ObjectInputStream in) throws IOException {
System.out.println("readObject");
}
// 在writeExternal之前执行,只是为了说明执行顺序,实际不需要写。
private Object writeReplace() throws ObjectStreamException {
// this.setName("kobe");
System.out.println("writeReplace");
return this;
}
// 在readExternal之后执行,只是为了说明执行顺序,实际不需要写。
private Object readResolve() throws ObjectStreamException {
// this.setName("kobe");
System.out.println("readresolve");
return this;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", password='" + password + '\'' +
'}';
}
}
class Test{
void testExternal() throws Exception{
User u = new User();
u.setName("Lucas");
u.setPassword("32sdfs");
ObjectOutputStream oout = new ObjectOutputStream(new FileOutputStream("./doc/user.dat"));
oout.writeObject(u);
oout.close();
u = null;
ObjectInputStream oin = new ObjectInputStream(new FileInputStream("./doc/user.dat"));
u = (User) oin.readObject();
oin.close();
System.out.println(u);
}
}
public class Exp10 {
public static void main(String[] args) throws Exception{
Test t = new Test();
t.testExternal();
}
}
结果如下:
constructor
writeReplace
writeExternal
constructor
readExternal
readresolve
User{name=‘Lucas’, password=‘32sdfs’}
随机读写
虽然 Java 对象化的方法将输入输出看作顺序化的流,但也可以实现在 C 语言中可以完成的随机读写。实现的类是 RandomAccessFile 类,可以使用 seek(), skipBytes(),getFilePointer() 等方法将文件指针定位到文件中的任意地方,从而实现文件的随机读写,而不破坏文件。
构造方法
以下是实现随机读写文件的一个例子
class Employee {
// \\u 表示后面是一个 unicode 编码
char[] name = {'\u0000', '\u0000', '\u0000', '\u0000',
'\u0000', '\u0000', '\u0000', '\u0000'};
int age;
public Employee(String name, int age) throws Exception{
if(name.toCharArray().length > 8)
System.arraycopy(name.toCharArray(), 0, this.name, 0, 8);
else
System.arraycopy(name.toCharArray(), 0, this.name, 0, name.toCharArray().length);
this.age = age;
}
}
public class Exp11 {
String FileName;
public Exp11(String fileName){
this.FileName = fileName;
}
public void writeEmployee(Employee e, int n) throws Exception{
RandomAccessFile ra = new RandomAccessFile(FileName, "rw");
ra.seek(n*20);
for(int i=0; i<8; i++) ra.writeChar(e.name[i]);
ra.writeInt(e.age);
ra.close();
}
public void readEmployee(int n) throws Exception{
char buf[] = new char[8];
RandomAccessFile ra = new RandomAccessFile(FileName, "r");
ra.seek(n*20);
for(int i=0; i<8; i++) buf[i] = ra.readChar();
System.out.print("name: ");
System.out.println(buf);
System.out.println("age: "+ra.readInt());
ra.close();
}
public static void main(String[] args) throws Exception{
Exp11 ep = new Exp11("./doc/random.txt");
Employee e1 = new Employee("LucasP", 23);
Employee e2 = new Employee("李华", 19);
Employee e3 = new Employee("张力", 33);
ep.writeEmployee(e1, 0);
ep.writeEmployee(e3, 2);
System.out.println("第一个雇员信息:");
ep.readEmployee(0);
System.out.println("第二个雇员信息:");
ep.readEmployee(2);
ep.writeEmployee(e2, 1);
System.out.println("第三个雇员信息: ");
ep.readEmployee(1);
}
}
结果如下:
第一个雇员信息:
name: LucasP
age: 23
第二个雇员信息:
name: 张力
age: 33
第三个雇员信息:
name: 李华
age: 19