Java基础之文件IO

发布于:2023-02-06 ⋅ 阅读:(759) ⋅ 点赞:(0)

IO

  • Java io 包中几乎包含了所有操作输入、输出需要的类
    • 这些流类代表了输入源和输出目标
  • 一个流可以理解为一个数据的序列
    • 输入流表示从一个源读取数据、输出流表示向一个目标写数据
    • io 包中的流支持很多格式;比如:基本类型、对象、本地化字符集等
  • Java为I/O提供了强大而灵活的支持,使其更广泛的应用到文件传输和网络编程中

File

File

Java中提供了对文件操作、系统操作的支持

  • Java文件类以抽象的方式代表文件名和目录路径名

  • Java.io.File

    • File 类是 Comparable 接口的子类
      • File 对象可以进行排序处理
      • windows 系统中文件可按照时间、大小、名称等排序
  • io 包中File 类是唯一一个与文件本身操作相关的类

    • File对象代表磁盘中实际存在的文件和目录
      • 主要用于文件和目录的创建、文件的查找和文件的删除等
      • 只针对于文件或目录本身进行操作,不涉及文件内容的处理
    • 进行File类的操作必须提供完整的路径,调用相关方法进行处理
  • 在不同系统环境下有不同的路径分隔符

    • windows中:\ ;linux:/

    • File 提供常量 Static final String separator表示分隔符

      new File("D:" + File.separator + "test.txt");
      // 常量小写因为早期变量命名没有约束规范都是用小写
      
    • 现在的路径操作可以随意使用分隔符

      new File("D:/Java\\demo/demo.txt");
      // 可以随意使用 / 或 \
      // 因为Java中的转义字符故 \ 需要写作 \\
      
  • 在使用 File 进行文件处理时流程

    • 程序 → JVM → 操作系统函数 → 文件处理
    • 因此重复删除或创建可能会出现延迟问题,最好是不要重名

构造方法

  • File对象代表磁盘中实际存在的文件和目录
  • 通过以下构造方法创建一个File对象
//通过给定的父抽象路径名和子路径名字符串创建一个新的File实例
File(File parent, String child);
//通过将给定路径名字符串转换成抽象路径名来创建一个新 File 实例
 File(String pathname) 
//根据 parent 路径名字符串和 child 路径名字符串创建新 File 实例
 File(String parent, String child) 
//将给定的 file: URI 转换成一个抽象路径名来创建一个新的 File 实例
 File(URI uri) 

常用方法

创建、删除

public boolean createNewFile(){}; 					// 文件不存在创建新文件,返回true;存在则不执行,返回false;创建时父路径必须存在
public boolean exists(){};							// 判断是否存在该文件;存在返回true,否则返回false
public boolean delete(){};							// 删除空目录或文件;成功返回true,否则返回false
public FIle getParentFile(){};						//获取父文件路径
public boolean mkdir(){};							// 创建目录(文件夹)
public boolean mkdirs(){};							// 创建多级目录

获取信息

public boolean canRead(){};							// 文件是否可读
public Boolean canWrite(){};						// 文件是否可写
public long length(){};								// 返回字节长度
public long lastModified(){};						// 最后一次修改时间;返回long类型数据,可使用simpleDateFormat转换为日期格式
public boolean isFile(){};							// 是否是文件
public boolean isDirectory(){};						// 是否是目录(目录即是特殊的文件)
public File[] listFiles(){};						// 获取该目录下的目录、文件数组
public String[] list(FilenameFilter obj){};			// 字符串数组形式返回目录下的指定类型的所有文件
public File[] listFile(FilenameFilter obj)			// File对象数组形式返回目录下的指定类型的所有文件
//FilenameFilter  是一个接口,该接口有一个方法
public boolean accept(File f,String name);

方法列表

方法 描述
public String getName() 返回由此抽象路径名表示的文件或目录的名称
public String getParent() 返回此抽象路径名的父路径名的路径名字符串,如果此路径名没有指定父目录,则返回 null
public File getParentFile() 返回此抽象路径名的父路径名的抽象路径名,如果此路径名没有指定父目录,则返回 null
public String getPath() 将此抽象路径名转换为一个路径名字符串
public boolean isAbsolute() 测试此抽象路径名是否为绝对路径名
public String getAbsolutePath() 返回抽象路径名的绝对路径名字符串
public boolean canRead() 测试应用程序是否可以读取此抽象路径名表示的文件
public boolean canWrite() 测试应用程序是否可以修改此抽象路径名表示的文件
public boolean exists() 测试此抽象路径名表示的文件或目录是否存在
public boolean isDirectory() 测试此抽象路径名表示的文件是否是一个目录
public boolean isFile() 测试此抽象路径名表示的文件是否是一个标准文件
public long lastModified() 返回此抽象路径名表示的文件最后一次被修改的时间
public long length() 返回由此抽象路径名表示的文件的长度
public boolean createNewFile() throws IOException 当且仅当不存在具有此抽象路径名指定的名称的文件时,原子地创建由此抽象路径名指定的一个新的空文件
public boolean delete() 删除此抽象路径名表示的文件或空目录
public void deleteOnExit() 在虚拟机终止时,请求删除此抽象路径名表示的文件或目录
public String[] list() 返回由此抽象路径名所表示的目录中的文件和目录的名称所组成字符串数组
public String[] list(FilenameFilter filter) 返回由包含在目录中的文件和目录的名称所组成的字符串数组,这一目录是通过满足指定过滤器的抽象路径名来表示的
public File[] listFiles() 返回一个抽象路径名数组,这些路径名表示此抽象路径名所表示目录中的文件
public File[] listFiles(FileFilter filter) 返回表示此抽象路径名所表示目录中的文件和目录的抽象路径名数组,这些路径名满足特定过滤器
public boolean mkdir() 创建此抽象路径名指定的目录
public boolean mkdirs() 创建此抽象路径名指定的目录,包括创建必需但不存在的父目录
public boolean renameTo(File dest) 重新命名此抽象路径名表示的文件
public boolean setLastModified(long time) 设置由此抽象路径名所指定的文件或目录的最后一次修改时间
public boolean setReadOnly() 标记此抽象路径名指定的文件或目录,以便只可对其进行读操作
public static File createTempFile(String prefix, String suffix, File directory) throws IOException 在指定目录中创建一个新的空文件,使用给定的前缀和后缀字符串生成其名称
public static File createTempFile(String prefix, String suffix) throws IOException 在默认临时文件目录中创建一个空文件,使用给定前缀和后缀生成其名称
public int compareTo(File pathname) 按字母顺序比较两个抽象路径名
public int compareTo(Object o) 按字母顺序比较抽象路径名与给定对象
public boolean equals(Object obj) 测试此抽象路径名与给定对象是否相等
public String toString() 返回此抽象路径名的路径名字符串

目录

一个目录其实就是一个 File 对象,包含其他文件和文件夹

创建目录

  • mkdir( ):创建文件夹,成功则返回true,失败则返回false
    • 失败表明File对象指定的路径已经存在,或者由于整个路径还不存在,该文件夹不能被创建
  • mkdirs():创建一个文件夹和它的所有父文件夹
String dirname = "/tmp/user/java/bin";     
File d = new File(dirname);        	// 现在创建目录   
d.mkdirs();							//编译并执行上面代码来创建目录 "/tmp/user/java/bin"
//注意:Java 在 UNIX 和 Windows 自动按约定分辨文件路径分隔符。在 Windows 版本的 Java 中使用分隔符 (/) ,路径依然能够被正确解析

