文件高级技术

一、常见文件类型处理

一)属性文件

属性文件很简单,一行表示一个属性,属性就是键值对,键和值用(=)或者(:)分隔。

#ready to work
name = tang
age =
phone =

Java使用专门的类java.util.Properties处理这种文件。主要方法:

public synchronized void load(InputStream inStream)
public String getProperty(String key)
public String getProperty(String key, String defaultValue)
Properties props = new Properties();
try {
props.load(new FileReader("tang.properties"));
System.out.println("The name is " + props.getProperty("name"));
} catch (IOException e) {
e.printStackTrace();
}

优势:可以自动处理空格,自动忽略空行,以#或者!开头的会被视为注释。

二)压缩文件

Java SDK支持两种:gzip和zip,gzip只能压缩一个文件,而zip文件可以包含多个。

先看gzip:

java.util.zip.GZIPOutputStream
java.util.zip.GZIPInputStream

它们都是InputStream和OutputStream的子类,都是装饰类,GZIPOutputStream加到

已有的流上,就可以实现压缩,而GZIPInputStream加到已有的流上就可以实现解压。

public static void gzip(String fileName) {
InputStream in = null;
String gzipFileName = fileName + ".gz";
OutputStream out = null;
try {
in = new FileInputStream(fileName);
out = new GZIPOutputStream(new FileOutputStream(gzipFileName));
copy(in, out);
} catch (Exception e) {
e.printStackTrace();
}
}
public static void unGzip(String gzipFileName, String fileName) {
InputStream in = null;
OutputStream out = null;
try {
in = new GZIPInputStream(new BufferedInputStream(
new FileInputStream(gzipFileName)
));
out = new BufferedOutputStream(
new FileOutputStream(fileName)
);
copy(in, out);
} catch (Exception e) {
e.printStackTrace();
}
}
public static void copy(InputStream in, OutputStream out) throws IOException {
try {
byte[] buf = new byte[1024];
int count = 0;
while ((count = in.read(buf)) != -1) {
out.write(buf, 0, count);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (in != null) in.close();
if (out != null) out.close();
}
}

zip支持压缩文件包中包含多个文件,Java API中的主要类是:

java.util.zip.ZipOutputStream
java.util.zip.ZipInputStream

ZipOutputStream可以写入多个文件,它有一个重要方法:

//在写入一个文件前,必须先调用该方法,表示准备写入一个压缩条目ZipEntry
public void putNextEntry(ZipEntry e) throws IOException
//每个压缩条目都有一个名称,这个名称是压缩文件的相对路径,如果以'/'结尾表示目录
public ZipEntry(String name)
/**
* 压缩一个文件或者目录
* @param inFile 表示输入,可以是文件或者目录
* @param zipFile 表示输出的zip文件
* */
public static void zip(File inFile, File zipFile) throws IOException {
ZipOutputStream zipOut = new ZipOutputStream(new BufferedOutputStream(
new FileOutputStream(zipFile)));
try {
//输入文件不存在抛出异常
if (!inFile.exists())
throw new FileNotFoundException(inFile.getAbsolutePath());
inFile = inFile.getCanonicalFile();
String rootPath = inFile.getParent();
//如果根路径不是以"/"结尾
if (!rootPath.endsWith(File.separator)) {
rootPath += File.separator;
}
addFileToZipOut(inFile, zipOut, rootPath);
} catch (Exception e) {
e.printStackTrace();
} finally {
zipOut.close();
}
}
public static void addFileToZipOut(File inFile, ZipOutputStream out, String rootPath) throws IOException {
String relativePath = inFile.getCanonicalPath()
.substring(rootPath.length());
if (inFile.isFile()) {
out.putNextEntry(new ZipEntry(relativePath));
InputStream in = new BufferedInputStream(
new FileInputStream(inFile)
);
try {
copy(in, out);
} finally {
in.close();
}
//如果是目录
} else {
out.putNextEntry(new ZipEntry(relativePath + File.separator));
for (File f : inFile.listFiles()) {
addFileToZipOut(f, out, rootPath);
}

二、随机读写文件(RandomAccessFile)

一)用法

RandomAccessFile构造方法:

public RandomAccessFile(String name, String mode) throws FileNotFoundException
public RandomAccessFile(File file, String mode) throws FileNotFoundException

其中name和file,表示文件路径和File对象。mode表示打开模式:

1)r:只读

2)rw:读和写

3)rws:读和写,另外,要求文件内容和元数据的任何更新都同步到设备上。

4)rwd:读和写,另外,文件内容的更新同步到设备上,元数据更新不同步。

该类有类似于InputStream/OutputStream类似的读写字节流的方法。

