对IO流关闭的思考

流必须要关闭的原因

java相对C,C++来说不需要手动释放内存,在对象引用被消除之后,正常情况下内存资源是会被垃圾回收,那么在使用完IO流之后为什么需要手动关闭.
这是为了回收系统资源,比如释放占用的端口,文件句柄,网络操作数据库应用等.对Unix系统来说,所有的资源都可以抽象成文件,所以可以通过lsof来观察。

看下面这个例子,我们创建许多的IO流但是不关闭

public class Hello {
    public static void main(String[] args) throws Exception {
        for (int i = 0; i < 100; i++) {
            FileInputStream is = new FileInputStream("/tmp/data/data"+i);
            byte[] bs = new byte[512];
            int len;
            while ((len = is.read(bs)) != -1) ;
            Thread.sleep(1000L);
        }
        Thread.sleep(10000L);
    }
}

运行这个程序之后,使用losf命令查看相关的文件句柄,会发现文件句柄始终在增长,当积累到一定时间之后会出现too many open files错误

如何关闭流

在java7以前流的关闭比较繁琐,我们使用try-finally块进行关闭操作,同时还要考虑流关闭过程中可能的异常

InputStream is = null;
OutputStream os = null;
try{
   // ...
}
finally{
  if(is != null)
     try{
       is.close();
     }
     catch(IOException e){}

  if(os != null)
     try{
       os.close()
     }
     catch(IOException e){}
}

在java7以后,我们可以使用try-with-resources,将需要关闭的流对象放在try的()中创建,需要注意的是只有实现Closeable接口的对象才可以放在这里创建

try (BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));) {
    String s;
    while ((s = reader.readLine()) != null) {
        if (s.equalsIgnoreCase("quit")){
            break;
        }
        System.out.println(s.toUpperCase());
    }
} catch (Exception e) {
    e.printStackTrace();
}

包装流的关闭

包装流关闭的时候会自动调用被包装的流的关闭方法

看下面这个例子
打开的文件句柄必须要关闭,但是这个例子是例外,从控制台中读取字符串并打印

try (BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));) {
    String s;
    while ((s = reader.readLine()) != null) {
        if (s.equalsIgnoreCase("quit")){
            break;
        }
        System.out.println(s.toUpperCase());
    }
} catch (Exception e) {
    e.printStackTrace();
}

这里使用了try-with-resources的写法(后面会具体的讲到)自动关闭reader对象,但是这里需要注意,BufferedReader包装的是System.in流,这个流是java的标准流用于接收键盘数据,如果这里讲System.in流关闭,那么其它的程序可能会出错,那么我们究竟需要关闭哪些流呢?统一的原则是 谁创建谁销毁,如果我们必须要在关闭不是自己创建的流对象,一定要通过文档告知接口的调用方

关闭包装流与被包装流的时候有没有顺序视情况而定

我们在使用缓存输出流的情况下,必须要优先关闭缓存输出流否则抛出异常.原因很简单,看下面这个例子,这里为了方便没有规范的关闭流

 FileOutputStream fos = new FileOutputStream(filepath);
 OutputStreamWriter osw = new OutputStreamWriter(fos, "UTF-8");
 BufferedWriter bw = new BufferedWriter(osw);
 bw.write("java IO close test");
 // 从内带外顺序顺序会报异常
 fos.close();
 osw.close();
 bw.close();

缓存输出流的作用是将输出数据先缓存起来,等待缓存区满了之后一次性输出来提高通道利用率,在调用缓存输出流的close方法的时候,会调用被装饰对象的write方法,如果被装饰对象被提前关闭了,那么自然就抛出异常

所以上面的例子修改为

bw.close();
fos.close();
osw.close();

如果没有涉及到缓存输出流那么就无所谓关闭顺序了

FileOutputStream fileOutputStream = new FileOutputStream("c:\\a.txt");
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream);

bufferedOutputStream.write("test write something".getBytes());
bufferedOutputStream.flush();

