Java I/O 入门篇(1)

简介: Java I/O 入门篇

对于Java I/O来说,I意味着Input(输入),O意味着Output(输出)。读书写作并非易事,而创建一个好的I/O系统更是一项艰难的任务。


古人云:“读书破万卷,下笔如有神”。也就是说,只有大量的阅读,写作的时候才能风生水起——写作意味着输出(我的知识传播给他人),而读书意味着输入(从他人的知识中汲取营养)。


01、数据流之字节与字符


Java所有的I/O机制都是基于数据流进行的输入输出。数据流可分为两种:


1)字节流,未经加工的原始二进制数据,最小的数据单元是字节。


2)字符流,经过一定编码处理后符合某种格式规定的数据,最小的数据单元是字符——占用两个字节。


OutputStream和InputStream用来处理字节流;Writer和Reader用来处理字符流;OutputStreamWriter可以把OutputStream转换为Writer,InputStreamReader可以把InputStream转换为Reader。


Java的设计者为此设计了众多的类,见下图。


image.png



看到这么多类,你一定感觉头晕目眩。反正我已经看得不耐烦了。搞这么多类,看起来头真的大——这也从侧面说明实际的应用场景各有各的不同——你也完全不用担心,因为实际项目当中,根本就不可能全用到(我就没用过SequenceOutputStream)。


我建议你在学习的时候要掌握一种“挑三拣四”的能力——学习自己感兴趣的、必须掌握的、对能力有所提升的知识。切不可囫囵吞枣,强迫自己什么都学。什么都学,最后的结果可能是什么都不会。


字符流是基于字节流的,因此,我们先来学习一下字节流的两个最基础的类——OutputStream和InputStream,它们是必须要掌握的。


1)OutputStream


OutputStream提供了4个非常有用的方法,如下。


public void write(byte b[]):将数组b中的字节写到输出流。

public void write(byte b[], int off, int len):将数组b的从偏移量off开始的len个字节写到输出流。

public void flush() : 将数据缓冲区中数据全部输出,并清空缓冲区。

public void close() : 关闭输出流并释放与流相关的系统资源。

其子类ByteArrayOutputStream和BufferedOuputStream最为常用(File相关类放在下个小节)。


①、ByteArrayOutputStream通常用于在内存中创建一个字节数组缓冲区,数据被“临时”放在此缓冲区中,并不会输出到文件或者网络套接字中——就好像一个中转站,负责把输入流中的数据读入到内存缓冲区中,你可以调用它的toByteArray()方法来获取字节数组。


来看下例。


public static byte[] readBytes(InputStream in, long length) throws IOException {
    ByteArrayOutputStream bo = new ByteArrayOutputStream();
    byte[] buffer = new byte[1024];
    int read = 0;
    while (read < length) {
        int cur = in.read(buffer, 0, (int) Math.min(1024, length - read));
        if (cur < 0) {
            break;
        }
        read += cur;
        bo.write(buffer, 0, cur);
    }
    return bo.toByteArray();
}

ByteArrayOutputStream的责任就是把InputStream中的字节流“一字不差”的读出来——这个工具方法很重要,很重要,很重要——可以解决粘包的问题。


②、BufferedOuputStream实现了一个缓冲输出流,可以将很多小的数据缓存为一个大块的数据,然后一次性地输出到文件或者网络套接字中——这里的“缓冲”和ByteArrayOutputStream的“缓冲”有着很大的不同——前者是为了下一次的一次性输出,后者就是单纯的为了缓冲,不存在输出。


来看下例。


protected void write(byte[] data) throws IOException {
    out.write(intToByte(data.length));
    out.write(data);
    out.flush();
    out.close();
}
public static byte[] intToByte(int num)
{
    byte[] data = new byte[4];
    for (int i = 0; i < data.length; i++)
    {
        data[3-i] = (byte)(num % 256);
        num = num / 256;
    }
    return data;
}

使用BufferedOuputStream的时候,一定要记得调用flush()方法将数据从缓冲区中全部输出。使用完毕后,调用close()方法关闭输出流,释放与流相关的系统资源。


2)InputStream


InputStream也提供了4个非常有用的方法,如下。


public int read(byte b[]):读取b.length个字节的数据放到数组b中,返回值是读取的字节数。

public int read(byte b[], int off, int len):从输入流中最多读取len个字节的数据,存放到偏移量为off的数组b中。

public int available():返回输入流中可以读取的字节数。

public int close() :使用完后,对打开的流进行关闭。

其子类BufferedInputStream(缓冲输入流)最为常用,效率最高(当我们不确定读入的是大数据还是小数据)。


无缓冲流上的每个读取请求通常会导致对操作系统的调用以读取所请求的字节数——进行系统调用的开销非常大。但缓冲输入流就不一样了,它通过对内部缓冲区执行(例如)高达8k字节的大量读取,然后针对缓冲区的大小再分配字节来减少系统调用的开销——性能会提高很多。


使用示例如下。


先来看一个辅助方法byteToInt,把字节转换成int。


public static int byteToInt(byte[] b) {
    int num = 0;
    for (int i = 0; i < b.length; i++) {
        num*=256;
        num+=(b[i]+256)%256;
    }
    return num;
}