读取目录

  • 创建一个 File 对象并且是一个目录,调用 isDirectory() 方法会返回 true
    • 通过调用该对象上的 list() 方法提取包含的文件和文件夹的列表
String dirname = "/tmp";
File f1 = new File(dirname);
if (f1.isDirectory()) {
    System.out.println("目录 " + dirname);
    String s[] = f1.list();
    for (int i = 0; i < s.length; i++) {
        File f = new File(dirname + "/" + s[i]);
        if (f.isDirectory()) {
            System.out.println(s[i] + " 是一个目录");
        } else {
            System.out.println(s[i] + " 是一个文件");
          }
    }
} else {
    System.out.println(dirname + " 不是一个目录");
  }
运行结果:
目录 /tmp
bin 是一个目录
lib 是一个目录
demo 是一个目录
test.txt 是一个文件
README 是一个文件
index.html 是一个文件
include 是一个目录

删除目录或文件

  • 删除文件使用 java.io.File.delete() 方法
  • 当删除某一目录时,必须保证该目录下没有其他文件才能正确删除,否则将删除失败
/**
测试目录结构:
    /tmp/java/
    |-- 1.log|
    -- test  
 */   
public static void main(String[] args) {   
    File folder = new File("/tmp/java/");  
    deleteFolder(folder);}		// 删除文件及目录
public static void deleteFolder(File folder) { 
    File[] files = folder.listFiles();    
    // 遍历文件夹列表
    if (files != null) {         
        for (File f : files) {          
            // 文件夹中还是文件夹递归调用
            if (f.isDirectory()) {           
                deleteFolder(f)           
            } else {               
                f.delete();     	// 否则删除该文件   
            }        
        }   
    }    
    folder.delete();				// 最后才删除此文件夹
}

实例

简单使用
import java.io.File;
import java.io.IOException;
public class Test {
   public static void main(String[] args) throws IOException {
        //创建 File 对象,需要有完整路径
        File file = new File("D:/Java/demo\\demo.txt");
        //判断文件是否存在
        if (!file.exists()) {
            //判断文件的父路径是否存在
            if (!file.getParentFile().exists()) {
                //父路径不存在则创建目录
                if (file.getParentFile().mkdirs()) {
                    System.out.println("目录创建成功");
                }else {
                    System.out.println("目录创建失败");
                }
            }
/*
判断并建立父目录的操作一般只需要一次
	保留判断在代码中会使时间复杂度提升
这时若要提升性能要先保证目录已创建,可取消判断操作
	但目录可能被用户删除,因此是否使用判断要合理安排
*/
            //创建文件
            if (file.createNewFile()) {
                System.out.println("文件创建成功");
            }
        }else {
            if (file.delete()) {
                System.out.println("文件已删除");
            }
        }
    }
}
批量修改文件名

运行时输入目录名,将该目录下的所有文件名后缀下修改

对此类操作须有假定的约定:

  • 有后缀的文件名可以重命名
    • 0 到最后一个.为区间截取文件名
  • 没有后缀的文件名追加后缀
import java.io.File;
import java.io.IOException;
public class Test {
    public static void renameDir(File file, String name) {
        //判断文件是否存在,不存在则直接返回
        if (!file.exists()){
            System.out.println("路径不存在");
            return;
        }
        //判断是否是目录,是则取出其中所有文件
        if (file.isDirectory()) {
            File[] files = file.listFiles();
            if (files != null) {
                //递归调用本方法对目录中文件处理
                for (File f : files) {
                    renameDir(f, name);
                }
            }
            //判断是文件
        } else if (file.isFile()) {
            String fileName;
            //文件名中存在 . 则截取文件名最后一个点以前内容(即文件名),并添加自定义后缀
            if (file.getName().contains(".")) {
                fileName = file.getName().substring(0, file.getName().lastIndexOf(".")) + "." + name;
            }//不存在后缀则直接用原文件名加自定义后缀 
            else {
                fileName = file.getName() + "." + name;
            } 
            //定义新文件,使用父文件路径、新文件名
            File newFile = new File(file.getParentFile(), fileName);
            //将原文件改名为新文件
            file.renameTo(newFile);
        }
    }

    public static void main(String[] args) throws IOException {
        File file = new File("D:/Java/demo");
        renameDir(file, "docx");
    }
}

流操作步骤

  • 文件读写操作必须通过 File 类找到文件路径
  • 字节流或字符流的子类实例化父类对象
  • 利用字节、字符流方法实现数据的输入与输出
  • 流的操作属于资源操作
    • 资源操作必须进行关闭处理
  • input 输入:文件到程序(读取)
  • output 输出:程序到文件(写入)

在这里插入图片描述

字节流

  • 二进制传输(字节传输;视频、音乐等文件)

Inputstream

字节输入流,读取到程序

类结构

在这里插入图片描述

核心方法
//读取单个字节,若已经到最后则返回 -1(文件中所有字节已经全部读取完成之后的下一次)
public abstract int read() thrwos IOException;
//读取一组字节,返回读取到的字节数;若读取到最后返回 -1
public abstract int read(byte[] b);
//读取部分字节,从 off 开始 读取 len 个,若已到最后返回 -1
public abstract int read(byte[], int off, int len);

// JKD 1.9 新增方法,读取较大时( > 10K )会使程序崩溃,一般无用
public byte[] readAllBYtes();

在这里插入图片描述

  • 实现子类
    • FileInputStream:文件输入流
    • ByteArrayInputStream:访问字节数组
    • PipedInputStream:访问管道
    • BufferInputStream:缓冲输入流
    • ObjectInputStream:对象输入流

OutputStrean

类结构
  • 字节输出流,写入到文件
    • 定义了公共输出操作标准

在这里插入图片描述

  • Closeable 是 AutoCloseable 的子接口

    • 可以简化使用 close() 方法,使用 try…catch 实现自动关闭
    • 是否自动关闭取决于项目整体结构
  • 实现子类

    • FileOutputStrean:文件输出流

    • CharArrayInputStream:访问字节数组

    • PipedInputStream:访问管道

    • BufferOutputStrean:缓冲输出流

    • ObjectOutputStrean:对象输出流

  • 三个内容输出的方法

    //输出单个字节数据
    public abstract void write(int b) throws IOException;
    //输出一组字节数据
    public abstract void write(byte[] b);
    //输出部分字节数据,从 off 开始读取 len 个
    public abstract void write(byte[] b, int off, int len)
    
    • 向文件写入内容的时候若文件不存在可以自动创建
      • 但路径必须存在
  • 本类为抽象类,即实例化要通过其子类完成

    //覆盖原文件内容
    public FileOutputStream(File file) throws FileNotFoundException;
    //追加在原文件内容后面
    public File utputStream(File file, boolean append) throws FileNotFoundException
    

字符流

  • 字符传输(适用于文本文档)
  • 相对于字节流可以直接使用字符串,便于处理中文数据

Reader

  • 字符输入流,读取数据到程序

  • Reader 类没有提供整个字符串的输入处理操作,只能利用字符数组接收
    在这里插入图片描述

  • FileReader:文件输入流

  • CharArrayReader访问字符数组

  • PipedReader:访问管道

  • BufferReader:缓冲输入流

  • StringReader:访问字符串

Writer

  • 字符输出流:必须关闭(close)或刷新(flush)流才能写入数据

