java的文件IO举例分析

这篇文章主要讲解了“java的文件IO举例分析”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“java的文件IO举例分析”吧!

成都创新互联公司专注于同心企业网站建设,成都响应式网站建设公司,商城系统网站开发。同心网站建设公司,为同心等地区提供建站服务。全流程按需定制,专业设计,全程项目跟踪,成都创新互联公司专业和态度为您提供的服务

文件I/O

        JDK 1.4的java.nio.*包中引入了新的IO类库,其目的在于提高速度。实际上,旧的IO包已经使用nio重新实现过,以便充分利用这种速度提高。速度提高源自于所使用的结构更接近于操作系统执行IO的方式:通道缓冲器。我们可以把它想象成一个煤矿,通道是一个包含煤层(数据)的矿藏,而缓冲器则是派送到矿藏的卡车。卡车载满煤炭而归,我们再从卡车上获得煤炭。也就是说,我们并没有直接和通道交互,我们只是和缓冲器交互,并把缓冲器派送到通道。要么从缓冲器获得数据,要么向缓冲器发送数据。

        唯一直接与通道交互的缓冲器是ByteBuffer——可以存储未加工字节的缓冲器。java.nio.ByteBuffer是相当基础的类:通过告知分配多少存储空间来创建一个ByteBuffer对象,并且还有一个方法选择集,用于以原始的字节形式或基本数据类型输出和读取数据。但是,没办法输出或读取对象,即使是字符串对象也不行。这种处理虽然很低级,但却正好,因为这是大多数操作系统中更有效的映射方式。

        旧IO类库有三个类被修改了,用以产生FileChannel。这三个被修改类是FileInputStream、FileOutputStream以及用于既读又写的RandomAccessFile。这些都是字节操作流,与底层nio性质一致。Reader和Writer这些字符模式类不能用于产生通道;但是java.nio.channels.Channels类提供了适用方法,用于在通道中产生Reader和Writer。

//演示上面三种类型的流,用于产生可写的,可读可写的及可读的通道  
package com.wise.tiger;  
  
import java.io.FileInputStream;  
import java.io.FileOutputStream;  
import java.io.IOException;  
import java.io.RandomAccessFile;  
import java.nio.ByteBuffer;  
import java.nio.channels.FileChannel;  
  
public class GetChannel {  
    private static final int BUFF_SIZE = 1024;  
  
    public static void main(String[] args) throws IOException {  
        // 写文件  
        FileChannel fc = new FileOutputStream("data.txt").getChannel();  
        /* 
         * 使用warp()方法将已存在的字节数组包装到ByteBuffer中 也可以使用put方法直接进行填充 
         */  
        fc.write(ByteBuffer.wrap("I'm Peppa Pig.".getBytes()));  
        fc.close();  
  
        // 文件末尾添加内容  
        fc = new RandomAccessFile("data.txt", "rw").getChannel();  
        fc.position(fc.size());// 移动到文件末尾  
        fc.write(ByteBuffer.wrap("This is my little brother, George".getBytes()));  
        fc.close();  
  
        // 读文件  
        fc = new FileInputStream("data.txt").getChannel();  
  
        /* 
           * 分配ByteBuffer,对于只读访问,必须显式地使用静态的allocate()方法来分配ByteBuffer 
         * nio的目标就是快速移动大量数据,因此ByteBuffer的大小就显得尤为重要(必须通过实际运行程序找到最佳尺寸) 
         */  
        ByteBuffer buff = ByteBuffer.allocate(BUFF_SIZE);  
        /* 
         *  一旦调用read()来告知FileChannel向ByteBuffer存储字节,就必须调用缓冲器上的flip(),让它做好让别人读取字节的准备。 
         * 如果打算使用缓冲器执行进一步read()操作,也必须得使用clear()来为每个read()做好准备,如下copy文件 
         */  
        fc.read(buff);  
        buff.flip();  
        while (buff.hasRemaining()) {  
            System.out.print((char) buff.get());  
        }  
        fc.close();  
    }  
}

   这里展示的任何流类,getChannel()将会产生一个FileChanel。通道是一个相当基础的东西:可以向它传送用于读写的ByteBuffer,并且可以锁定文件的某些区域用于独占式访问。

public class ChannelCopy {  
    private static final int CAPACITY = 1024;  
    public static void main(String[] args) throws IOException {  
        if(args.length != 2)System.exit(1);  
        FileChannel   
            in = new FileInputStream(args[0]).getChannel(),  
            out = new FileOutputStream(args[1]).getChannel();  
        ByteBuffer buff = java.nio.ByteBuffer.allocate(CAPACITY);  
        while(in.read(buff) != -1) {  
            buff.flip();  
            out.write(buff);  
            buff.clear();  
        }  
        out.close();  
        in.close();  
    }  
}

  然而,这个程序并不是处理此类操作的理想方式,特殊方法transferTo()和transferFrom()则允许我们将一个通道和另一个通道直接相连

