热门标签 | HotTags
当前位置:  开发笔记 > 开发工具 > 正文

Java中的字节流文件读取教程(一)

这篇文章主要给大家介绍了关于Java中字节流文件读取的相关资料,文中通过示例代码介绍的非常详细,对大家学习或者使用java具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

前言

上篇文章我们介绍了抽象化磁盘文件的 File 类型,它仅仅用于抽象化描述一个磁盘文件或目录,却不具备访问和修改一个文件内容的能力。

Java 的 IO 流就是用于读写文件内容的一种设计,它能完成将磁盘文件内容输出到内存或者是将内存数据输出到磁盘文件的数据传输工作。

Java IO 流的设计并不是完美的,设计了大量的类,增加了我们对于 IO 流的理解,但无外乎为两大类,一类是针对二进制文件的字节流,另一类是针对文本文件的字符流。而本篇我们就先来学习有关字节流的相关类型的原理以及使用场景等细节,主要涉及的具体流类型如下:

基类字节流 Input/OutputStream

InputStream 和 OutputStream 分别作为读字节流和写字节流的基类,所有字节相关的流都必然继承自他们中任意一个,而它们本身作为一个抽象类,也定义了最基本的读写操作,我们一起来看看:

以 InputStream 为例:

public abstract int read() throws IOException;

这是一个抽象的方法,并没有提供默认实现,要求子类必须实现。而这个方法的作用就是为你返回当前文件的下一个字节。

当然,你也会发现这个方法的返回值是使用的整型类型「int」来接收的,为什么不用「byte」?

首先,read 方法返回的值一定是一个八位的二进制,而一个八位的二进制可以取值的值区间为:「0000 0000,1111 1111」,也就是范围 [-128,127]。

read 方法同时又规定当读取到文件的末尾,即文件没有下一个字节供读取了,将返回值 -1 。所以如果使用 byte 作为返回值类型,那么当方法返回一个 -1 ,我们该判定这是文件中数据内容,还是流的末尾呢?

而 int 类型占四个字节,高位的三个字节全部为 0,我们只使用它的最低位字节,当遇到流结尾标志时,返回四个字节表示的 -1(32 个 1),这就自然的和表示数据的值 -1(24 个 0 + 8 个 1)区别开来了。

接下来也是一个 read 方法,但是 InputStream 提供默认实现:

public int read(byte b[]) throws IOException {
 return read(b, 0, b.length);
}

public int read(byte b[], int off, int len) throws IOException{
 //为了不使篇幅过长,方法体大家可自行查看 jdk 源码
}

这两个方法本质上是一样的,第一个方法是第二个方法的特殊形态,它允许传入一个字节数组,并要求程序将文件中读到的字节从数组索引位置 0 开始填充,供填充数组长度个字节数。

而第二个方法更加宽泛一点,它允许你指定起始位置和字节总数。

InputStream 中还有其他几个方法,基本都没怎么具体实现,留待子类实现,我们简单看看。

  • public long skip(long n):跳过 n 个字节,返回实际跳过的字节数
  • public void close():关闭流并释放对应的资源
  • public synchronized void mark(int readlimit)
  • public synchronized void reset()
  • public boolean markSupported()

mark 方法会在当前流读取位置打上一个标志,reset 方法即重置读取指针到该标志处。

事实上,文件读取是不可能重置回头读取的,而一般都是将标志位置到重置点之间所有的字节临时保存了,当调用 reset 方法时,其实是从保存的临时字节集合进行重复读取,所以 readlimit 用于限制最大缓存容量。

而 markSupported 方法则用于确定当前流是否支持这种「回退式」读取操作。

OutputStream 和 InputStream 是类似的,只不过一个是写一个是读,此处我们不再赘述了。

文件字节流 FileInput/OutputStream

我们依然着重点于 FileInputStream,而 FileOutputStream 是类似的。

首先 FileInputStream 有以下几种构造器实例化一个对象:

public FileInputStream(String name) throws FileNotFoundException {
 this(name != null ? new File(name) : null);
}
public FileInputStream(File file) throws FileNotFoundException {
 String name = (file != null ? file.getPath() : null);
 SecurityManager security = System.getSecurityManager();
 if (security != null) {
 security.checkRead(name);
 }
 if (name == null) {
 throw new NullPointerException();
 }
 if (file.isInvalid()) {
 throw new FileNotFoundException("Invalid file path");
 }
 fd = new FileDescriptor();
 fd.attach(this);
 path = name;
 open(name);
}

这两个构造器本质上也是一样的,前者是后者的特殊形态。其实你别看后者的方法体一大堆代码,大部分都只是在做安全校验,核心的就是一个 open 方法,用于打开一个文件。

主要是这两种构造器,如果文件不存在或者文件路径和名称不合法,都将抛出 FileNotFoundException 异常。

记得我们说过,基类 InputStream 中有一个抽象方法 read 要求所有子类进行实现,而 FileInputStream 使用本地方法进行了实现:

public int read() throws IOException {
 return read0();
}

private native int read0() throws IOException;

这个 read0 的具体实现我们暂时无从探究,但是你必须明确的是,这个 read 方法的作用,它用于返回流中下一个字节,返回 -1 说明读取到文件末尾,已无字节可读。

除此之外,FileInputStream 中还有一些其他的读取相关方法,但大多采用了本地方法进行了实现,此处我们简单看看:

  • public int read(byte b[]):读取 b.length() 个长度的字节到数组中
  • public int read(byte b[], int off, int len):读取指定长度的字节数到数组中
  • public native long skip(long n):跳过 n 的字节进行读取
  • public void close():释放流资源

FileInputStream 的内部方法基本就这么些,还有一些高级的复杂的,我们暂时用不到,以后再进行学习,下面我们简单看一个文件读取的例子:

public static void main(String[] args) throws IOException {
 FileInputStream input = new FileInputStream("C:\\Users\\yanga\\Desktop\\test.txt");
 byte[] buffer = new byte[1024];
 int len = input.read(buffer);
 String str = new String(buffer);
 System.out.println(str);
 System.out.println(len);
 input.close();
}

输出结果很简单,会打印出我们 test 文件中的内容和实际读出的字节数,但细心的同学就会发现了,你怎么就能保证 test 文件中内容不会超过 1024 个字节呢?

为了能够完整的读出文件中的内容,一种解决办法是:将 buffer 定义的足够大,以期望尽可能的能够存储下文件中的所有内容。

这种方法显然是不可取的,因为我们根本不可能实现知道待读文件的实际大小,一味的创建过大的字节数组其本身也是一种很差劲的方案。

第二种方式就是使用我们的动态字节数组流,它可以动态调整内部字节数组的大小,保证适当的容量,这一点我们后文中将详细介绍。

关于 FileOutputStream,还需要强调一点的是它的构造器,其中有以下两个构造器:

public FileOutputStream(String name, boolean append)

public FileOutputStream(File file, boolean append)

参数 append 指明了,此流的写入操作是覆盖还是追加,true 表示追加,false 表示覆盖。

字节数组流 ByteArrayInput/OutputStream

所谓的「字节数组流」就是围绕一个字节数组运作的流,它并不像其他流一样,针对文件进行流的读写操作。

字节数组流虽然并不是基于文件的流,但却依然是一个很重要的流,因为它内部封装的字节数组并不是固定的,而是动态可扩容的,往往基于某些场景下,非常合适。

ByteArrayInputStream 是读字节数组流,可以通过以下构造函数被实例化:

protected byte buf[];
protected int pos;
protected int count;

public ByteArrayInputStream(byte buf[]) {
 this.buf = buf;
 this.pos = 0;
 this.count = buf.length;
}

public ByteArrayInputStream(byte buf[], int offset, int length)

buf 就是被封装在 ByteArrayInputStream 内部的一个字节数组,ByteArrayInputStream 的所有读操作都是围绕着它进行的。

所以,实例化一个 ByteArrayInputStream 对象的时候,至少传入一个目标字节数组的。

pos 属性用于记录当前流读取的位置,count 记录了目标字节数组最后一个有效字节索引的后一个位置。

理解了这一点,有关它各种的 read 方法就不难了:

//读取下一个字节
public synchronized int read() {
 return (pos 

除此之外,ByteArrayInputStream 还非常简单的实现了「重复读取」操作。

public void mark(int readAheadLimit) {
 mark = pos;
}

public synchronized void reset() {
 pos = mark;
}

因为 ByteArrayInputStream 是基于字节数组的,所有重复读取操作的实现就比较容易了,基于索引实现就可以了。

ByteArrayOutputStream 是写的字节数组流,很多实现还是很有自己的特点的,我们一起来看看。

首先,这两个属性是必须的:

protected byte buf[];

//这里的 count 表示的是 buf 中有效字节个个数
protected int count;

构造器:

public ByteArrayOutputStream() {
 this(32);
}
 
public ByteArrayOutputStream(int size) {
 if (size <0) {
 throw new IllegalArgumentException("Negative initial size: "+ size);
 }
 buf = new byte[size];
}

构造器的核心任务是,初始化内部的字节数组 buf,允许你传入 size 显式限制初始化的字节数组大小,否则将默认长度 32 。

从外部向 ByteArrayOutputStream 写内容:

public synchronized void write(int b) {
 ensureCapacity(count + 1);
 buf[count] = (byte) b;
 count += 1;
}

public synchronized void write(byte b[], int off, int len){
 if ((off <0) || (off > b.length) || (len <0) ||
 ((off + len) - b.length > 0)) {
 throw new IndexOutOfBoundsException();
 }
 ensureCapacity(count + len);
 System.arraycopy(b, off, buf, count, len);
 count += len;
}

看到没有,所有写操作的第一步都是 ensureCapacity 方法的调用,目的是为了确保当前流内的字节数组能容纳本次写操作。

而这个方法也很有意思了,如果计算后发现,内部的 buf 不能够支持本次写操作,则会调用 grow 方法做一次扩容。扩容的原理和 ArrayList 的实现是类似的,扩大为原来的两倍容量。

除此之外,ByteArrayOutputStream 还有一个 writeTo 方法:

public synchronized void writeTo(OutputStream out) throws IOException {
 out.write(buf, 0, count);
}

将我们内部封装的字节数组写到某个输出流当中。

剩余的一些方法也很常用:

  • public synchronized byte toByteArray()[]:返回内部封装的字节数组
  • public synchronized int size():返回 buf 的有效字节数
  • public synchronized String toString():返回该数组对应的字符串形式

注意到,这两个流虽然被称作「流」,但是它们本质上并没有像真正的流一样去分配一些资源,所以我们无需调用它的 close 方法,调了也没用(人家官方说了,has no effect)。

测试的案例就不放出来了,等会我会上传本篇文章用到的所有代码案例,大家自行选择下载即可。

为了控制篇幅,余下流的学习,放在下篇文章。

文章中的所有代码、图片、文件都云存储在我的 GitHub 上:

(https://github.com/SingleYam/overview_java)

大家也可以选择通过本地下载。

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对的支持。


推荐阅读
  • Centos7.6安装Gitlab教程及注意事项
    本文介绍了在Centos7.6系统下安装Gitlab的详细教程,并提供了一些注意事项。教程包括查看系统版本、安装必要的软件包、配置防火墙等步骤。同时,还强调了使用阿里云服务器时的特殊配置需求,以及建议至少4GB的可用RAM来运行GitLab。 ... [详细]
  • 在Docker中,将主机目录挂载到容器中作为volume使用时,常常会遇到文件权限问题。这是因为容器内外的UID不同所导致的。本文介绍了解决这个问题的方法,包括使用gosu和suexec工具以及在Dockerfile中配置volume的权限。通过这些方法,可以避免在使用Docker时出现无写权限的情况。 ... [详细]
  • EPICS Archiver Appliance存储waveform记录的尝试及资源需求分析
    本文介绍了EPICS Archiver Appliance存储waveform记录的尝试过程,并分析了其所需的资源容量。通过解决错误提示和调整内存大小,成功存储了波形数据。然后,讨论了储存环逐束团信号的意义,以及通过记录多圈的束团信号进行参数分析的可能性。波形数据的存储需求巨大,每天需要近250G,一年需要90T。然而,储存环逐束团信号具有重要意义,可以揭示出每个束团的纵向振荡频率和模式。 ... [详细]
  • 云原生边缘计算之KubeEdge简介及功能特点
    本文介绍了云原生边缘计算中的KubeEdge系统,该系统是一个开源系统,用于将容器化应用程序编排功能扩展到Edge的主机。它基于Kubernetes构建,并为网络应用程序提供基础架构支持。同时,KubeEdge具有离线模式、基于Kubernetes的节点、群集、应用程序和设备管理、资源优化等特点。此外,KubeEdge还支持跨平台工作,在私有、公共和混合云中都可以运行。同时,KubeEdge还提供数据管理和数据分析管道引擎的支持。最后,本文还介绍了KubeEdge系统生成证书的方法。 ... [详细]
  • 学习笔记(34):第三阶段4.2.6:SpringCloud Config配置中心的应用与原理第三阶段4.2.6SpringCloud Config配置中心的应用与原理
    立即学习:https:edu.csdn.netcourseplay29983432482?utm_sourceblogtoedu配置中心得核心逻辑springcloudconfi ... [详细]
  • 目录实现效果:实现环境实现方法一:基本思路主要代码JavaScript代码总结方法二主要代码总结方法三基本思路主要代码JavaScriptHTML总结实 ... [详细]
  • Android中高级面试必知必会,积累总结
    本文介绍了Android中高级面试的必知必会内容,并总结了相关经验。文章指出,如今的Android市场对开发人员的要求更高,需要更专业的人才。同时,文章还给出了针对Android岗位的职责和要求,并提供了简历突出的建议。 ... [详细]
  • 禁止程序接收鼠标事件的工具_VNC Viewer for Mac(远程桌面工具)免费版
    VNCViewerforMac是一款运行在Mac平台上的远程桌面工具,vncviewermac版可以帮助您使用Mac的键盘和鼠标来控制远程计算机,操作简 ... [详细]
  • 生成对抗式网络GAN及其衍生CGAN、DCGAN、WGAN、LSGAN、BEGAN介绍
    一、GAN原理介绍学习GAN的第一篇论文当然由是IanGoodfellow于2014年发表的GenerativeAdversarialNetworks(论文下载链接arxiv:[h ... [详细]
  • baresip android编译、运行教程1语音通话
    本文介绍了如何在安卓平台上编译和运行baresip android,包括下载相关的sdk和ndk,修改ndk路径和输出目录,以及创建一个c++的安卓工程并将目录考到cpp下。详细步骤可参考给出的链接和文档。 ... [详细]
  • 信息安全等级保护是指对国家秘密信息、法人和其他组织及公民的专有信息以及公开信息和存储、传输、处理这些信息的信息系统分等级实行安全保护,对信息系统中使用的信息安全产品实 ... [详细]
  • 20211101CleverTap参与度和分析工具功能平台学习/实践
    1.应用场景主要用于学习CleverTap的使用,该平台主要用于客户保留与参与平台.为客户提供价值.这里接触到的原因,是目前公司用到该平台的服务~2.学习操作 ... [详细]
  • t-io 2.0.0发布-法网天眼第一版的回顾和更新说明
    本文回顾了t-io 1.x版本的工程结构和性能数据,并介绍了t-io在码云上的成绩和用户反馈。同时,还提到了@openSeLi同学发布的t-io 30W长连接并发压力测试报告。最后,详细介绍了t-io 2.0.0版本的更新内容,包括更简洁的使用方式和内置的httpsession功能。 ... [详细]
  • 如何用UE4制作2D游戏文档——计算篇
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了如何用UE4制作2D游戏文档——计算篇相关的知识,希望对你有一定的参考价值。 ... [详细]
  • 使用在线工具jsonschema2pojo根据json生成java对象
    本文介绍了使用在线工具jsonschema2pojo根据json生成java对象的方法。通过该工具,用户只需将json字符串复制到输入框中,即可自动将其转换成java对象。该工具还能解析列表式的json数据,并将嵌套在内层的对象也解析出来。本文以请求github的api为例,展示了使用该工具的步骤和效果。 ... [详细]
author-avatar
从了良后
新手
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有