JAVA中的NIO(二)

一、内存文件映射


内存文件映射允许我们创建和修改那些因为太大而不能放入内存中的文件。有了内存文件映射,我们就可以假定整个文件都在内存中,而且可以完全把文件当作数组来访问。

package com.dy.xidian;

import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;

public class LargeMappedFiles {
    static int length = 0x8FFFFFF;
    public static void main(String[] args) throws Exception {
        @SuppressWarnings("resource")
        MappedByteBuffer out = new RandomAccessFile("test.dat", "rw")
                .getChannel().map(FileChannel.MapMode.READ_WRITE, 0, length);
        for (int i = 0; i < length; i++)
            out.put((byte) 'x');
        System.out.println("Finished writing");
        for (int i = length / 2; i < length / 2 + 6; i++)
            System.out.print((char) out.get(i));
    }
}

map中的0表示从文件的起始位置开始映射,映射的大小为length(128MB)。MappedByteBuffer是直接缓冲区(JVM对直接缓冲区的内容会直接进行IO操作,如果缓冲区是非直接的,JVM会先将用户缓冲区的数据copy到自己的缓冲区,在对自己的缓冲区进行IO操作),

所以MappedByteBuffer对大文件的操作速度要比普通的IO流快。MappedByteBuffer继承自ByteBuffer,所以我们对ByteBuffer的操作都适用于ByteBuffer。

二、对映射文件的部分加锁 


  文件映射通常应用于极大的文件。我们可能需要对这种巨大的文件进行部分加锁,以便其它进程可以修改文件中未被加锁的部分。例如:数据库就是这样吗,因此多个用户可以同时访问它

package com.dy.xidian;

import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;

public class LockingMappedFiles {
    static final int LENGTH = 0X8FFFFFF;
    static FileChannel fc;
    @SuppressWarnings("resource")
    public static void main(String[] args) throws Exception {
        fc = new RandomAccessFile("test.dat", "rw").getChannel();
        MappedByteBuffer out = fc
                .map(FileChannel.MapMode.READ_WRITE, 0, LENGTH);
        for (int i = 0; i < LENGTH; i++)
            out.put((byte) 'x');
        new LockAndModify(out, 0, LENGTH / 3);
        new LockAndModify(out, LENGTH / 2, LENGTH / 2 + LENGTH / 4);
    }
    private static class LockAndModify extends Thread {
        private ByteBuffer buff;
        private int start, end;

        LockAndModify(ByteBuffer buff, int start, int end) {
            super();
            this.start = start;
            this.end = end;
            this.buff = buff;
            buff.limit(end);
            buff.position(start);
            //创建一个新缓冲区标识对象
            //该对象的position=0,limit和capacity等于原缓冲区剩余字节数。
            buff = buff.slice();
            start();
        }

