Java日记(day21)--Java 流(Stream)、文件(File)和IO

199 阅读24分钟

Java 流(Stream)、文件(File)和IO

Java.io 包几乎包含了所有操作输入、输出需要的类。所有这些流类代表了输入源和输出目标。

Java.io 包中的流支持很多种格式,比如:基本类型、对象、本地化字符集等等。

一个流可以理解为一个数据的序列。输入流表示从一个源读取数据,输出流表示向一个目标写数据。Java 为 I/O 提供了强大的而灵活的支持,使其更广泛地应用到文件传输和网络编程中。

1、File类的使用

  • File类的一个对象,代表一个文件或一个文件目录(俗称:文件夹)
  • File类声明在java.io包下
  • File类中涉及到关于文件或文件目录的创建、删除、重命名、修改时间、文件大小等方法,并未涉及到写入或读取文件内容的操作。如果需要读取或写入文件内容,必须使用IO流来完成
  • 后续File类的对象常会作为参数传递到流的构造器中,指明读取或写入的"终点".

2、File类的常用构造器

image.png

1、public File(String pathname)

public File(String pathname) {
    if (pathname == null) {
        throw new NullPointerException();
    }
    this.path = fs.normalize(pathname);
    this.prefixLength = fs.prefixLength(this.path);
}

pathname为路径创建File对象,可以是绝对路径或者相对路径,如果pathname是相对路径,则默认的当前路径在系统属性user.dir中存储。

  • 绝对路径:是一个固定的路径,从盘符开始
  • 相对路径:是相对于某个位置开始

实例:

File file1 = new File("hello.txt");//相对路径,相对于当前module 
File file2 =  new File("D:\\workspace_idea1\\JavaSenior\\day08\\he.txt"); //绝对路径
System.out.println(file1);//hello.txt

路径分隔符

  • windows:\
  • unix:/

Java程序支持跨平台运行,因此路径分隔符要慎用。但是为了解决这个隐患,File类提供了一个常量:

public static final String separator,根据操作系统,动态的提供分隔符,使用如下所示:

File file1 = new File("d:\\atguigu\\info.txt");
File file2 = new File("d:" + File.separator + "atguigu" + File.separator + "info.txt");
File file3 = new File("d:/atguigu");

2、public File(String parent,String child)

parent为父路径,child为子路径创建File对象

File file3 = new File("D:\\workspace_idea1","JavaSenior");

3、public File(File parent,String child)

根据一个父File对象和子文件路径创建File对象

File file3 = new File("D:\\workspace_idea1","JavaSenior");
File file4 = new File(file3,"hi.txt");  

3、File类常用API

1、File类的获取功能

  • public String getAbsolutePath():获取绝对路径
  • public String getPath() :获取路径
  • public String getName() :获取名称
  • public String getParent():获取上层文件目录路径。若无,返回null
  • public long length() :获取文件长度(即:字节数)。不能获取目录的长度。
  • public long lastModified() :获取最后一次的修改时间,毫秒值
  • 如下的两个方法适用于文件目录:
    • public String[] list() :获取指定目录下的所有文件或者文件目录的名称数组
    • public File[] listFiles() :获取指定目录下的所有文件或者文件目录的File数组

实例:

public class JUnitTest {
    @Test
    public void test2(){
        File file1 = new File("hello. txt");
        File file2 = new File("d:\\io\\hi.txt");
        System.out.println(file1.getAbsolutePath());
        System.out.println(file1.getPath());
        System.out.println(file1.getName());
        System. out. println(file1.getParent());
        System.out.println(file1.length());
        System.out.println(new Date(file1.lastModified()));
        System.out.println("======================");
        System.out.println(file2.getAbsolutePath());
        System.out.println(file2.getPath());
        System.out.println(file2.getName());
        System.out.println(file2.getParent());
        System.out.println(file2.length());
        System.out.println(file2.lastModified());
    }

    @Test
    public void test3(){
        File file = new File("D:\\workspace_ idea1\\JavaSenior"); 
        String[] list = file.list();
        for(String s : list){
            System.out.printLn(s);  //java.lang.NullPointerException,因为没有该目录,没有文件存在
        }
        System.out.printLn();

        File[] files = file.listFiles();
        for(File f : files){
            System.out.println(f);
        }
    }
}

结果:

E:\java_xer2\hello.txt
hello.txt
hello.txt
null
9  //一个汉字三个字节
Sun Feb 27 19:53:13 CST 2022
======================
d:\io\hi.txt
d:\io\hi.txt
hi.txt
d:\io
0
0

2、File类的重命名功能

  • public boolean renameTo(File dest):把文件重命名为指定的文件路径,即剪贴到另外路径下
  • 比如:file1.renameTo(file2)为例:要想保证返回true,需要file1在硬盘中是存在的,且file2不能在硬盘中存在。
@Test
public void test4(){
    File file1 = new File("hello. txt");
    File file2 = new File("D:\\io\\hi. txt");
    booLean renameTo = file1.renameTo(file2);
    System.out.println(renameTo); //true
}

经过上述修改,file1的路径就到了D:\\io\\hello.txt里面了

3、File类的判断功能

  • public boolean isDirectory():判断是否是文件目录
  • public boolean isFile() :判断是否是文件
  • public boolean exists() :判断是否存在
  • public boolean canRead() :判断是否可读
  • public boolean canWrite() :判断是否可写
  • public boolean isHidden() :判断是否隐藏
@Test
public void test5(){
    File file1 = new File("hello. txt");
    file1 = new File("hello1.txt");
    System.out.println(file1.isDirectory());
    System.out.println(file1.isFile());
    System.out.println(file1.exists());
    System.out.println(file1.canRead());
    System.out.printLn(file1.canWrite());
    System.out.println(file1.isHidden());
    System.out.printLn("=================");
    File file2 = new File("d:\\io");
    file2 = new File("d:\\io1");
    System.out.println(file2.isDirectory());
    System.out.println(file2.isFile());
    System.out.println(file2.exists());
    System.out.println(file2.canRead());
    System.out.println(file2.canWrite());
    System.out.printLn(file2.isHidden());
}