另外,它还实现了DataInput/DataOutput接口。

public int read() throws IOException
public int read(byte b[]) throws IOException
public final int readInt() throws IOException
public final void writeInt(int v) throws IOException
public void write(byte b[]) throws IOException

另外还有两个read方法:

public final void readFully(byte b[]) throws IOException
public final void readFully(byte b[], int off, int len) throws IOException

与对应的read方法的区别是,它们可以确保读够期望的长度,如果到了文件结尾也没读够,抛异常。

RandomAccessFile内部有一个文件指针,指向当前的读写位置,各种read/write操作都会自动更新

该指针。与流操作不同的是,RandomAccessFile可以获取该指针,也可以更改该指针。

//获取当前指针
public native long getFilePointer() throws IOException
//更改当前指针到pos
public native void seek(long pos) throws IOException

跳过输入流中的n个字节:

public int skipBytes(int n) throws IOException //通过更改指针实现

获取文件字节数:

public native long length() throws IOException

修改文件长度:

//如果当前文件的长度小于newLength,则文件会扩展,大于会收缩,文件指针比newLength
//大则会调整到newLength
public native void setLength(long newLength) throws IOException

请避免使用以下两个方法:

public final void writeBytes(String s) throws IOException
public final String readLine() throws IOException

三、内存映射文件

内存映射文件不是Java引入的概念,而是操作系统提供的一种功能,大部分操作系统都支持。

一)基本概念

所谓内存映射文件,就是将文件映射到内存,文件对应于内存的一个字节数组,对文件的操作

变为对这个字节数组的操作,而字节数组的操作直接映射到文件上。这种映射可以是文件的全部

区域也可以是部分区域。

内存映射文件特点:

1)使用的是操作系统内核内存空间,只有一次复制,比普通读写效率高

2)可被多个不同程序共享,一个程序对内存的修改,其他程序也可以看

到,这使得它特别适合不同程序间的通信

操作系统自身在加载可执行文件的时候,一般都利用了内存映射。

内存映射局限性:

因为是按页分配内存,对小文件来说浪费内存

二)用法

内存映射文件需要通过FileInputStream/FileOutputStream/RandomAccessFile,它们都有方法:

public FileChannel getChannel()

FileChannel都有方法:

/**
* 该方法将当前文件映射到内存,映射结果就是MappedByteBuffer对象,它代表内存中的字节数组
* 如果映射区域超过了文件的范围,文件会自动扩展
* @param mode 表示映射模式:
* READ_ONLY:只读
* READ_WRITE:读写
* PRIVATE:私有模式,更改不反映到文件,也不被其他程序看到
* @param position 表示映射的起始位置
* @param size 表示映射的长度
* @return 映射完成后,文件就可以关闭,对文件的后续读写可以通过MappedByteBuffer
* */
public MappedByteBuffer map(MapMode mode, long position,
long size) throws IOException{
}

MappedByteBuffer是ByteBuffer的子类,ByteBuffer可以理解为封装了一个长度不可变的字节数组,

在内存映射文件中这个长度由map方法中的size决定。ByteBuffer有一个基本属性position,表示当前

读写位置,相关方法是:

public final int position() //获取当前读写位置
public final Buffer position(int newPosition) //修改当前读写位置

ByteBuffer中有很多基于当前读写位置的读写数据方法:

public abstract byte get()  //从当前位置获取一个字节
public ByteBuffer get(byte[] dst) //从当前位置获取dst.length长度的字节到dst
public abstract int getInt() //从当前位置读取一个int
public final ByteBuffer put(byte[] src) //将字节数组src写入到当前位置
public abstract ByteBuffer putLong(long value) //将value写入到当前位置

这些方法读写后都会自动增加position,与之对应的还有一组方法可以指定position:

public abstract int getInt(int index)  //从index处读取一个int
public abstract double getDouble(int index)
public abstract ByteBuffer putDouble(int index, double value)
public abstract ByteBuffer putLong(int index, long value)

这些方法在读写时,不会改变当前的读写位置。

MappedByteBuffer自己还定义了一些方法:

//检查文件内容是否真正加载到了内存,仅供参考
public final boolean isLoaded()
//尽量将文件内容加载到内存
public final MappedByteBuffer load()
//将对内存的修改强制同步到硬盘上
public final MappedByteBuffer force()

四、标准序列化机制

序列化就是将对象转换为字节流,反序列化就是将字节流转换为对象。

一)基本用法

要让一个类支持序列化,只需要让这个类实现接口java.io.Serializable,该接口是一个标记接口。

读取/保存声明了Serializable接口的类可以使用ObjecOutputStream/ObjectInputStream流了。

