前言: 最近我遇到一个问题,我新安装的eclipse里面没有修改默认的编码(GBK),导致了它一直都是以默认编码创建的Java文件(包括一些配置文件)。所以,当我把这个项目里面的代码拷贝到其它电脑或者反过来操作,打开都是一片乱码(这里面主要是我平时学习写的一些示例代码)。虽然,解决这个问题,也很简单,通常我就是将Java源文件使用notepad++打开,然后修改文件的编码。但是,这种手工操作,毕竟还是效率太低了。因为我现在遇到一个问题,我已经创建了20多个项目了(都是个人学习创建的),然后每个里面还有不同的包,包里面还有若干个java文件。因为我要直接修改整个Eclipse的编码了,这样会导致所有的java文件里面的中文注释全部乱码,所以必须想一个办法把这些文件的编码全部转换过来。

效果演示


IDE乱码文件恢复及修改演示视频


注意:如果你修改编码的文件本身的编码和当前工程中的其它文件编码相同,在修改后恢复会导致乱码的产生!在视频中我的文件损坏就是因为这个原因。这里推荐三种解决方式:

1.这个修改编码的源文件在其它路径执行(不在当前路径)。
2.这个修改编码的源文件和当前的编码不同,这样它读取自身时会发生异常,即转换失败。
3.复制一份,调换变量SRC_ENCODE和DES_ENCODE的位置。(感觉有点傻了)

如下是一个工程目录的文件结构:

使用windows的cmd命令可以查看:tree /F

java 运行程序 指定编码 java指定文件编码_源文件


java 运行程序 指定编码 java指定文件编码_java 运行程序 指定编码_02


java 运行程序 指定编码 java指定文件编码_System_03

执行程序

java 运行程序 指定编码 java指定文件编码_java_04

执行结果

java 运行程序 指定编码 java指定文件编码_java_05

java 运行程序 指定编码 java指定文件编码_乱码_06

注意:执行的过程中可能会报异常。因为我这里修改文件编码的文件,也在eclipse的目录下面,但是它是我改变了eclipse编码后创建的,即它是UTF-8编码的文件,当尝试使用GBK读取它时,会报如下异常,对于此文件的转换即是失败的(本身就是正常的,不需要转换,它要是转换了,反而是一个麻烦了。)对于,此异常可以忽略它,它的意思就是尝试以指定编码读取的文件的格式不对。

java 运行程序 指定编码 java指定文件编码_乱码_07

PS:前几天看到了一张很有用的表,这里可以参考一下,应该是没有问题的。对于我们这里的乱码来说它是正确的。

java 运行程序 指定编码 java指定文件编码_java 运行程序 指定编码_08

具体思路

思路:
1.首先指定需要进行编码转换的文件所在的目标路径
2.遍历这个目标路径下的所有文件夹和Java源文件(或者你想要修改编码的文本文件)
3.对于其中的每一个文件执行如下操作:
a)、如果该文件是文件夹,执行步骤2;
b)、如果该文件是java源文件,修改其字符集编码为目标字符集编码。

这就是这个程序的主要流程了,一个简单的递归代码。

注: 所谓的修改编码其实也很简单,直接将该java源文件的所有内容以原有编码格式读入内存,然后将其以指定编码格式再写入硬盘上,并且采用的是覆盖的方式。因为读取和写入传入的是同一个路径,所以再次写入会将源文件覆盖掉。这样就达到了对一个工程中所有的java源文件编码进行转换的目的了(给人的感觉好像就是原文件的编码改变了,实际上是原文件被新文件给覆盖了),你可以考虑一下如果不覆盖有什么缺点?

完整代码

package re;

import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.StandardOpenOption;
import java.util.Arrays;
import java.util.List;

public class SourceFileTransferEncoder {
	
	/**
	 * 知识分子的真正陷阱是沦入过度专业化与技术化的陷阱,失去了对更广阔世界的好奇心。
	 * */
	