4、File类的创建功能

  • public boolean createNewFile() :创建文件。若文件存在,则不创建,返回false
    • 如果未指明明确的创建位置,即相对路径方式,该文件会默认创建在该项目路径下
    • 如果路径不存在则会创建失败,createNewFile只能创建文件,不能创建文件夹,java.io.Exception:系统找不到指定的路径
  • public boolean mkdir() :创建文件目录。如果此文件目录存在,就不创建了。如果此文件目录的上层目录不存在,也不创建。
  • public boolean mkdirs() :创建文件目录。如果此文件目录存在,就不创建了。如果上层文件目录不存在,一并创建,即创建多层文件夹

注意事项:如果你创建文件或者文件目录没有写盘符路径,那么,默认在项目路径下。

@Test
public void test6() throws IOException {
    File file1 = new File("hi.txt");
    if(!file1.exists()){
        //文件的创建
        file1.createNewFile();
        System.out.println("创建成功");
    }else{//文件存在
        file1.delete();
        System.out.println("删除成功");
    }
}
@Test
public void test7(){
    //文件目录的创建
    File file1 = new File("d:\\io\\io1\\io3");
    boolean mkdir = file1. mkdir();
    if(mkdir){
        System.out.printLn("创建成功1");
    }
    File file2 = new File("d:\\io\\io1\\1o4"); 
    boolean mkdir1 = file2.mkdirs();
    if(mkdir1){
        System.out.printLn("创建成功2");
    }
    //要想删除成功,104 文件目录下不能有于目录或文件
    File file3 = new File("D:\\i0\\101\\i04");
    file3 = new File("D:\\i0\\io1");
    System.out.printLn(file3. delete());
}

5、File类的删除功能

public boolean delete():删除文件或者文件夹

删除注意事项: Java中的删除不走回收站。 要删除一个文件目录,请注意该文件目录File对象内不能包含文件或者文件目录

4、File类练习

1、利用File构造器,new 一个文件目录file

  • 在其中创建多个文件和目录
  • 编写方法,实现删除file中指定文件的操作
  • 判断指定目录下是否有后缀名为.jpg的文件,如果有,就输出该文件名称

实例

package IO_File;

import java.io.File;
import java.io.IOException;

/**
 * @Author Lemons
 * @create 2022-02-27-20:23
 */
public class FileDemo {

    //创建一个与file同目录下的文件:haha.txt
    public static void creatFile(File file) throws IOException {
        File destFile = new File(file,"haha.txt");//还在内存层面
        boolean newFile = destFile.createNewFile();
        if(newFile){
            System.out.println("创建成功");
        }
    }

    //找到File目录下所有的.jpg文件
    public static void findJPG(File file, String format) {
        int count = 0;  //用于记录.jpg文件的数量
        //1.获取file目录下所有的File对象
        File[] files = file.listFiles();
        //2.遍历File数组
        for (int i = 0; i<files.length; i++){
            File fileOrDir = files[i];
            //3.判断是否是文件,是 就比较格式
            if (fileOrDir.isFile()){
                String[] newString = fileOrDir.getName().split("\\.");//正则表达式
                if (format.equals(newString[1])){
                    count++;
                    System.out.println(fileOrDir.getName());
                }
            }
        }
        if (count==0){
            System.out.println("此文件目录下没有.jpg文件!");
        }
    }

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

        String directory = "D:\\xuexi\\javaio"; //该目录现在不存在
        File file = new File(directory);
        boolean mkdir1 = file.mkdirs();//创建该目录
        if(mkdir1){
            System.out.printLn("创建成功");
        }
        FileDemo.creatFile(mkdir1);


        String format = "jpg";
        FileDemo.findJPG(file,format);
    }
}


2、遍历指定目录所有文件名称,包括子文件目录中的文件

  • 拓展1:并计算指定目录占用空间的大小
  • 拓展2:删除指定文件目录及其下的所有文件

实例

public class RecursionDir {
 
    @Test
    public void Test1() {
        String directory = "D:\\xuexi\\javaio";
        File file = new File(directory);
 
        findAllFileName(file);
 
        Long fileDirSize = getFileDirSize();
        System.out.println(file.getAbsolutePath() + "-目录大小为:" + fileDirSize + " 字节");
    }
    
    //拓展1:并计算指定目录占用空间的大小
    long fileDirSize = 0;
    
    //获取指定目录下以及子目录下的所有文件名称
    private void findAllFileName(File file) {
        File[] files = file.listFiles();//获取此目录下所有的文件对象
        for (File file1 : files) {//遍历
            if (file1.isFile()) {//判断是否是文件
                System.out.println(file1.getName());
                fileDirSize += file1.length();
            }
            if (file1.isDirectory()) {//判断是否是目录
                findAllFileName(file1);//递归
            }
        }
    }
 
    //获取目录大小;
    private Long getFileDirSize() {
        return this.fileDirSize;
    }
 
 
    /**
     * 拓展2:删除指定文件目录及其下的所有文件
     */
    @Test
    public void Test2() {
 
        String directory = "D:\\xuexi\\javaio";
        File file = new File(directory);
        deleteDir(file);
        //delete方法不能删除javaio下有子目录和文件的目录
    }
 
    private void deleteDir(File file) {
        File[] files = file.listFiles();//获取此目录下所有的文件对象
        for (File file1 : files) {//遍历
            if (file1.isFile()) {//判断是否是文件
                file1.delete();//删除文件
            }
            if (file1.isDirectory()) {//判断是否是目录
                deleteDir(file1);//递归删除文件
                file1.delete();//删除目录
            }
        }
    }
}

5、IO流原理及流的分类

5.1、Java IO原理

  • I/OInput/Output的缩写, I/O技术是非常实用的技术,用于 处理设备之间的数据传输。如读/写文件,网络通讯等。
  • Java程序中,对于数据的输入/输出操作以 “流(stream)” 的方式进行。
  • java.io包下提供了各种“流”类和接口,用以获取不同种类的数据,并通过标准的方法输入或输出数据。

