Java基础梳理之-IO操作

回想最开始学习Java IO相关的操作时, 被各种Reader/Stream绕晕。 现在再回头梳理这一块的知识点,感觉清晰了很多。 Java作为编程语言,
大部分东西都是从系统层面带来的, 所以学习的知识点虽然在Java, 但是背后的答案却在操作系统层面。

首先理解核心概念:IO, 究竟什么是IO? 所谓IO就是内存与外设相关的数据传输。常用的外设有硬盘,网卡,打印机, 鼠标...
我们接触最频繁的IO操作是硬盘上文件的读写,所以学习IO基本上都是以文件操作为例子。IO作为操作系统的核心,知识点相当庞杂,如果没有合适的切入点,容易迷失其中。

如果一定要找一个切入点,学习的先后顺序,个人建议如下:

  1. RandomAccessFile

对于文件的操作,最适合是RandomAccessFile: 它能读能写能定位。 使用RandomAccessFile, 就是把文件当作一个数组,只不过这个数组是在硬盘上而已。读写文件就像操作数组一样。更难能可贵的是, RandomAccessFile封装了Java所有的基础类型, 可以说基本满足操作单个文件的使用需求了。

    public static void main(String[] args) throws IOException {

        RandomAccessFile bigArray = new RandomAccessFile(new File("/home/shgy/a.txt"),"rw");
        // 写
        bigArray.seek(10);
        bigArray.writeUTF("hello,world");

        System.out.println("filePointer at " + bigArray.getFilePointer());
        // 读
        bigArray.seek(10);
        System.out.println(bigArray.readUTF());
        bigArray.close();
    }

RandomAccessFile类有14个写的方法,17个读的方法,2个定位方法,2个长度相关操作,1个获取当前文件游标getFilePointer()的方法, 1个关闭文件释放系统资源的方法。 剩下的两个方法getChannel()getFD()getChannel() 跟NIO相关,getFD()是系统文件描述符,就本文所要总结的内容而言,已经超出三界之外,不在五行之中,暂时略过不提。

当文件数量多了以后,必然面临管理的问题。无论是windows还是Linux都采用层级管理,最后形成目录树。这带来一个新的问题就是文件的路径以及文件的归类。 面对这样的需求,Java提供了Files类来解决。通过了解Files类提供的API, 可以看出,其功能特点在于粗粒度的文件读写及文件属性的管理。
使用Files来读写文件更简单:

    public static void main(String[] args) throws IOException {

        Files.write("hello world", new File("/home/shgy/a.txt"),Charset.defaultCharset());

        System.out.println(Files.readLines(new File("/home/shgy/a.txt"), Charset.defaultCharset()));
    }

Files 可以读写文件,可以重命名文件,可以读取设置文件属性,简直是瑞士×××般的存在。这里涉及了更多文件相关的知识点,如果有学习过《鸟哥的Linux私房菜》第七章,再学习代码操作文件,就不会那么困惑了。

使用Files操纵文件引出了一个新的知识点Charset, 即字符集。字符集产生的原因很简单: 人类语言是字符形式,计算机只能以字节的方式存储数据,字符跟字节之间得有个映射关系。比如上例中存储的hello world, 实际上存储的内容可以使用vim的xdd命令查看:

// vim  + %!xdd 命令即可

00000000: 6865 6c6c 6f20 776f 726c 640a            hello world.

关于字符集的知识,可以参考阮一峰的《字符编码笔记:ASCII,Unicode 和 UTF-8》。

理解了字符集,再进入Java的IO模块,才顺理成章。前面已经说过,所谓IO,就是内存与计算机外设的数据传输。Java从语言层面对IO进行了抽象, 这个抽象就是Steam, 数据流。这样的话,无论数据来源是文件,网页,内存块还是其他,都以一种统一的视角和处理方式看待。 所以Java定义了InputStream和OutputStream。
InputStream用于将数据读入内存, 对应的操作是read; OutputStream用于将数据写入外设,对应的操作是write。InputStream和OutputStream操纵的数据只能是字节或者字节数组, 这样就不用关心数据是文本,图片,音频,视频了,毕竟不管什么类型的数据,最终的呈现形式就是字节流。
这样,文件的操作就相当繁琐了:

public static void main(String[] args) throws IOException {

        // 读取文件
        FileInputStream fis = new FileInputStream(new File("/home/shgy/a.txt"));
        byte[] bytes = new byte[1024];
        int n = fis.read(bytes);
        if(n>0){
            System.out.println(new String(bytes,0,n));
        }
        fis.close();

        // 写文件
        FileOutputStream fos = new FileOutputStream(new File("/home/shgy/a.txt"));
        fos.write("hello, world".getBytes());
        fos.close();
}

鉴于我们处理的文件,绝大部分都是字符类型的文件,而且以字节的方式操纵字符确实过于原始,于是Java也定义了字符IO, 即Reader/Writer。

    public static void main(String[] args) throws IOException {

        // 读取文件
        FileReader fr = new FileReader(new File("/home/shgy/a.txt"));
        char[] buf = new char[1024];
        int n = fr.read(buf);
        System.out.println(new String(buf,0,n));
        fr.close();

        FileWriter fw = new FileWriter(new File("/home/shgy/a.txt"));
        fw.write("hello, world");
        fw.close();
    }

由于计算机本质是处理字节,所以字符和字节之间需要一个桥梁,这个就是InputStreamReader/OutputStreamWriter. 为了应对各种字符集和字节之间的编码解码,所以定义了StreamEncoder/StreamDecoder。

对于文件的读写,由于是需要操作硬盘或者网卡;考虑到安全性, 在系统层面需要系统调用,由用户态切入内核态。这个操作代价较高。所以又添加了一层缓冲,即BufferedInputStream/BufferedOutputStream 和 BufferedReader/BufferedWriter。

整个IO操作在InputStream/OutputStream和Reader/Writer基础之上丰富多彩起来。

由于外设,比如硬盘和网络数据的传输效率相比CPU的处理效率相差太远, 在《性能之颠》中有这样一个让人影响深刻的对比:
1个CPU周期为0.3ns, 1次机械磁盘IO周期为1~10ms, 1次从旧金山到纽约的互联网传输需要40ms; 由于时间单位太小,我们没有概念。 我们放大一下,假如:
1个CPU周期为1s, 则一次机械磁盘IO周期为1~12个月,1次从旧金山到纽约的互联网传输需要4年。 在这样一个差距面前,如何提高IO的效率,就显得尤为重要,
这就是NIO的由来。

在《UNIX网络编程卷1:套接字联网API》一书中总结了5种IO模型: 阻塞,非阻塞,IO复用,信号驱动,异步IO。Java的NIO是采用了IO复用(select)模型。

NIO处理数据,方式跟Stream有所不同。 Stream比较碎,以字节为最小粒度; NIO以数据块为最小粒度。所以可以避免数据的反复搬运,更高效,操作起来就更繁琐一些。

// 使用NIO写数据到文件
    public static void main(String[] args) throws IOException {

        FileOutputStream fos = new FileOutputStream(new File("/home/shgy/a.txt"));

        FileChannel fc = fos.getChannel();

        ByteBuffer buf = ByteBuffer.allocate(1024);
        buf.put("hello,world".getBytes());
        buf.flip();
        fc.write(buf);
        System.out.println("file channel position is " + fc.position());
        fos.close();

    }

NIO有如下的几个优点:

  1. channel是支持读写的,所以相比Stream更灵活。
  2. buffer可以分配堆外内存,这个对于IO来说,避免了数据从堆内存中倒腾一边,也避免了Java的GC, 性能自然有提升。
  3. 对于网络IO, NIO可以在同一个线程同时监听多个端口,避免了创建多个线程和线程管理的开销。

由于IO这一块的知识点过于庞杂,不是一篇博客能说清楚的,这里只是简单梳理一下学习思路。

原文地址:http://blog.51cto.com/sbp810050504/2347753

时间: 2024-10-06 09:23:22

Java基础梳理之-IO操作的相关文章

黑马程序员--Java基础--文件数据IO操作