	/**
	 * 源文件字符集
	 * */
	private static final Charset SRC_ENCODE = Charset.forName("GBK");
	
	/**
	 * 目标文件字符集
	 * */
	private static final Charset DES_ENCODE = StandardCharsets.UTF_8;
	
	
	public static void main(String[] args) {
		String dirPath = "D:\\Eclipse\\workspace\\request_learn";
		File baseDir = new File(dirPath);
		// 开始执行
		transfer3(baseDir);  
	}
	
	static void transfer(File baseDir) {
		for (File file : baseDir.listFiles(f -> f.isDirectory() || f.getName().endsWith(".java"))) {
			if (file.isDirectory()) {
				System.out.println("进入文件目录:" + file.getAbsolutePath());
				transfer(file);   // 递归操作
			} else {
				try {
					List<String> lines = Files.readAllLines(file.toPath(), SRC_ENCODE);
					Files.write(file.toPath(), lines, DES_ENCODE, StandardOpenOption.TRUNCATE_EXISTING);
					System.out.println("文件编码转换成功:" + file.getAbsolutePath());
				} catch (IOException e) {
					e.printStackTrace();
					System.out.println("文件编码转换失败:" + file.getAbsolutePath());
				}
			}
		}
	}
}

代码重构

原有的代码采用的是早已经存在的forEach循环了,现在我们使用java8的语法对其进行改进,使之更符合java的演化特点。

static void transfer2(File baseDir) {
    Arrays.stream(baseDir.listFiles(f -> f.isDirectory() || f.getName().endsWith(".java"))).forEach(file -> {
        if (file.isDirectory()) {
            System.out.println("进入文件目录:" + file.getAbsolutePath());
            transfer2(file);   // 递归操作
        } else {
            try {
                List<String> lines = Files.readAllLines(file.toPath(), SRC_ENCODE);
                Files.write(file.toPath(), lines, DES_ENCODE, StandardOpenOption.TRUNCATE_EXISTING);
                System.out.println("文件编码转换成功:" + file.getAbsolutePath());
            } catch (IOException e) {
                e.printStackTrace();
                System.out.println("文件编码转换失败:" + file.getAbsolutePath());
            }
        }
    });
}

这里重构之后,代码缺依然不是很简洁,主要还是因为最后这个foreach操作太大了,它是实现的细节部分,影响了整体的可阅读性。所以我们将其移走,单独变成一个静态方法,再次重构:

static void transfer3(File baseDir) {
    Arrays.stream(baseDir.listFiles(f->f.isDirectory() || f.getName().endsWith(".java")))   // 获取目标路径下面所有的文件夹和 Java文件
        .forEach(SourceFileTransferEncoder::execute);                                     // 递归调用文件
}

static void execute(File file) {
    if (file.isDirectory()) {
        System.out.println("进入文件目录:" + file.getAbsolutePath());
        transfer3(file);   // 递归操作
    } else {
        try {
            List<String> lines = Files.readAllLines(file.toPath(), SRC_ENCODE);
            Files.write(file.toPath(), lines, DES_ENCODE, StandardOpenOption.TRUNCATE_EXISTING);
            System.out.println("文件编码转换成功:" + file.getAbsolutePath());
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("文件编码转换失败:" + file.getAbsolutePath());
        }
    }
}

注:这里可以将文件的过滤操作放到流操作的后面,即使用filter操作进行过滤,但是考虑到如果前面已经可以做到了,那么何必多此一举呢?哈哈。

f->f.isDirectory() || f.getName().endsWith(".java"))

说明

本来还想要将这个中的 .java 也抽取出来,把这个类变成一个工具类,但是我忽然想到了一句话:

知识分子的真正陷阱是沦入过度专业化与技术化的陷阱,失去了对更广阔世界的好奇心。

其实,这里的主要目的很简单,没必要做的这么复杂,所以就到此为止了。