image.png

输入input:读取外部数据(磁盘、光盘等存储设备的数据)到 程序(内存)中。

输出output:将程序(内存) 数据输出到磁盘、光盘等存储设 备中。

image.png

5.2、流的分类

一个流被定义为一个数据序列。输入流用于从源读取数据,输出流用于向目标写数据

  • 按操作数据单位不同分为:字节流(8 bit),字符流(16 bit)
  • 按数据流的流向不同分为:输入流,输出流
  • 按流的角色的不同分为:节点流,处理流,处理流就是相当于在已有的流上包了一层,可以加快数据传输速度

image.png

Java的IO流共涉及40多个类,实际上非常规则,都是从上面4个抽象基类派生的。由这四个类派生出来的实现子类名称都是以其父类名作为子类名后缀。

节点流(文件流)

节点流为可以从一个特定的数据源节点(如:文件,内存)或目的地读写数据,只用一根管道连接程序和数据源,这根管道就叫作节点流。

image.png

处理流:

不直接连接到数据源或目的地,而是“连接”在已存在的流(如节点流或处理流---一层套一层)之上,通过对数据的处理为程序提供更为强大的读写功能。 这两根蓝色管道都叫处理流,蓝色的管道包着浅蓝色的管道,浅蓝色的管道又包着节点管道。

image.png

综上:可以将下面图看成是一根复合管的切面图

image.png

IO流体系

image.png

  • 缓冲流:处理流的一种

下图是一个描述输入流和输出流的类层次图

image.png

5.3、4个抽象基类的方法

InputStream

int read()
从输入流中读取数据的下一个字节。返回 0255 范围内的 int 字节值。如果因为已经到达流末尾而没有可用的字节,则返回值 -1int read(byte[] b)
从此输入流中将最多 b.length 个字节的数据读入一个 byte 数组中。如果因为已经到达流末尾而没有可用的字节,则返回值 -1。否则以整数形式返回实际读取的字节数。

int read(byte[] b, int off,int len)
将输入流中最多 len 个数据字节读入 byte 数组。尝试读取 len 个字节,但读取的字节也可能小于该值。以整数形式返回实际读取的字节数。如果因为流位于文件末尾而没有可用的字节,则返回值 -1public void close() throws IOException
关闭此输入流并释放与该流关联的所有系统资源。

Reader

int read()
读取单个字符。作为整数读取的字符,范围在 065535 之间 (0x00-0xffff)(2个字节的Unicode码),如果已到达流的末尾,则返回 -1

int read(char[] cbuf)
将字符读入数组。如果已到达流的末尾,则返回 -1。否则返回本次读取的字符数。

int read(char[] cbuf,int off,int len)
将字符读入数组的某一部分。存到数组cbuf中,从off处开始存储,最多读len个字符。如果已到达流的末尾,则返回 -1。否则返回本次读取的字符数。

public void close() throws IOException
关闭此输入流并释放与该流关联的所有系统资源。

OutputStream

void write(int b)
将指定的字节写入此输出流。write 的常规协定是:向输出流写入一个字节。要写入的字节是参数 b 的八个低位。b 的 24 个高位将被忽略。 即写入0~255范围的。

void write(byte[] b)
将 b.length 个字节从指定的 byte 数组写入此输出流。write(b) 的常规协定是:应该与调用 write(b, 0, b.length) 的效果完全相同。

void write(byte[] b,int off,int len)
将指定 byte 数组中从偏移量 off 开始的 len 个字节写入此输出流。

public void flush()throws IOException
刷新此输出流并强制写出所有缓冲的输出字节,调用此方法指示应将这些字节立即写入它们预期的目标。

public void close() throws IOException
关闭此输出流并释放与该流关联的所有系统资源。

Writer

void write(int c)
写入单个字符。要写入的字符包含在给定整数值的16个低位中,16高位被忽略。即写入065535 之间的Unicode码。

void write(char[] cbuf)
写入字符数组。

void write(char[] cbuf,int off,int len)
写入字符数组的某一部分。从off开始,写入len个字符

void write(String str)
写入字符串。

void write(String str,int off,int len)
写入字符串的某一部分。

void flush()
刷新该流的缓冲,则立即将它们写入预期目标。

public void close() throws IOException
关闭此输出流并释放与该流关联的所有系统资源。

6、关于文件的路径问题补充

1、在main方法里面如果是相对路径,则它是相对于当前工程,所以该文件如果在当前moudle下,会找不到

public class FileReaderWriterTest {  
   public static void main(String[] args) {  
       File file = new File("hello.txt");//相较于当前工程  
       System.out.println(file.getAbsolutePath()); 
   }
}

2、那如果我就想在main方法下使用我们的moudle下的文件,我们就得把路径补全

public class FileReaderWriterTest {  
    public static void main(String[] args) {  
        File file = new File("day09\hello.txt");
        System.out.println(file.getAbsolutePath()); 
    }
}

3、我们不是在main方法里面写的相对路径,就是在当前moudle下

//1.实例化File类的对象,指明要操作的文件  
File file = new File("hello.txt");//相较于当前Module  

7、节点流(文件流)

7.1、字符输入流:Filereader

Filereader:读入操作

引入:将当前module下的hello.txt文件内容读入程序中,并输出到控制台

image.png

说明:

  • read()的理解:返回读入的一个字符,为int类型,并移动光标,如果达到文件末尾,返回-1
    • 读取单个字符。作为整数读取的字符,范围在 0 到 65535 之间 (0x00-0xffff)(2个 字节的Unicode码),如果已到达流的末尾,则返回 -1
  • 异常的处理:为了保证流资源一定可以执行关闭操作。需要使用try-catch-finally处理
  • 读入的文件一定要存在,否则就会报FileNotFoundException

实例:读入方式一