ObjectOutputStream是OutputStream的子类,但实现了ObjectOutput接口,该接口是DataOutput

的子接口,增加了一个方法:

public void writeObject(Object obj) throws IOExceptio

该方法能把对象obj转化为字节,写到流中。

ObjectInputStream核心方法:

public Object readObject() throws ClassNotFoundException, IOException

该方法中流中读取字节,转化为对象。

public static void writeStudents(List<Student> students)
throws IOException {
ObjectOutputStream out = new ObjectOutputStream(
new BufferedOutputStream(new FileOutputStream("students.dat")));
try {
out.writeInt(students.size());for(Student s : students) {
out.writeObject(s);
}
} finally {
out.close();
}
}
public static List<Student> readStudents() throws IOException,
ClassNotFoundException {
ObjectInputStream in = new ObjectInputStream(new BufferedInputStream(
new FileInputStream("students.dat")));
try {
int size = in.readInt();
List<Student> list = new ArrayList<>(size);
for(int i = 0; i < size; i++) {
list.add((Student) in.readObject());
}
return list;
} finally {
in.close();
}
}

二)定制序列化

主要有两种定制序列化的机制:

1)使用transient关键字  

声明为transient的字段,Java的默认序列化机制就不会保存该字段了,

但可以通过writeObject来自己保存。

2)实现writeObject和readObject方法

writeObject声明必须为:

private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException

ArrayList中有:

private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException{
// Write out element count, and any hidden stuff
int expectedModCount = modCount;
//该方法必须被调用,即使类中所有的字段都是transient
s.defaultWriteObject();
// Write out size as capacity for behavioural compatibility with clone()
s.writeInt(size);
// Write out all elements in the proper order.
for (int i=0; i<size; i++) {
s.writeObject(elementData[i]);
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}

readObject方法必须声明为:

private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
elementData = EMPTY_ELEMENTDATA;
// Read in size, and any hidden stuff
s.defaultReadObject();
// Read in capacity
s.readInt(); // ignored
if (size > 0) {
// be like clone(), allocate array based upon size not capacity
ensureCapacityInternal(size);
Object[] a = elementData;
// Read in all elements in the proper order.
for (int i=0; i<size; i++) {
a[i] = s.readObject();
}
}
}

三)基本逻辑

writeObject基本逻辑:

1)如果对象没有实现Serializable,抛出NotSerializable异常

2)每个对象都有一个编号,如果之前已经写过该对象,下次写入只会写该

对象的引用,这可以解决对象引用和循环引用的问题

3)如果对象实现了writeObject方法,调用它的自定义方法

4)利用的是反射机制

readObject基本逻辑:

1)不调用任何构造方法

2)它自己就相当于一个独立的构造方法,根据字节流初始化对象,利用的也是反射机制

3)在解析流时,对于引用到的类型信息会动态加载,如果找不到,抛出ClassNotFoundException

四)版本问题

需要解决的问题:序列化到文件的对象是持久保存的,不会自己改变的,而我们

的代码是不断演变改进的,如果类的定义发生了变化,反序列化会出现问题。

解决方法:Java会给类自动定义一个版本号,这个版本号是根据类中的信息生成的。

在反序列化时,如果类的定义发生了变化,版本号就会变化,与流中的版本号就会

不匹配,反序列化就会抛出java.in.InvalidClassException.

但因为Java自动生成版本号性能较低,还有为了更好地控制,我们通常自定义这个

版本号。注意通过编辑器自动生成的版本号不会自己更新。

如果版本号一样,但实际字段不匹配:

1)字段删除了:即使流中有该字段,类定义中有,该字段会被忽略;

2)新增了字段:即类定义中有,而流中没有,该字段会被设置为默认值;

3)字段类型改变:抛出InvalidClassException;

五、使用Jackson序列化

一)基本用法

1.JSON

Student student = new Student("ୟӣ", 18, 80.9d);
//它是一个线程安全类
ObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT);
//默认情况下会保存所有声明为public或者有public getter方法的字段
String str = mapper.writeValueAsString(student);
System.out.println(str);
ObjectMapper mapper = new ObjectMapper();
//默认情况下,被反序列化的类必须要有无参构造函数
Student s = mapper.readValue(new File("student.json"), Student.class);
System.out.println(s.toString());

其他重要方法:

public byte[] writeValueAsBytes(Object value)
public void writeValue(OutputStream out, Object value)
public void writeValue(Writer w, Object value)
public void writeValue(File resultFile, Object value) public <T> T readValue(InputStream src, Class<T> valueType)
public <T> T readValue(Reader src, Class<T> valueType)
public <T> T readValue(String content, Class<T> valueType)
public <T> T readValue(byte[] src, Class<T> valueType)

