说到读写文件,不得不提效率很高的NIO

那么先回顾下NIO读写文件的基本操作

直接上java代码

public void nioTest() {

        String path = "D:\\\\BaiduYunDownload\\\\access_2013_05_30.log";
        File file = new File(path);
        FileInputStream fis = null;
        try {
            fis = new FileInputStream(file);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        FileChannel fileChannel = fis.getChannel();
        int capacity = 1024;
        ByteBuffer buffer = ByteBuffer.allocate(capacity);
        String outputPath = "D:\\\\BaiduYunDownload\\\\access.log";
        File outputFile = new File(outputPath);
        File parentFile = outputFile.getParentFile();
        if(!parentFile.exists()) file.mkdirs();
        try {
            outputFile.createNewFile();
        } catch (IOException e) {
            e.printStackTrace();
        }
        FileOutputStream fos = null;
        try {
            fos = new FileOutputStream(outputFile);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        FileChannel outputFileChannel = fos.getChannel();
        try {
            while(fileChannel.read(buffer) != -1) {
                buffer.flip();
                outputFileChannel.write(buffer);
                buffer.clear();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                fis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                fos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                fileChannel.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                outputFileChannel.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

大体思路

分别创建读文件流和写文件流的通道,设定缓冲区大小,读取字符到缓冲区。此时切换“”读“”模式到“写”模式,写入缓冲区内的字符到写文件通道,然后清空缓存区,循环上述过程。

 

特殊情况:当文本中出现中文时,可能导致因设置的capacity而无法一次性读取完一个汉字(只读了一半),进而出现中文乱码现象。按理来说是这样的,但是在jdk1.8环境下测试多个测试案例未发现中文乱码现象。假设出现中文乱码,可尝试以下代码:

Charset charset = Charset.forName("GBK");
charset.decode(buffer);

如果还不行,那么需要通过拼接汉字来解决中文乱码问题。

 

为什么NIO读取文件速度快?

因为它是非阻塞的,读取数据时,如果内核IO操作完成,则读取数据并返回;如果未完成,则返回失败。

而阻塞方式的读取数据,如果数据未准备好,则重新读取数据,因此比较耗时。

 

拓展:NIO不仅是非阻塞的,还是同步的。因为客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询IO,当有IO请求时,启动一个线程处理。而对于异步方式,当有IO请求,需要等待通知才启动线程处理。

 

clear() compact()的区别

clear 清除缓冲区的内容,compact清除缓冲区中已经读取过的内容。

 

为什么使用传统方式:缓冲流读取hdfs文件?

读取hdfs文件需要用FSDataOutputStream和FSDataInputStream,而这两种流均不支持创建通道,因此无法使用NIO完成输入输出。

 

缓冲流读写hdfs文件代码(按行读)

public void readAndWriteHdfs() throws IOException {

        FileSystem fileSystem = getFileSystem();
        Path inputPath = new Path(url+"/access.log");
        FSDataInputStream fsDataInputStream = null;
        try {
            fsDataInputStream = fileSystem.open(inputPath);
        } catch (IOException e) {
            e.printStackTrace();
        }
        Reader reader = new InputStreamReader(fsDataInputStream,"UTF-8");
        BufferedReader bufferedReader = new BufferedReader(reader);
        FSDataOutputStream fsDataOutputStream = fileSystem.create(new Path(url+"/test.log"));
        Writer writer = new OutputStreamWriter(fsDataOutputStream,"UTF-8");
        BufferedWriter bufferedWriter = new BufferedWriter(writer);
        String content = null;
        while((content = bufferedReader.readLine()) != null) {
            byte[] bytes = content.getBytes("UTF-8");
            bufferedWriter.write(new String(bytes)+"\n");
        }
        bufferedWriter.close();
        writer.close();
        fsDataOutputStream.close();
        bufferedReader.close();
        reader.close();
        fsDataInputStream.close();
        fileSystem.close();
    }

按行读需要在读取的每行末尾添加换行符号,unix操作系统添加"\n",windows操作系统添加"\r\n"。

如果不按行读,而逐个读取字节,可能会出现中文乱码现象,暂时没有找到解决方案。