//读入方式一
@Test  
public void testFileReader() throws IOException{  //文件可能不存在,所以要抛异常
    
    //1.实例化File类的对象,指明要操作的文件  
    File file = new File("hello.txt");//相较于当前Module  
    //2.提供具体的流  
    FileReader fr = new FileReader(file);  
    //3.数据的读入  
    //read():返回读入的一个字符。如果达到文件末尾,返回-1  

    //方式一:  
    int data = fr.read(); //read方法返回的是一个Int型变量 
    while(data != -1){  
        //输出到控制台
        System.out.print((char)data); //强转成字符
        data = fr.read();  
    }  
    //4.流的关闭操作  
    fr.close();
}

结果

我爱你

实例:读入方式二

//方式二:语法上针对于方式一的修改  
int data;  
while((data = fr.read()) != -1){  
    System.out.print((char)data);  
}  

实例:改进--版本3

异常的处理:为了保证流资源一定可以执行关闭操作。需要使用try-catch-finally处理

如果我们使用throws来处理异常,执行这个File file = new File("hello.txt");文件不存在就抛异常,让后面的代码就不会执行,流就可能不会关闭,所以最好使用 我们最好使用try-catch-finally处理,保证关闭资源

public class FileReaderTest {

   @Test
   public void testFileReader() throws IOException {  //文件可能不存在,所以要抛异常
       FileReader fr = null;
       try {
           //1.实例化File类的对象,指明要操作的文件
           File file = new File("hello.txt");//相较于当前Module
           //2.FileReader流的实例化
           fr = new FileReader(file);

           //3.数据的读入
           //read():返回读入的一个字符。如果达到文件末尾,返回-1
           int data;  
           while((data = fr.read()) != -1){  
               System.out.print((char)data);  
           }  
       } catch (IOException e) {
           e.printStackTrace();
       } finally {
           //4.资源的关闭 
           if(fr != null){ 
               try { 
                   fr.close();
               } catch (IOException e) {  
                   e.printStackTrace();
               }
           }
       }
   }
}

实例:最终版本4

由于我们的read()方法是每次读一个,效率不高,所以这里使用read重载方法read(char[] cbuf)

read(char[] cbuf):返回每次读入cbuf数组中的字符的个数。如果达到文件末尾,返回-1

public class FileReaderTest {

    @Test
    public void testFileReader() throws IOException {  //文件可能不存在,所以要抛异常
        FileReader fr = null;
        try {
            //1.实例化File类的对象,指明要操作的文件
            File file = new File("hello.txt");//相较于当前Module
            //2.FileReader流的实例化
            fr = new FileReader(file);

            //3.数据的读入
            char[] cbuf = new char[5];  //每次读5个
            int len;  
            while((len = fr.read(cbuf)) != -1){  
                //方式一:  
                //错误的写法,会多输出,这样永远是读5个数,有些不是5的倍数
                //for(int i = 0;i < cbuf.length;i++){  
                //   System.out.print(cbuf[i]);  
                //}  

                //正确的写法  
                //for(int i = 0;i < len;i++){  
                //    System.out.print(cbuf[i]);  
                //}  

                //方式二: char型数组到String的转换 
                //错误的写法,对应着方式一的错误的写法,永远都读5个  
                //String str = new String(cbuf);  
                //System.out.print(str);  
                //正确的写法 每次读0-len个字符 
                String str = new String(cbuf,0,len);  
                System.out.print(str);  
            }  

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //4.资源的关闭 
            if(fr != null){ 
                try { 
                    fr.close();
                } catch (IOException e) {  
                    e.printStackTrace();
                }
            }
        }
    }
}

7.2、字符输出流:FileWriter

FileWriter:写出操作,从内存中写出数据到硬盘的文件里。

说明:

  • 输出操作,对应的File可以不存在的,并不会报异常
  • File对应的硬盘中的文件如果不存在,在输出的过程中,会自动创建此文件。
  • File对应的硬盘中的文件如果存在:
    • 如果流使用的构造器是:FileWriter(file,false) / FileWriter(file):对原有文件内容的覆盖
    • 如果流使用的构造器是:FileWriter(file,true) :不会对原有文件覆盖,而是在原有文件基础上追加内容

实例

@Test  
public void testFileWriter() throws IOException{  
    FileWriter fw = null;  
    try {  
        //1.提供File类的对象,指明写出到的文件  
        File file = new File("hello1.txt"); //当前module下 
        //2.提供FileWriter的对象,用于数据的写出  
        fw = new FileWriter(file,false);  
        //3.写出的操作  
        fw.write("I have a dream!\n");  
        fw.write("you need to have a dream!");  
    } catch (IOException e) {  
        e.printStackTrace();  
    } finally {  
        //4.流资源的关闭  
        if(fw != null){  
            try {  
                fw.close();  
            } catch (IOException e) {  
                e.printStackTrace();  
            }  
        }  
    }
}

7.3、Filereader和Filewriter结合实现文本文件的复制

目标:实现复制hello.txt文件里的内容到hello2.txt文件里面

注意:不能使用字符流来处理图片等字节数据

@Test  
public void testFileReaderFileWriter() {  
    FileReader fr = null;  
    FileWriter fw = null;  
    try {  
        //1.创建File类的对象,指明读入和写出的文件  
        File srcFile = new File("hello.txt");  
        File destFile = new File("hello2.txt");  
        
        //不能使用字符流来处理图片等字节数据  
        //File srcFile = new File("爱情与友情.jpg");  
        //File destFile = new File("爱情与友情1.jpg");  

        //2.创建输入流和输出流的对象  
        fr = new FileReader(srcFile);  
        fw = new FileWriter(destFile);  

        //3.数据的读入和写出操作  
        char[] cbuf = new char[5];  
        int len;//记录每次读入到cbuf数组中的字符的个数  
        while((len = fr.read(cbuf)) != -1){  
            //每次写出len个字符  
            fw.write(cbuf,0,len);  
        }  
    } catch (IOException e) {  
        e.printStackTrace();  
    } finally {  

        //4.关闭流资源  
        try {  
            if(fw != null)  
                fw.close();  
        } catch (IOException e) {  
            e.printStackTrace();  
        }  
        try {  
            if(fr != null)  
                fr.close();  
        } catch (IOException e) {  
            e.printStackTrace();  
        }  
    }
}