在这里插入图片描述

  • FileWriter:文件输入流
  • CharArrayWriter:访问字符数组
  • PipedWriter:访问管道
  • BufferWriter:缓冲输出流
  • StringWriter:访问字符串
//输出字符数组
public void write(char[] cbuf) throws IOException;
//输出字符串,默认覆盖原数据
public void write(String str) throws IOException;
//追加字符串,可直接在原文件后面追加内容,不会覆盖原数据
	// 或构造对象时添加参数为 true 也可追加内容
public Writer append(CharSequence csq) throws IOException;

字符流和字节流的区别

  • OutputStream 输出不使用 close 关闭可以正常写入文件
    • Writer 必须 close(关闭) 或 flush(刷新)才能写入文件
    • 因为 Writer 输出流使用了缓冲区
      • 使用 close 方法时会强制刷新缓冲区,将数据内容写入文件
  • 字节流处理不使用缓冲区,而字符流处理使用了缓冲区
    • 字符流在处理数据时会在缓冲区将二进制数据进行先期处理
      • 此时就方便处理文本信息,尤其是中文数据
      • 中文在不同编码下占用字节不同,所以字符流更适合处理中文数据
    • 在开发中涉及中文数据的输出一般都使用字符流处理
  • 字节流和字符流的基本处理方式基本相同;都使用 close() 方法关闭

节点流

  • 文件操作流

    • 利用 InputStream 读取文件内容,利用 OutputStream 写入内容

    • 所有操作以文件为终端(磁盘终端)

    • 例如:FileInputStreamFileReader

  • 从特定数据源读写数据,如FileInputStream

    • 底层流(低级流),直接跟数据源相接

FileInputStream

构造方法
  • 从文件读取数据;其对象可以用关键字 new 创建,有多种构造方法可用来创建对象
//可以使用字符串类型的文件名来创建一个输入流对象来读取文件
InputStream f = new FileInputStream("C:/java/hello");
//可以使用一个文件对象来创建一个输入流对象来读取文件
File f = new File("C:/java/hello");
InputStream in = new FileInputStream(f);
常用方法
方法 描述
public void close() throws IOException{} 关闭此文件输入流并释放与此流有关的所有系统资源。抛出IOException异常。
protected void finalize()throws IOException {} 清除与该文件的连接。确保在不再引用文件输入流时调用其 close 方法。抛出IOException异常。
public int read(int r)throws IOException{} InputStream 对象读取指定字节的数据。返回为整数值。如果已经到结尾则返回-1。
public int read(byte[] r) throws IOException{} 从输入流读取r.length长度的字节。返回读取的字节数。如果是文件结尾则返回-1。
public int available() throws IOException{} 返回下一次对此输入流调用的方法可以不受阻塞地从此输入流读取的字节数。返回一个整数值

FileOutputStream

构造方法
  • 创建一个文件并向文件中写数据
    • 如果该流在打开文件进行输出前,目标文件不存在,那么会自动创建该文件
//使用字符串类型的文件名来创建一个输出流对象
OutputStream f = new FileOutputStream("C:/java/hello")
//也可以使用一个文件对象来创建一个输出流来写文件首先得使用File()方法来创建一个文件对象
File f = new File("C:/java/hello");
OutputStream fOut = new FileOutputStream(f);
常用方法
方法 描述
public void close() throws IOException{} 关闭此文件输入流并释放与此流有关的所有系统资源。抛出IOException异常
protected void finalize() throws IOException {} 清除与该文件的连接。确保在不再引用文件输入流时调用其 close 方法。抛出IOException异常
public void write(int w) throws IOException{} 把指定的字节写到输出流中
public void write(byte[] w) 把指定数组中w.length长度的字节写到OutputStream
示例
public class fileStreamTest {
    public static void main(String[] args) {
        try {
            byte bWrite[] = { 11, 21, 3, 40, 5 };
            OutputStream os = new FileOutputStream("test.txt");			// 实例化文件输出流对象
            for (int x = 0; x < bWrite.length; x++) {
                os.write(bWrite[x]); 									// 通过字节数组输出
            }
            os.close();													// 关闭输出流
            InputStream is = new FileInputStream("test.txt");			// 实例化同一个文件到输入流对象
            int size = is.available();									// 可读取文件字节长度
            for (int i = 0; i < size; i++) {
                System.out.print((char) is.read() + "  ");				// 单个读取
            }
            is.close();													// 关闭输入流
        } catch (IOException e) {
            System.out.print("Exception");
        }
    }
}
/**
首先创建文件test.txt,并把给定的数字以二进制形式写进该文件,同时输出到控制台上
由于是二进制写入,可能存在乱码, 跟字符集编码无关, 是由于读写的格式不同导致的
*/
解决乱码
// 通过以下代码实例来解决乱码问题
public class fileStreamTest2 {
    public static void main(String[] args) throws IOException {
        File f = new File("a.txt");								// 实例化文件对象
        FileOutputStream fop = new FileOutputStream(f);			// 得到文件输出流 
        OutputStreamWriter writer = new OutputStreamWriter(fop, "UTF-8");	// 构建输出转换流对象,指定编码,windows默认编码是gbk
        writer.append("中文输入");								 // 写入到缓冲区 
        writer.append("\r\n");       	   						// 换行 
        writer.append("English");
        write.flush 											// 刷新缓存写入到文件,如果没有后续写入直接close也可以
        writer.close();     			  						// 关闭写入流,同时会把缓冲区内容写入文件 
        fop.close();					  						// 关闭输出流,释放系统资源
        FileInputStream fip = new FileInputStream(f);    		// 构建FileInputStream对象
        InputStreamReader reader = new InputStreamReader(fip, "UTF-8");		// 构建转换输入流对象,编码与写入相同
        StringBuffer sb = new StringBuffer();
        while (reader.ready()) {
            sb.append((char) reader.read());					// 转成char加到StringBuffer对象中
        }
        System.out.println(sb.toString());
        reader.close();		 									// 关闭读取流
        fip.close();											// 关闭输入流,释放系统资源
    }
}

FileReader

  • FileReader类从InputStreamReader类继承而来
    • 该类按字符读取流中数据
构造方法
// 构造方法:在给定从中读取数据的 File 的情况下创建一个新 FileReader
FileReader(File file);
// 在给定从中读取数据的 FileDescriptor 的情况下创建一个新 FileReader
FileReader(FileDescriptor fd);
// 在给定从中读取数据的文件名的情况下创建一个新 FileReader
FileReader(String fileName); 
常用方法
方法 描述
public int read() throws IOException 读取单个字符,返回一个int型变量代表读取到的字符
public int read(char [] c, int offset, int len) 读取字符到 c 数组,返回读取到字符的个数

FileWriter

  • FileWriter 类从 OutputStreamWriter 类继承而来
    • 该类按字符向流中写入数据
构造方法
//在给出 File 对象的情况下构造一个 FileWriter 对象
FileWriter(File file);
//在给出 File 对象的情况下构造一个 FileWriter 对象
FileWriter(File file, boolean append);
	//file:要写入数据的 File 对象	
	//append:参数为true,将字节写入文件末尾处,相当于追加信息;参数为 false, 则写入文件开始处
