小师妹学javaio之:文件读取那些事

flydean程序那些事      2022-05-03     690

关键词:

简介

小师妹最新对java IO中的reader和stream产生了一点点困惑,不知道到底该用哪一个才对,怎么读取文件才是正确的姿势呢?今天F师兄现场为她解答。

字符和字节

小师妹最近很迷糊:F师兄,上次你讲到IO的读取分为两大类,分别是Reader,InputStream,这两大类有什么区别吗?为什么我看到有些类即是Reader又是Stream?比如:InputStreamReader?

小师妹,你知道哲学家的终极三问吗?你是谁?从哪里来?到哪里去?

F师兄,你是不是迷糊了,我在问你java,你扯什么哲学。

小师妹,其实吧,哲学是一切学问的基础,你知道科学原理的英文怎么翻译吗?the philosophy of science,科学的原理就是哲学。

你看计算机中代码的本质是什么?代码的本质就是0和1组成的一串长长的二进制数,这么多二进制数组合起来就成了计算机中的代码,也就是JVM可以识别可以运行的二进制代码。

更多内容请访问www.flydean.com

小师妹一脸崇拜:F师兄说的好像很有道理,但是这和Reader,InputStream有什么关系呢?

别急,冥冥中自有定数,先问你一个问题,java中存储的最小单位是什么?

小师妹:容我想想,java中最小的应该是boolean,true和false正好和二进制1,0对应。

对了一半,虽然boolean也是java中存储的最小单位,但是它需要占用一个字节Byte的空间。java中最小的存储单位其实是字节Byte。不信的话可以用之前我介绍的JOL工具来验证一下:

[main] INFO com.flydean.JolUsage - java.lang.Boolean object internals:
 OFFSET  SIZE      TYPE DESCRIPTION                               VALUE
      0    12           (object header)                           N/A
     12     1   boolean Boolean.value                             N/A
     13     3           (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total

上面是装箱过后的Boolean,可以看到虽然Boolean最后占用16bytes,但是里面的boolean只有1byte。

byte翻译成中文就是字节,字节是java中存储的基本单位。

有了字节,我们就可以解释字符了,字符就是由字节组成的,根据编码方式的不同,字符可以有1个,2个或者多个字节组成。我们人类可以肉眼识别的汉字呀,英文什么的都可以看做是字符。

而Reader就是按照一定编码格式读取的字符,而InputStream就是直接读取的更加底层的字节。

小师妹:我懂了,如果是文本文件我们就可以用Reader,非文本文件我们就可以用InputStream。

孺子可教,小师妹进步的很快。

按字符读取的方式

小师妹,接下来F师兄给你讲下按字符读取文件的几种方式,第一种就是使用FileReader来读取File,但是FileReader本身并没有提供任何读取数据的方法,想要真正的读取数据,我们还是要用到BufferedReader来连接FileReader,BufferedReader提供了读取的缓存,可以一次读取一行:

public void withFileReader() throws IOException {
        File file = new File("src/main/resources/www.flydean.com");

        try (FileReader fr = new FileReader(file); BufferedReader br = new BufferedReader(fr)) {
            String line;
            while ((line = br.readLine()) != null) {
                if (line.contains("www.flydean.com")) {
                    log.info(line);
                }
            }
        }
    }

每次读取一行,可以把这些行连起来就组成了stream,通过Files.lines,我们获取到了一个stream,在stream中我们就可以使用lambda表达式来读取文件了,这是谓第二种方式:

public void withStream() throws IOException {
        Path filePath = Paths.get("src/main/resources", "www.flydean.com");
        try (Stream<String> lines = Files.lines(filePath))
        {
            List<String> filteredLines = lines.filter(s -> s.contains("www.flydean.com"))
                    .collect(Collectors.toList());
            filteredLines.forEach(log::info);
        }
    }

第三种其实并不常用,但是师兄也想教给你。这一种方式就是用工具类中的Scanner。通过Scanner可以通过换行符来分割文件,用起来也不错:

public void withScanner() throws FileNotFoundException {
        FileInputStream fin = new FileInputStream(new File("src/main/resources/www.flydean.com"));
        Scanner scanner = new Scanner(fin,"UTF-8").useDelimiter("
");
        String theString = scanner.hasNext() ? scanner.next() : "";
        log.info(theString);
        scanner.close();
    }

按字节读取的方式

小师妹听得很满足,连忙催促我:F师兄,字符读取方式我都懂了,快将字节读取吧。

我点了点头,小师妹,哲学的本质还记得吗?字节就是java存储的本质。掌握到本质才能勘破一切虚伪。

还记得之前讲过的Files工具类吗?这个工具类提供了很多文件操作相关的方法,其中就有读取所有bytes的方法,小师妹要注意了,这里是一次性读取所有的字节!一定要慎用,只可用于文件较少的场景,切记切记。

public void readBytes() throws IOException {
        Path path = Paths.get("src/main/resources/www.flydean.com");
        byte[] data = Files.readAllBytes(path);
        log.info("{}",data);
    }

如果是比较大的文件,那么可以使用FileInputStream来一次读取一定数量的bytes:

public void readWithStream() throws IOException {
        File file = new File("src/main/resources/www.flydean.com");
        byte[] bFile = new byte[(int) file.length()];
        try(FileInputStream fileInputStream  = new FileInputStream(file))
        {
            fileInputStream.read(bFile);
            for (int i = 0; i < bFile.length; i++) {
                log.info("{}",bFile[i]);
            }
        }
    }

Stream读取都是一个字节一个字节来读的,这样做会比较慢,我们使用NIO中的FileChannel和ByteBuffer来加快一些读取速度:

public void readWithBlock() throws IOException {
        try (RandomAccessFile aFile = new RandomAccessFile("src/main/resources/www.flydean.com", "r");
             FileChannel inChannel = aFile.getChannel();) {
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            while (inChannel.read(buffer) > 0) {
                buffer.flip();
                for (int i = 0; i < buffer.limit(); i++) {
                    log.info("{}", buffer.get());
                }
                buffer.clear();
            }
        }
    }

小师妹:如果是非常非常大的文件的读取,有没有更快的方法呢?

当然有,记得上次我们讲过的虚拟地址空间的映射吧:

技术图片

我们可以直接将用户的地址空间和系统的地址空间同时map到同一个虚拟地址内存中,这样就免除了拷贝带来的性能开销:

public void copyWithMap() throws IOException{
        try (RandomAccessFile aFile = new RandomAccessFile("src/main/resources/www.flydean.com", "r");
             FileChannel inChannel = aFile.getChannel()) {
             MappedByteBuffer buffer = inChannel.map(FileChannel.MapMode.READ_ONLY, 0, inChannel.size());
             buffer.load();
            for (int i = 0; i < buffer.limit(); i++)
            {
                log.info("{}", buffer.get());
            }
            buffer.clear();
        }
    }

寻找出错的行数

小师妹:好赞!F师兄你讲得真好,小师妹我还有一个问题:最近在做文件解析,有些文件格式不规范,解析到一半就解析失败了,但是也没有个错误提示到底错在哪一行,很难定位问题呀,有没有什么好的解决办法?

看看天色已经不早了,师兄就再教你一个方法,java中有一个类叫做LineNumberReader,使用它来读取文件可以打印出行号,是不是就满足了你的需求:

public void useLineNumberReader() throws IOException {
        try(LineNumberReader lineNumberReader = new LineNumberReader(new FileReader("src/main/resources/www.flydean.com")))
        {
            //输出初始行数
            log.info("Line {}" , lineNumberReader.getLineNumber());
            //重置行数
            lineNumberReader.setLineNumber(2);
            //获取现有行数
            log.info("Line {} ", lineNumberReader.getLineNumber());
            //读取所有文件内容
            String line = null;
            while ((line = lineNumberReader.readLine()) != null)
            {
                log.info("Line {} is : {}" , lineNumberReader.getLineNumber() , line);
            }
        }
    }

总结

今天给小师妹讲解了字符流和字节流,还讲解了文件读取的基本方法,不虚此行。

本文的例子https://github.com/ddean2009/learn-java-io-nio

本文作者:flydean程序那些事

本文链接:http://www.flydean.com/io-file-reader/

本文来源:flydean的博客

欢迎关注我的公众号:程序那些事,更多精彩等着您!

小师妹学javaio之:trywith和它的底层原理

...用trywithresourcetrywithresource的原理自定义resource总结简介小师妹是个java初学者,最近正在学习使用javaIO,作为大师兄的我自然要给她最给力的支持了。一起来看看她都遇到了什么问题和问题是怎么被解决的吧。IO关闭的问题这一天... 查看详情

小师妹学javaio之:文件编码和字符集unicode

...件编码解决Properties中的乱码真.终极解决办法总结简介小师妹一时兴起,使用了一项从来都没用过的新技能,没想却出现了一个无法解决的问题。把大象装进冰箱到底有几步?乱码的问题又是怎么解决的?快来跟F师兄一起看看吧... 查看详情

小师妹学javaio之:文件编码和字符集unicode

...件编码解决Properties中的乱码真.终极解决办法总结简介小师妹一时兴起,使用了一项从来都没用过的新技能,没想却出现了一个无法解决的问题。把大象装进冰箱到底有几步?乱码的问题又是怎么解决的?快来跟F师兄一起看看吧... 查看详情

小师妹学jvm之:jit中的printcompilation(代码片段)

...释了LogCompilation日志文件中的内容定义。今天我们再和小师妹一起学习LogCompilation的姊妹篇PrintCompilation,看看都有什么妙用吧。PrintCompilati 查看详情

小师妹学jvm之:jvm的架构和执行过程(代码片段)

...个不同操作系统中运行的机器代码并运行。今天我们和小师妹一起走进java的核心JVM,领略java在设计上的哲学。JVM是一种标准小师妹:F师兄,经常听到有人说hotspotVM,这 查看详情

小师妹学jvm之:逃逸分析和tlab(代码片段)

...多线程的环境中,如何提升内存的分配效率呢?快来跟小师妹一起学习TLAB技术吧。逃逸分析和栈上分配小师妹:F师兄,从前大家都说对象是在堆中分配的,然后我就 查看详情

小师妹学jvm之:jit中的printassembly(代码片段)

...近的地方来观看JVM的运行原理:Assembly。使用PrintAssembly小师妹:F师兄,上次你给我介绍了java中的字节码,还有JIT中的Log 查看详情

小师妹学jvm之:cacheline对代码性能的影响(代码片段)

...sembly来理解我们之前不能理解的问题。一个奇怪的现象小师妹:F师兄,之前你讲了那么多JVM中JIT在编译中的性能优化,讲真的,在工作中我们真的需要知道这些东西吗?知道这些东西对我们的工作有什么好处吗 查看详情

javaio读取文件之二

packagecom.lf.iopreoject;importjava.io.BufferedReader;importjava.io.File;importjava.io.FileInputStream;importjava.io.FileReader;importjava.io.InputStreamReader;publicclassTestCode{publicstaticvoidmain 查看详情

javaio流之详细总结

什么是io流?分为两种:输入流:可以从文件中读取到程序,从源数据源读取到程序,叫做输入流。输出流:可以从程序中读取到文件,从程序写,使用输出流,写入到文件中。叫做输出流。 使用File操作文件或目录属性:&nb... 查看详情

初学javaio之使用fileinputstream和filereader读取文件四十一

importjava.io.*;publicclassFileInputStreamTest{ publicstaticvoidmain(String[]args)throwsIOException { //创建字节输入流 FileInputStreamfis=newFileInputStream("FileInputStreamTest.java"); //创建一个长度为1024的竹筒by 查看详情

utf-8那些事

...题,但光在程序中支持UTF-8有时还是不够的,环境及输入文件的问题也很突出。下面记录几个问题及解决方法:1.JAVA读取properties文件时第一个属性总是读取不到发现这个很奇怪的现象,用vim查看fileenoding是UTF-8,没有问题。在文... 查看详情

javaio流之字符缓冲流

字符流:1、加入字符缓存流,增强读取功能(readLine)2、更高效的读取数据BufferedReader从字符输入流读取文本,缓冲各个字符,从而实现字符、数组和行的高效读取。   FileReader:内部使用InputStreamReader,解码过程,byt... 查看详情

form表单的file文件上传那些事(代码片段)

fileAPI客户端直接访问用户计算机的文件,2000以前,在表单添加了<inputtype="file">字段.fileAPI是为給web开发提供安全的方式,以便在客户端更好访问用户的文件,字段的基础上加了一些直接访问文件信息的接口-files集合.与fileReader类... 查看详情

js之dom那些事

  DOM是DocumentObjectModel(文档对象模型)的缩写。DOM分为核心DOM、XML DOM、HTML DOM,我们接触的主要是HTML DOM,HTMLDOM定义了所有HTML元素的对象和属性,以及访问它们的方法。换言之,HTMLDOM是关于如何获取、修改、添加... 查看详情

关于css的那些事

1.在style标签或者css文件中定义了多个class,有两个以上的class被添加到同一html标签上时,如果这些class具有重复的属性,位于文件后面定义的class的属性,会覆盖前面class定义的属性。言简之就是,样式覆盖与在标签中class属性值... 查看详情

javaio流之字节到字符流的转换流

一.转换流的概念Java使用Unicode编码,而读取的文件所使用的编码不一是Unicode编码,所以Java需要对字符数据进行解码(其他编码到Unicode编码的转换)要将Java中的文本写出到目标文件中,则需要将Unicode编码转换为目标文件的编码,... 查看详情

java网络编程系列之javaio的“今生”:nio非阻塞模型(代码片段)

java网络编程系列之JavaIO的“今生”:NIO非阻塞模型BIO中的阻塞非阻塞式NIOChannel与Buffer剖析Buffer向Buffer中写入数据剖析channel几个重要的channel多方法实现本地文件拷贝字节输入流拷贝文件字节缓冲流拷贝文件FileChannel拷贝文件... 查看详情