7.4、字节输入流:FileInputStream

该流用于从文件读取数据,它的对象可以用关键字 new 来创建。有多种构造方法可用来创建对象。

  • 可以使用字符串类型的文件名来创建一个输入流对象来读取文件:InputStream f = new FileInputStream("C:/java/hello");

  • 也可以使用一个文件对象来创建一个输入流对象来读取文件。我们首先得使用 File() 方法来创建一个文件对象:

    File f = new File("C:/java/hello");
    InputStream in = new FileInputStream(f);
    

创建了InputStream对象,就可以使用下面的方法来读取流或者进行其他的流操作。

序号方法及描述
1public void close() throws IOException{} 关闭此文件输入流并释放与此流有关的所有系统资源。抛出IOException异常。
2protected void finalize()throws IOException {} 这个方法清除与该文件的连接。确保在不再引用文件输入流时调用其 close 方法。抛出IOException异常。
3public int read(int r)throws IOException{} 这个方法从 InputStream 对象读取指定字节的数据。返回为整数值。返回下一字节数据,如果已经到结尾则返回-1。
4public int read(byte[] r) throws IOException{} 这个方法从输入流读取r.length长度的字节。返回读取的字节数。如果是文件结尾则返回-1。
5public int available() throws IOException{} 返回下一次对此输入流调用的方法可以不受阻塞地从此输入流读取的字节数。返回一个整数值。

除了 InputStream 外,还有一些其他的输入流,

  • ByteArrayInputStream
  • DataInputStream

处理文本文件

使用字节流FileInputStream处理文本文件,可能出现乱码

public class FileInputOutputStreamTest {  

    //使用字节流FileInputStream处理文本文件,可能出现乱码。  
    @Test  
    public void testFileInputStream() {  
        FileInputStream fis = null;  
        try {  
            //1. 造文件  
            File file = new File("hello.txt");  
            //2.造流  
            fis = new FileInputStream(file);  
            //3.读数据  
            byte[] buffer = new byte[5];  
            int len;//记录每次读取的字节的个数  
            while((len = fis.read(buffer)) != -1){  
                String str = new String(buffer,0,len);  
                System.out.print(str);  //输出乱码
            }  
        } catch (IOException e) {  
            e.printStackTrace();  
        } finally {  
            if(fis != null){  
                //4.关闭资源  
                try {  
                    fis.close();  
                } catch (IOException e) {  
                    e.printStackTrace();  
                } 
            }
        }
    }
}

7.5、字节输出流:FileOutputStream

该类用来创建一个文件并向文件中写数据。如果该流在打开文件进行输出前,目标文件不存在,那么该流会创建该文件。有两个构造方法可以用来创建 FileOutputStream 对象。

  • 使用字符串类型的文件名来创建一个输出流对象:

    OutputStream f = new FileOutputStream("C:/java/hello")
    
  • 也可以使用一个文件对象来创建一个输出流来写文件。我们首先得使用File()方法来创建一个文件对象:

    File f = new File("C:/java/hello");
    OutputStream fOut = new FileOutputStream(f);
    

创建OutputStream 对象完成后,就可以使用下面的方法来写入流或者进行其他的流操作。

序号方法及描述
1public void close() throws IOException{} 关闭此文件输入流并释放与此流有关的所有系统资源。抛出IOException异常。
2protected void finalize()throws IOException {} 这个方法清除与该文件的连接。确保在不再引用文件输入流时调用其 close 方法。抛出IOException异常。
3public void write(int w)throws IOException{} 这个方法把指定的字节写到输出流中。
4public void write(byte[] w) 把指定数组中w.length长度的字节写到OutputStream中。

除了OutputStream外,还有一些其他的输出流

  • ByteArrayOutputStream
  • DataOutputStream

7.6、FileInputStream 和 FileOutputStream 的使用

实现对图片的复制操作

@Test  
public void testFileInputOutputStream()  {  
    FileInputStream fis = null;  
    FileOutputStream fos = null;  
    try {  
        File srcFile = new File("爱情与友情.jpg");  
        File destFile = new File("爱情与友情2.jpg");  
        fis = new FileInputStream(srcFile);  
        fos = new FileOutputStream(destFile);  

        //复制的过程  
        byte[] buffer = new byte[5];  
        int len;  
        while((len = fis.read(buffer)) != -1){  
            fos.write(buffer,0,len);  
        }  
    } catch (IOException e) {  
        e.printStackTrace();  
    } finally {  
        if(fos != null){  
            try {  
                fos.close();  
            } catch (IOException e) {  
                e.printStackTrace();  
            }  
        }  
        if(fis != null){  
            try {  
                fis.close();  
            } catch (IOException e) {  
                e.printStackTrace();    
            }
        }
    }
}

指定路径下非文本文件的复制

public void copyFile(String srcPath,String destPath){  
    FileInputStream fis = null;  
    FileOutputStream fos = null;  
    try {  
        File srcFile = new File(srcPath);  
        File destFile = new File(destPath);  
        fis = new FileInputStream(srcFile);  
        fos = new FileOutputStream(destFile);  

        //复制的过程  
        byte[] buffer = new byte[1024];  
        int len;  
        while((len = fis.read(buffer)) != -1){  
            fos.write(buffer,0,len);  
        }  
    } catch (IOException e) {  
        e.printStackTrace();  
    } finally {  
        if(fos != null){  
            try {  
                fos.close();  
            } catch (IOException e) {  
                e.printStackTrace();  
            }  
        }  
        if(fis != null){  
            try {  
                fis.close();  
            } catch (IOException e) {  
                e.printStackTrace();  
            }    
        }  
    }
}

测试

@Test
public void testCopyFile(){
    Long start = System.currentTimeMillis();
    String srcPath = "C:\\Users\\Administratorl \Desktop\\01-视频.avi";
    String destPath = "C:\\Users\\Administrator\lDesktop\\02-视频.avi";
    //String srcPath = "hello.txt";
    //String destPath = "hello3.txt";
    copyFile(srcPath, destPath);
    Long end = System.currentTimeMillis();
    System.out.println("复制操作花费的时间为:"+ (end - start));//618
}