//构造与某个文件描述符相关联的 FileWriter 对象
FileWriter(FileDescriptor fd)
//在给出文件名的情况下构造 FileWriter 对象,它具有指示是否挂起写入数据的 boolean 值
FileWriter(String fileName, boolean append)
常用方法
方法 描述
public void write(int c) throws IOException 写入单个字符c
public void write(char [] c, int offset, int len) 写入字符数组中开始为offset长度为len的某一部分
public void write(String s, int offset, int len) 写入字符串中开始为offset长度为len的某一部分
Reader 和 Writer 实例
public static void main(String args[]) throws IOException {    
    File file = new File("Hello1.txt");     	   // 创建文件   
    file.createNewFile();       
    FileWriter writer = new FileWriter(file);            
    writer.write("This\n is\n an\n example\n");      // 向文件写入内容
    writer.flush();   
    writer.close();       
    FileReader fr = new FileReader(file);			// 创建 FileReader 对象    
    char[] a = new char[50];       
    fr.read(a); // 从数组中读取内容     
    for (char c : a)          
        System.out.print(c); // 一个个打印字符     
    fr.close();   
}
输出结果:
This
is
an
exampl

内存操作流

优势

  • 实现IO操作
  • 不产生文件(相当于临时文件),以内存为终端处理

字节

  • ByteArrayInputStream
    • 直接继承 InputStream
    • public ByteArrayInputStream(byte[] buf)
  • ByteArrayOutputStream
    • 直接继承 OutputStream
    • public ByteArrayOutputStream(byte[] buf)
    • 获取数据
      • public byte[] toByteArray():获取字节数组
      • public String toString():获取字符串
ByteArrayInputStream
  • 字节数组输入流在内存中创建一个字节数组缓冲区
    • 从输入流读取的数据保存在该字节数组缓冲区中
//接收字节数组作为参数创建
ByteArrayInputStream bArray = new ByteArrayInputStream(byte [] a);
//接收一个字节数组和两个整形变量 off、len,off表示第一个读取的字节,len表示读取字节的长度
ByteArrayInputStream bArray = new ByteArrayInputStream(byte []a, int off, int len);
方法 描述
public int read() 从此输入流中读取下一个数据字节
public int read(byte[] r, int off, int len) 将最多 len 个数据字节从此输入流读入字节数组
public int available() 返回可不发生阻塞地从此输入流读取的字节数
public void mark(int read) 设置流中的当前标记位置
public long skip(long n) 从此输入流中跳过 n 个输入字节
ByteArrayOutputStream
  • 字节数组输出流在内存中创建一个字节数组缓冲区
    • 所有发送到输出流的数据保存在该字节数组缓冲区中