        public void run() {
            try {
                // 获取通道上的锁,指定加锁的起始位置和结束位置
                // 将锁设置为独占锁
                FileLock fl = fc.lock(start, end, false);
                System.out.println("Locked: " + start + " to " + end);
                while (buff.position() < buff.limit() - 1)
                    buff.put((byte) (buff.get() + 1));
                fl.release();
                System.out.println("Released: " + start + " to " + end);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

3.GZIP的使用范例


 

package com.dy.xidian;

import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;

public class GZIPcompress {
    public static void main(String[] args) throws IOException {
        if (args.length == 0) {
            System.out.println("Usage: \n\tGZIPcompress file\n"
                    + "\tUses GZIP compress to compress "
                    + "the file to test.gz");
            System.exit(1);
        }
        BufferedReader in = new BufferedReader(new FileReader(args[0]));
        BufferedOutputStream out = new BufferedOutputStream(
                new GZIPOutputStream(new FileOutputStream("test.gz")));
        System.out.println("Writing file");
        int c;
        while ((c = in.read()) != -1) {
            out.write(c);
        }
        in.close();
        out.close();
        System.out.println("Reading file");
        BufferedReader in2 = new BufferedReader(new InputStreamReader(
                new GZIPInputStream(new FileInputStream("test.gz"))));
        String s;
        while ((s = in2.readLine()) != null)
            System.out.println(s);
        in2.close();
    }
}

 4.对象序列化


  对象的序列化就是将那些实现了Seriallizable接口的对象转换成一个字节序列,并能够在以后将这个字节序列完全恢复为原来的对象。而且这种方式可以跨操作系统实现对象的传输,可以在window上将对象序列化,并通过网络传输给linux操作系统,然后在linux系统上进行对象的还原。对象的序列化可以实现轻量级持久性。"持久性"意味着一个对象的生存周期并不取决于程序是否正在执行,它可以存在于程序的调用之间。通过序列化将一个对象对象写入磁盘,然后在重新调用程序时恢复该对象。对象必须在程序中显示地序列化和反序列化还原。

  • 序列化实例

  对象的序列化首先要创建某些OutputStream对象,然后将其封装在一个ObjectOutpuStream对象中。这时,只需要调用writeObject()即可将对象序列化,并将其发送给OutputStream(对象的序列化是基于子节的,因要使用InputStream和OutputStream)。要反向进行该过程,需要将一个InputStream封装在ObjectInputStream内,然后调用readObject()。和往常一样,我们最后获得个是一个引用,它指向一个向上转型的Object(),所以必须向下转型才能设置他们。

package com.dy.xidian;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Random;

@SuppressWarnings("serial")
// Serializble是一个空接口,作为标识来表明该对象可以序列化
class Data implements Serializable {
    private int n;

    public Data(int n) {
        super();
        this.n = n;
    }
    public String toString() {
        return Integer.toString(n);
    }
}

@SuppressWarnings("serial")
public class Worm implements Serializable {
    private static Random rand = new Random(47);
    private Data[] d = {new Data(rand.nextInt(10)), new Data(rand.nextInt(10)),
            new Data(rand.nextInt(10))};

    private Worm next;
    private char c;
    public Worm(int i, char x) {
        System.out.println("Worm constructor: " + i);
        c = x;
        // 递归创建对象,每个对象中的next指向下一个对象
        if (--i > 0)
            next = new Worm(i, (char) (x + 1));
    }

    public Worm() {
        System.out.println("Default constructor");
    }

    public String toString() {
        StringBuilder result = new StringBuilder(":");
        result.append(c);
        result.append("(");
        for (Data dat : d) {
            result.append(dat);
        }
        result.append(")");
        // 递归打印对象信息
        if (next != null)
            result.append(next);
        return result.toString();
    }

    public static void main(String[] args) throws Exception {
        Worm w = new Worm(6, 'a');
        System.out.println("w = " + w);
        // 对基本流进行包装,将对象写到文件中
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(
                "worm.out"));
        out.writeObject("Worm storage\n");
        out.writeObject(w);
        out.close();
        // 从文件中读取对象
        ObjectInputStream in = new ObjectInputStream(new FileInputStream(
                "worm.out"));
        String s = (String) in.readObject();
        Worm w2 = (Worm) in.readObject();
        if (w2 instanceof Worm)
            System.out.println(s + "w2 = " + w2);
        in.close();
        
        //将对象序列化写到内存中
        ByteArrayOutputStream bout = new ByteArrayOutputStream();
        ObjectOutputStream out2 = new ObjectOutputStream(bout);
        out2.writeObject("Worm storage\n");
        out2.writeObject(w);
        out2.flush();
        out2.close();
        //创建一个新的byteArray数组
        //bout存储区的数据复制到新数组中
        ObjectInputStream in2 = new ObjectInputStream(new ByteArrayInputStream(
                bout.toByteArray()));
        s = (String) in2.readObject();
        Worm w3 = (Worm) in2.readObject();
        System.out.println(s + "w3 = " + w3);
        in2.close();
    }
}
  • 寻找类

  反序列化在生成对象时不是凭空的,它需要参考该对象的类文件(class),然后通过类文件才能还原该对象(因为反序列化底层应用了反射机制)。所以类文件应该放在classpath路径下。

  • 序列化的控制

  有时我们考虑到安全问题,不希望一个的对象的所有部分都被序列化;或者是,一个对象被还原后,某子对象需要重新创建,从而不必将该对象序列化。在这种特殊情况下,可以通过实现Externalizable接口来替代Serializable接口。Extertnalizable接口继承了Serializable接口,同时增添了两个方法:writeExternal()和readExternal()。这个方法会在序列化和反序列化还原的过程中被自动调用,以便执行一些特殊的操作。注意: 一旦继承Externalizable接口,意味着对象的所有属性都不会再自动序列化。

package com.dy.xidian;

import java.io.Externalizable;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;

public class Blip implements Externalizable {
    private int i;
    private String s;
    public Blip() {
        System.out.println("Blip Constructor");
    }

    public Blip(String x, int a) {
        System.out.println("Blip(String x, int a)");
        s = x;
        i = a;
    }

    public String toString() {
        return s + i;
    }

    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        System.out.println("Blip.writeExternal");
        out.writeObject(s);
        out.writeInt(i);
    }

    @Override
    public void readExternal(ObjectInput in) throws IOException,
            ClassNotFoundException {
        System.out.println("Blip.readExternal");
        s = (String) in.readObject();
        i = in.readInt();
    }

    public static void main(String[] args) throws IOException,
            ClassNotFoundException {
        System.out.println("Constructing objects: ");
        Blip b = new Blip("A String", 47);
        System.out.println(b);
        ObjectOutputStream o = new ObjectOutputStream(new FileOutputStream(
                "Blip.out"));
        System.out.println("Saving object");
        o.writeObject(b);
        o.close();
        ObjectInputStream in = new ObjectInputStream(new FileInputStream(
                "Blip.out"));
        System.out.println("Recovering b:");
        b = (Blip) in.readObject();
        System.out.println(b);
        in.close();
    }
}

我们可以在writeExternal()和readExternal()来指定,将对象中那些属性进行序列化和反序列化。

Externalizable与Serializable的区别:Serializable是从存储的二进制数据中直接恢复出对象的,它不会调用构造器。而对于一个Externalizable对象,则默认的构造器都会被调用,然后再调用readExternal()。