public class ChannelCopy {  
    private static final int CAPACITY = 1024;  
    public static void main(String[] args) throws IOException {  
        if(args.length != 2)System.exit(1);  
        FileChannel   
            in = new FileInputStream(args[0]).getChannel(),  
            out = new FileOutputStream(args[1]).getChannel();  
        in.transferTo(0,in.size(),out);//or out.transferFrom(in,0,in.size);  
        out.close();  
        in.close();  
    }  
}

转换数据

     在GetChannel.java中,必须每次只读取一个字节的数据,然后将每个byte类型强制转换成char类型。而java.nio.CharBuffer有一个toString方法:返回一个包含缓冲器中所有字符的字符串。ByteBuffer可以看做是具有asCharBuffer()方法的CharBuffer。

public class BufferToText {  
    private static final int CAPACITY = 1024;  
    public static void main(String[] args) {  
        try{  
            var fc = new FileOutputStream("data.txt").getChannel();  
            fc.write(ByteBuffer.wrap("来来,我是一个香蕉。。".getBytes()));  
            fc.close();  
              
            fc = new FileInputStream("data.txt").getChannel();  
            var buff = ByteBuffer.allocate(CAPACITY);  
            fc.read(buff);  
            buff.flip();  
            System.out.println(buff.asCharBuffer());  
            buff.rewind();//返回到数据开始部分  
            var encoding = System.getProperty("file.encoding");//获取默认字符集  
            System.out.println("使用" + encoding + "解码结果: " + Charset.forName(encoding).decode(buff));  
              
            fc = new FileOutputStream("data.txt").getChannel();  
            fc.write(ByteBuffer.wrap("来来,我是一个榴莲".getBytes("UTF-8")));  
            fc.close();  
              
            fc = new FileInputStream("data.txt").getChannel();  
            buff.clear();  
            fc.read(buff);  
            buff.flip();  
            System.out.println(buff.asCharBuffer());  
              
            fc = new FileOutputStream("data.txt").getChannel();  
            buff = ByteBuffer.allocate(24);  
            buff.asCharBuffer().put("如花貌美容颜");  
            fc.write(buff);  
            fc.close();  
              
            fc = new FileInputStream("data.txt").getChannel();  
            buff.clear();  
            fc.read(buff);  
            buff.flip();  
            System.out.println(buff.asCharBuffer());  
        }catch (IOException e) {  
            e.printStackTrace();  
        }  
    }  
}

     缓冲器容纳的是普通的字节,为了把它们转换成字符,我们要么在输入它们的时候对其进行编码,要么在将其从缓冲器输出时对他们进行解码(可以使用java.nio.charset.Charset类实现这些功能).  

获取基本类型

      尽管ByteBuffer只能保存字节类型数据,但是它可以从其所容纳的字节中产生出各种不同的基本类型值的方法

  • CharBuffer  ====>  asCharBuffer();

  • ShorBuffer  ====>  asShortBuffer();

  • IntBuffer   ====>  asIntBuffer();

  • LongBuffer  ====>  asLongBuffer();

  • FloatBuffer  ====>  asFloatBuffer();

  • DoubleBuffer  ====>  asDoubleBuffer();

这些Buffer覆盖了你能通过IO发送的基本数据类型:byte, short, int, long, float, double 和 char。

注意:使用shortBuffer的put()方法时,需要进行类型转换。

视图缓冲器

  视图缓冲器(view buffer)可以让我们通过某个特定的基本数据类型的视窗查看其底层的ByteBuffer。ByteBuffer依然是实际存储数据的地方,“支持”着前面的视图,因此对视图的任何修改都会映射成为对ByteBuffer中数据的修改。

  一旦底层的ByteBuffer通过视图缓冲器填满了整数或其他基本类型时,就可以直接被写到通道中。正像从通道中读取那样容易,然后使用视图缓冲器可以把任何数据都转化为某一特定的基本类型。

用缓冲器操作数据

      下图阐明了nio类之间的关系,便于我们理解怎么移动和转换数据。如果想把一个字节数组写到文件中去,那么就应该使用ByteBuffer.wrap()方法把字节数组包装起来,然后用getChannel()方法在FileOutputStream上打开一个通道,接着将来自于ByteBuffer的数据写到FileChannel。

注意:BytBuffer是将数据移进移出通道的唯一方式,并且只能创建一个独立的基本类型缓冲器,或者使用as方法从ByteBuffer中获得。也就是说,不能把基本类型的缓冲器转换成ByteBuffer。然而,我们可以经由视图缓冲器将基本类型数据移进移出Bytebuffer,所以也就不是什么真正的限制了。

java的文件IO举例分析
 

缓冲器细节

      Buffer有数据和可以高效地访问及操作这些数据的四个索引组成,mark(标记)、position(位置)、limit(界限)和capacity(容量)。下面是用于设置和复位索引以及查询它们的值的方法。

capacity()返回缓冲区容量
clear()清空缓存区,将position设置为0,limit设置为容量。可以调用此方法覆写缓冲区
flip()将limit设置为position,position置位0.此方法用于准备从缓冲区读取已经写入的数据
limit()返回limit的值
limit(int lim)返回limit的值
mark()将mark设置为position
position()返回position值
position(int pos)返回position值
remaining()返回(limit - position)
hasRemaining()是否有介于position和limit之间的元素

   在缓冲器中插入和提取数据的方法会更新这些索引,用于反应所发生的变化。