//构造方法创建一个32字节(默认大小)的缓冲区
OutputStream Out = new ByteArrayOutputStream();
//另一个构造方法创建一个大小为 a 字节的缓冲区。
OutputStream Out = new ByteArrayOutputStream(int a)
方法 描述
public void reset() 将此字节数组输出流的 count 字段重置为零,从而丢弃输出流中目前已累积的所有数据输出
public byte[] toByteArray() 创建一个新分配的字节数组。数组的大小和当前输出流的大小,内容是当前输出流的拷贝
public String toString() 将缓冲区的内容转换为字符串,根据平台的默认字符编码将字节转换成字符
public void write(int w) 将指定的字节写入此字节数组输出流
public void write(byte []b, int off, int len) 将指定字节数组中从偏移量 off 开始的 len 个字节写入此字节数组输出流
public void writeTo(OutputStream outSt) 将此字节数组输出流的全部内容写入到指定的输出流参数中
实例
public class ByteStreamTest {
   public static void main(String args[])throws IOException {
      ByteArrayOutputStream bOutput = new ByteArrayOutputStream(12);
      while( bOutput.size()!= 5 ) {					// 流长度不到 5 持续输入
         bOutput.write(System.in.read()); 			// 写入用户输入内容
      }
      byte b [] = bOutput.toByteArray();			// 转为字节数组
      System.out.print("输入内容:");
      for(int x= 0 ; x < b.length; x++) {
         System.out.print((char)b[x]  + "   ");  	// 打印字符 
      }
      System.out.println();							// 换行
      int c;										
      ByteArrayInputStream bInput = new ByteArrayInputStream(b);			// 通过写入输出流的字节数组构建字节数组输入流
      System.out.println("所有字符转为大写:" );
      for(int y = 0 ; y < 1; y++ ) {
         while(( c= bInput.read())!= -1) {						
            System.out.println(Character.toUpperCase((char)c));				// 从流中读取字符并转为大写输出
         }
         bInput.reset(); 													// 重置流数据
      }
   }
}
/**
运行结果:
asdfg
输入内容:a   s   d   f   g
所有字符转为大写:A	S	D	F	G

字符

  • CharArrayReader
    • 直接继承 Reader
  • CharArrayWriter
    • 直接继承 Writer

处理流

作用

也叫包装流,连接已存在的流,为程序提供更强大的读写能力

  1. 包装节点流,既消除不同节点流的实现差异,也提供更方便的方法完成输入输出
  2. 对节点流包装使用了修饰器包装模式,不跟数据源直接相连

功能体现

  1. 性能的提高:以增加缓冲的方式提高输入输出效率
  2. 操作的便捷:处理流可能提供了一系列便捷的方法来一次输入输出大批量数据,使用更加灵活方便
  • 实现类
    • BufferInputStream:缓冲输入流
    • BufferOutputStrean:缓冲输出流
    • BufferReader:缓冲输入流
    • BufferWriter:缓冲输出流
    • ObjectOutputStream:对象处理流,提供序列化功能
    • ObjectInputStream:对象处理流,提供反序列化功能

字符编码

计算机中只有0、1数据,想描述一些数据就需要对其进行组合,即编码

  • 编码之后想正确解析内容需要解码,编码和解码采用统一标准
  • 若不统一就会出现乱码问题
  • 编码的统一是解决乱码问题的唯一途径
常用编码
  • GBK/GB2312:国标编码,可描述中文信息
    • GBK 包含简体中文和繁体中文,GBK2312只有简体中文
  • ISO8859_1:国际通用编码,可描述所有文字信息
    • 但处理不当也会出现乱码
  • Unicode:采用 16 进制方式存储,可描述所有文字信息
    • 最大缺点是大
  • UTF:象形文字部分使用16进制编码,普通字母采用 ISO8859_1编码
    • 优势在于适合快速的传输,节约宽带;因此开发中首选编码
    • 存在 UTF-16 和 UTF-8,主要使用 UTF-8
使用

列出本机全部属性

  • 包含文件路径分隔符、文件默认编码方式等
  • 获取当前系统支持的编码规则
    • System.getProperties().list(System.out);
    • 默认文件编码格式为 UTF-8
  • 强制设置编码
    • getByte()方法中可传入参数设置编码
    • 转换流可直接设置编码格式
    • 强制设为其他格式编码可能会出现乱码

项目中出现的乱码问题就是编码和解码标准的不统一

  • 最好的解决方法就是所有编码都使用 UTF-8

对象处理流

ObjectOutputStreamObjectInputStream

  • 进行序列化和反序列化
注意事项
  1. 读写顺序要一致
  2. 要求实现序列化或反序列化对象必须实现 Serializable接口
  3. 序列化的类中建议添加Serial VersionUID,提高版本兼容性
    • 同ID的类中属性更新会认为是更新了类的版本,而非是一个新的类
  4. 序列化对象时,默认将所有属性序列化
    • 但除了statictransient修饰的成员
  5. 序列化对象时,要求里面属性的类型也实现序列化接口
  6. 序列化具备可继承性
    • 某类实现序列化后,其所有子类也默认实现序列化

序列化与反序列化必须使用对象操作流

  • 因为有关二进制数据的格式,所以不能够自定义处理
  • 要想实现一组对象的序列化,则可以使用对象数组完成
  • 在很多的实际项目开发过程之中,很少见到 ObjectOutputStreamObjectInputStream直接操
    • 会有一些容器帮助自动实现
类名称 序列化:ObjectOutputStream 反序列化:ObjectInputStream
构造方法 Public ObjectOutputStream(OutputStream out) public ObjectInputStream(InputStream in)
操作方法 public final void writeObject(Object obj) throws IOException public final Object readObject() throws IOException, ClassNotFoundException
序列化
  • 又称为持久化,将其写入磁盘中

    • 该机制中,一个对象可以被表示为一个字节序列
    • 包括该对象的数据、对象的类型和存储在对象中数据的类型
  • 使用 ObjectOutputStream

    • 将内存中保存的对象以二进制数据流形式处理

      • 保存数据时保存数据的值和类型
      • 可以是某类的实例化对象
        • 要有该类的访问权限

        • 且该类必须可以序列化

  • 要使某类的对象支持序列化机制,则该类必须可序列化

    • 实现 Serializable 接口

      • 标记接口(常用,无方法)

      • 建议实现该接口的类中加上 SerializableID

        • SerializableID 号根据类的特征和类的签名算出,长是为了避免重复

        • 给类加 id 用于判断类和对象是否是同一个版本

        • 如果未显式声明 serialVersionUID,序列化运行时将基于该类的各个方面计算该类的默认 serialVersionUID

          • 计算默认的 serialVersionUID 对类的详细信息具有较高的敏感性,根据编译器实现的不同可能千差万别
          • 在反序列化过程中可能会导致意外的 InvalidClassException
    • 实现 Externalizable 接口

      • 继承了Serializable,此接口存在方法需要重写
  • transient:表示瞬态,被 transient 关键字修饰的变量不再能被序列化

    • 变量被transient修饰,将不再是对象持久化的一部分
      • 该变量内容在序列化后无法获得访问,会初始化默认值
      • 静态变量不管是否被transient修饰,均不能被序列化
    • 只能修饰变量,而不能修饰方法和类
      • 本地变量不能被 transient 修饰的
      • 用户自定义类变量,该类需要实现Serializable接口
    • 实际开发需要序列化的大多是简单Java类,transient出现频率并不高
  • 序列化方法

    • public final void writeObject(Object x) throws IOException

      • 序列化一个对象,并发送到输出流
      • 序列化一个对象到文件时,按照 Java 标准约定给文件一个 .ser 扩展名
    • WriteObject 方法实际调用 writeObject0 方法执行序列化

      • 当对象类型为 StringArrayEnumSerializable 时正常执行
      • 否则抛出 NotSerializableException 异常
    • private void writeObject0(Object obj, boolean unshared)throws IOException {
      	// ...... 省略代码
          if (obj instanceof String) {
              writeString((String) obj, unshared);
          } else if (cl.isArray()) {
              writeArray(obj, desc, unshared);
          } else if (obj instanceof Enum) {
              writeEnum((Enum<?>) obj, desc, unshared);
          } else if (obj instanceof Serializable) {
              writeOrdinaryObject(obj, desc, unshared);
          } else {
              if (extendedDebugInfo) {
                  throw new NotSerializableException(
                      cl.getName() + "\n" + debugInfoStack.toString());
              } else {
                  throw new NotSerializableException(cl.getName());
              }
          }
          // ...... 省略代码
      }
      
反序列化
  • 将序列化对象写入文件之后,可从文件中读取出来,并进行反序列化
    • 即对象的类型信息、对象的数据、对象中的数据类型可以用来在内存中新建对象
  • 使用 ObjectInputStream
    • 恢复数据时恢复数据的值和数据类型
    • public final Object readObject() throws IOException, ClassNotFoundException
      • 反序列化对象
      • 从流中取出下一个对象,并将对象反序列化
      • 返回值为Object,需要将转换成合适的数据类型
        • 不能被序列化的属性反序列化后只有默认值
  • JVM 反序列化对象,必须能够找到字节码的类
    • 如果反序列化对象的过程中找不到该类,抛出 ClassNotFoundException 异常

缓冲流

字节
BufferInputStream
BufferOutputStrean
字符
BufferReader
  • 提供缓冲字符输入流,在 JDK 1.5 之前使用来提供完善的数据输入处理
  • 通过转换流传入 System.in(nputStream),使用BufferReader 类的 readLine方法进行键盘输入
  • 读取到字符串在进行各类复杂操作
  • JDK 1.5 之后出现了 Scanner 工具类替代

在这里插入图片描述

读取多字符输入

  • BufferedReader对象读取一个字符要用read()方法

    • int read() throws IOException
      
      • 每次调用read()方法,会从输入流读取一个字符并把该字符作为整数值返回

      • 当流结束的时候返回-1

      • 该方法抛出异常IOException

    • public static void main(String[] args) throws IOException{  
          char c;   
          // 使用 System.in 创建 BufferedReader  
          BufferedReader br = new BufferedReader(new InputStreamReader(System.in));   
          // 读取字符
          do {    
              c = (char) br.read();   
              System.out.print("已读取:" + c+" "); 
          } while (c != 'q');
      }//用 read() 方法从控制台不断读取字符直到用户输入 q
      运行结果:
      runoob
      已读取:r 已读取:u 已读取:n 已读取:o 已读取:o 已读取:b  已读取:	
      q
      已读取:q 
      

读取字符串

  • 从标准输入读取一个字符串需要使用 BufferedReader readLine() 方法

    • String readLine() throws IOException
      
  • public static void main(String[] args) throws IOException {  
        // 使用 System.in 创建 BufferedReader 
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); 
        String str;   
        do {     
            str = br.readLine();						//读取字符串,直到控制台输入回车   
            System.out.println({"已读取:"+str);  
            } while (!str.equals("end"));				// 到用户输入 end 时停止 while 循环
    }
    //读取和显示字符行直到输入了单词"end"
    运行结果:
    测试 文本
    已读取:测试 文本
    二次测试
    已读取:二次测试
    end
    已读取:end
    
BufferWriter

转换流

  • InputStreamReader

    • Reader的子类,可以将InputStream包装(转换)成Reader
    • FileReader 类继承该类
  • OutputStreamWriter

    • Writer的子类,可以将OutputStream包装(转换)成Writer
    • FileWriter类继承该类
  • 在处理纯文本数据时,使用字符流效率更高,且有效解决中文问题,建议将字节流转换成字符流

    • 可以在使用时指定编码格式
  • 文件以二进制方式存储,即所有文件在电脑中都是二进制数据的集合

    • 缓冲区会将二进制数据进行初步处理
    • 转换流的处理在定义结构上更清楚的描述出所有读取到的字节数据,并要求进行转换处理

在这里插入图片描述

在这里插入图片描述

Scanner

java.util.Scanner 是 JDK 1.5 之后追加的工具类

  • 主要为解决输入流的访问问题

    • BufferReader 的替代类

    • 通过Scanner类来获取用户的输入

      • Scanner in = new Scanner(System.in);//基本语法	
        
    • 以换行结束输入

  • 在开发中数据输出使用 打印流

  • 数据输入使用 Scanner(首选)或 BufferedReader

  • //构造方法
    	//接收 InputStream 对象
        public Scanner(InputStream source) {};   // Scanner sc = new Scanner(System.in);
    	//也可直接传入文件,在底层代码使用 InputStream 创建对象
    //判断是否有数据,传入正则式可直接验证格式
    	//hasNext() 和 next() 方法一一对应
        public boolean hasNext();	 
    //取出数据
    	//从读取以一个非空字符开始,到第一个空格结束
    		//不包括换行,传入正则式直接验证格式
        public String next(); 		
    	//从第一个字符开始(包括空字符)到换行为结束
    		//包括换行,可直接传入正则限制输入格式
        public String nextLine();	
    //设置分隔符
        public Scanner useDelimiter(String pattern)
    

BufferedReader、Scanner

  • BufferedReader 是支持同步的,而 Scanner 不支持
    • 处理多线程程序,BufferedReader 应当使用
  • BufferedReader 相对于 Scanner 有足够大的缓冲区内存
    • Scanner 有很少的缓冲区(1KB 字符缓冲)相对于 BufferedReader(8KB字节缓冲)
    • 但是这是绰绰有余的
  • BufferedReader 相对于 Scanner 来说要快一点
    • Scanner 能对输入数据进行类解析,而 BufferedReader 只是简单地读取字符序列

为获得一个绑定到控制台的字符流,可以把System.in包装在一个BufferedReader对象中来创建一个字符流

BufferedReader br = new BufferedReader(new InputStreamReader(System.in));		//基本用法
//BufferedReader对象创建后,可以使用read()方法从控制台读取一个字符或reanLine()方法读取一个字符串

字符串读取

  • 通过 Scanner 类的 next()nextLine() 方法获取输入的字符串

    • 读取前一般需要使用 hasNext hasNextLine 判断是否还有输入的数据
Scanner in = new Scanner(System.in);	
System.out.print("请输入:");
if (in.hasNext()) {	
    String str = in.next();	
    System.out.println("next方法接收写入的数据为:" + str);	
}
in.close();
运行结果:
请输入:宝儿
next方法接收写入的数据为:宝儿
---------------------------------------------------
Scanner in = new Scanner(System.in);	
System.out.print("请输入:");   
if (in.hasNextLine()) {		
    String str1 = in.nextLine();	
    System.out.println("nextLine方法接收写入的数据为:" + str1);  
}
in.close();
运行结果:
请输入:宝儿 你知道吗
nextLine方法接收写入的数据为:宝儿 你知道吗
---------------------------------------------------
// 通过StringTokenizer类可以分解输入的整行得到的带空格的字符串
// 默认情况下,StringTokenizer以空格,制表符,换行符和回车符作为分割依据
Scanner scanner = new Scanner(System.in);
System.out.println("输入数据:");
StringTokenizer stringTokenizer = new StringTokenizer(scanner.nextLine());
System.out.println("分隔后:");
while(stringTokenizer.hasMoreTokens()){    
     System.out.println(stringTokenizer.nextToken());
}
运行结果:
输入数据:
宝儿 你知道吗
分隔后:
宝儿
你知道吗   

next() 与 nextLine()

  1. next()

    1. 一定要读到有效字符才可以结束输入
    2. 对输入有效字符前遇到的空白,next()方法会自动将其去掉
    3. 只有输入有效字符后才将后面输入的空白作为分隔符或结束符
    4. next()不能得到带有空格的字符2
  2. nextLine()

    1. Enter为结束符,即nextLine()方法返回的是输入回车前的所有字符
    2. 可以获得空白

数字类型

  • 输入intfloat类型或其它类型的数据的数据
    • 可以使用nextIntnextFloatnextXxx来读取,但最好先使用hashNextXxx方法验证
//处理接受整理和小数的逻辑时,需要在 else 的分支逻辑里把非整数的输入接收走,否则会影响后续 hasNextFloat 判断和接受小数的逻辑
Scanner in = new Scanner(System.in);
System.out.print("输入整数:");
if(in.hasNextInt()){   
    int i = in.nextInt();   
    System.out.println("输入的数:");
}else{   
    in.nextLine();    //把其他类型收掉,否则会影响到后边接受小数的逻辑 
    System.out.println("输入的不是整数");
}
System.out.print("输入小数:");
if(in.hasNextFloat()){   
    float f = in.nextFloat();   
    System.out.println("输入的数:");
}else{   
    in.nextLine();//把非整数的输入接收走   
    System.out.println("输入的不是小数");
}in.close();
运行结果:
输入整数:12
整数数据:12
输入小数:1.2
小数数据:1.2
  • 输入多个数字,并求其总和与平均数,每输入一个数字用回车确认,通过输入非数字来结束输入并输出执行结果
System.out.println("请输入数字:");
Scanner scan = new Scanner(System.in);
double sum = 0;
int m = 0;
while (scan.hasNextDouble()) {
    double x = scan.nextDouble();
    m = m + 1;
    sum = sum + x;
}
System.out.println(m + "个数的和为" + sum);
System.out.println(m + "个数的平均值是" + (sum / m));
scan.close();
运行结果:
请输入数字:
1 2 3 4 5.67 8
end
6个数的和为23.4
6个数的平均值是3.9

控制台输入

  • Java的控制台由System.in完成
    • 使用 ScannerBufferReader 读取

控制台输出

  • print()println() 完成

    • 方法都由类 PrintStream 定义
    • System.out 是该类对象的一个引用
  • PrintStream 继承了 OutputStream

    • 实现了方法 write()

    • write() 也可以用来往控制台写操作

    • void write(int byteval)		//最简单格式,将 byteval 的低八位字节写到流中
      
  • int b = 'A';
    System.out.write(b);
    System.out.write('\n');
    运行结果:
    A
    

Console

  • 输入的时候字符都是可见的,所以Scanner类不适合从控制台读取密码

    • 从Java SE 6开始特别引入了Console类来实现这个目的
    • 如果直接在集成开发环境下执行代码会报错
  • 若要读取一个密码,可以采用下面这段代码

  • Console cons = System.console();
    String username = cons.readline("User name: ");
    char[] passwd = cons.readPassword("Password: ");
    
    • 为了安全起见,返回的密码存放在一维字符数组中,而不是字符串中

      • 对密码进行处理之后,应用一个填充值覆盖数组元素
    • 采用Console对象处理输入不如采用Scanner方便

      • 每次只能读取一行输入,而没有能够读取一个单词或者一个数值的方法

管道流

  • 实现两个线程之间的 IO 操作

  • 管道两侧一个只负责发送,另一个只负责接收;中间靠管道连接

  • 单次发送(循环写入的时候是将所有循环执行完毕后合为一条语句发送)

    • 读取也是单次读取所有数据

    • IO 中不能自动循环发送和接收(while、for)

字节

  • PipedInputStreamPipedOutputStream

    • InputStreamOutputStream 直接子类
  • 连接处理

    //PipedOutputStream
    public synchronized void connect(PipedInputStream snk);
    //PipedInputStream
    public void connect(PipedOutputStream src){
        //调用PipedOutputStream 类的 connect() 方法连接此对象
    	src.connect(this);
    }
    

字符

  • PipedReaderPipedWriter

    • ReaderWriter 直接子类
  • 连接处理

    //PipedReader 类
    public void connect(PipedWriter src){
        //调用 PipWriter类方法与此对象的连接
        src.connect(this);
    }
    //PipedWriter 类
    public synchronized void connect(PipedReader snk);
    

RandomAccessFile

  • 文件内容处理操作主要通过 InputStreamOutputStreamReaderWriter 实现,

    • 这些类实现的内容读取只能将部分数据读取进来

    • 若文件很大,按照传统IO操作进行读取和分析无法完成

  • java.io包中 RandmAccessFile 类可实现文件跳跃式读取内容

    • 需要有完善的保存形式,数据保存位数确定
    • 每项数据占固定位数,每行数据固定位数
public RandomAccessFile(File file)throws FileNotFoundException;
// 文件处理模式: r rw (rws、rwd 不常用)
/*
该类最大特点在于数据的读取处理:
因为所有数据是按照固定长度保存,所以可以进行跳字节读取
*/
//向下跳
public int skipBytes(int n)throws IOException;
//往回跳
public void seek(long pos)throws IOException;