再来看如何从输入流中,根据指定的长度contentLength来读取数据。readBytes()方法在之前已经提到过。


BufferedInputStream in = new BufferedInputStream(socket.getInputStream());
byte[] tmpByte = new byte[4];
// 读取四个字节判断消息长度
in.read(tmpByte, 0, 4);
// 将byte转为int
int contentLength = byteToInt(tmpByte);
byte[] buf = null;
if (contentLength > in.available()) {
    // 之前提到的方法
    buf = readBytes(in, contentLength);
} else {
    buf = new byte[contentLength];
    in.read(buf, 0, contentLength);
    // 发生粘包了
    if (in.available() > 0) {
    }
}

我敢保证,只要你搞懂了字节流,字符流也就不在话下——所以,我们在此略过字符流。


相关文章
|
10天前
|
存储 Oracle Java
java零基础学习者入门课程
本课程为Java零基础入门教程,涵盖环境搭建、变量、运算符、条件循环、数组及面向对象基础,每讲配示例代码与实践建议,助你循序渐进掌握核心知识,轻松迈入Java编程世界。
90 0
|
2月前
|
Java
java入门代码示例
本文介绍Java入门基础,包含Hello World、变量类型、条件判断、循环及方法定义等核心语法示例,帮助初学者快速掌握Java编程基本结构与逻辑。
289 0
|
3月前
|
安全 Java 数据库连接
2025 年最新 Java 学习路线图含实操指南助你高效入门 Java 编程掌握核心技能
2025年最新Java学习路线图,涵盖基础环境搭建、核心特性(如密封类、虚拟线程)、模块化开发、响应式编程、主流框架(Spring Boot 3、Spring Security 6)、数据库操作(JPA + Hibernate 6)及微服务实战,助你掌握企业级开发技能。
423 3
|
5月前
|
Java API 微服务
2025 年 Java 从入门到精通学习笔记全新版
《Java学习笔记:从入门到精通(2025更新版)》是一本全面覆盖Java开发核心技能的指南,适合零基础到高级开发者。内容包括Java基础(如开发环境配置、核心语法增强)、面向对象编程(密封类、接口增强)、进阶技术(虚拟线程、结构化并发、向量API)、实用类库与框架(HTTP客户端、Spring Boot)、微服务与云原生(容器化、Kubernetes)、响应式编程(Reactor、WebFlux)、函数式编程(Stream API)、测试技术(JUnit 5、Mockito)、数据持久化(JPA、R2DBC)以及实战项目(Todo应用)。
277 5
|
2月前
|
前端开发 Java 数据库连接
帮助新手快速上手的 JAVA 学习路线最详细版涵盖从入门到进阶的 JAVA 学习路线
本Java学习路线涵盖从基础语法、面向对象、异常处理到高级框架、微服务、JVM调优等内容,适合新手入门到进阶,助力掌握企业级开发技能,快速成为合格Java开发者。
374 3
|
3月前
|
NoSQL Java 关系型数据库
Java 从入门到进阶完整学习路线图规划与实战开发最佳实践指南
本文为Java开发者提供从入门到进阶的完整学习路线图,涵盖基础语法、面向对象、数据结构与算法、并发编程、JVM调优、主流框架(如Spring Boot)、数据库操作(MySQL、Redis)、微服务架构及云原生开发等内容,并结合实战案例与最佳实践,助力高效掌握Java核心技术。
306 1
|
3月前
|
Java 测试技术 API
Java IO流(二):文件操作与NIO入门
本文详解Java NIO与传统IO的区别与优势,涵盖Path、Files类、Channel、Buffer、Selector等核心概念,深入讲解文件操作、目录遍历、NIO实战及性能优化技巧,适合处理大文件与高并发场景,助力高效IO编程与面试准备。
|
3月前
|
Java 编译器 API
Java Lambda表达式与函数式编程入门
Lambda表达式是Java 8引入的重要特性,简化了函数式编程的实现方式。它通过简洁的语法替代传统的匿名内部类,使代码更清晰、易读。本文深入讲解Lambda表达式的基本语法、函数式接口、方法引用等核心概念,并结合集合操作、线程处理、事件回调等实战案例,帮助开发者掌握现代Java编程技巧。同时,还解析了面试中高频出现的相关问题,助你深入理解其原理与应用场景。
|
2月前
|
Java API 数据库
2025 年最新 Java 实操学习路线,从入门到高级应用详细指南
2025年Java最新实操学习路线,涵盖从环境搭建到微服务、容器化部署的全流程实战内容,助你掌握Java 21核心特性、Spring Boot 3.2开发、云原生与微服务架构,提升企业级项目开发能力,适合从入门到高级应用的学习需求。
478 0
|
3月前
|
前端开发 Java 数据库
Java 项目实战从入门到精通 :Java Web 在线商城项目开发指南
本文介绍了一个基于Java Web的在线商城项目,涵盖技术方案与应用实例。项目采用Spring、Spring MVC和MyBatis框架,结合MySQL数据库,实现商品展示、购物车、用户注册登录等核心功能。通过Spring Boot快速搭建项目结构,使用JPA进行数据持久化,并通过Thymeleaf模板展示页面。项目结构清晰,适合Java Web初学者学习与拓展。
238 1