Browse Source

add NioUtil

Looly 5 years ago
parent
commit
4f6155c45a

+ 2 - 0
CHANGELOG.md

@@ -18,6 +18,7 @@
 * 【poi    】     Excel07SaxReader拆分出SheetDataSaxHandler
 * 【core   】     CollUtil.addAll增加判空(pr#228@Gitee)
 * 【core   】     修正DateUtil.betweenXXX注释错误(issue#I28XGW@Gitee)
+* 【core   】     增加NioUtil
 
 ### Bug修复
 * 【cache  】     修复Cache中get重复misCount计数问题(issue#1281@Github)
@@ -29,6 +30,7 @@
 * 【db     】     修复表名包含点导致的问题(issue#1300@Github)
 * 【poi    】     修复xdr:row标签导致的问题(issue#1297@Github)
 * 【core   】     修复FileUtil.loopFiles使用FileFilter无效问题(issue#I28V48@Gitee)
+* 【extra  】     修复JschUtil.execByShell返回空的问题(issue#1067@Github)
 
 -------------------------------------------------------------------------------------------------------------
 

+ 54 - 200
hutool-core/src/main/java/cn/hutool/core/io/IoUtil.java

@@ -28,13 +28,8 @@ import java.io.PushbackReader;
 import java.io.Reader;
 import java.io.Serializable;
 import java.io.Writer;
-import java.nio.ByteBuffer;
 import java.nio.CharBuffer;
-import java.nio.MappedByteBuffer;
-import java.nio.channels.Channels;
 import java.nio.channels.FileChannel;
-import java.nio.channels.ReadableByteChannel;
-import java.nio.channels.WritableByteChannel;
 import java.nio.charset.Charset;
 import java.util.Collection;
 import java.util.Objects;
@@ -48,25 +43,7 @@ import java.util.zip.Checksum;
  *
  * @author xiaoleilu
  */
-public class IoUtil {
-
-	/**
-	 * 默认缓存大小 8192
-	 */
-	public static final int DEFAULT_BUFFER_SIZE = 2 << 12;
-	/**
-	 * 默认中等缓存大小 16384
-	 */
-	public static final int DEFAULT_MIDDLE_BUFFER_SIZE = 2 << 13;
-	/**
-	 * 默认大缓存大小 32768
-	 */
-	public static final int DEFAULT_LARGE_BUFFER_SIZE = 2 << 14;
-
-	/**
-	 * 数据流末尾
-	 */
-	public static final int EOF = -1;
+public class IoUtil extends NioUtil{
 
 	// -------------------------------------------------------------------------------------- Copy start
 
@@ -196,21 +173,6 @@ public class IoUtil {
 	}
 
 	/**
-	 * 拷贝流 thanks to: https://github.com/venusdrogon/feilong-io/blob/master/src/main/java/com/feilong/io/IOWriteUtil.java<br>
-	 * 本方法不会关闭流
-	 *
-	 * @param in             输入流
-	 * @param out            输出流
-	 * @param bufferSize     缓存大小
-	 * @param streamProgress 进度条
-	 * @return 传输的byte数
-	 * @throws IORuntimeException IO异常
-	 */
-	public static long copyByNIO(InputStream in, OutputStream out, int bufferSize, StreamProgress streamProgress) throws IORuntimeException {
-		return copy(Channels.newChannel(in), Channels.newChannel(out), bufferSize, streamProgress);
-	}
-
-	/**
 	 * 拷贝文件流,使用NIO
 	 *
 	 * @param in  输入
@@ -227,79 +189,13 @@ public class IoUtil {
 		try {
 			inChannel = in.getChannel();
 			outChannel = out.getChannel();
-			return inChannel.transferTo(0, inChannel.size(), outChannel);
-		} catch (IOException e) {
-			throw new IORuntimeException(e);
+			return copy(inChannel, outChannel);
 		} finally {
 			close(outChannel);
 			close(inChannel);
 		}
 	}
 
-	/**
-	 * 拷贝流,使用NIO,不会关闭流
-	 *
-	 * @param in  {@link ReadableByteChannel}
-	 * @param out {@link WritableByteChannel}
-	 * @return 拷贝的字节数
-	 * @throws IORuntimeException IO异常
-	 * @since 4.5.0
-	 */
-	public static long copy(ReadableByteChannel in, WritableByteChannel out) throws IORuntimeException {
-		return copy(in, out, DEFAULT_BUFFER_SIZE);
-	}
-
-	/**
-	 * 拷贝流,使用NIO,不会关闭流
-	 *
-	 * @param in         {@link ReadableByteChannel}
-	 * @param out        {@link WritableByteChannel}
-	 * @param bufferSize 缓冲大小,如果小于等于0,使用默认
-	 * @return 拷贝的字节数
-	 * @throws IORuntimeException IO异常
-	 * @since 4.5.0
-	 */
-	public static long copy(ReadableByteChannel in, WritableByteChannel out, int bufferSize) throws IORuntimeException {
-		return copy(in, out, bufferSize, null);
-	}
-
-	/**
-	 * 拷贝流,使用NIO,不会关闭流
-	 *
-	 * @param in             {@link ReadableByteChannel}
-	 * @param out            {@link WritableByteChannel}
-	 * @param bufferSize     缓冲大小,如果小于等于0,使用默认
-	 * @param streamProgress {@link StreamProgress}进度处理器
-	 * @return 拷贝的字节数
-	 * @throws IORuntimeException IO异常
-	 */
-	public static long copy(ReadableByteChannel in, WritableByteChannel out, int bufferSize, StreamProgress streamProgress) throws IORuntimeException {
-		Assert.notNull(in, "InputStream is null !");
-		Assert.notNull(out, "OutputStream is null !");
-
-		ByteBuffer byteBuffer = ByteBuffer.allocate(bufferSize <= 0 ? DEFAULT_BUFFER_SIZE : bufferSize);
-		long size = 0;
-		if (null != streamProgress) {
-			streamProgress.start();
-		}
-		try {
-			while (in.read(byteBuffer) != EOF) {
-				byteBuffer.flip();// 写转读
-				size += out.write(byteBuffer);
-				byteBuffer.clear();
-				if (null != streamProgress) {
-					streamProgress.progress(size);
-				}
-			}
-		} catch (IOException e) {
-			throw new IORuntimeException(e);
-		}
-		if (null != streamProgress) {
-			streamProgress.finish();
-		}
-
-		return size;
-	}
 	// -------------------------------------------------------------------------------------- Copy end
 
 	// -------------------------------------------------------------------------------------- getReader and getWriter start
@@ -455,22 +351,7 @@ public class IoUtil {
 	 * @throws IORuntimeException IO异常
 	 */
 	public static String read(InputStream in, Charset charset) throws IORuntimeException {
-		FastByteArrayOutputStream out = read(in);
-		return null == charset ? out.toString() : out.toString(charset);
-	}
-
-	/**
-	 * 从流中读取内容,读取完毕后并不关闭流
-	 *
-	 * @param channel 可读通道,读取完毕后并不关闭通道
-	 * @param charset 字符集
-	 * @return 内容
-	 * @throws IORuntimeException IO异常
-	 * @since 4.5.0
-	 */
-	public static String read(ReadableByteChannel channel, Charset charset) throws IORuntimeException {
-		FastByteArrayOutputStream out = read(channel);
-		return null == charset ? out.toString() : out.toString(charset);
+		return StrUtil.str(readBytes(in), charset);
 	}
 
 	/**
@@ -487,19 +368,6 @@ public class IoUtil {
 	}
 
 	/**
-	 * 从流中读取内容,读到输出流中
-	 *
-	 * @param channel 可读通道,读取完毕后并不关闭通道
-	 * @return 输出流
-	 * @throws IORuntimeException IO异常
-	 */
-	public static FastByteArrayOutputStream read(ReadableByteChannel channel) throws IORuntimeException {
-		final FastByteArrayOutputStream out = new FastByteArrayOutputStream();
-		copy(channel, Channels.newChannel(out));
-		return out;
-	}
-
-	/**
 	 * 从Reader中读取String,读取完毕后关闭Reader
 	 *
 	 * @param reader Reader
@@ -513,7 +381,7 @@ public class IoUtil {
 	/**
 	 * 从{@link Reader}中读取String
 	 *
-	 * @param reader {@link Reader}
+	 * @param reader  {@link Reader}
 	 * @param isClose 是否关闭{@link Reader}
 	 * @return String
 	 * @throws IORuntimeException IO异常
@@ -527,8 +395,8 @@ public class IoUtil {
 			}
 		} catch (IOException e) {
 			throw new IORuntimeException(e);
-		} finally{
-			if(isClose){
+		} finally {
+			if (isClose) {
 				IoUtil.close(reader);
 			}
 		}
@@ -536,47 +404,6 @@ public class IoUtil {
 	}
 
 	/**
-	 * 从FileChannel中读取UTF-8编码内容
-	 *
-	 * @param fileChannel 文件管道
-	 * @return 内容
-	 * @throws IORuntimeException IO异常
-	 */
-	public static String readUtf8(FileChannel fileChannel) throws IORuntimeException {
-		return read(fileChannel, CharsetUtil.CHARSET_UTF_8);
-	}
-
-	/**
-	 * 从FileChannel中读取内容,读取完毕后并不关闭Channel
-	 *
-	 * @param fileChannel 文件管道
-	 * @param charsetName 字符集
-	 * @return 内容
-	 * @throws IORuntimeException IO异常
-	 */
-	public static String read(FileChannel fileChannel, String charsetName) throws IORuntimeException {
-		return read(fileChannel, CharsetUtil.charset(charsetName));
-	}
-
-	/**
-	 * 从FileChannel中读取内容
-	 *
-	 * @param fileChannel 文件管道
-	 * @param charset     字符集
-	 * @return 内容
-	 * @throws IORuntimeException IO异常
-	 */
-	public static String read(FileChannel fileChannel, Charset charset) throws IORuntimeException {
-		MappedByteBuffer buffer;
-		try {
-			buffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, fileChannel.size()).load();
-		} catch (IOException e) {
-			throw new IORuntimeException(e);
-		}
-		return StrUtil.str(buffer, charset);
-	}
-
-	/**
 	 * 从流中读取bytes,读取完毕后关闭流
 	 *
 	 * @param in {@link InputStream}
@@ -597,12 +424,19 @@ public class IoUtil {
 	 * @since 5.0.4
 	 */
 	public static byte[] readBytes(InputStream in, boolean isCloseStream) throws IORuntimeException {
-		final FastByteArrayOutputStream out = new FastByteArrayOutputStream();
-		copy(in, out);
-		if (isCloseStream) {
-			close(in);
+		final InputStream availableStream = toAvailableStream(in);
+		try{
+			final int available = availableStream.available();
+			if(available > 0){
+				byte[] result = new byte[available];
+				//noinspection ResultOfMethodCallIgnored
+				availableStream.read(result);
+				return result;
+			}
+		} catch (IOException e){
+			throw new IORuntimeException(e);
 		}
-		return out.toByteArray();
+		return new byte[0];
 	}
 
 	/**
@@ -967,6 +801,42 @@ public class IoUtil {
 	}
 
 	/**
+	 * 将指定{@link InputStream} 转换为{@link InputStream#available()}方法可用的流。<br>
+	 * 在Socket通信流中,服务端未返回数据情况下{@link InputStream#available()}方法始终为{@code 0}<br>
+	 * 因此,在读取前需要调用{@link InputStream#read()}读取一个字节(未返回会阻塞),一旦读取到了,{@link InputStream#available()}方法就正常了。<br>
+	 * 此方法返回对象的规则为:
+	 *
+	 * <ul>
+	 *     <li>FileInputStream 返回原对象,因为文件流的available方法本身可用</li>
+	 *     <li>其它InputStream 返回PushbackInputStream</li>
+	 * </ul>
+	 *
+	 * @param in 被转换的流
+	 * @return 转换后的流,可能为{@link PushbackInputStream}
+	 * @since 5.5.3
+	 */
+	public static InputStream toAvailableStream(InputStream in) {
+		if(in instanceof FileInputStream){
+			// FileInputStream本身支持available方法。
+			return in;
+		}
+
+		final PushbackInputStream pushbackInputStream = toPushbackStream(in, 1);
+		try {
+			final int available = pushbackInputStream.available();
+			if (available <= 0) {
+				//此操作会阻塞,直到有数据被读到
+				int b = pushbackInputStream.read();
+				pushbackInputStream.unread(b);
+			}
+		} catch (IOException e) {
+			throw new IORuntimeException(e);
+		}
+
+		return pushbackInputStream;
+	}
+
+	/**
 	 * 将byte[]写到流中
 	 *
 	 * @param out        输出流
@@ -1114,22 +984,6 @@ public class IoUtil {
 	}
 
 	/**
-	 * 关闭<br>
-	 * 关闭失败不会抛出异常
-	 *
-	 * @param closeable 被关闭的对象
-	 */
-	public static void close(AutoCloseable closeable) {
-		if (null != closeable) {
-			try {
-				closeable.close();
-			} catch (Exception e) {
-				// 静默关闭
-			}
-		}
-	}
-
-	/**
 	 * 尝试关闭指定对象<br>
 	 * 判断对象如果实现了{@link AutoCloseable},则调用之
 	 *

+ 227 - 0
hutool-core/src/main/java/cn/hutool/core/io/NioUtil.java

@@ -0,0 +1,227 @@
+package cn.hutool.core.io;
+
+import cn.hutool.core.lang.Assert;
+import cn.hutool.core.util.CharsetUtil;
+import cn.hutool.core.util.StrUtil;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.nio.MappedByteBuffer;
+import java.nio.channels.Channels;
+import java.nio.channels.FileChannel;
+import java.nio.channels.ReadableByteChannel;
+import java.nio.channels.WritableByteChannel;
+import java.nio.charset.Charset;
+
+/**
+ * NIO相关工具封装,主要针对Channel读写、拷贝等封装
+ *
+ * @author looly
+ * @since 5.5.3
+ */
+public class NioUtil {
+
+	/**
+	 * 默认缓存大小 8192
+	 */
+	public static final int DEFAULT_BUFFER_SIZE = 2 << 12;
+	/**
+	 * 默认中等缓存大小 16384
+	 */
+	public static final int DEFAULT_MIDDLE_BUFFER_SIZE = 2 << 13;
+	/**
+	 * 默认大缓存大小 32768
+	 */
+	public static final int DEFAULT_LARGE_BUFFER_SIZE = 2 << 14;
+
+	/**
+	 * 数据流末尾
+	 */
+	public static final int EOF = -1;
+
+	/**
+	 * 拷贝流 thanks to: https://github.com/venusdrogon/feilong-io/blob/master/src/main/java/com/feilong/io/IOWriteUtil.java<br>
+	 * 本方法不会关闭流
+	 *
+	 * @param in             输入流
+	 * @param out            输出流
+	 * @param bufferSize     缓存大小
+	 * @param streamProgress 进度条
+	 * @return 传输的byte数
+	 * @throws IORuntimeException IO异常
+	 */
+	public static long copyByNIO(InputStream in, OutputStream out, int bufferSize, StreamProgress streamProgress) throws IORuntimeException {
+		return copy(Channels.newChannel(in), Channels.newChannel(out), bufferSize, streamProgress);
+	}
+
+	/**
+	 * 拷贝文件Channel,使用NIO,拷贝后不会关闭channel
+	 *
+	 * @param inChannel  {@link FileChannel}
+	 * @param outChannel {@link FileChannel}
+	 * @return 拷贝的字节数
+	 * @throws IORuntimeException IO异常
+	 * @since 5.5.3
+	 */
+	public static long copy(FileChannel inChannel, FileChannel outChannel) throws IORuntimeException {
+		Assert.notNull(inChannel, "In channel is null!");
+		Assert.notNull(outChannel, "Out channel is null!");
+
+		try {
+			return inChannel.transferTo(0, inChannel.size(), outChannel);
+		} catch (IOException e) {
+			throw new IORuntimeException(e);
+		}
+	}
+
+	/**
+	 * 拷贝流,使用NIO,不会关闭channel
+	 *
+	 * @param in  {@link ReadableByteChannel}
+	 * @param out {@link WritableByteChannel}
+	 * @return 拷贝的字节数
+	 * @throws IORuntimeException IO异常
+	 * @since 4.5.0
+	 */
+	public static long copy(ReadableByteChannel in, WritableByteChannel out) throws IORuntimeException {
+		return copy(in, out, DEFAULT_BUFFER_SIZE);
+	}
+
+	/**
+	 * 拷贝流,使用NIO,不会关闭channel
+	 *
+	 * @param in         {@link ReadableByteChannel}
+	 * @param out        {@link WritableByteChannel}
+	 * @param bufferSize 缓冲大小,如果小于等于0,使用默认
+	 * @return 拷贝的字节数
+	 * @throws IORuntimeException IO异常
+	 * @since 4.5.0
+	 */
+	public static long copy(ReadableByteChannel in, WritableByteChannel out, int bufferSize) throws IORuntimeException {
+		return copy(in, out, bufferSize, null);
+	}
+
+	/**
+	 * 拷贝流,使用NIO,不会关闭channel
+	 *
+	 * @param in             {@link ReadableByteChannel}
+	 * @param out            {@link WritableByteChannel}
+	 * @param bufferSize     缓冲大小,如果小于等于0,使用默认
+	 * @param streamProgress {@link StreamProgress}进度处理器
+	 * @return 拷贝的字节数
+	 * @throws IORuntimeException IO异常
+	 */
+	public static long copy(ReadableByteChannel in, WritableByteChannel out, int bufferSize, StreamProgress streamProgress) throws IORuntimeException {
+		Assert.notNull(in, "InputStream is null !");
+		Assert.notNull(out, "OutputStream is null !");
+
+		ByteBuffer byteBuffer = ByteBuffer.allocate(bufferSize <= 0 ? DEFAULT_BUFFER_SIZE : bufferSize);
+		long size = 0;
+		if (null != streamProgress) {
+			streamProgress.start();
+		}
+		try {
+			while (in.read(byteBuffer) != EOF) {
+				byteBuffer.flip();// 写转读
+				size += out.write(byteBuffer);
+				byteBuffer.clear();
+				if (null != streamProgress) {
+					streamProgress.progress(size);
+				}
+			}
+		} catch (IOException e) {
+			throw new IORuntimeException(e);
+		}
+		if (null != streamProgress) {
+			streamProgress.finish();
+		}
+
+		return size;
+	}
+
+	/**
+	 * 从流中读取内容,读取完毕后并不关闭流
+	 *
+	 * @param channel 可读通道,读取完毕后并不关闭通道
+	 * @param charset 字符集
+	 * @return 内容
+	 * @throws IORuntimeException IO异常
+	 * @since 4.5.0
+	 */
+	public static String read(ReadableByteChannel channel, Charset charset) throws IORuntimeException {
+		FastByteArrayOutputStream out = read(channel);
+		return null == charset ? out.toString() : out.toString(charset);
+	}
+
+	/**
+	 * 从流中读取内容,读到输出流中
+	 *
+	 * @param channel 可读通道,读取完毕后并不关闭通道
+	 * @return 输出流
+	 * @throws IORuntimeException IO异常
+	 */
+	public static FastByteArrayOutputStream read(ReadableByteChannel channel) throws IORuntimeException {
+		final FastByteArrayOutputStream out = new FastByteArrayOutputStream();
+		copy(channel, Channels.newChannel(out));
+		return out;
+	}
+
+	/**
+	 * 从FileChannel中读取UTF-8编码内容
+	 *
+	 * @param fileChannel 文件管道
+	 * @return 内容
+	 * @throws IORuntimeException IO异常
+	 */
+	public static String readUtf8(FileChannel fileChannel) throws IORuntimeException {
+		return read(fileChannel, CharsetUtil.CHARSET_UTF_8);
+	}
+
+	/**
+	 * 从FileChannel中读取内容,读取完毕后并不关闭Channel
+	 *
+	 * @param fileChannel 文件管道
+	 * @param charsetName 字符集
+	 * @return 内容
+	 * @throws IORuntimeException IO异常
+	 */
+	public static String read(FileChannel fileChannel, String charsetName) throws IORuntimeException {
+		return read(fileChannel, CharsetUtil.charset(charsetName));
+	}
+
+	/**
+	 * 从FileChannel中读取内容
+	 *
+	 * @param fileChannel 文件管道
+	 * @param charset     字符集
+	 * @return 内容
+	 * @throws IORuntimeException IO异常
+	 */
+	public static String read(FileChannel fileChannel, Charset charset) throws IORuntimeException {
+		MappedByteBuffer buffer;
+		try {
+			buffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, fileChannel.size()).load();
+		} catch (IOException e) {
+			throw new IORuntimeException(e);
+		}
+		return StrUtil.str(buffer, charset);
+	}
+
+	/**
+	 * 关闭<br>
+	 * 关闭失败不会抛出异常
+	 *
+	 * @param closeable 被关闭的对象
+	 */
+	public static void close(AutoCloseable closeable) {
+		if (null != closeable) {
+			try {
+				closeable.close();
+			} catch (Exception e) {
+				// 静默关闭
+			}
+		}
+	}
+}

+ 2 - 6
hutool-extra/src/main/java/cn/hutool/extra/ssh/JschUtil.java

@@ -432,7 +432,7 @@ public class JschUtil {
 		try {
 			channel.connect();
 			in = channel.getInputStream();
-			return IoUtil.read(in, CharsetUtil.CHARSET_UTF_8);
+			return IoUtil.read(in, charset);
 		} catch (IOException e) {
 			throw new IORuntimeException(e);
 		} catch (JSchException e) {
@@ -461,7 +461,6 @@ public class JschUtil {
 		shell.setPty(true);
 		OutputStream out = null;
 		InputStream in = null;
-		final StringBuilder result = StrUtil.builder();
 		try {
 			out = shell.getOutputStream();
 			in = shell.getInputStream();
@@ -469,9 +468,7 @@ public class JschUtil {
 			out.write(StrUtil.bytes(cmd, charset));
 			out.flush();
 
-			while (in.available() > 0) {
-				result.append(IoUtil.read(in, charset));
-			}
+			return IoUtil.read(in, charset);
 		} catch (IOException e) {
 			throw new IORuntimeException(e);
 		} finally {
@@ -479,7 +476,6 @@ public class JschUtil {
 			IoUtil.close(in);
 			close(shell);
 		}
-		return result.toString();
 	}
 
 	/**