实例

public class Test{
    public static void main(String[] args){
        File file = new File("D:\\Java\\demo\\demo.txt");
        //创建RandomAccessFile对象,rw:读写模式
        RandomAccessFile rw = new RandomAccessFile(file, "rw");
      /*
        写入数据
            //姓名占8位,年龄占4位(int类型占4字节)
        String[] names = {"zhangsan","wangwu  ","lisi    "};
        int[] ages = {25,20,15};
        //循环写入数据,格式:姓名+年龄
        for (int i = 0; i < names.length; i++) {
            rw.write(names[i].getBytes());
            rw.writeInt(ages[i]);
        }
      */
        //读取 lisi: 跳过 24 位,一次读取8位
        byte[] data = new byte[8];
        rw.skipBytes(24);
        rw.read(data);
        System.out.println("姓名: " + new String(data).trim() + ",年龄: " + rw.readInt());
        //回到第12位开始向后读取;读取 wangwu
        rw.seek(12);
        rw.read(data);
        System.out.println("姓名: " + new String(data).trim() + ",年龄: " + rw.readInt());
        //回到起点;读取 zhangsan
        rw.seek(0);
        rw.read(data);
        System.out.println("姓名: " + new String(data).trim() + ",年龄: " + rw.readInt());
        rw.close();
    }
}