文件数据IO操作 1. Reader和Writer 1.1. 字符流原理 Reader是所有字符输入流的父类而Writer是所有字符输出流的父类.字符流是以字符(char)为单位读写数据的.一次处理一个unicode.字符流都是高级流,其底层都是依靠字节流进行读写数据的,所以底层仍然是基于字节读写数据的. 1.2. 常用方法 Reader的常用方法: int read() 读取一个字符,返回的int"值低16"位有效. int read(char[] chs) 从该流中读取一个字符数组

java基础梳理--朝花夕拾(三)

1.了解面向对象的编程思想以及面向对象的特性: 对象: EveryThing is Object: 万物皆是对象,在程序中,我们可以将类.接口.方法.属性等都认为是对象: 面向对象: 是一种程序设计方法,它以对象作为基本的单元来构建系统,它利用对象将系统的复杂性隐藏在对象里(即封装),从而构建大型的工业级系统和大型系统. 面向对象包括三个过程: 面向对象分析(OOA).面向对象设计(OOD).面向对象编程(OOP). 面向对象的三个特性: 继承.封装.多态 面向对象和面向过程的区别? 面向过程就

java基础梳理--朝花夕拾(二)

1.Java语言语法规则和文件格式: 第一个Java程序:/** 第一个Java程序:控制台输出Hello world!*/public class Test{    //访问修饰符 class关键词用于声明类使用 后面跟类名   public static void main(String args[]){//程序的入口,主函数main方法:        System.out.println("Hello world!");// 输出语句   }} Java语言区分大小写:publ

复习java基础第六天(IO)

一:File 类 • 输入:读取外部数据(磁盘.光盘等存储设备的数据)到程序(内存)中. • 输出:将程序(内存)数据输出到磁盘.光盘等存储设备中 • Java 的 IO 流主要包括输入.输出两种 IO 流,每种输入.输出流有可分为字节流和字符流两大类: – 字节流以字节为单位来处理输入.输出操作 – 字符流以字符为单位来处理输入.输出操作 注意:输入.输出是以程序为参照. • File 类代表与平台无关的文件和目录. • File  能新建.删除.重命名文件和目录,但 File 不能访问文件内

Java基础知识之IO(2)

文件复制小案例(温习Java基础知识之IO(1)中的知识) package copy; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; public class CopyDemo { public stat

JAVA基础知识之IO——Java IO体系及常用类

Java IO体系 个人觉得可以用"字节流操作类和字符流操作类组成了Java IO体系"来高度概括Java IO体系. 借用几张网络图片来说明(图片来自 http://blog.csdn.net/zhangerqing/article/details/8466532 )  基于字节的IO操作 基于字符的IO操作   从上图可以看到,整个Java IO体系都是基于字符流(InputStream/OutputStream) 和 字节流(Reader/Writer)作为基类,根据不同的数据载

Java基础知识之IO(1)

IO中主要的类和接口 File.InputStream.OutputStream.Reader.Writer.Serialzable接口 File类 File类应用的小例子 import java.io.File; import java.io.IOException; public class FileDemo { public static void main(String[] args) { //指定文件路径和名称 String path = "D:"+File.separato

Java基础知识系列——目录操作

Java对目录操作的许多方法与上一篇文件操作的方法很多是一样的. java.io.File file = new File( "D:\1\2\3\4"); 1.递归创建目录 file.mkdirs(); 2.删除目录 file.isDirectory(); //判读是否目录 file.list(); //返回没有完整目录的文件名 file.delete(); //删除 3.是否为空 file.list() > 0 //目录不为空 file.getPath(); //目录路径 4.

Java基础之文件IO

概述 Java 的 I/O 操作类在包 java.io 下,大概有将近 80 个类,但是这些类大概可以分成四组,分别是: 按处理数据类型来分:字节流和字符流: 基于字节操作的 I/O 接口:InputStream 和 OutputStream 基于字符操作的 I/O 接口:Writer 和 Reader 按传输数据的方式:磁盘操作和网络操作 基于磁盘操作的 I/O 接口:File 基于网络操作的 I/O 接口:Socket 按流的方向来分:输入流和输入流 要读的话就用输入流,要写的话,就用输出流