Java IO流

一、文件
1. 创建文件

创建文件方式一:new File(String pathname) 根据文件全路径创建

public static void main(String[] args) {
    // 文件路径也可以写成 d:/new.txt,第一个\是转义符
    File file = new File("d:\\new.txt");
    try {
        file.createNewFile();
        System.out.println("文件创建成功");
    } catch (IOException e) {
        e.printStackTrace();
    }
}

创建文件方式二:new File(File parent, String child) 根据父目录文件对象+子路径创建

public static void main(String[] args) {
    File parentFile = new File("d:\\");
    File file = new File(parentFile, "new.txt");
    try {
        file.createNewFile();
        System.out.println("文件创建成功");
    } catch (IOException e) {
        e.printStackTrace();
    }
}

创建文件方式三:new File(String parent, String child) 根据父路径+子路径创建

public static void main(String[] args) {
    File file = new File("d:\\", "new.txt");
    try {
        file.createNewFile();
        System.out.println("文件创建成功");
    } catch (IOException e) {
        e.printStackTrace();
    }
}

注意:如果创建文件对象传入的路径名是多级目录,则必须确保每一级目录都存在,否则系统会报找不到指定的路径。

2. 文件相关操作

获取文件信息

public static void main(String[] args) {
    // 创建文件对象
    File file = new File("d:\\test\\new.txt");
    // 创建文件
    try {
        file.createNewFile();
        System.out.println("文件创建成功");
    } catch (IOException e) {
        e.printStackTrace();
    }
    // 获取文件名
    System.out.println(file.getName()); // new.txt
    // 获取文件绝对路径
    System.out.println(file.getAbsolutePath()); // d:\test\new.txt
    // 获取文件父级目录
    System.out.println(file.getParent()); // d:\test
    // 获取文件大小(多少个字节)UTF-8编码:一个汉字占三个字节,一个英文字符占一个字节
    // 例如:"hello你好"(总共占5+3*2=11个字节)
    System.out.println(file.length()); // 0
    // 判断文件是否存在
    System.out.println(file.exists()); // true
    // 判断是不是一个文件
    System.out.println(file.isFile()); // true
    // 判断是不是一个目录
    System.out.println(file.isDirectory()); // false
}

删除文件

public static void main(String[] args) {
    // 创建文件对象
    File file = new File("d:\\new.txt");
    // 判断文件是否存在,存在则删除
    if (file.exists()) {
        if (file.delete()) {
            System.out.println("文件删除成功");
        } else {
            System.out.println("文件删除失败");
        }
    } else {
        System.out.println("文件不存在");
    }
}
3. 目录相关操作
public static void main(String[] args) {
    // 目录也可以当做文件来操作
    File file = new File("d:\\demo");
    // 判断目录是否存在,存在则删除,不存在则创建
    if (file.exists()) {
        System.out.println("目录已存在");
        // 只能删除空目录
        if (file.delete()) {
            System.out.println("目录已删除");
        } else {
            System.out.println("目录删除失败,目录为空才可以删除");
        }
    } else {
        System.out.println("目录不存在");
        // 创建多级目录需要使用 file.mkdirs()
        if (file.mkdir()) {
            System.out.println("目录创建成功");
        } else {
            System.out.println("目录创建失败");
        }
    }
}
二、IO流

I/O是Input/Output的缩写,I/O技术是非常实用的技术,用于处理数据传输,如读写文件,网络通信等。Java程序中,对于数据的输入输出操作是以流的方式进行,例如最常见的要将Java内存中的数据输出到磁盘的文件中或者要将磁盘文件中的数据写入Java内存中都是以I/O流的方式。

1. IO流的分类
  • 按操作数据单位不同分为:字节流、字符流,字节流通常用于处理二进制文件,字符流通常用于处理文本文件;因为文本文件中通常会包含中文字符,在UTF-8编码中,一个汉字占三个字节,如果我们以字节流去方式一个字节一个字节去读取,必然会出现乱码
  • 按数据流的流向不同分为:输入流、输出流,对于Java程序来讲,输入流指的是外部数据源(例如磁盘上的文件)的数据读取到Java程序(内存)中,输出流指的是Java程序(内存)中的数据输出到外部数据源
2. IO流体系图

请添加图片描述

三、字节文件流
1. 字节文件输入流 FileInputStream

例子:通过FileInputStream读取磁盘文件中的内容到Java程序中,并打印到控制台

通过read()方法进行单个字节的读取,效率比较低