System 类

标准输入

  • System.in

    • public static final InputStream in = null

      • 编译类型:InputStream

      • 运行类型:BufferedInputStream

    • 默认设备:键盘

  • 实际开发键盘输入都使用字符串接收

    • 方便后续进行验证以及各种复杂处理

标准输出

  • System.out

    • public static final PrintStream out = null

    • 编译、运行类型:PrintStream

    • 默认设备:显示器

错误输出

  • System.err

    • public static final PrintStream err = null
      • 编译、运行类型:PrintStream
  • System.outSystem.err 输出结果相同

    • 早期设计目标:一个输出用户可见信息,另一个输出用户不可见信息

    • 现在使用 err 进行输出IDE会自动处理为红色字体, out 为黑色字体

打印流

场景

只有输出,没有输入

  • 通过程序实现内容的输出的核心本质一定是OutputStream

    • 该类最大的缺点是数据输出操作功能有限
      • 所有数据必须转为字节数组之后才能输出
    • long、double、Date 等类型数据必须转为字节形式处理,十分麻烦
      • 最初的开发会自行定义功能类进行输出简化
  • 相比于使用OutputStream 类,打印流使用更加方便

    • 只进行内容输出时全部使用打印流完成

    • 进行文件操作输入流进行时才考虑使用循环拷贝(IO 流参与)

在这里插入图片描述

PrintStream
  • 字节打印流

    • 超类:OutputStream,父类:FilterOutputStream
  • PrintStream out = System.out;				 //运行类型为PrintStream
    out.print("你好");
    try {
        out.write("你好".getBytes());				//将字符串转化为字符数组
        // 本质上讲两种方法打印效果一致1,print() 底层使用 write() 方法打印
    } catch (IOException e) {
        e.printStackTrace()
    }
    out.close();								 //运行后关闭流
    /**
    运行结果:
    你好你好
    */
    
    //修改打印位置
    try {
    	System.setOut(new PrintStream("D:/new.txt"));//传入文件路径
        //将打印位置修改到D:/new.txt
    } catch (FileNotFoundException e) {
    	e.printStackTrace();
    }
    System.out.println("你好世界");
    // 运行结果:在D盘新建 new.txt 文件并将 “你好世界” 写进去
    
PrintWriter
  • 字符打印流

    • 父类:Writer;可接收 OutuputStream 和 Writer
  • PrintWriter pw = null;
    try {
    	pw = new PrintWriter(new FileWriter("D:\\a.txt"));
        //修改打印位置到D:\\a.txt
        pw.print("你好,世界");
    } catch (IOException e) {
        e.printStackTrace();
    }finally{
        pw.close();
        //如果没有关闭或flush()则不会写入数据,在close()方法底层才写数据
    }
    //运行结果:
    在文件 D:\\a.txt 中写入数据:你好,世界
    
  • JDK 1.5 之后追加格式化输出支持

    • public PrintStream printf(String format, Object ... args)

    • public PrintWriter printf(String format, Object...args)

装饰设计模式

例如:OutputStream 是唯一可以实现输出的操作标准类

  • 以其为核心根本,但该类输出的操作功能有限,不方便输出各类型数据
  • 为其进行包装,此时设计思想为 “ 装饰设计模式 ”
示例
  • 简单实现多种数据写入