fileOutputStream.close();//先关闭被包装流
bufferedOutputStream.close();
<think>我们首先需要明确问题:使用Java IO读取TXT文件时,出现大小写字母丢失的问题。根据描述,我们需要排查原因并提供解决方案。 根据引用[1]和引用[4]: - 引用[1]展示了使用FileReader(字符)读取文件,一次读取一个字符,并强制转换为char类型。 - 引用[4]提到,按照读取数据方式同,分为字节和字符。字节一次读取一个字节(8位),字符则按照字符来读取。 问题:大小写字母丢失。可能的原因分析: 1. 使用字节读取文本文件时,如果文本文件包含多字节字符(如中文),可能会出现乱码,但这里提到的是大小写字母丢失,所以可能是多字节字符的问题。但是,如果是使用字节读取,然后转换为字符时处理当,也可能导致问题。 2. 在读取过程中,可能对读取的数据进行了某种转换(如转换为小写)?但问题描述中没有提到。 3. 另一种可能是,在读取过程中,由于编码问题,导致某些字符被错误地转换。 然而,根据用户提供的引用[3]中的例子,使用RandomAccessFile(它既可以读字节也可以读字符)时,因为先读取了一个字节(对应一个英文字符),然后剩下的读取到一个字节数组中,再转换为字符串时,跳过了第一个字符,导致首字母丢失。但这里的问题描述是大小写字母丢失,而是首字母丢失。 重新思考:大小写字母丢失,意味着原本的大写字母变成了小写字母,或者小写字母变成了大写字母?还是说某些字母直接丢失了?问题描述没有明确,但根据“丢失”一词,可能是某些字母没有被正确读取。但用户提到的是“大小写字母丢失”,所以我们考虑两种情况:一是大小写转换了(如全部变成小写),二是某些字母(可能特定大小写)丢失了。 但根据常见的IO问题,我们更倾向于认为可能是编码问题或者读取方式问题。 常见原因: 1. 编码问题:如果读取文件时使用的编码与文件实际编码一致,可能会导致字符解析错误。例如,文件是UTF-8编码,而读取时使用ISO-8859-1,那么对于英文字母可能没有问题(因为单字节字符在UTF-8和ISO-8859-1中相同),但如果是其他字符可能出错。但大小写字母都是单字节,所以通常会丢失,除非有特殊字符。 2. 读取方式:如果使用字节读取,然后自己将字节数组转换为字符串,如果转换时指定了错误的编码,也可能导致问题。 但是,问题描述中并没有说明使用的是字符还是字节,也没有说明编码设置。 再看引用[1]中,使用FileReader读取,FileReader使用的是系统默认编码(在Windows中文环境下可能是GBK,Linux可能是UTF-8)。如果文件编码与系统默认编码一致,可能会导致乱码,但一般会导致大小写字母丢失,而是整个乱码。 那么,另一种可能:在读取过程中,对字符进行了处理,比如转成了小写?但代码中没有体现。 根据用户提供的引用[3]的例子,我们注意到一个细节:RandomAccessFile的read方法读取一个字节(注意:read()方法读取一个字节,返回0-255的整数,如果到达文件末尾返回-1)。而英文字母在ASCII中,大写字母范围是65-90,小写字母是97-122。如果我们在读取字节后,将其强制转换为char,那么会得到对应的字符。所以,如果使用字节并按字节读取,然后转换为char,对于英文字母是会有大小写丢失的。 但是,如果文件是UTF-8编码,并且包含非ASCII字符(比如中文),那么一个中文字符由多个字节组成。如果按字节读取并强制转换为char,那么每个字节都会被当作一个字符(可能是乱码),这样就会导致原始字符被拆分成多个乱码字符,其中可能包含一些字母被错误地显示出来,但会导致大小写转换。 所以,我们考虑可能是用户代码中使用了字节,并且处理方式当,导致部分字节被忽略或转换错误。 然而,用户的问题描述是“大小写字母丢失”,这比较奇怪。我们可能需要更多的信息,但根据已有信息,我们可以给出一些排查方向。 解决方案建议: 1. 确认文件编码和读取时使用的编码是否一致。建议使用字符,并明确指定编码。例如,使用InputStreamReader,并指定编码。 2. 检查读取代码,是否在读取过程中对字符进行了处理(如大小写转换、过滤等)。 3. 确保使用正确的类型:对于文本文件,推荐使用字符(如FileReader或BufferedReader),或者使用字节配合正确的编码转换。 示例代码(使用字符,并指定编码): ```java import java.io.*; public class ReadTextFile { public static void main(String[] args) { File file = new File("test.txt"); // 使用try-with-resources确保关闭 try (Reader reader = new InputStreamReader(new FileInputStream(file), "UTF-8")) { int c; while ((c = reader.read()) != -1) { char ch = (char) c; // 这里直接输出,也可以存储到字符串中 System.out.print(ch); } } catch (IOException e) { e.printStackTrace(); } } } ``` 或者使用BufferedReader读取行: ```java import java.io.*; public class ReadTextFileLineByLine { public static void main(String[] args) { File file = new File("test.txt"); try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file), "UTF-8"))) { String line; while ((line = reader.readLine()) != null) { System.out.println(line); } } catch (IOException e) { e.printStackTrace(); } } } ``` 如果问题仍然存在,请检查原始文件内容是否确实包含大小写字母,以及是否在写入文件时就已经丢失。 另外,根据引用[3]中的例子,如果使用RandomAccessFile,并且先读取了一个字节,那么后续读取会从下一个字节开始,导致第一个字节丢失。所以,检查代码逻辑是否在读取过程中有跳过部分字节的情况。 总结可能的原因: 1. 编码一致导致乱码,但通常会只影响大小写。 2. 读取过程中有部分字节未被处理(如读取了部分字节后没有继续读取)。 3. 程序中对读取的字符进行了大小写转换(如调用了toLowerCase())。 4. 文件本身在写入时就已经丢失了大小写字母。 因此,我们建议: - 检查读取代码,确保没有进行任何转换操作。 - 使用字符并明确指定与文件相同的编码。 - 使用十六进制编辑器查看文件内容,确认文件中的字母大小写是否正确。 - 逐字节读取文件,并输出每个字节的值(0-255)和对应的字符,观察是否所有字母都被正确读取。 如果问题仍然无法解决,请提供具体的代码片段和文件样本(部分内容)以便进一步分析。 相关问题: 1. 如何确定文本文件的编码格式? 2. Java中字节和字符有什么区别? 3. 如何解决Java读取文件时出现的中文乱码问题?</think>### Java IO读取TXT文件大小写字母丢失问题排查与解决方案 #### 问题原因分析 根据引用[3]和引用[4]的原理,Java IO读取文件时大小写字母丢失通常由以下原因导致: 1. **字节读取字符完整** 字节(如`FileInputStream`)按字节读取数据(每个字节8位),但UTF-8编码中英文字母虽是单字节,若错误处理多字节字符(如中文),可能导致后续字节被错误解析(引用[4])。 2. **指针位置错误** 如引用[3]的`RandomAccessFile.read()`示例:先读一个字节后未重置指针,导致后续读取从第二个字节开始,造成首字母丢失(如"Hello"变成"ello")。 3. **编码匹配** 文件实际编码(如UTF-8)与读取时指定的编码(如默认ISO-8859-1)一致,导致字符解码错误(引用[1])。 4. **数据截断错误** 使用`read(byte[])`时未正确处理返回值`len`,错误转换全部缓冲区(如`new String(bytes)`而非`new String(bytes, 0, len)`),可能包含无效字节。 #### 解决方案 ##### 方法1:使用字符(推荐) ```java try (FileReader fr = new FileReader("file.txt", StandardCharsets.UTF_8)) { int c; while ((c = fr.read()) != -1) { char ch = (char) c; // 直接转换为字符 System.out.print(ch); // 保留原始大小写 } } ``` - **优势**:字符自动处理编码转换,避免字节操作错误(引用[1])。 ##### 方法2:字节+显式指定编码 ```java try (FileInputStream fis = new FileInputStream("file.txt"); InputStreamReader isr = new InputStreamReader(fis, StandardCharsets.UTF_8)) { int c; while ((c = isr.read()) != -1) { System.out.print((char) c); // 正确转换字节为字符 } } ``` ##### 方法3:修复指针和缓冲区 ```java try (RandomAccessFile raf = new RandomAccessFile("file.txt", "r")) { raf.seek(0); // 重置指针到文件开头 byte[] bytes = new byte[(int) raf.length()]; int len = raf.read(bytes); System.out.println(new String(bytes, 0, len, "UTF-8")); // 按实际长度转换 } ``` #### 关键注意事项 1. **显式指定编码** 始终通过`StandardCharsets.UTF_8`或`new String(bytes, "UTF-8")`声明编码(引用[4])。 2. **正确处理读取长度** 使用`read(byte[])`时,必须用返回值`len`限定有效数据范围。 3. **避免强制类型转换错误** 字节读取的`int`值需通过`(char)`转换,而非直接处理字节值(引用[1])。 4. **优先使用BufferedReader** 高效读取文本: ```java try (BufferedReader br = new BufferedReader(new FileReader("file.txt"))) { String line; while ((line = br.readLine()) != null) { System.out.println(line); // 保留原始大小写 } } ``` #### 验证步骤 1. 检查文件真实编码:`file -i file.txt`(Linux)或文本编辑器查看 2. 用十六进制工具验证文件内容:`xxd file.txt` 3. 逐字节输出对比: ```java FileInputStream fis = new FileInputStream("file.txt"); int b; while ((b = fis.read()) != -1) { System.out.printf("%02X ", b); // 打印原始字节值 } ``` > **根本原因**:问题通常源于字节/字符混用、编码未指定或指针位置错误,而非IO本身的大小写转换(引用[3][4])。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值