2.XML 

与序列化为JSON类似:

Student student = new Student("tom", 18, 80.9d);
ObjectMapper mapper = new XmlMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT);
String str = mapper.writeValueAsString(student);
mapper.writeValue(new File("student.xml"), student);
System.out.println(str);

3.MessagePack 

MessagePack是一种二进制形式的JSON编码更为精简高效,因为是二进制,因此不能写出为String。

Student student = new Student("jim", 18, 80.9d);
ObjectMapper mapper = new ObjectMapper(new MessagePackFactory());
byte[] bytes = mapper.writeValueAsBytes(student);
mapper.writeValue(new File("student.bson"), student); Student s = mapper.readValue(new File("student.bson"), Student.class);
System.out.println(s.toString())

4.容器对象 

List<Student> students = Arrays.asList(new Student[] {
new Student("tom", 18, 80.9d), new Student("๫ ",ࢥ17, 67.5d) });
ObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT);
String str = mapper.writeValueAsString(students);
mapper.writeValue(new File("students.json"), students);
System.out.println(str);
//反序列化不同,需要新建一个TypeReference对象
List<Student> list = mapper.readValue(new File("students.json"),
new TypeReference<List<Student>>() {});
System.out.println(list.toString());

二)定制序列化

1.忽略字段 

//用于字段,getter、setter方法
@JsonIgnore
double score;
//用于类,指定忽略字段
@JsonIgnoreProperties("score")
public class Student {

2.引用同一个对象 

问题:

Dog price = new Dog("Price");
Person jim = new Person("Jim", price, price);
ObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT);
String value = mapper.writeValueAsString(jim);
System.out.println(value);
Person person = mapper.readValue(value, Person.class);
if (person.getFirst() == person.getSecond()) {
System.out.println("Same");
} else {
System.out.println("Different"); //different 指向了不同的对象
}

解决办法:

@JsonIdentityInfo(
generator = ObjectIdGenerators.IntSequenceGenerator.class,
property = "id"
)
public class Dog {

3.循环引用 

问题:

Parent parent = new Parent();
parent.name = "Father";
Child child = new Child();
child.name = "Child";
parent.child = child;
child.parent = parent;
ObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT);
String s = mapper.writeValueAsString(parent); //java.lang.StackOverflowError
System.out.println(s);

解决办法:

public class Parent {
public String name;
@JsonManagedReference //标记为主引用
public Child child;
}
public class Child {
public String name;
@JsonBackReference //标记为反向引用
public Parent parent;
}

4.反序列化时忽略未知字段 

问题:与Java标准序列化不同,在反序列化时,对于未知字段,Jackson默认会抛出异常:UnrecognizedPropertyException.

解决办法:

mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);

或者:

@JsonIgnoreProperties(ignoreUnknown=true)
public class Student {
//...
}

5.继承和多态 

Jackson不能自动处理继承和多态:

public class Shape {
} public class Circle extends Shape {
private int r;
public Circle() {
}
public Circle(int r) {
this.r = r;
}
} public class Square extends Shape {
private int l;
public Square() {
}
public Square(int l) {
this.l = l;
}
}
public class ShapeManager {
private List<Shape> shapeList;
public List<Shape> getShapeList() {
return shapeList;
}
public void setShapeList(List<Shape> shapeList) {
this.shapeList = shapeList;
}
}

解决办法:

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
@JsonSubTypes({
@JsonSubTypes.Type(value = Circle.class, name = "circle"),
@JsonSubTypes.Type(value = Square.class, name = "square")
})
public class Shape {
}

6.修改字段名称 

@JsonProperty("名称:") //改变输出
String name;
//对于xml修改根元素名称
@JsonRootName("student")
public class Student {

7.格式化日期 

默认情况下日期会被序列化为一个长整数。解决:

@JsonFormat(pattern="yyyy-MM-dd HH:mm:ss", timezone="GMT+8")
public Date date = new Date();

8.配置构造方法 

序列化时,如果类没有无参构造函数,会抛异常。解决:

@JsonCreator
public Student(
@JsonProperty("name") String name,
@JsonProperty("age") int age,
@JsonProperty("score") double score) {
this.name = name;
this.age = age;
this.score = score;
}

Java基础(十三) 文件高级技术的更多相关文章

  1. java基础篇---I/O技术

    java基础篇---I/O技术   对于任何程序设计语言而言,输入输出(I/O)系统都是比较复杂的而且还是比较核心的.在java.io.包中提供了相关的API. java中流的概念划分 流的方向: 输 ...