  transient(瞬时)关键字

  该关键字使得我们在使用serializable时,可以让敏感属性不被序列化。

package com.dy.xidian;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Date;
import java.util.concurrent.TimeUnit;

public class Login implements Serializable {
    /**
     * 
     */
    private static final long serialVersionUID = 1L;
    private Date date = new Date();
    private String username;
    private transient String password;
    public Login(String name, String pwd) {
        username = name;
        password = pwd;
    }

    public String toString() {
        return "logon info:\n username: " + username + "\n date: " + date
                + "\n password: " + password;
    }

    public static void main(String[] args) throws IOException,
            InterruptedException, ClassNotFoundException {
        Login a = new Login("dy", "111111");
        System.out.println("login a = " + a);
        ObjectOutputStream o = new ObjectOutputStream(new FileOutputStream(
                "Login.out"));
        o.writeObject(a);
        o.close();
        TimeUnit.SECONDS.sleep(1);
        ObjectInputStream in = new ObjectInputStream(new FileInputStream(
                "Login.out"));
        System.out.println("Recovering object at " + new Date());
        a = (Login) in.readObject();
        System.out.println("Login a = " + a);
        in.close();
    }
}

   当我们将同一个对象在同一个流进行多次序列化后(对同一个对象多次调用writeObject方法),然后在同一流上对该对象多次反序列化后(多次调用readObject方法),那么反序列化后,内存中只会存在一个该对象,这很正常,因为在序列化时该对象也只有一个(即使我们序列化了多次)。但是,我们如果我们使用不同的流对该对象进行反序列话,那么内存会产生两个这样的对象。原因在于流之间是不会检测所要反序列化的对象是否存在于对方的流中。可以这样来记忆,每使用一个新的流进行反序列化时,就好像在new一个对象,使用同一流来反序列化同一种对象时,就好像在进行引用赋值。

package com.dy.xidian;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.Serializable;


class Horse implements Serializable{}
class Animal implements Serializable{
    private Horse h;
    private String name;
    
    public Animal(String nm, Horse h){
        name = nm;
        this.h = h;
    }
    
    public String toString(){
        return name + super.toString() + this.h;
    }
}

public class MyWorld {
    public static void main(String[] args) throws IOException {
        Horse h = new Horse();
        Animal n1 = new Animal("a", h);
        ByteArrayOutputStream buffer1 = new ByteArrayOutputStream();
        ObjectOutputStream o1 = new ObjectOutputStream(buffer1);
        o1.writeObject(n1);
        o1.writeObject(n1);
        ByteArrayOutputStream buffer2 = new ByteArrayOutputStream();
        ObjectOutputStream o2 = new ObjectOutputStream(buffer1);
        o1.writeObject(n1);
    }
}

#

 

5.受限数据集存储


 

Preferences类提供了对于小的数据集的存储方式,它对于数据集的存储的方式和操作系统(比如在windows操作系统上会被存储到windows的注册表中)相关,我们不必了解具体的存储细节,主要关注一下该API如何使用

package cn.xdysite;
public class Test {
    public static void main(String[] args) {
        Preferences prefs = Preferences.userNodeForPackage(Test.class);
        prefs.put("name", "Bob");
        prefs.putInt("Bob", 123456789);
        prefs.putBoolean("single", true);
        int a = prefs.getInt("count", 0);
        System.out.println(a);
        prefs.putInt("count", ++a);
    }
}

Preferences会根据传入的参数(必须是Class对象,这里是Test.class)创建一个存储节点,然后将数据存储的到节点,我们每运行一次上面的代码,会发现输出的值会比上一次大1.对于存储节点的区分是根据包来区分的,同一个包下的class对象会生成同一个存储节点,比如cn.xdysite下有个Test1和Test2类,那么我们传入Test1.class作为存储节点,可以用Test1.clTest2.class来取数据

7.参考文章

序列化安全问题:http://my.oschina.net/u/1188877/blog/529611?p={{page}}

 

posted @ 2016-04-26 14:52  被罚站的树  阅读(272)  评论(0编辑  收藏  举报