内存映射文件

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

public class LargeMappedFiles {  
    static int length = 0x8FFFFFF; // 128 MB  
    public static void main(String[] args) throws Exception {  
        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');  
        }  
        print("Finished writing");  
        for (int i = length / 2; i < length / 2 + 6; i++) {  
            printnb((char) out.get(i));  
        }  
    }  
}

     使用map()产生MappedByteBuffer,一个特殊类型的直接缓冲器,注意:必须指定映射文件初始位置和映射区域长度,意味着可以映射某个大文件的较小部分。

  MappedByteBuffer由ByteBuffer继承而来,因此它具有ByteBuffer的所有方法。

  前面程序创建的文件为128MB,这可能比操作系统所允许一次载入内存的空间大。但似乎我们可以一次访问到整个文件。因为只有一部分文件放入了内存,其他被交换了出去。用这种方式,很大的文件(可达2GB)也可以很容易地修改。注意底层操作系统的文件映射工具是用来最大化地提高性能的。

     映射文件的所有输出必须使用RandomAccessFile。

文件加锁

      JDK 1.4引入了文件加锁机制,允许同步访问某个作为共享资源的文件。文件锁对其他的操作系统进程是可见的,因为Java的文件加锁直接映射到本地操作系统的加锁工具。

public class FileLocking {  
  
  public static void main(String[] args) throws Exception {  
  
    FileOutputStream fos= new FileOutputStream("file.txt");  
    FileLock fl = fos.getChannel().tryLock();  
    if(fl != null) {  
      System.out.println("Locked File");  
      TimeUnit.MILLISECONDS.sleep(100);  
      fl.release();  
      System.out.println("Released Lock");  
    }  
    fos.close();  
  }  
} /* 
Locked File 
Released Lock 
*/

     通过对FileChannel调用tryLock()或lock(),就可以获得整个文件的FileLock。(SocketChannel、DatagramChannel和ServerSocketChannel不需要加锁,因为它们是从单进程实体继承而来,通常不在两个进程之间共享socket。)tryLock()是非阻塞式的,它设法获取锁,如果不能得到(当其他一些进程已经持有相同的锁,并且不共享时),它将直接从方法调用返回。lock()是阻塞式的,它要阻塞进程直至锁可以获得,或调用lock()的线程中断,或调用lock()的通道关闭。使用FileLock.release()可以释放锁。  

  也可以使用如下方法对文件的一部分上锁:

tryLock(long position, long size, boolean shared)

或者

lock(long position, long size, boolean shared)

   其中加锁区域由size-position决定,第三个参数指定是否共享锁。  

  尽管无参的加锁方法将根据文件尺寸变化而变化,但是具有固定尺寸的锁不随文件尺寸变化而变化。如果你获得了某一区域(从position到position+size)上的锁,当文件增大超出position+size时,那么在position+size之外的部分不会被锁定。无参数的加锁方法会对整个文件进行加锁,甚至文件变大后也是如此。

  对于独占锁或者共享锁的支持必须有底层的操作系统提供。如操作系统不支持共享锁并未每一个请求都创建锁,那么它就会使用独占锁。锁的类型可以通过FileLock.isShared()进行查询。

对映射文件的部分加锁

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

public class LockingMappedFIles {  
    static final int LENGTH = 0x8FFFFFF;//128M  
    static FileChannel fc;  
    public static void main(String[] args) throws IOException {  
        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, 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;  
        public LockAndModify(ByteBuffer buff, int start, int end) {  
            this.start = start;  
            this.end = end;  
            buff.limit(end);  
            buff.position(start);  
            this.buff = buff.slice();  
        }  
        @Override  
        public void run() {  
            try {  
                FileLock lock = fc.lock(start,end,false);  
                System.out.println("Locked:" + start + "to" + end);  
                while(buff.position() < buff.limit() - 1)  
                    buff.put((byte)(buff.get() + 1));  
                lock.release();  
                System.out.println("Released:" + start + "to" + end);  
            } catch (IOException e) {  
                  
                e.printStackTrace();  
            }  
        }  
          
    }  
}

       线程类LockAndModify创建了缓冲区和利于修改的slice(),然后再run中,获得文件通道上的锁(不能获得缓冲器上的锁,只能获得通道上的)。lock()类似于获得一个对象的线程锁----现在处在临界区,即对该部分文件具有独占访问权。  

  如果有java虚拟机,它会自动释放锁,或者关闭加锁的通道。不过也可以像程序中那样,显式地为FileLock对象调用release()来释放。

感谢各位的阅读,以上就是“java的文件IO举例分析”的内容了,经过本文的学习后,相信大家对java的文件IO举例分析这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是创新互联,小编将为大家推送更多相关知识点的文章,欢迎关注!


本文标题:java的文件IO举例分析
新闻来源:http://hbruida.cn/article/jcsdoc.html