  2. java基础篇---I/O技术(三)

    接上一篇java基础篇---I/O技术(二) Java对象的序列化和反序列化 什么叫对象的序列化和反序列化 要想完成对象的输入或输出,还必须依靠对象输出流(ObjectOutputStream)和对象 ...

  3. 阿里P7整理“硬核”面试文档:Java基础+数据库+算法+框架技术等

    现在的程序员越来越多,大部分的程序员都想着自己能够进入大厂工作,但每个人的能力都是有差距的,所以并不是人人都能跨进BATJ.即使如此,但身在职场的我们一刻也不能懈怠,既然对BATJ好奇,那么就要朝这个 ...

  4. java基础篇---I/O技术(二)

    接着上篇http://www.cnblogs.com/oumyye/p/4314412.html java I/O流---内存操作流 ByteArrayInputStream和ByteArrayOut ...

  5. Java基础IO文件拷贝练习题

    /** * 编写一个程序,把指定目录下的所有的带.java文件都拷贝到另一个目录中,拷贝成功后,把后缀名是.java的改成.txt. */ 1.我们看到这个题还是用大化小的思想来做 分析:1.拷贝 & ...

  6. Java基础(十三)--深拷贝和浅拷贝

    在上篇文章:Java基础(十二)--clone()方法,我们简单介绍了clone()的使用 clone()对于基本数据类型的拷贝是完全没问题的,但是如果是引用数据类型呢? @Data @NoArgsC ...

  7. Java基础之文件的输入输出流操作

    在介绍输入输出流之前,首先需要了解如何创建文件,创建文件夹以及遍历文件夹等各种操作,这里面不在一一介绍,主要介绍的是文件的输入输出流操作. 在起初学习文件操作之前,总是喜欢将输入输出弄混淆,后来通过看 ...

  8. java基础(十三)-----详解内部类——Java高级开发必须懂的

    可以将一个类的定义放在另一个类的定义内部,这就是内部类. 为什么要使用内部类 为什么要使用内部类?在<Think in java>中有这样一句话:使用内部类最吸引人的原因是:每个内部类都能 ...

  9. Java读写Word文件常用技术

      Java操作操作Word文件,最近花了几天时间解决使用Word模板导出数据的问题,收集到一些资料分享下. 常见的技术如下: 1.POI(兼容doc.docx文件) 官方网站:http://poi. ...

随机推荐

  1. hdu2460 e-DCC染色缩点+暴力LCA

    /* 给定一个无向图,往里面加边,问加第i条边时图中的桥数 首先肯定要求初始状态下的桥,染色缩点 每次给定的边为(u,v), 那么u->lca(u,v)->v路上的所有边都不再是桥 求LC ...

  2. C++ Primer 笔记——const 限定符

    1.因为const对象一旦创建后其值就不能再改变,所以const对象必须初始化. 2.默认情况下const对象只在文件内有效,如果想在多个文件之间共享const对象,必须在变量的定义之前添加exter ...

  3. Python模块之sys模块

    sys模块是与Python解释器交互的一个接口 有如下方法 sys.argv   命令行参数的一个列表,第一个参数为程序本身的路径 sys.exit(n)  退出程序,正常退出exit(0) ,异常退 ...

  4. mysql的基础知识

    一.存储引擎 mysql> show engines; +--------------------+---------+------------------------------------- ...

  5. idea的操作

  6. base | Tread类

    Tread类 Linux中,每个进程有一个pid,类型pid_t,由getpid()取得.Linux下的POSIX线程也有一个id,类型 pthread_t,由pthread_self()取得,该id ...

  7. 关于The specified Android SDK Build Tools version (26.0.2) is ignored, as it is below the minimum...

    今天将项目迁移到另一台笔记本,进行build出现以下问题,导致build失败 The specified Android SDK Build Tools version (26.0.2) is ign ...

  8. 一脸懵逼学习Hadoop分布式集群HA模式部署(七台机器跑集群)

    1)集群规划:主机名        IP      安装的软件                     运行的进程master    192.168.199.130   jdk.hadoop      ...

  9. javascript 中 x offsetX clientX screenX pageX的区别

    在javascript的事件对象中的关于事件鼠标位置的几个属性(x, pageX, offsetX, scrrenX clientX)及(y, pageY, offsetY, screenY, cli ...

  10. Java 集合Collection与List的详解

    1.什么是集合 存储对象的容器,面向对象语言对事物的体现都是以对象的形式,所以为了方便对多个对象的操作,存储对象,集合是存储对象最常用的一种方式. 集合的出现就是为了持有对象.集合中可以存储任意类型的 ...