public static void main(String[] args) {
    String filePath = "d:\\hello.txt";
    // 创建一个文件输入流,将给输入流和文件关联起来,就可以通过该输入流来操作文件了
    FileInputStream fileInputStream = null;
    int readData = 0;
    try {
        fileInputStream = new FileInputStream(filePath);
        // fileInputStream.read()的返回值是一个int类型的值,正常情况下返回当前读取的字节的值,如果返回-1说明已到达文件末尾
        while ((readData = fileInputStream.read()) != -1) {
            System.out.print((char)readData);
        }
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        // 关闭流资源
        try {
            fileInputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

通过read(byte[] b)方法一次最多可以读取b.length个字节的数据到byte数组里,提高读取

public static void main(String[] args) {
    String filePath = "d:\\hello.txt";
    // 创建一个文件输入流,将输入流和文件关联起来,就可以通过该输入流来操作文件了
    FileInputStream fileInputStream = null;
    // 字节数组,一次读取8个字节
    byte[] bytes = new byte[8];
    int readLen = 0;
    try {
        fileInputStream = new FileInputStream(filePath);
        // fileInputStream.read()的返回值是一个int类型的值,正常情况下返回实际读取的字节数,如果返回-1说明已到达文件末尾
        while ((readLen = fileInputStream.read(bytes)) != -1) {
            System.out.print(new String(bytes, 0, readLen));
        }
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        // 关闭流资源
        try {
            fileInputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
2. 字节文件输出流 FileOutputStream
public static void main(String[] args) {
    String filePath = "d:\\hello.txt";
    // 创建一个文件输出流,将输出流和文件关联起来,就可以通过该输出流来操作文件了
    FileOutputStream fileOutputStream = null;
    try {
        fileOutputStream = new FileOutputStream(filePath);
        // 写入一个字节 write(int b)
        fileOutputStream.write('a');
        // 写入字符串 write(byte[] b)
        String str = "hello,world";
        fileOutputStream.write(str.getBytes());
        // 从位移量off开始读取byte数组中的len个字节 write(byte[] b, int off, int len)
        fileOutputStream.write(str.getBytes(), 0, 3);
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        // 关闭流资源
        try {
            fileOutputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

例子:通过FileOutputStream将Java内存中的List的值写入磁盘的文件中,如果文件不存在,会自动创建文件(前提是目录都已存在)

public static void main(String[] args) {
    String filePath = "d:\\hello.txt";
    // 创建一个文件输出流,将输出流和文件关联起来,就可以通过该输出流来操作文件了
    FileOutputStream fileOutputStream = null;
    try {
        // fileOutputStream = new FileOutputStream(filePath, true); // 以追加的形式写入文件
        fileOutputStream = new FileOutputStream(filePath); // 以覆盖的形式写入文件
        List<String> list = new ArrayList<>();
        list.add("abc");
        list.add("def");
        for (String s : list) {
            fileOutputStream.write(s.getBytes());
        }
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        // 关闭流资源
        try {
            fileOutputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
四、字符文件流
1. 字符文件输入流 FileReader

FileReader继承于InputStreamReader,而InputStreamReader又继承于Reader

单个字符的读取

public static void main(String[] args) {
    String filePath = "d:\\hello.txt";
    File file = new File(filePath);
    // 创建一个字符文件输入流,将输入流和文件关联起来,就可以通过该输入流来操作文件了
    FileReader fileReader = null;
    int readData = 0;
    try {
        fileReader = new FileReader(file);
        while ((readData = fileReader.read()) != -1) {
            System.out.print((char)readData);
        }
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        // 关闭流资源
        try {
            fileReader.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

字符数组的读取

public static void main(String[] args) {
    String filePath = "d:\\hello.txt";
    File file = new File(filePath);
    // 创建一个字符文件输入流,将输入流和文件关联起来,就可以通过该输入流来操作文件了
    FileReader fileReader = null;
    int readLen = 0;
    char[] chars = new char[8];
    try {
        fileReader = new FileReader(file);
        while ((readLen = fileReader.read(chars)) != -1) {
            System.out.print(new String(chars, 0, readLen));
        }
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        // 关闭流资源
        try {
            fileReader.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
2. 字符文件输出流 FileWriter

FileWriter继承于OutputStreamWriter,而OutputStreamWriter又继承于Writer

FileWriter使用后,必须要关闭(close)或者刷新(flush),否则写入不到指定的文件

public static void main(String[] args) {
    String filePath = "d:\\hello.txt";
    // 创建一个字符文件输出流,将输出流和文件关联起来,就可以通过该输出流来操作文件了
    FileWriter fileWriter = null;
    try {
        fileWriter = new FileWriter(filePath); // 默认是覆盖的方式
        // 写入一个字符 write(int)
        fileWriter.write('a');
        // 写入字符数组 write(char[])
        char[] ch = {'b', 'c', '你', '好'};
        fileWriter.write(ch);
        // 写入字符数组的指定部分 write(char[], off, len)
        fileWriter.write(ch, 0, 2);
        // 写入字符串 write(String)
        fileWriter.write("nihao");
        // 写入字符串 write(String, off, len)
        fileWriter.write("我爱中国", 0, 2);
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        // 关闭流资源
        try {
            fileWriter.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
五、节点流和处理流
  • 节点流可以从一个特定的数据源读写数据,例如FileInputStream、FileOutputStream、FileReader、FileWriter就属于节点流,只能读取文件或者写入文件,无法操作其它数据源。
  • 处理流也叫包装流,是可以对节点流进行包装,增强节点流的功能,为程序提供更为强大的读写功能,例如BufferedInputStream、BufferedOutputStream、BufferedReader、BufferedWriter是最常用的包装流,包装流可以处理多种数据源,只需要在创建包装流对象的时候动态传递对应的参数即可。

一般来说,我们都会使用包装流,而不会直接使用节点流,因为使用包装流性能可以得以提高,例如缓冲流BufferedReader/BufferedWriter就以增加缓冲的方式来提高输入输出的效率,并且包装流也会提供一系列便捷的方法来一次输入输出大批量的数据,使用更加灵活方便。

包装流的设计就使用了装饰者模式,节点流和包装流共同继承了父类,例如InputStream,并且包装流内部将InputStream父类聚合进来,相当于把节点流聚合到内部,这样一来,可以在不改变原有对象的基础上动态增强节点流的功能,例如原来的节点流FileInputStream只能文件一行行读取,通过包装流包装之后,可以实现对文件多行读取,底层调用的还是节点流的方法,只是对原来的方法做了增强,同时,包装流也提供了一些更为丰富的方法,增强了节点流的读写功能。

关于装饰者模式,可以参考文章:https://blog.youkuaiyun.com/can_chen/article/details/105786680

六、字符缓冲流
1. 字符输入缓冲流 BufferedReader

是一个包装流,从字符输入流读取文本,缓冲字符,以提供字符、数组和行的高效读取

public static void main(String[] args) {
    String filePath = "d:\\hello.txt";
    // 创建一个节点流
    FileReader fileReader = null;
    // 创建一个包装流
    BufferedReader bufferedReader = null;
    String line = null;
    try {
        fileReader = new FileReader(filePath);
        bufferedReader = new BufferedReader(fileReader);
        // 一行一行读取数据,效率高
        while ((line = bufferedReader.readLine()) != null) {
            System.out.println(line);
        }
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        // 关闭流资源
        try {
            // 关闭最外层的流就可以,底层实际去关闭的也是节点流
            bufferedReader.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
2. 字符输出缓冲流 BufferedWriter
public static void main(String[] args) throws IOException {
    String filePath = "d:\\hello.txt";
    // 以追加的方式写入
    // BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(filePath, true));
    // 以覆盖的方式写入
    BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(filePath));
    bufferedWriter.write("hello,world");
    // 插入一个和系统相关的换行符
    bufferedWriter.newLine();
    bufferedWriter.write("我是程序猿");
    bufferedWriter.close();
}
3. 通过BufferedReader和BufferedWriter完成文件拷贝
public static void main(String[] args) {
    String srcFilePath = "d:\\hello.txt";
    String destFilePath = "e:\\hello.txt";
    BufferedWriter bw = null;
    BufferedReader br = null;
    String line = null;
    try {
        // 注意:BufferedReader和BufferedWriter是按照字符操作,应该用于操作文本文件
        // 不要用来操作二进制文件(图片、视频、doc、pdf)会造成文件损坏
        br = new BufferedReader(new FileReader(srcFilePath));
        bw = new BufferedWriter(new FileWriter(destFilePath));
        // readLine读取一行内容,但是没有换行,需要手动加上
        while ((line = br.readLine()) != null) {
            bw.write(line);
            bw.newLine();
        }
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        if (br != null) {
            try {
                br.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        if (bw != null) {
            try {
                bw.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
七、标准输入输出流
  • System.in 代表标准输入,默认设备是键盘,in是System类的一个变量 public final static InputStream in,该变量的编译类型是InputStream,运行类型是BufferedInputStream
    请添加图片描述
    应用:Scanner sc = new Scanner(System.in); Scanner 是从标准输入键盘接收数据
  • System.out 代表标准输出,默认设备是显示器,out是System类的一个变量 public final static PrintStream out,该变量的编译类型是PrintStream,运行类型也是PrintStream
    请添加图片描述
    应用:System.out.println("");是使用out对象将数据输出到显示器
八、转换流InputStreamReader和OutputStreamWriter

转换流的意思是可以将字节流转换为字符流,使用转换流的作用是解决乱码问题;因为一般来说,如果我们读取文件时,没有指定文件的编码格式,那么默认会以UTF-8的编码格式读取文件,那如果我们的文件不是以UTF-8编码的格式保存的,例如gbk编码(ANSI),那么读取该文件时就会出现乱码。

一般来说,读取文本文件,会使用字符流读取,效率较高,如果读取的文件编码格式不是默认的UTF-8,那么需要使用转换流InputStreamReader指定具体编码格式,同时,会传入一个字节流进行转换,最终使用字符流去读取文件,可以避免乱码问题。

public static void main(String[] args) throws IOException {
    String filePath = "d:\\hello.txt";
    // 创建一个转换流,该转换流将字节流转换成字符流,并指定读取的文件编码格式为gbk
    InputStreamReader inputStreamReader = new InputStreamReader(new FileInputStream(filePath), "gbk");
    // 创建一个字符包装流,将转换流作为包装的对象,提高转换流的读取效率
    BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
    String line = null;
    while ((line = bufferedReader.readLine()) != null) {
        System.out.println(line);
    }
    // 关闭最外层的流即可
    bufferedReader.close();
}

同样,当我们使用输出流往文件写入数据时,默认该文件也是以utf8编码的格式保存,如果我们想以指定的编码格式保存,可以使用转换流OutputStreamWriter

public static void main(String[] args) throws IOException {
        String filePath = "d:\\hello.txt";
        OutputStreamWriter outputStreamWriter = new OutputStreamWriter(new FileOutputStream(filePath), "gbk");
        outputStreamWriter.write("以gbk编码格式保存文件");
        outputStreamWriter.close();
    }
}
九、Properties类

位于java.util包下,它的父类是Hashtable,是一个专门用于读取配置文件的集合类,它要求配置文件的格式为:键=值,并且键值对不需要有空格,值不需要用引号引起来,默认类型是String,例如:user=root

以下是传统方式读取配置文件,这种方式不够灵活,如果我们只想读取配置文件中某个键的值,这种方式不方便

public static void main(String[] args) throws IOException {
    // 传统方式读取配置文件
    BufferedReader bufferedReader = new BufferedReader(new FileReader("src\\mysql.properties"));
    String line = "";
    while ((line = bufferedReader.readLine()) != null) {
        System.out.println(line);
    }
    bufferedReader.close();
}

通过Properties类来读取配置文件

public static void main(String[] args) throws IOException {
    // 创建Properties对象
    Properties properties = new Properties();
    // 加载指定配置文件
    properties.load(new FileReader("src\\mysql.properties"));
    // 把所有kv键值对打印到控制台
    properties.list(System.out);
    // 根据key获取指定的值
    String user = properties.getProperty("user");
    System.out.println(user);
}

通过Properties类来创建配置文件/修改配置文件内容

public static void main(String[] args) throws IOException {
    // 创建Properties对象
    Properties properties = new Properties();
    // 设置属性(如果key存在,则是修改,不存在则是创建)
    properties.setProperty("charset", "utf8");
    properties.setProperty("name", "小明"); // 中文保存的是Unicode码值
    // 保存配置文件(第二个参数comments指的是配置文件的注释内容,设置之后会显示在配置文件第一行,不需要注释则设置为null)
    properties.store(new FileOutputStream("src\\mysql2.properties"), null);
}
十、获取文件编码

通常,在使用IO流读取一个文件时,如果不知道文件编码格式的前提下,可能会出现中文乱码问题;以下代码可以判断出大部分情况下的常见文件编码,拿到文件编码之后,使用输入流读取文件时指定编码,就可以避免乱码问题。

public String getFileEncode(String path) {
    String charset = "GBK";
    byte[] head = new byte[3];
    BufferedInputStream bis = null;
    // 代码扫描修改
    int firstKey = 0;
    int secondKey = 1;
    int thirdKey = 2;
    int firstValue = -17;
    int secondValue = -69;
    int thirdValue = -65;
    try {
        boolean checked = false;
        bis = new BufferedInputStream(new FileInputStream(path));
        bis.mark(0);
        int read = bis.read(head, 0, 3);
        if (read == -1) {
            // 文件内容为空,返回默认编码
            return charset;
        }
        if (head[firstKey] == -1 && head[secondKey] == -2) {
            // UTF-16LE
            charset = "Unicode";
            checked = true;
        } else if (head[firstKey] == -2 && head[secondKey] == -1) {
            // UTF-16BE
            charset = "Unicode";
            checked = true;
        } else if (head[firstKey] == firstValue && head[secondKey] == secondValue && head[thirdKey] == thirdValue) {
            // UTF8带BOM
            charset = "UTF-8";
            checked = true;
        }
        bis.reset();
        if (!checked) {
            int len = 0;
            int loc = 0;
            while ((read = bis.read()) != -1) {
                loc++;
                if (read >= 0xF0) {
                    break;
                }
                if (0x80 <= read && read <= 0xBF) {
                    // 单独出现BF以下的,也算是GBK
                    break;
                }
                if (0xC0 <= read && read <= 0xDF) {
                    read = bis.read();
                    if (0x80 <= read && read <= 0xBF) {
                        // 双字节 (0xC0 - 0xDF) (0x80 - 0xBF), 也可能在GBK编码内
                        continue;
                    } else {
                        break;
                    }
                } else if (0xE0 <= read && read <= 0xEF) {
                    // 也有可能出错,但是几率较小
                    read = bis.read();
                    if (0x80 <= read && read <= 0xBF) {
                        read = bis.read();
                        if (0x80 <= read && read <= 0xBF) {
                            charset = "UTF-8";
                            break;
                        } else {
                            break;
                        }
                    } else {
                        break;
                    }
                }
            }
        }
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        if (bis != null) {
            try {
                bis.close();
            } catch (IOException ex) {
            }
        }
    }
    return charset;
}

PS:读取UTF-8带BOM文件时,可能会出现读取到的字符串多了一个额外字符’\uFEFF’,这个字符位于字符串的第一个字符,过滤掉即可。

String firstSql = "文件读取出来的字符串";
if ("\uFEFF".equals(firstSql.substring(0,1))) {
    firstSql = firstSql.substring(1);
}
<think>好的,我现在需要帮助用户了解Java IO的使用教程和常见问题解决。首先,我得整理一下Java IO的基础知识,确保覆盖主要概念和常见问题。用户可能刚接触IO,所以需要从基础开始,结构清晰。 首先,Java IO的分类。记得IO主要分为字节和字符,还有节点和处理。根据引用[5],节点是直接操作数据源的,比如文件,而处理是对其他的包装,增加功能。可能需要举一些例子,比如FileInputStream是节点,BufferedInputStream是处理。 然后,常用IO类。用户可能需要知道具体的类如何使用,比如FileReader、BufferedReader这些。要提到输入输出流的基类,如InputStream、OutputStream、Reader、Writer,并说明它们的关系。引用[5]提到,IO类都是从这四个抽象基类派生的,子类名以后缀形式出现,比如FileInputStream继承自InputStream,这点要说明。 接下来是基础使用教程。分步骤讲解,比如文件读取、写入、复制。可以结合引用[3]中的代码示例,比如用FileInputStream读取文件,循环读取直到-1。但要注意,用户可能需要字符的例子,比如使用FileReader和BufferedReader读取文本文件。同时,文件写入可以用FileOutputStream或FileWriter,记得关闭或者使用try-with-resources自动管理。 然后是常见问题及解决。用户可能遇到的问题比如乱码,这时候需要提到字符编码,使用转换InputStreamReader和OutputStreamWriter,指定正确的编码,比如UTF-8。还有资源未关闭的问题,引用[2]提到JDK7的try-with-resources可以自动关闭资源,这样更安全。类找不到异常,比如引用[1]提到的序列化问题,虽然不是直接IO的问题,但可能涉及对象,需要提醒用户确保环境一致。处理包装顺序的问题,比如先关闭外层,内层会自动关闭,或者正确的包装顺序,比如先FileInputStream再BufferedInputStream。 性能优化部分,可以提到使用缓冲提升效率,比如BufferedInputStream,或者使用NIO的Files类进行文件操作,更高效。引用[4]中的装饰设计模式,处理通过包装增强功能,比如PrintStream包装OutputStream,这也需要解释一下。 可能用户没有提到的但需要注意的点,比如JDK7和JDK9对资源管理的改进,引用[2]中提到只能放置资源对象,否则报错,所以在try-with-resources中必须使用实现了Closeable接口的类。这点要提醒用户,避免错误。 最后生成相关问题,要覆盖分类、解决乱码、资源管理、设计模式、性能优化等方面的问题,帮助用户进一步学习。 检查是否有遗漏的部分,比如内存和打印,引用[4]提到了内存,可能需要简单说明,但用户的问题可能更关注基础。确保每个部分都有对应的引用支持,比如处理部分引用[5],转换引用[4],资源管理引用[2]和[3]。 现在组织回答的结构:分类、常用类、教程、常见问题、优化,最后相关问题。确保每个段落简短,使用代码示例,正确引用标记。比如在讲转换时,引用[4],在资源管理时引用[2]和[3]。 还要注意LaTeX格式,行内公式用$...$,独立公式不需要,因为问题可能不涉及数学,但如果有变量或术语可能需要,不过这里可能用不上。所以主要检查代码块是否正确,引用标记是否在正确位置,比如关闭资源处添加[^2][^3],转换处[^4]。 最后生成的相关问题要覆盖教程中的各个部分,比如分类、乱码解决、资源管理、设计模式、NIO对比等,确保用户能深入了解更多内容。</think># Java IO使用教程与常见问题解决 ## 一、Java IO基础分类 Java IO按数据单位分为**字节**(处理二进制数据)和**字符**(处理文本数据),按角色分为: - **节点**:直接操作数据源(如文件、内存),例如:`FileInputStream`、`FileReader`[^5]。 - **处理/包装**:对其他进行包装以增强功能,例如:`BufferedInputStream`、`InputStreamReader`。 --- ## 二、常用IO类 | 类型 | 字节类 | 字符类 | |------------|------------------------|-----------------------| | **输入流** | `InputStream` | `Reader` | | **输出流** | `OutputStream` | `Writer` | | **文件操作** | `FileInputStream` | `FileReader` | | **缓冲优化** | `BufferedInputStream` | `BufferedReader` | | **转换** | `InputStreamReader` | `OutputStreamWriter` | --- ## 三、基础使用教程 ### 1. 文件读取(字节) ```java try (FileInputStream fis = new FileInputStream("test.txt")) { int data; while ((data = fis.read()) != -1) { System.out.print((char) data); } } catch (IOException e) { e.printStackTrace(); } ``` > **说明**:使用`try-with-resources`自动关闭,避免资源泄漏。 ### 2. 文件写入(字符) ```java try (FileWriter fw = new FileWriter("output.txt")) { fw.write("Hello, Java IO!"); } catch (IOException e) { e.printStackTrace(); } ``` ### 3. 文件复制(缓冲优化) ```java try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream("source.txt")); BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("target.txt"))) { byte[] buffer = new byte[1024]; int len; while ((len = bis.read(buffer)) != -1) { bos.write(buffer, 0, len); } } catch (IOException e) { e.printStackTrace(); } ``` --- ## 四、常见问题与解决方案 ### 1. 乱码问题 - **原因**:字节直接读取文本时未指定编码。 - **解决**:使用转换明确指定编码: ```java try (InputStreamReader isr = new InputStreamReader(new FileInputStream("text.txt"), StandardCharsets.UTF_8)) { // 读取操作 } ``` > 引用[^4]:转换通过装饰模式增强功能。 ### 2. 资源未关闭导致泄漏 - **解决**:优先使用`try-with-resources`语法,确保资源自动关闭[^2]。 ### 3. 类找不到异常(ClassNotFoundException) - **场景**:序列化与反序列化时跨JVM环境。 - **解决**:确保序列化与反序列化的类在相同JVM中[^1]。 ### 4. 处理包装顺序错误 - **原则**:先关闭外层,内层会自动关闭。例如: ```java try (BufferedReader br = new BufferedReader(new FileReader("file.txt"))) { // 无需单独关闭FileReader } ``` --- ## 五、性能优化建议 1. **使用缓冲**:如`BufferedInputStream`减少IO次数。 2. **优先使用NIO**:JDK7+的`Files`类提供更高效的文件操作。 3. **避免频繁开关**:批量读写代替单字节操作。 --- §§ 1. 字节和字符的应用场景有何区别? 2. 如何解决Java IO读取文本文件时的乱码问题? 3. `try-with-resources`语法是如何实现资源自动关闭的? 4. 装饰设计模式在Java IO中如何体现? 5. NIO与传统IO在性能上有哪些改进?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值