hutool 解读(三)—— IO流

封装

io包的封装主要针对流、文件的读写封装,主要以工具类为主,提供常用功能的封装,这包括:

  • IoUtil 流操作工具类
  • FileUtil 文件读写和操作的工具类。
  • FileTypeUtil 文件类型判断工具类
  • WatchMonitor 目录、文件监听,封装了JDK1.7中的WatchService
  • ClassPathResource针对ClassPath中资源的访问封装
  • FileReader 封装文件读取
  • FileWriter 封装文件写入

流扩展

除了针对JDK的读写封装外,还针对特定环境和文件扩展了流实现。

包括:

  • BOMInputStream针对含有BOM头的流读取
  • FastByteArrayOutputStream 基于快速缓冲FastByteBuffer的OutputStream,随着数据的增长自动扩充缓冲区(from blade)
  • FastByteBuffer 快速缓冲,将数据存放在缓冲集中,取代以往的单一数组(from blade)

IO工具类-IoUtil

拷贝

流的读写可以总结为从输入流读取,从输出流写出,这个过程我们定义为拷贝。这个是一个基本过程,也是文件、流操作的基础。

BufferedInputStream in = FileUtil.getInputStream("d:/test.txt");
BufferedOutputStream out = FileUtil.getOutputStream("d:/test2.txt");
long copySize = IoUtil.copy(in, out, IoUtil.DEFAULT_BUFFER_SIZE);

Stream转Reader、Writer

  • IoUtil.getReader:将InputStream转为BufferedReader用于读取字符流,它是部分readXXX方法的基础。
  • IoUtil.getWriter:将OutputStream转为OutputStreamWriter用于写入字符流,它是部分writeXXX的基础。

本质上这两个方法只是简单new一个新的Reader或者Writer对象,但是封装为工具方法配合IDE的自动提示可以大大减少查阅次数(例如你对BufferedReader、OutputStreamWriter不熟悉,是不需要搜索一下相关类?)

读取流中的内容

读取流中的内容总结下来,可以分为read方法和readXXX方法。

read方法有诸多的重载方法,根据参数不同,可以读取不同对象中的内容,这包括:

  • InputStream
  • Reader
  • FileChannel

这三个重载大部分返回String字符串,为字符流读取提供极大便利。

readXXX方法主要针对返回值做一些处理,例如:

  • readBytes 返回byte数组(读取图片等)
  • readHex 读取16进制字符串
  • readObj 读取序列化对象(反序列化)
  • readLines 按行读取

toStream方法则是将某些对象转换为流对象,便于在某些情况下操作:

  • String 转换为ByteArrayInputStream
  • File 转换为FileInputStream

写入到流

  • IoUtil.write方法有两个重载方法,一个直接调用OutputStream.write方法,另一个用于将对象转换为字符串(调用toString方法),然后写入到流中。
  • IoUtil.writeObjects 用于将可序列化对象序列化后写入到流中。

write方法并没有提供writeXXX,需要自己转换为String或byte[]。

关闭

对于IO操作来说,使用频率最高(也是最容易被遗忘)的就是close操作,好在Java规范使用了优雅的Closeable接口,这样我们只需简单封装调用此接口的方法即可。

关闭操作会面临两个问题:

  1. 被关闭对象为空
  2. 对象关闭失败(或对象已关闭)

IoUtil.close方法很好的解决了这两个问题。

在JDK1.7中,提供了AutoCloseable接口,在IoUtil中同样提供相应的重载方法,在使用中并不能感觉到有哪些不同。

文件工具类-FileUtil

文件操作:包括文件目录的新建、删除、复制、移动、改名等
文件判断:判断文件或目录是否非空,是否为目录,是否为文件等等。
绝对路径:针对ClassPath中的文件转换为绝对路径文件。
文件名:主文件名,扩展名的获取
读操作:包括类似IoUtil中的getReader、readXXX操作
写操作:包括getWriter和writeXXX操作

在FileUtil中,我努力将方法名与Linux相一致

文件类型判断-FileTypeUtil

File file = FileUtil.file("d:/test.jpg");
String type = FileTypeUtil.getType(file);
//输出 jpg则说明确实为jpg文件
Console.log(type);

文件监听-WatchMonitor

WatchMonitor提供的事件有:

  • ENTRY_MODIFY 文件修改的事件
  • ENTRY_CREATE 文件或目录创建的事件
  • ENTRY_DELETE 文件或目录删除的事件
  • OVERFLOW 丢失的事件