8、处理流之一:缓冲流的使用

一种基本的缓冲流处理方式:字节缓冲流

image.png

缓冲流:,一种处理流,处理流,就是“套接”在已有的流的基础上提高效率

  • 字节型
    • BufferedInputStream
    • BufferedOutputStream
  • 字符型
    • BufferedReader
    • BufferedWriter
  • 作用:提供流的读取、写入的速度,提高读写速度的原因:内部提供了一个缓冲区

8.1、字节缓冲流:BufferedOutputStream和BufferedInputStream

8.1.1、实现非文本文件的复制

@Test  
public void BufferedStreamTest() throws FileNotFoundException {  
    BufferedInputStream bis = null;  
    BufferedOutputStream bos = null;  
    try {  
        //1.造文件  
        File srcFile = new File("爱情与友情.jpg");  
        File destFile = new File("爱情与友情3.jpg");  
        //2.造流  
        //2.1 造节点流  
        FileInputStream fis = new FileInputStream((srcFile));  
        FileOutputStream fos = new FileOutputStream(destFile);  
        //2.2 造缓冲流  
        bis = new BufferedInputStream(fis);  
        bos = new BufferedOutputStream(fos);  

        //3.复制的细节:读取、写入  
        byte[] buffer = new byte[10];  
        int len;  
        while((len = bis.read(buffer)) != -1){  
            bos.write(buffer,0,len);  
            //bos.flush();//刷新缓冲区  
        }  
    } catch (IOException e) {  
        e.printStackTrace();  
    } finally {  
        //4.资源关闭  
        //要求:先关闭外层的流,再关闭内层的流  
        if(bos != null){  
            try {  
                bos.close();  
            } catch (IOException e) {  
                e.printStackTrace();  
            }  
        }  
        if(bis != null){  
            try {  
                bis.close();  
            } catch (IOException e) {  
                e.printStackTrace();  
            }  

        }  
        //说明:关闭外层流的同时,内层流也会自动的进行关闭。关于内层流的关闭,我们可以省略.  
        //fos.close();  
        //fis.close();  
    } 
}

所以,可见缓冲流提高读写速度

8.1.2、指定路径复制

//实现文件复制的方法  
public void copyFileWithBuffered(String srcPath,String destPath){  
   BufferedInputStream bis = null;  
   BufferedOutputStream bos = null;  
   try {  
       //1.造文件  
       File srcFile = new File(srcPath);  
       File destFile = new File(destPath);  
       //2.造流  
       //2.1 造节点流  
       FileInputStream fis = new FileInputStream((srcFile));  
       FileOutputStream fos = new FileOutputStream(destFile);  
       //2.2 造缓冲流  
       bis = new BufferedInputStream(fis);  
       bos = new BufferedOutputStream(fos);  

       //3.复制的细节:读取、写入  
       byte[] buffer = new byte[1024];  
       int len;  
       while((len = bis.read(buffer)) != -1){  
           bos.write(buffer,0,len);  
       }  
   } catch (IOException e) {  
       e.printStackTrace();  
   } finally {  
       //4.资源关闭  
       //要求:先关闭外层的流,再关闭内层的流  
       if(bos != null){  
           try {  
               bos.close();  
           } catch (IOException e) {  
               e.printStackTrace();  
           }  

       }  
       if(bis != null){  
           try {  
               bis.close();  
           } catch (IOException e) {  
               e.printStackTrace();  
           }  

       }  
       //说明:关闭外层流的同时,内层流也会自动的进行关闭。关于内层流的关闭,我们可以省略.  
       //fos.close();  
       //fis.close();  
   }  
}  

8.2、字符缓冲流:BufferedReader和BufferedWriter制

8.2.1、实现文本文件的复制

@Test  
public void testBufferedReaderBufferedWriter(){  
    BufferedReader br = null;  
    BufferedWriter bw = null;  
    try {  
        //创建文件和相应的流  
        br = new BufferedReader(new FileReader(new File("dbcp.txt")));  
        bw = new BufferedWriter(new FileWriter(new File("dbcp1.txt")));  

        //读写操作  
        //方式一:使用char[]数组  
        //char[] cbuf = new char[1024];  
        //int len;  
        //while((len = br.read(cbuf)) != -1){  
        //	bw.write(cbuf,0,len);  
        //	bw.flush();  
        //}  

        //方式二:使用String  
        String data;  
        while((data = br.readLine()) != null){  
            //方法一:  
            //bw.write(data + "\n");//data中不包含换行符 

            //方法二:  
            bw.write(data);//data中不包含换行符  
            bw.newLine();//提供换行的操作  

        }  
    } catch (IOException e) {  
        e.printStackTrace();  
    } finally {  
        //关闭资源  
        if(bw != null){  

            try {  
                bw.close();  
            } catch (IOException e) {  
                e.printStackTrace();  
            }  
        }  
        if(br != null){  
            try {  
                br.close();  
            } catch (IOException e) {  
                e.printStackTrace();  
            }  
        }  
    }  
}

9、处理流之二:转换流的使用

转换流,属于字符流,提供了在字节流和字符流之间的转换

  • 解码:字节、字节数组 --->字符数组、字符串
  • 编码:字符数组、字符串 ---> 字节、字节数组

Java API提供了两个转换流:

  • InputStreamReader:将InputStream转换为Reader,即将一个字节的输入流转换为字符的输入流
  • OutputStreamWriter:将Writer转换为OutputStream,即将一个字符的输出流转换为字节的输出流

字节流中的数据都是字符时,转成字符流操作更高效。 很多时候我们使用转换流来处理文件乱码问题。实现编码和解码

image.png

字符集

  • ASCII:美国标准信息交换码。用一个字节的7位可以表示。
  • ISO8859-1:拉丁码表。欧洲码表。用一个字节的8位表示。
  • GB2312:中国的中文编码表。最多两个字节编码所有字符
  • GBK:中国的中文编码表升级,融合了更多的中文文字符号。最多两个字节编码
  • Unicode:国际标准码,融合了目前人类使用的所有字符。为每个字符分配唯一的字符码。所有的文字都用两个字节来表示。
  • UTF-8:变长的编码方式,可用1-4个字节来表示一个字符。

