io包的封装主要针对流、文件的读写封装,主要以工具类为主,提供常用功能的封装,这包括:
除了针对JDK的读写封装外,还针对特定环境和文件扩展了流实现。
包括:
流的读写可以总结为从输入流读取,从输出流写出,这个过程我们定义为拷贝。这个是一个基本过程,也是文件、流操作的基础。
BufferedInputStream in = FileUtil.getInputStream("d:/test.txt");
BufferedOutputStream out = FileUtil.getOutputStream("d:/test2.txt");
long copySize = IoUtil.copy(in, out, IoUtil.DEFAULT_BUFFER_SIZE);
本质上这两个方法只是简单new一个新的Reader或者Writer对象,但是封装为工具方法配合IDE的自动提示可以大大减少查阅次数(例如你对BufferedReader、OutputStreamWriter不熟悉,是不需要搜索一下相关类?)
读取流中的内容总结下来,可以分为read方法和readXXX方法。
read方法有诸多的重载方法,根据参数不同,可以读取不同对象中的内容,这包括:
这三个重载大部分返回String字符串,为字符流读取提供极大便利。
readXXX方法主要针对返回值做一些处理,例如:
toStream方法则是将某些对象转换为流对象,便于在某些情况下操作:
write方法并没有提供writeXXX,需要自己转换为String或byte[]。
对于IO操作来说,使用频率最高(也是最容易被遗忘)的就是close操作,好在Java规范使用了优雅的Closeable接口,这样我们只需简单封装调用此接口的方法即可。
关闭操作会面临两个问题:
IoUtil.close方法很好的解决了这两个问题。
在JDK1.7中,提供了AutoCloseable接口,在IoUtil中同样提供相应的重载方法,在使用中并不能感觉到有哪些不同。
文件操作:包括文件目录的新建、删除、复制、移动、改名等
文件判断:判断文件或目录是否非空,是否为目录,是否为文件等等。
绝对路径:针对ClassPath中的文件转换为绝对路径文件。
文件名:主文件名,扩展名的获取
读操作:包括类似IoUtil中的getReader、readXXX操作
写操作:包括getWriter和writeXXX操作
在FileUtil中,我努力将方法名与Linux相一致
File file = FileUtil.file("d:/test.jpg");
String type = FileTypeUtil.getType(file);
//输出 jpg则说明确实为jpg文件
Console.log(type);
WatchMonitor提供的事件有:
这些事件对应StandardWatchEventKinds中的事件。
File file = FileUtil.file("example.properties");
//这里只监听文件或目录的修改事件
WatchMonitor watchMonitor = WatchMonitor.create(file, WatchMonitor.ENTRY_MODIFY);
watchMonitor.setWatcher(new Watcher(){
@Override
public void onCreate(WatchEvent<?> event, Path currentPath) {
Object obj = event.context();
Console.log("创建:{}-> {}", currentPath, obj);
}
@Override
public void onModify(WatchEvent<?> event, Path currentPath) {
Object obj = event.context();
Console.log("修改:{}-> {}", currentPath, obj);
}
@Override
public void onDelete(WatchEvent<?> event, Path currentPath) {
Object obj = event.context();
Console.log("删除:{}-> {}", currentPath, obj);
}
@Override
public void onOverflow(WatchEvent<?> event, Path currentPath) {
Object obj = event.context();
Console.log("Overflow:{}-> {}", currentPath, obj);
}
});
//设置监听目录的最大深入,目录层级大于制定层级的变更将不被监听,默认只监听当前层级目录
watchMonitor.setMaxDepth(3);
//启动监听
watchMonitor.start();
WatchMonitor.createAll(file, new SimpleWatcher(){
@Override
public void onModify(WatchEvent<?> event, Path currentPath) {
Console.log("EVENT modify");
}
}).start();
WatchMonitor monitor = WatchMonitor.createAll("d:/", new DelayWatcher(watcher, 500));
monitor.start();
//默认UTF-8编码,可以在构造中传入第二个参数做为编码
FileReader fileReader = new FileReader("test.properties");
String result = fileReader.readString();
byte[] bytes = fileReader.readBytes();
List<String> strings = fileReader.readLines();
//转为流文件
BufferedInputStream inputStream = fileReader.getInputStream();
BufferedReader reader = fileReader.getReader();
FileWriter writer = new FileWriter("test.properties");
// 第二个参数标识是否覆盖
writer.write("test",true);
PrintWriter printWriter = writer.getPrintWriter(false);
BufferedWriter writer1 = writer.getWriter(false);
BufferedOutputStream outputStream = writer.getOutputStream();
FileAppender appender = new FileAppender(file, 16, true);
appender.append("123");
appender.append("abc");
appender.append("xyz");
appender.flush();
appender.toString();
在调用append方法后会缓存于内存,只有超过容量后才会一次性写入文件,因此内存中随时有剩余未写入文件的内容,在最后必须调用flush方法将剩余内容刷入文件。
也就是说,这是一个支持缓存的文件内容追加器。此类主要用于类似于日志写出这类需求所用。
有时候我们要启动一个线程实时“监控”文件的变化,比如有新内容写出到文件时,我们可以及时打印出来,这个功能非常类似于Linux下的tail -f命令。
Tailer tailer = new Tailer(FileUtil.file("f:/test/test.log"), Tailer.CONSOLE_HANDLER, 2);
tailer.start();
其中Tailer.CONSOLE_HANDLER表示文件新增内容默认输出到控制台。
/**
* 命令行打印的行处理器
*/
public static class ConsoleLineHandler implements LineHandler {
@Override
public void handle(String line) {
Console.log(line);
}
}
File file = FileUtil.file("/opt/test.txt");
// test.txt
String name = FileNameUtil.getName(file);
File file = FileUtil.file("/opt/test.txt");
// "test"
String name = FileNameUtil.mainName(file);
// "txt"
String name = FileNameUtil.extName(file);
在实际编码当中,我们需要读取一些数据,比如配置文件、文本内容、图片甚至是任何二进制流,为此我们要加入很多的重载方法,比如:
read(File file){...}
read(InputStream in){...}
read(byte[] bytes){...}
read(URL url){...}
等等如此,这样会造成整个代码变得非常冗余,查找API也很费劲。其实无论数据来自哪里,最终目的是,我们想从这些地方读到byte[]或者String。那么,我们就可以抽象一个Resource接口,让代码变得简单:
read(Resource resource){...}
用户只需传入Resource的实现即可。
public interface Resource {
String getName();
URL getUrl();
InputStream getStream();
BufferedReader getReader(Charset charset);
String readStr(Charset charset);
}
ResourceUtil中最核心的方法是getResourceObj,此方法可以根据传入路径是否为绝对路径而返回不同的实现。比如路径是:file:/opt/test,或者/opt/test都会被当作绝对路径,此时调用FileResource来读取数据。如果不满足以上条件,默认调用ClassPathResource读取classpath中的资源或者文件。
同样,此工具类还封装了readBytes和readStr用于快捷读取bytes和字符串。
举个例子,假设我们在classpath下放了一个test.xml,读取就变得非常简单:
String str = ResourceUtil.readUtf8Str("test.xml");
假设我们的文件存放在src/resources/config目录下,则读取改为:
String str = ResourceUtil.readUtf8Str("config/test.xml");
注意 在IDEA中,新加入文件到src/resources目录下,需要重新import项目,以便在编译时顺利把资源文件拷贝到target目录下。如果提示找不到文件,请去target目录下确认文件是否存在。
ClassPathResource resource = new ClassPathResource("test.properties");
Properties properties = new Properties();
properties.load(resource.getStream());
Console.log("Properties: {}", properties);
Hutool提供针对properties的封装类Props,同时提供更加强大的配置文件Setting类,这两个类已经针对ClassPath做过相应封装,可以以更加便捷的方式读取配置文件。相关文档请参阅Hutool-setting章节