Java之IO流

本文深入讲解Java中的IO流概念,包括字节流与字符流的区别,流的分类及常用操作,如读写操作、对象序列化与反序列化,以及RandomAccessFile的使用方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

流是一组有顺序,有起点和重点的字节集合,是对数据传输的总称或抽象,流的本质是数据传输。IO有两类,分为BIO(Blocking IO)和NIO(Non-Blocking IO),不过我们平常说的IO指的是BIO.

基础知识

字节:计算机存储数据的单元,一个8位的二进制数;

字符:人们使用的一个记号,例:1,2,3,S,¥等;

字符编码:是一套法则,使用该法则能够对自然语言的字符的一个集合(如字母表或音节表),与其他东西的一个集合进行配对。

编码方式:

ASCII编码:美国制定了一套字符编码,对英语字符与二进制位之间的关系,做了统一规定;

UTF-8编码:最大的一个特点,就是它是一种变长的编码方式。它可以使用1~4个字节表示一个符号,根据不同的符号而变化字节长度。UTF-8 就是在互联网上使用最广的一种 Unicode 的实现方式。

File类介绍

File类翻译过来是一个文件,实际上它并不是一个文件,定义为Path更为合适,这个Path可以是文件的路径也可以是文件夹的路径,因为当我们new File的时候,只是创建了一个路径,这个路径如果创建成功,没有后缀名就是文件夹,有后缀名则创建了一个空文件。

注:当我们通过new File()来创建一个文件时,并不是一定创建成功,而且失败也不会报异常,我们可以通过File的exists()方法判断是否创建成功,不成功则调用createNewFile()创建新文件,未成功会报异常:

File file = new File("D:");
if(file.exists()){
    //进行相关操作
}else{
    try {
        boolean result = file.createNewFile();
    } catch (IOException e) {
        e.printStackTrace();
    }
}
  • 路径:

绝对路径:带有盘符的路径  e.g   windows:   D:\JAVA

相对路径:不带盘符的路径  e.g   .(当前目录)   ..(上一级目录)

  • 分隔符:
  • windows: "/" "\" 都可以
  • linux/unix: "/"

注意:因为Java中"\"代表转义字符所以推荐都使用"/",但由于Windows系统函数的行为会与时俱进发生变化,对于可移植的程序来说,应该使用程序所运行平台的文件分隔符,通过File.separator来获取,表示跨平台分隔符 。

  • 创建与删除

boolean createNewFile();//创建具体的文件

boolean mkdir();//创建单个目录

boolean mkdirs();//创建多个目录

boolean delete();//删除File复制代码

  • 判断方法

boolean canRead();//判断文件是否可读

boolean canWrite();//判断文件是否可写

boolean exists();//判断文件是否存在

boolean isDirectory();//判断是否是目录

boolean isFile();//判断是否是文件

boolean isAbsolute();//判断是否是绝对路径复制代码

  • 获取方法

String getName();//返回文件或者是目录的名称

String getPath();//返回路径

String getAbsolutePath();//返回绝对路径

String getParent();//返回父目录,如果没有父目录则返回null

long lastModified();//返回最后一次修改的时间

long length();//返回文件的长度

File[] listRoots();// 列出所有的根目录(Window中就是所有系统的盘符)

String[] list() ;//返回一个字符串数组,给定路径下的文件或目录名称字符串

String[] list(FilenameFilter filter);//返回满足过滤器要求的一个字符串数组

File[] listFiles();//返回一个文件对象数组,给定路径下文件或目录

  • 文件过滤

File[] listFiles(FilenameFilter filter);//返回满足过滤器要求的一个文件对象数组

其中包含了一个重要的接口FileNameFilter,该接口是个文件过滤器,包含了一个accept(File dir,String name)方法,该方法依次对指定File的所有子目录或者文件进行迭代,按照指定条件,进行过滤,过滤出满足条件的所有文件。

  示例:指定位置返回所有后缀名是txt的文件

// 文件过滤
File[] files = file.listFiles(new FilenameFilter() {
    @Override
    public boolean accept(File file, String filename) {
        return filename.endsWith(".txt");
    }
});

示例:输出指定位置含有关键字的文件