9.1、InputStreamReader的使用

9.1、InputStreamReader实现数据的读入

  • FileInputStream是字节流,最好用于图片、音频等字节文件
  • 如果FileInputStream操作的是文本数据,则建议转换成字符流,所以需要使用到转换流InputStreamReader
public class InputStreamReaderTest {     
    @Test  
    public void test1() throws IOException {    
        FileInputStream fis = new FileInputStream("dbcp.txt");  //字节流 
        InputStreamReader isr = new InputStreamReader(fis,"UTF-8");//显示的写        
        char[] cbuf = new char[20];  
        int len;  
        while((len = isr.read(cbuf)) != -1){  
            String str = new String(cbuf,0,len);  
            System.out.print(str);  
        }  
        isr.close();    
    } 
}

说明:

  • inputStreamReader isr = new InputStreamReader(fis); 这是使用系统默认的字符集utf-8
  • InputStreamReader isr = new InputStreamReader(fis,"UTF-8"); ,参数2指明了字符集,具体使用哪个字符集,取决于文件dbcp.txt保存时使用的字符集

9.2、使用InputStreamReader和OutputStreamWriter实现文件的读入、读出

//此时处理异常的话,仍然应该使用try-catch-finally 
@Test  
public void test2() throws Exception {  
    
    //1.造文件、造流  
    File file1 = new File("dbcp.txt");  //utf-8存的
    File file2 = new File("dbcp_gbk.txt");  

    FileInputStream fis = new FileInputStream(file1); 
    FileOutputStream fos = new FileOutputStream(file2); 

    InputStreamReader isr = new InputStreamReader(fis,"utf-8");  
    OutputStreamWriter osw = new OutputStreamWriter(fos,"gbk");//gbk方式存储  

    //2.读写过程  
    char[] cbuf = new char[20];  
    int len;  
    while((len = isr.read(cbuf)) != -1){  
        osw.write(cbuf,0,len);  
    }  

    //3.关闭资源  
    isr.close();  
    osw.close();    
}  

综上:字节(utf-8)——字符——字节(gbk)

10、标准输入、输出流

System.inSystem.out分别代表了系统标准的输入和输出设备

  • 默认输入设备是:键盘,输出设备是:显示器
  • System.in:标准的输入流,默认从键盘输入
  • System.out:标准的输出流,默认从控制台输出

重定向:通过System类的setInsetOut方法对默认设备进行改变。即重新指定输入和输出的流

  • public static void setIn(InputStream in)
  • public static void setOut(PrintStream out)

实例

从键盘输入字符串,要求将读取到的整行字符串转成大写输出。然后继续 进行输入操作,直至当输入“e”或者“exit”时,退出程序。

  • 方法一:使用Scanner实现,调用next()返回一个字符串
  • 方法二:使用System.in实现

写到main方法里面