这些事件对应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();

文件读取-FileReader

//默认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

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

FileAppender appender = new FileAppender(file, 16, true);
appender.append("123");
appender.append("abc");
appender.append("xyz");

appender.flush();
appender.toString();

在调用append方法后会缓存于内存,只有超过容量后才会一次性写入文件,因此内存中随时有剩余未写入文件的内容,在最后必须调用flush方法将剩余内容刷入文件。
也就是说,这是一个支持缓存的文件内容追加器。此类主要用于类似于日志写出这类需求所用。

文件跟随-Tailer

有时候我们要启动一个线程实时“监控”文件的变化,比如有新内容写出到文件时,我们可以及时打印出来,这个功能非常类似于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);
    }
}

文件名工具-FileNameUtil

获取文件名

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);

资源类 Resourse

在实际编码当中,我们需要读取一些数据,比如配置文件、文本内容、图片甚至是任何二进制流,为此我们要加入很多的重载方法,比如:

read(File file){...}

read(InputStream in){...}

read(byte[] bytes){...}

read(URL url){...}

等等如此,这样会造成整个代码变得非常冗余,查找API也很费劲。其实无论数据来自哪里,最终目的是,我们想从这些地方读到byte[]或者String。那么,我们就可以抽象一个Resource接口,让代码变得简单:

read(Resource resource){...}

用户只需传入Resource的实现即可。

Resourse定义

public interface Resource {
    String getName();
    URL getUrl();
    InputStream getStream();
    BufferedReader getReader(Charset charset);
    String readStr(Charset charset);
}

资源工具-ResourceUtil

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目录下确认文件是否存在。

ClassPath资源访问-ClassPathResource

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章节

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Hutool是一个Java工具包,其中包含了很多常用的工具类和功能。Hutool也提供了一些网络相关的工具类,可以用来实现非阻塞IO Server服务器。 以下是一个简单的使用Hutool实现非阻塞IO Server服务器的示例代码: ```java import cn.hutool.core.io.IoUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.log.StaticLog; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.util.Iterator; public class NonBlockingServer { public static void main(String[] args) throws IOException { Selector selector = Selector.open(); ServerSocketChannel serverSocket = ServerSocketChannel.open(); serverSocket.bind(new InetSocketAddress(8080)); serverSocket.configureBlocking(false); serverSocket.register(selector, SelectionKey.OP_ACCEPT); while (true) { if (selector.select() == 0) { continue; } Iterator<SelectionKey> iterator = selector.selectedKeys().iterator(); while (iterator.hasNext()) { SelectionKey key = iterator.next(); iterator.remove(); if (key.isAcceptable()) { ServerSocketChannel server = (ServerSocketChannel) key.channel(); SocketChannel socket = server.accept(); socket.configureBlocking(false); socket.register(selector, SelectionKey.OP_READ); StaticLog.info("新客户端连接:{}", socket.getRemoteAddress()); } else if (key.isReadable()) { SocketChannel socket = (SocketChannel) key.channel(); ByteBuffer buffer = ByteBuffer.allocate(1024); int read = socket.read(buffer); if (read > 0) { String msg = StrUtil.utf8Str(buffer.array(), 0, read); StaticLog.info("收到客户端消息:{}", msg); } else if (read < 0) { StaticLog.info("客户端断开连接:{}", socket.getRemoteAddress()); key.cancel(); IoUtil.close(socket); } } } } } } ``` 该示例代码中,使用了Java NIO的非阻塞IO方式,通过Hutool提供的工具类进行IO操作。主要步骤如下: 1. 创建Selector对象和ServerSocketChannel对象,并将ServerSocketChannel注册到Selector中,监听OP_ACCEPT事件。 2. 进入循环,使用Selector的select()方法等待事件就绪。 3. 遍历已经就绪的事件,并根据事件类型执行相应的操作。其中,如果事件类型是OP_ACCEPT,则新建一个SocketChannel对象,并将它注册到Selector中,监听OP_READ事件;如果事件类型是OP_READ,则读取客户端发送的数据,并输出到控制台。 4. 返回第2步,继续等待事件就绪。 需要注意的是,在使用非阻塞IO方式时,要注意处理事件就绪时的异常情况,以及在操作完成后要及时关闭相关的资源。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值