public static void searchFile(String filePath,String filter){
    File file = new File(filePath);
    //该文件不存在,返回
    if(!file.exists()) return;
    //文件含有关键字,打印
    if(file.isFile() && file.getName().contains(filter)){
        System.out.println(file.getAbsoluteFile());
    }
    //注意文件判空
    File[] list = file.listFiles();
    if(list == null)return;
    for (File f:list) {
        if(f.isDirectory()){//是目录,递归继续遍历下一级
            searchFile(f.toString(),filter);
        }
        if(f.isFile() && f.getName().contains(filter) && !f.isHidden()){
            System.out.println(f.getAbsoluteFile()+" " + f.getName());
        }
    }
}

流的分类

  • 根据数据流向划分,可以分为:输入流和输出流

IOæµæç§æµåå

输入流:由数据源到程序(数据源:鼠标,磁盘,网络等)

输出流:由程序到目标源(目标源:屏幕,网络,磁盘等)

InputStream和OutputStream构成了输入/输出(I/O)的层次结构基础。

  • 根据处理单元划分,分为:字节流和字符流

IOæµåç±»

字节流:(机器可识别)二进制文件流

字符流:人工可以识别的(因为码表的不同,有了对对象高效操作的流对象)

因为面向字节的流不便于处理以Unicode形式存储的信息(每个字符由多个字节来表示),所以从抽象类Reader和Writer中继承出来了一个专门用于处理Unicode字符的类的单独层次结构。

区别:

    读写单位不同:字节流以字节(8bit)为单位,而字符流以字符为单位(根据码表,一次可能读写多个字节);     

    处理对象不同:字节流可以处理任何类型的数据(音频和视频),字符流只能处理字符类型的数据;

  • 根据流的角色划分,分为:节点流和处理流

IOæµæç§è§è²å

节点流:直接与数据源相连(低级流);

处理流:通过节点流与数据源相连(高级流),主要指FilterInputStream,FilterOutputStream和FilterReader,FilterWriter的子类;

如上图总结 ,常用的处理流:

缓冲流:BufferedInputStream,BufferedOutputStream,BufferedReader,BufferedWriter增加缓冲功能,避免频繁操作磁盘,提高效率;

转换流:InputStreamReader,OutputStreamWriter,字节流与字符流进行相互转换(需要InputStream或OutputStream作为参数);

数据流:DataInputStream,DataOutputStream将基础数据类型写入或读出;

读写操作:

写字节

String filePath = "D:"+File.separator+"hello.txt";
//打开字节文件输出流:FileOutputStream  异常:文件目录不正确就会抛出FileNotFoundException
FileOutputStream outputStream = new FileOutputStream(filePath);
//往输出流中写入数据
//每次写入一个字节
outputStream.write(99);
//每次写入一个byte数据的数据
byte[] bytes = "abcd".getBytes();
outputStream.write(bytes);
//写入指定长度的数组 byte b[]:表示读取数据源, off,在数据源中读取起始位置 len:表示从起始位置读物数据的长度
outputStream.write(bytes, 1, 1);
//刷新缓冲区操作
outputStream.flush();
//数据写完将流关闭
outputStream.close();

读字节

String filePath = "D:"+File.separator+"hello.txt";
        //打开文件输入流 异常:FileNotFoundException
        FileInputStream inputStream = new FileInputStream(filePath);
//读取数据 每次读取一个字节数据,返回的是字节
        int read = inputStream.read();
        System.out.println((char) read);
        byte[] bytes = new byte[1024];
//读取数据,数据写入byte数组,返回值表示读取的有效数据个数  数据读到结束位置返回-1
        int read1 = inputStream.read(bytes);
        inputStream.read(bytes, 4,  inputStream.available());
        System.out.println(new String(bytes));
        //当前流的有效数据
        inputStream.available();
        //数据源跳跃读取 n<inputStream.available()
        inputStream.skip(3);
        //标记位置
        inputStream.mark(4);
        //判断当前是否支持mark操作
        inputStream.markSupported();
        //重置操作
        inputStream.reset();;
//关闭输入流
        inputStream.close();

读写字符流与字节流操作基本一致,通过FileWriter和FileReader对象,调用相关read或write方法;

Object流

ObjetcInputStream和ObjectOutputStream

对象操作设计序列化和反序列化

序列化:将对象转化为字节流的过程( ObjectOutputStream)

反序列化:将字节流转化为对象的过程(ObjetcInputStream)

特点:

1、在Java中只要一个类实现了java.io.Serializable接口,就可以被序列化;

2、通过 ObjectOutputStream和 ObjectInputStream进行序列化和反序列化;(非节点流需要通过其它流连接数据源)

3、虚拟机是否允许反序列化,不仅要取决于类路径和功能代码是否一致,还要取决于两个类的序列化ID是否一致(即 private static final long serialVersionUID);

4、父类对象要序列化,则父类也要实现java.io.Serialzable接口;

5、tansient关键字用于控制变量序列化,一个变量有该关键字修饰,则可以阻止该对象被序列化到文件中,反序列化后时,该关键字修饰的变量会变成初始值;(int型是0,对象型是null);

作用:

             1、网络数据传输中,敏感数据不进行传输;

             2、提高序列化效率;

6、服务器端给客户端发送序列化对象数据,对象中有一些数据是敏感的,比如密码字符串等, 希望对该密码字段在序列化时,进行加密,而客户端如果拥有解密的密钥,只有在客户端进行反序列化时, 才可以对密码进行读取,这样可以一定程度保证序列化对象的数据安全;

IO中的清流--RandomAccessFile类

RandomAccessFile是Java中输入,输出流体系中功能最丰富的文件内容访问类,它提供很多方法来操作文件,包括读写支持,与普通的IO流相比,它最大的特别之处就是支持任意访问的方式,程序可以直接跳到任意地方来读写数据。

该对象只能操作文件,所以构造函数接收两种类型的参数:a.字符串文件路径;b.File对象。

该对象既可以对文件进行读操作,也能进行写操作,在进行对象实例化时可指定操作模式(r,rw)

 注意:该对象在实例化时,如果要操作的文件不存在,会自动创建;如果文件存在,写数据未指定位置,会从头开始写,即覆盖原有的内容。

方法

构造方法

RandomAccessFile(String name, String mode)

       model各个参数详解

              r 代表以只读方式打开指定文件

              rw 以读写方式打开指定文件

              rws 读写方式打开,并对内容或元数据都同步写入底层存储设备

              rwd 读写方式打开,对文件内容的更新同步更新至底层存储设备

特有方法

getFilePointer()   返回文件记录指针的当前位置

seek(long pos)   将文件记录指针定位到pos的位置

功能

  • 1.读取任意位置的数据

  • 2.追加数据

  • 3.任意位置插入数据

示例:从指定位置追加写内容

public static void main(String[] args) {
    String path = "D:\\test.txt";
    String content = "abcd";
    addContentsToFile(path,2,content);
}
/**
 * 当RandomAccessFile向指定文件中插入内容时,将会覆盖掉原有内容。如果不想覆盖掉,
 *     则需要将原有内容先读取出来,然后先把插入内容插入后再把原有内容追加到插入内容后。
 */
public static void addContentsToFile(String filePath,int position,String contents){
    try {
        File tmp = File.createTempFile("tmp",null);
        tmp.deleteOnExit();
        RandomAccessFile r = new RandomAccessFile(filePath,"rw");
        FileInputStream temIn = new FileInputStream(tmp);
        FileOutputStream temOut = new FileOutputStream(tmp);
        //将原文件指定位置后的内容写入临时文件中
        r.seek(position);
        int t;
        while((t = r.read()) != -1){
            temOut.write(t);
        }
        //将追加内容插入到指定位置
        r.seek(position);
        r.write(contents.getBytes());
        //将临时文件的内容写入原文件
        while((t = temIn.read()) != -1){
            r.write(t);
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}

操作流需要明确的4个问题(老师总结):

问题1:操作时进行读还是写(数据源到当前程序、当前程序到数据目的)

源:读数据  inputStream \reader

目的:写数据   outputStream\ writer

 

问题2:明确操作的是字节还是字符

源:

   字节:InputStream

   字符:Reader

目的:

   字节:OutputStream

   字符:Writer

(明确操作那个基类的流)

 

问题3:数据做在的具体设备

源:

  磁盘数据:File

  内存数据:char、array

  键盘:System.in

  网络:socket

目的:

  磁盘数据:File

  内存数据:char、array

  屏幕:System.out

  网络:socket   

(明确具体实现类)

 

问题4:是否需要额外功能

考虑效率: BufferXXX

流转换:inputStreamReader\outputStreamWriter

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值