System.out.println("请输入信息(退出输入e或exit):");
// 把"标准"输入流(键盘输入)这个字节流包装成字符流,再包装成缓冲流
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String s = null;
try {
    while ((s = br.readLine()) != null) { // 读取用户输入的一行数据 --> 阻塞程序
        if ("e".equalsIgnoreCase(s) || "exit".equalsIgnoreCase(s)) {
            System.out.println("安全退出!!");
            break;
        }
        // 将读取到的整行字符串转成大写输出
        System.out.println("-->:" + s.toUpperCase());
        System.out.println("继续输入信息");
    }
} catch (IOException e) {
    e.printStackTrace();
} finally {
    try {
        if (br != null) {
            br.close(); // 关闭过滤流时,会自动关闭它包装的底层节点流
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}

11、打印流:PrintStream 和PrintWriter

控制台的输出由 print( ) 和 println() 完成。这些方法都由类 PrintStream 定义,System.out 是该类对象的一个引用。PrintStream 继承了 OutputStream类,并且实现了方法 write()。这样,write() 也可以用来往控制台写操作,但是write() 方法不经常使用,因为 print() 和 println() 方法用起来更为方便。

  • 实现将基本数据类型的数据格式转化为字符串输出
  • 打印流:PrintStreamPrintWriter
    • 提供了一系列重载的print()println()方法,用于多种数据类型的输出
    • PrintStreamPrintWriter的输出不会抛出IOException异常
    • PrintStreamPrintWriter有自动flush功能
    • PrintStream 打印的所有字符都使用平台的默认字符编码转换为字节。 在需要写入字符而不是写入字节的情况下,应该使用PrintWriter 类。
    • System.out返回的是PrintStream的实例
PrintStream ps = null;
try {
    FileOutputStream fos = new FileOutputStream(new File("D:\\IO\\text.txt"));
    // 创建打印输出流,设置为自动刷新模式(写入换行符或字节 '\n' 时都会刷新输出缓冲区)
    ps = new PrintStream(fos, true);
    if (ps != null) {// 把标准输出流(控制台输出)改成文件
        System.setOut(ps);
    }
    for (int i = 0; i <= 255; i++) { // 输出ASCII字符
        System.out.print((char) i);
        if (i % 50 == 0) { // 每50个数据一行
            System.out.println(); // 换行
        }
    }
} catch (FileNotFoundException e) {
    e.printStackTrace();
} finally {
    if (ps != null) {
        ps.close();
    }
}

12、数据流:DataInputStream 和 DataOutputStream

为了方便地操作Java语言的基本数据类型和String的数据,可以使用数据流。 数据流有两个类:(用于读取和写出基本数据类型、String类的数据)DataInputStreamDataOutputStream 。分别“套接”在 InputStreamOutputStream 子类的流上

DataInputStream中的方法

- boolean readBoolean() 
- byte readByte() 
- char readChar() 
- float readFloat() 
- double readDouble() 
- short readShort()
- long readLong()
- int readInt() 
- String readUTF() 
- void readFully(byte[] b) 

DataOutputStream中的方法

  • 将上述的方法的read改为相应的write即可。

实例

1、练习:将内存中的字符串、基本数据类型的变量写出到文件中

DataOutputStream dos = null;
try { 
    // 创建连接到指定文件的数据输出流对象
    dos = new DataOutputStream(new FileOutputStream("destData.dat"));
    dos.writeUTF("我爱北京天安门"); // 写UTF字符串
    dos.flush();//刷新操作,将内存中的数据写入文件
    dos.writeBoolean(false); // 写入布尔值
    dos.flush();
    dos.writeLong(1234567890L); // 写入长整数
    dos.flush();
    System.out.println("写文件成功!");
} catch (IOException e) {
    e.printStackTrace();
} finally { // 关闭流对象
    try {
        if (dos != null) {
            // 关闭过滤流时,会自动关闭它包装的底层节点流
            dos.close();
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}

2、将文件中存储的基本数据类型变量和字符串读取到内存中,保存在变量中

注意点:读取不同类型的数据的顺序要与当初写入文件时,保存的数据的顺序一致!

DataInputStream dis = null;
try {
    dis = new DataInputStream(new FileInputStream("destData.dat"));
    //2. 保存在变量中,顺序要一致 
    String info = dis.readUTF();
    boolean flag = dis.readBoolean();
    long time = dis.readLong();
    System.out.println(info);
    System.out.println(flag);
    System.out.println(time);
} catch (Exception e) {
    e.printStackTrace();
} finally {
    if (dis != null) {
        try {
            dis.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

13、随机存取文件流

13.1、RandomAccessFile类

RandomAccessFile 声明在java.io包下,但直接继承于java.lang.Object类。并且它实现了DataInputDataOutput这两个接口,因此RandomAccessFile既可以作为一个输入流,又可以作为一个输出流。也就意味着这个类既可以读也可以写,我们输出输入就不用造两个流了,但是要造两个RandomAccessFile对象

RandomAccessFile 类支持 “随机访问” 的方式,程序可以直接跳到文件的任意地方来读、写文件

  • 支持只访问文件的部分内容
  • 可以向已存在的文件后追加内容

RandomAccessFile 对象包含一个记录指针,用以标示当前读写处的位置。 RandomAccessFile 类对象可以自由移动记录指针:

  • long getFilePointer():获取文件记录指针的当前位置
  • void seek(long pos):将文件记录指针定位到 pos 位置,默认是0

13.2、构造方法

构造器

  • public RandomAccessFile(File file, String mode)
  • public RandomAccessFile(String name, String mode)

创建 RandomAccessFile 类实例需要指定一个 mode 参数,该参数指定 RandomAccessFile 的访问模式:

  • r: 以只读方式打开
  • rw:打开以便读取和写入
  • rwd :打开以便读取和写入;同步文件内容的更新
  • rws :打开以便读取和写入;同步文件内容和元数据的更新
  • 如果模式为只读r。则不会创建文件,而是会去读取一个已经存在的文件, 如果读取的文件不存在则会出现异常。
  • 如果模式为rw读写。如果文件不 存在则会去创建文件,如果存在则不会创建。

13.3、实例

实现数据的读写操作--图片重命名

public void test1()  {
    RandomAccessFile raf1= null;
    RandomAccessFile raf2= null;
    try {
        raf1 = new RandomAccessFile(new File("背景图.jpg"),"r");
        raf2 = new RandomAccessFile(new File("背景图1.jpg"),"rw");
        byte[] buffer=new byte[1024];
        int len;
        while ((len=raf1.read(buffer))!=-1){
            raf2.write(buffer,0,len);
        }
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        if(raf1!=null) {
            try {
                raf1.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        if(raf2!=null) {
            try {
                raf2.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

2、如果RandomAccessFile作为输出流时,写出到的文件如果不存在,则在执行过程中自动创建。如果写出到的文件存在,则会对原有文件内容进行覆盖。(默认情况下,从头覆盖)

@Test
public void test2() throws I0Exception {
    //作为输出流
    RandomAccessFile raf1 = new RandonAccessFile("hello.txt", "rw");
    raf1.seek(3);//将指针调到角标为3的位置,插入数据
    //write方法里面是Byte数组,String得转换
    raf1.write("xyz".getBytes());//将xyz写到文件内容里面。默认从头覆盖
    raf1.close();
}

3、可以通过相关的操作,实现RandomAccessFile“插入”数据的效果

public void test3() throws IOException {
    RandomAccessFile raf1=new RandomAccessFile("hello.txt","rw");
    raf1.seek(3);//将指针调到角标为3的位置
    //保存指针3后面的所有数据到StringBuilder中
    StringBuilder builder=new StringBuilder((int) new File("hello.txt").length());
    byte[] buffer =new byte[20];
    int len;
    while((len=raf1.read(buffer))!=-1){
        builder.append(new String(buffer,0,len));
    }
    //返回指针,写入xyz
    raf1.seek(3);
    raf1.write("xyz".getBytes());
    //将Stringbuilder的数据写入到文件中
    raf1.write(builder.toString().getBytes());
    raf1.close();
    
    //思考: StringBuilder 替换 为ByteArrayoutputstream

}

思考:StringBuilder 替换为ByteArrayoutputstream

//避免出现乱码
ByteArrayoutputstream baos=new ByteArrayoutputstream();
byte[] buffer =new byte[20];
int len;
while((len=raf1.read(buffer))!=-1){
    baos.write(buffer,0,len);
}
return baos.toString();

14、第三方jar包commons-io 实现数据读写(掌握)

idea导入过程[Maven也行]

  • 当前module下新建一个包Lib(专门放jar包的)
  • 复制jar包,粘贴到Lib下
  • jar包上点右键,选择Add as library

image.png

实例

public class FileUtilsTest {

    public static void main(String[] args) {
        //main方法里面默认是当前工程下
        File srcFile = new File("day10\\爱情与友情.jpg");
        File destFile = new File("day10\\爱情与友情2.jpg");
        try {
            FileUtils.copyFile(srcFile,destFile);//文件复制
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}