package demo03;
import java.io.*;
import java.text.SimpleDateFormat;
import java.util.Date;
public class Test implements AutoCloseable{			// 实现自动关闭接口,可实现异常捕获自动关闭流功能
    private OutputStream output;  					// 定义输出流对象
    public Test(OutputStream output){
        this.output = output;						// 使用顶级接口接收;根据操作需求传入不同的子类实例对象
    }
    public void print(String str) throws IOException {
        this.output.write(str.getBytes());
    }
    public void println(String str) throws IOException {		// 复用本类代码,降低冗余
        this.print(str + "\n");
    }
    public void print(long l) throws IOException {				// 方法重载,实现多种数据类型接收
        this.print(String.valueOf(l));							// 复用 print(String str) 方法处理
    }
    public void println(long l) throws IOException{
        this.println(String.valueOf(l));
    }
    public void print(Date date) throws IOException {
        this.print(new SimpleDateFormat("yy-MM-dd hh:mm:ss").format(date));
    }
    public void println(Date date)throws IOException{
        this.println(new SimpleDateFormat("yy-MM-dd hh:mm:ss").format(date));
    }
    //重写 close() 方法实现关闭流(可自动关闭)
    @Override
    public void close() throws Exception {
        this.output.close();
    }
    public static void main(String[] args) throws IOException {
        File oldFile = new File("D:\\Java\\demo\\demo.txt");
        try {
            Test test = new Test(new FileOutputStream(oldFile));
            test.print(new Date());
            test.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

数据流

DataInputStream

构造
  • 数据输入流允许应用程序以与机器无关方式从底层输入流中读取基本 Java 数据类型

  • DataInputStream dis = new DataInputStream(InputStream in);		//构造方法
    DataInputStream dis = new DataInputStream (byte []a, int off, int len);
    //接收一个字节数组,和两个整形变量 
    //off、len,off表示第一个读取的字节,len表示读取字节的长度
    
常用方法
方法 描述
public final int read(byte[] r, int off, int len) throws IOException 从所包含的输入流中将 len 个字节读入一个字节数组中。如果len为-1,则返回已读字节数
Public final int read(byte [] b) throws IOException 从所包含的输入流中读取一定数量的字节,并将它们存储到缓冲区数组 b
public final Boolean readBooolean() throws IOException 从输入流中读取字节,返回输入流中两个字节作为对应的基本数据类型返回值
public final byte readByte() throws IOException
public final short readShort() throws IOException
public final Int readInt() throws IOException
public String readLine() throws IOException 从输入流中读取下一文本行

DataOutputStream

构造
  • 数据输出流允许应用程序以与机器无关方式将Java基本数据类型写到底层输出流

    • DataOutputStream out = new DataOutputStream(OutputStream out);		//构造方法
      
常用方法
方法 描述
public final void write(byte[] w, int off, int len) throws IOException 将指定字节数组中从偏移量 off 开始的 len 个字节写入此字节数组输出流
Public final int write(byte [] b) throws IOException 将指定的字节写入此字节数组输出流
public final void writeBooolean() throws IOException 这些方法将指定的基本数据类型以字节的方式写入到输出流
public final void writeByte() throws IOException
public final void writeShort() throws IOException
public final void writeInt() throws IOException
Public void flush() throws IOException 刷新此输出流并强制写出所有缓冲的输出字节
Public final void writeBytes(String s) throws IOException 将字符串以字节序列写入到底层的输出流,字符串中每个字符都按顺序写入,并丢弃其高八位。
实例
//DataInputStream和DataOutputStream的使用/
/从文本文件test.txt中读取5行,并转换成大写字母,最后保存在另一个文件test1.txt中
    public static void main(String args[])throws IOException{   
    DataInputStream in = new DataInputStream(new FileInputStream("test.txt"));   
    DataOutputStream out = new DataOutputStream(new  FileOutputStream("test1.txt")); 
    BufferedReader d  = new BufferedReader(new InputStreamReader(in));      
    String count;     
    while((count = d.readLine()) != null){      
        String u = count.toUpperCase();       
        System.out.println(u);      
        out.writeBytes(u + "  ,");     
    }  
    d.close();  
    out.close();  
}
运行结果:
RUNOOB1RUNOOB2RUNOOB3RUNOOB4RUNOOB5

文件拷贝

原始操作

package demo03;

import java.io.*;
public class Test {
    private File oldFile;
    private File newFile;

    public Test(String oldFile, String newFile) {
        this(new File(oldFile), new File(newFile));
    }

    public Test(File oldFile, File newFile) {
        this.oldFile = oldFile;
        this.newFile = newFile;
    }

    public boolean copy() {
        //判断原文件是否存在
        if (oldFile == null || !oldFile.exists()){
            System.out.println("原文件不存在");
            return false;
        }
        //判断目标路径是否正确
        if (newFile == null || !newFile.getParentFile().exists()){
            System.out.println("目标路径不正确");
            return false;
        }
        FileInputStream input = null;
        FileOutputStream output = null;
        try {
            input = new FileInputStream(oldFile);
            output = new FileOutputStream(newFile);
            byte[] data = new byte[10];
            int read = 0;
            //读取数据之后直接写入文件
            while ((read = input.read(data)) != -1) {
                output.write(data, 0, read);
            }
            return true;
        } catch (IOException e) {
            return false;
        } finally {
            //最后必须关闭流
            try {
                assert input != null;
                input.close();
                assert output != null;
                output.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) throws IOException {
        Test file = new Test("D:\\Java\\demo\\demo.txt", "D:\\Java\\test\\test.txt");
        if (file.copy()){
            System.out.println("拷贝成功");
        }else {
            System.out.println("拷贝失败");
        }

    }
}

转存方法

JDK 1.9 才支持此方法

//inputstream 和 reader 类中都追加有数据转存的处理操作方法
public long transferTo(Writer out) throws IOException;
public long transferTo(OutputStream out) throws IOExceptiom;
//前面代码同上
FileInputStream input = new FileInputStream(oldFile);
FileOutputStream output = new FileOutputStream(newFile);
//转存到目标文件
input.transferTo(output);
//后面代码同上

拷贝目录

package demo03;

import java.io.*;

public class Test {
    private File oldFile;
    private File newFile;

    public Test(File oldFile, File newFile) {
        this.oldFile = oldFile;
        this.newFile = newFile;
    }

    public boolean copyDir(){
        return copyImpl(oldFile);
    }
    /**
     * 拷贝目录实际实现
     * @param oldFile 原文件
     * @return 返回结果
     */
    private boolean copyImpl(File oldFile) {
        //判断原文件是否存在
        if (oldFile == null || !oldFile.exists()) {
            System.out.println("原文件路径不正确");
            return false;
        }
        if (oldFile.isDirectory()) {
            File[] files = oldFile.listFiles();
            if (files != null) {
                for (File file : files) {
                    copyImpl(file);
                }
            } else {
                System.out.println("原文件目录为 空");
                return false;
            }
        }else {
            /*
            将原目录中文件的路径改为目标目录中的对应路径
            oldFile.getFile() : 目录中的文件的路径,包含 原始路径 和 与原目录的相对路径
            this.oldFile.getPath() : 原目录原始路径
            this.newFile.getPath() : 目标目录原始路径
            src : 要拷贝到的文件路径
             */
            String src = oldFile.getPath().replace(this.oldFile.getPath(),this.newFile.getPath());
            return copyFileImpl(oldFile, new File(src));
        }
        return true;
    }

    /**
     * 拷贝文件的具体实现
     * @param oldFile 原文件
     * @param newFile 目标文件
     * @return 返回结果
     */
    private boolean copyFileImpl(File oldFile, File newFile) {
        //判断目标文件的父路径是否存在,不存在则直接创建
        if (!newFile.getParentFile().exists()){
            System.out.print(newFile.getParent());
            if (newFile.getParentFile().mkdirs()) {
                System.out.println("  目录不存在,已创建");
            }else {
                System.out.println("创建失败");
            }
        }
        FileInputStream input = null;
        FileOutputStream output = null;
        try {
            input = new FileInputStream(oldFile);
            output = new FileOutputStream(newFile);
            input.transferTo(output);
            return true;
        } catch (IOException e) {
            return false;
        } finally {
            //最后必须关闭流
            try {
                assert input != null;
                input.close();
                assert output != null;
                output.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 拷贝文件
     * @return 返回结果
     */
    public boolean copy() {
        //判断原文件是否存在
        if (oldFile == null || !oldFile.exists() || newFile == null) {
            System.out.println("参数错误 或 原文件不存在");
            return false;
        }
        return copyFileImpl(this.oldFile, this.newFile);
    }

    public static void main(String[] args) throws IOException {
        File oldFile = new File("D:\\Java\\demo\\");
        File newFile = new File("D:\\Java\\test\\");
        Test test = new Test(oldFile, newFile);
        if (oldFile.isDirectory() && newFile.isDirectory()) {
            if (test.copyDir()) {
                System.out.println("拷贝成功");
            } else {
                System.out.println("拷贝失败");
            }
        } else if (oldFile.isFile() && newFile.isFile()) {
            if (test.copy()) {
                System.out.println("目录拷贝成功");
            } else {
                System.out.println("目录拷贝失败");
            }
        } else {
            System.out.println("输入文件路径不匹配");
        }
    }
}
本文含有隐藏内容,请 开通VIP 后查看