/*
* JEF - Copyright 2009-2010 Jiyi (mr.jiyi@gmail.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package jef.tools;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.CharArrayWriter;
import java.io.Closeable;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.net.URL;
import java.net.URLConnection;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import jef.codegen.support.OverWrittenMode;
import jef.common.BigDataBuffer;
import jef.common.JefSerializable;
import jef.common.SimpleException;
import jef.common.log.LogUtil;
import jef.jre5support.ProcessUtil;
import jef.tools.TextFileCallback.Dealwith;
import jef.tools.collection.CollectionUtils;
import jef.tools.io.UnicodeReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.Function;
import com.google.common.collect.Multimap;
public class IOUtils {
private static final int DEFAULT_BUFFER_SIZE = 4096;
private static final File[] EMPTY = new File[0];
private static final Logger log=LoggerFactory.getLogger(IOUtils.class);
/**
* 关闭指定的对象,不会抛出异常
*
* @param input
* 需要关闭的资源
* @deprecated 请使用{@link #closeQuietly},该方法和Apache commons-io中的工具类同名,更适合代码复用
*/
public static void close(Closeable input) {
closeQuietly(input);
}
/**
* 关闭指定的对象,不会抛出异常
*
* @param input
* 需要关闭的资源
*/
public static void closeQuietly(Closeable input) {
if (input != null) {
try {
input.close();
} catch (IOException e) {
LogUtil.exception(e);
}
}
}
/**
* 在临时文件目录下创建一个目录并清空,如果不能正常操作就抛出权限不足的异常(在linux上经常出现)。<br>
* 这个临时目录会使用pid作为名称,因此一个程序如果PID不变,每次都能得到相同的目录。<br>
*
* 一般来说这个目录用于存放和本次运行相关的信息,如果上次运行后这个目录中存在数据,那么本次将清空这个目录。
*
* @param 临时文件的文件名
* @return 创建的临时目录
*/
public synchronized static File createTempDirectory(String name) {
String tempPath = System.getProperty("java.io.tmpdir");
File f = new File(tempPath, name + "." + ProcessUtil.getPid());
try {
if (f.isDirectory()) {
deleteAllChildren(f);
} else if (f.isFile()) {
f.delete();
}
f.mkdirs();
return f;
} catch (Exception e) {
String user = System.getProperty("user.name");
throw new SimpleException("Current user[" + user + "] doesn't have any permission to access folder " + e.getMessage());
}
}
/**
* 获得配置文件的项目。配置文件用= :等分隔对,语法同properties文件
* <p>
* 使用此方法可以代替使用JDK中的{@link java.util.Properties}工具。因为Properties操作中往往有以下不便
* <ol>
* <li>在遍历时,由于Properties继承了Map<Object,Object>泛型,不得不编写强制类型转换的代码。</li>
* <li>Properties继承了Hashtable性能低下,此外如果getProperty(null)还会抛出异常。</li>
* <li>Properties中的数据是乱序的,无法保持原先在文件中出现的顺序</li>
* <li>Properties保留了基于InputStream的接口,使用时容易出现编码错误</li>
* </ol>
* 因此,建议在加载.properties文件时,不要使用JDK中的{@link java.util.Properties}。
* <s>彻底淘汰落后的java.util.Properties</s>
*
* @param in
* 要读取的资源
* @return 文件中的键值对信息。
*/
public static Map<String, String> loadProperties(URL in) {
return loadProperties(in, false);
}
public static Map<String, String> loadProperties(URL in, Boolean supportSection) {
Map<String, String> result = new LinkedHashMap<String, String>();
loadProperties(getReader(in, null), result, supportSection);
return result;
}
/**
* 获得配置文件的项目。配置文件用= :等分隔对,语法同properties文件
* <p>
* 使用此方法可以代替使用JDK中的{@link java.util.Properties}工具。因为Properties操作中往往有以下不便
* <ol>
* <li>在遍历时,由于Properties继承了Map<Object,Object>泛型,不得不编写强制类型转换的代码。</li>
* <li>Properties继承了Hashtable性能低下,此外如果getProperty(null)还会抛出异常。</li>
* <li>Properties中的数据是乱序的,无法保持原先在文件中出现的顺序</li>
* <li>Properties保留了基于InputStream的接口,使用时容易出现编码错误</li>
* </ol>
* 因此,建议在加载.properties文件时,不要使用JDK中的{@link java.util.Properties}。
* <s>彻底淘汰落后的java.util.Properties</s>
*
* @param in
* 要读取的数据流。注意读取完成后流会被关闭。
* @return 文件中的键值对信息。
*/
public static Map<String, String> loadProperties(Reader in) {
return loadProperties(in, false);
}
/**
* 获得配置文件的项目。配置文件用= :等分隔对,语法同properties文件
* <p>
* 使用此方法可以代替使用JDK中的{@link java.util.Properties}工具。因为Properties操作中往往有以下不便
* <ol>
* <li>在遍历时,由于Properties继承了Map<Object,Object>泛型,不得不编写强制类型转换的代码。</li>
* <li>Properties继承了Hashtable性能低下,此外如果getProperty(null)还会抛出异常。</li>
* <li>Properties中的数据是乱序的,无法保持原先在文件中出现的顺序</li>
* <li>Properties保留了基于InputStream的接口,使用时容易出现编码错误</li>
* </ol>
* 因此,建议在加载.properties文件时,不要使用JDK中的{@link java.util.Properties}。
* <s>彻底淘汰落后的java.util.Properties</s>
*
* @param in
* 要读取的数据流。注意读取完成后流会被关闭。
* @param supportSection
* 支持分节。window下有一种很类似properties格式的配置文件INI。INI文件中可以使用[section]
* 对配置划分小节。开启此开关后,解析时会将当前节名称和配置名拼在一起。 形成 {@code 节名|配置名}的格式。
*
*
* @return 文件中的键值对信息。
*/
public static Map<String, String> loadProperties(Reader in, Boolean supportSection) {
Map<String, String> result = new LinkedHashMap<String, String>();
loadProperties(in, result, supportSection);
return result;
}
/**
* 将Map内的数据保存成properties文件
*
* @param writer
* 要输出的流
* @param map
* 要保存的键值对信息
* @param closeWriter
* true表示保存完后关闭输出流,false则保持不变
*/
public static void storeProperties(Writer writer, Map<String, String> map, boolean closeWriter) {
storeProperties(writer, map, closeWriter, false, 0);
}
/**
* 将Map内的数据保存成properties文件
*
* @param writer
* 要输出的流
* @param map
* 要保存的键值对信息
* @param closeWriter
* true表示保存完后关闭输出流,false则保持不变
* @param sectionSupport
* 支持分节写入
* @param 转义处理:1,正常KV均转义处理 0。KEY处理value不处理。 -1 KV均不处理
*
*/
public static void storeProperties(Writer writer, Map<String, String> map, boolean closeWriter, Boolean sectionSupport,int saveConvert) {
if(sectionSupport==null) {
int limit=3;
sectionSupport=true;
for(Entry<String,String> entry: map.entrySet()) {
limit--;
String key=entry.getKey();
if(key.indexOf('|')==-1) {
sectionSupport=false;
break;
}
if(limit<0) {
break;
}
}
}
try {
if (sectionSupport) {
Multimap<String, Map.Entry<String, String>> sections = CollectionUtils.group(map.entrySet(),
new Function<Map.Entry<String, String>, String>() {
public String apply(Entry<String, String> input) {
int sectionLen = input.getKey().indexOf('|');
return sectionLen == -1 ? "" : input.getKey().substring(0, sectionLen);
}
});
boolean hasNoSecLine=false;
for (Map.Entry<String, String> entry : sections.removeAll("")) {
writer.write(saveConvert(entry.getKey(), true, saveConvert));
writer.write('=');
writer.write(saveConvert(entry.getValue(), false,saveConvert));
writer.write(StringUtils.CRLF_STR);
hasNoSecLine=true;
}
if (!sections.isEmpty()) {
if(hasNoSecLine)writer.write(StringUtils.CRLF_STR);
for (String section : sections.keySet()) {
writer.write("[" + section + "]\r\n");
for (Map.Entry<String, String> entry : sections.get(section)) {
writer.write(saveConvert(entry.getKey().substring(section.length() + 1), true,saveConvert));
writer.write('=');
writer.write(saveConvert(entry.getValue(), false,saveConvert));
writer.write(StringUtils.CRLF_STR);
}
writer.write(StringUtils.CRLF_STR);
}
}
} else {
for (Map.Entry<String, String> entry : map.entrySet()) {
writer.write(saveConvert(entry.getKey(), true,saveConvert));
writer.write('=');
writer.write(saveConvert(entry.getValue(), false,saveConvert));
writer.write(StringUtils.CRLF_STR);
}
}
writer.flush();
} catch (IOException e1) {
LogUtil.exception(e1);
} finally {
if (closeWriter)
closeQuietly(writer);
}
}
/**
* 清空指定目录<br>
* 删除文件夹下的所有内容,文件夹本身不删除。 如果输入一个file,那么总是返回true
*
* @param f
* 要清空的目录
* @return true表示操作成功,如果某些文件不能正常删除返回false.
*/
public static boolean deleteAllChildren(File f) {
Assert.notNull(f);
if (!f.exists())
return true;
if (f.isDirectory()) {
for (File sub : listFolders(f)) {
if (!deleteTree(sub, true))
return false;
}
for (File sub : listFiles(f)) {
if (!sub.delete())
return false;
}
}
return true;
}
/**
* 删除整个文件夹树
*
* @param f
* 要删除的文件或文件夹
* @param includeSub
* 如果为false,那么如果目录非空,将不删除。返回false
* @return 成功删除返回true,没成功删除返回false。 如果文件夹一开始就不存在,也返回true。
*/
public static boolean deleteTree(File f, boolean includeSub) {
Assert.notNull(f);
if (!f.exists())
return true;
if (includeSub && f.isDirectory()) {
for (File sub : listFolders(f)) {
if (!deleteTree(sub, true))
return false;
}
for (File sub : listFiles(f)) {
if (!sub.delete())
return false;
}
}
return f.delete();
}
/**
* 递归列出(所有层级的)目录
*
* @param root
* 要搜索的目录
* @param folderFilter
* 自定义过滤器,可过滤掉不需要的文件夹
* @return 所有未被过滤的文件夹
*/
public static File[] listFoldersRecursive(File root, final FileFilter folderFilter) {
List<File> files = new ArrayList<File>();
for (File folder : listFolders(root)) {
if (folderFilter == null || folderFilter.accept(folder)) {
files.add(folder);
files.addAll(Arrays.asList(listFoldersRecursive(folder, folderFilter)));
}
}
return files.toArray(new File[files.size()]);
}
/**
* 递归列出目录下(所有层级的)文件。可以指定哪些扩展名,如果不指定扩展名则所有文件都被列出, <br>
* 不会列出目录
*
* @param root
* 要搜索的目录
* @param extnames
* 允许列出的扩展名,必须小写,不含.号。可以指定多种扩展名
* @return 所有指定扩展名的文件。
*/
public static File[] listFilesRecursive(File root, final String... extnames) {
List<File> files = new ArrayList<File>();
for (File folder : listFolders(root)) {
files.addAll(Arrays.asList(listFilesRecursive(folder, extnames)));
}
files.addAll(Arrays.asList(listFiles(root, extnames)));
return files.toArray(new File[files.size()]);
}
/**
* 递归列出目录下(所有层级的)文件。可以指定过滤器,过滤掉不需要的文件 <br>
* 不会列出目录
*
* @param root
* 要搜索的目录
* @param filter
* 指定的过滤器
* @param folderFilter
* 文件夹过滤器,可以用此过滤器来防止搜索不需要的目录
* @return 所有未被过滤的文件
*/
public static File[] listFilesRecursive(File root, final FileFilter fileFilter, final FileFilter folderFilter) {
List<File> files = new ArrayList<File>();
if (root.exists()) {
for (File f : root.listFiles()) {
if (f.isDirectory()) {
if (folderFilter != null && !folderFilter.accept(f)) {
continue;
}
files.addAll(Arrays.asList(listFilesRecursive(f, fileFilter, folderFilter)));
} else {
if (fileFilter != null && !fileFilter.accept(f)) {
continue;
}
files.add(f);
}
}
}
return files.toArray(new File[files.size()]);
}
/**
* 递归列出目录下(所有层级的)文件。可以指定文件名的模板进行匹配。
* <p>
* 匹配字符串。该字符串中可以用*表示任意字符,用?表示单个字符。 这个函数的功能类似于windows的文件搜索。
*
* @param root
* 要搜索的目录
* @param pattern
* 指定的匹配字符串。该字符串中可以用*表示任意字符,用?表示单个字符。
* @return 所有符合条件的文件
*/
public static File[] listFilesRecursiveLike(File root, final String pattern) {
return listFilesRecursive(root, new FileFilter() {
public boolean accept(File f) {
if (StringUtils.matches(f.getName(), pattern, true)) {
return true;
}
return false;
}
}, null);
}
/**
* 递归列出目录下文件。可以指定扩展名。
*
* @param file
* 要搜索的目录
* @param extnames
* 需要的文件类型(扩展名)。要求小写,无需带'.'符号。
* @return 该目录下符合指定类型的所有文件(只搜索一层,不会递归搜索)。<strong>仅列出文件,不会返回目录</strong>
*/
public static File[] listFiles(File file, final String... extnames) {
File[] r = file.listFiles(new FileFilter() {
public boolean accept(File f) {
boolean isAll = extnames.length == 0;
if (f.isFile() && (isAll || ArrayUtils.contains(extnames, getExtName(f.getName())))) {
return true;
}
return false;
}
});
return r == null ? EMPTY : r;
}
/**
* 列出指定目录下的文件。可以指定文件名的模板进行匹配。
*
* @param root
* 要搜索的目录
* @param pattern
* 搜索字符串,可以用*,?,+表示匹配任意字符。
* @return 该目录下,文件名符合指定搜索字符串的文件(只搜索一层,不会递归搜索)。仅列出文件,不会返回目录
*/
public static File[] listFilesLike(File root, final String pattern) {
File[] r = root.listFiles(new FileFilter() {
public boolean accept(File f) {
if (f.isFile() && StringUtils.matches(f.getName(), pattern, true)) {
return true;
}
return false;
}
});
return r == null ? EMPTY : r;
}
/**
* 列出指定目录下的文件夹
*
* @param root
* 指定目录
* @return 该目录下的所有文件夹
*/
public static File[] listFolders(File root) {
File[] r = root.listFiles(new FileFilter() {
public boolean accept(File f) {
if (f.isDirectory()) {
return true;
}
return false;
}
});
return r == null ? EMPTY : r;
}
/**
* 列出指定目录下文件夹,匹配指定的字符串
*
* @param root
* 要搜索的目录
* @param pattern
* 搜索字符串,可以用*,?,+表示匹配任意字符。
* @return 该目录下符合搜索字符串的所有文件夹。(只搜索一层,不会递归搜索)。仅列出文件夹,不会返回文件
*/
public static File[] listFoldersLike(File root, final String pattern) {
File[] r = root.listFiles(new FileFilter() {
public boolean accept(File f) {
if (f.isDirectory() && StringUtils.matches(f.getName(), pattern, true)) {
return true;
}
return false;
}
});
return r == null ? EMPTY : r;
}
/**
* 列出指定目录下的文件和文件夹,其中文件只列出符合扩展名的文件。
*
* @param root
* 要搜索的目录
* @param extnames
* 允许列出的扩展名,必须小写。不含.号
* @return 该目录下的所有文件夹(不管名称中有没有.xxx)以及符合类型的文件。
*/
public static File[] listFilesAndFolders(File root, final String... extnames) {
File[] r = root.listFiles(new FileFilter() {
public boolean accept(File f) {
boolean isAll = extnames.length == 0;
if (f.isDirectory()) {
return true;
}
if (isAll || ArrayUtils.contains(extnames, getExtName(f.getName()))) {
return true;
}
return false;
}
});
return r == null ? EMPTY : r;
}
/**
* 列出指定目录下的文件和文件夹,可以指定搜索字符串
*
* @param root
* 要搜索的目录
* @param pattern
* 搜索字符串
* @return 该目录下所有符合搜索串的 文件和目录。(只搜索一层,不会递归搜索)
*/
public static File[] listFilesAndFoldersLike(File root, final String pattern) {
File[] r = root.listFiles(new FileFilter() {
public boolean accept(File f) {
return StringUtils.matches(f.getName(), pattern, true);
}
});
return r == null ? EMPTY : r;
}
/**
* 给定一个File,确认其不存在于在磁盘上,如果存在就改名以回避 <br>
* 这个方法用于向磁盘输出文件时使用。<br>
* 比如输出名为 report.txt时,如果发现上一次的report.txt还在那么就会返回 "report(1).txt"。
* 如果"report(1).txt"也存在就会返回"report(2).txt"。 以此类推。
*
* @param file
* 目标文件
* @return 如果目标文件不存在,返回本身。如果目标文件已存在,就返回一个带后缀而磁盘上不存在的文件。
*/
public static File escapeExistFile(File file) {
if (!file.exists())
return file;
int pos = file.getName().lastIndexOf(".");
String path = file.getParent();
if (StringUtils.isEmpty(path)) {
throw new IllegalArgumentException(file.getAbsolutePath() + " has no valid parent folder.");
}
String baseFilename = null;
String extName = null;
if (pos > -1) {
baseFilename = file.getName().substring(0, pos);
extName = file.getName().substring(pos + 1);
} else {
baseFilename = file.getName();
}
int n = 1;
while (file.exists()) {
file = new File(path + "/" + baseFilename + "(" + n + ")" + ((extName == null) ? "" : "." + extName));
n++;
}
return file;
}
/**
* 得到文件的扩展名(小写如果没有则返回空字符串)。如果传入的文件名包含路径,分析时会考虑最后一个\或/字符后满的部分才作为文件名。
*
* @param fileName
* @return
*/
public static String getExtName(String fileName) {
int dashIndex1=fileName.lastIndexOf('/');
int dashIndex2=fileName.lastIndexOf('\\');
int dash=Math.max(dashIndex1, dashIndex2);//获得最后一个斜杠的位置
int pos = fileName.lastIndexOf(".");
if(pos>-1 && pos>dash){
return fileName.substring(pos + 1).toLowerCase();
} else {
return "";
}
}
/**
* 得到文件名除去扩展名的部分。如果传入的文件名包含路径,分析时会考虑最后一个\或/字符后满的部分才作为文件名。
* 去除扩展名后返回包含路径的部分。
* @param fileName
* @return
*/
public static String removeExt(String fileName) {
int dashIndex1=fileName.lastIndexOf('/');
int dashIndex2=fileName.lastIndexOf('\\');
int dash=Math.max(dashIndex1, dashIndex2);//获得最后一个斜杠的位置
int pos=fileName.lastIndexOf('.');
if(pos>-1 && pos>dash){
return fileName.substring(0,pos);
}
return fileName;
}
/**
* 判断文件是否存在
*
* @param path
* @return 如果该文件存在,且不是目录,返回true。否则返回false
*/
public static boolean isFile(String path) {
File f = new File(path);
return f.exists() && f.isFile();
}
/**
* 判断目录是否存在
*
* @param path
* @return 如果该文件存在,且是目录,返回true。否则返回false
*/
public static boolean isFolder(String path) {
File f = new File(path);
return f.exists() && f.isDirectory();
}
/**
* JDK6之前的很多代码要将中文写作\\uHHHH格式,这里可以将此种格式转换回正常格式文件
*
* @param source
* @param target
* @param charset
* @throws IOException
*/
public static void fromHexUnicodeString(File source, File target, String charset) throws IOException {
Reader r = getReader(source, null);
Writer w = getWriter(target, charset, false);
StringUtils.fromHexUnicodeString(r, w);
r.close();
w.close();
}
/**
* JDK6之前的很多代码要将中文写作\\uHHHH格式,这里可以将正常中文文件转换到此种格式
*
* @param source
* @param target
* @throws IOException
*/
public static void toHexUnicodeString(File source, File target, String sourceCharset) throws IOException {
Reader r = getReader(source, sourceCharset);
Writer w = getWriter(target, null, false);
StringUtils.toHexUnicodeString(r, w, "\\u");
r.close();
w.close();
}
/**
* 检查/创建 文件夹
*
* @param path
*/
public static void createFolder(String path) {
createFolder(new File(path));
}
public static void createFolder(File file) {
if (file.exists() && file.isFile()) {
throw new RuntimeException("Duplicate name file exist. can't create directory " + file.getPath());
} else if (!file.exists()) {
file.mkdirs();
}
}
/**
* 检查/创建文件在所的文件夹
*
* @param file
*/
public static void ensureParentFolder(File file) {
File f = file.getParentFile();
if (f != null && !f.exists()) {
f.mkdirs();
} else if (f != null && f.isFile()) {
throw new RuntimeException(f.getAbsolutePath() + " is a exist file, can't create directory.");
}
}
/**
* 在reader中读取字符,直到出现指定字符中的任一个,停止读取。 返回的字符串中包含了最后出现的字符
*
* @param reader
* @param appear
* @return
* @throws IOException
*/
public static String readUntill(Reader reader, char... appear) throws IOException {
StringBuilder sb = new StringBuilder();
while (true) {
int i = reader.read();
if (i < 0)
return sb.length() == 0 ? null : sb.toString();
sb.append((char) i);
for (int ind = 0; ind < appear.length; ind++) {
if (appear[ind] == i) {
return sb.toString();
}
}
}
}
/**
* 在reader中读取字符,直到出现指定字符中的任一个,停止读取。 返回的字符串中不包含最后出现的字符
*
* @param reader
* @param appear
* @return
* @throws IOException
*/
public static String readTill(Reader reader, char... appear) throws IOException {
StringBuilder sb = new StringBuilder();
while (true) {
int i = reader.read();
if (i < 0)
return sb.length() == 0 ? null : sb.toString();
for (int ind = 0; ind < appear.length; ind++) {
if (appear[ind] == i) {
return sb.toString();
}
}
sb.append((char) i);
}
}
public static String[] readLine(URL in, String charset, int... num) throws IOException {
BufferedReader is = getReader(in, charset);
try {
String line = null;
if (num.length == 0)
num = new int[] { -1 };
boolean isAll = num[0] == -1;
List<String> result = new ArrayList<String>(isAll ? 20 : num.length);
int n = 0;
while ((line = is.readLine()) != null) {
n++;
if (isAll || ArrayUtils.contains(num, n)) {
result.add(line);
}
if (!isAll && n >= num[num.length - 1])
break;
}
return result.toArray(new String[result.size()]);
} finally {
closeQuietly(is);
}
}
/**
* 得到文本文件的某几行,使用后文件会关闭 。
*
* @param inName
* 要读的文本文件
* @param num
* 指定的行号,可以指定多行,必须按顺序(如果不指定则表示读取全部行;如果指定的行号小于1,会返回第一行)
* @return
* @throws IOException
*/
public static String[] readLine(File inName, String charset, int... num) throws IOException {
BufferedReader is = getReader(inName, charset);
try {
String line = null;
if (num.length == 0)
num = new int[] { -1 };
boolean isAll = num[0] == -1;
List<String> result = new ArrayList<String>(isAll ? 20 : num.length);
int n = 0;
while ((line = is.readLine()) != null) {
n++;
if (isAll || ArrayUtils.contains(num, n)) {
result.add(line);
}
if (!isAll && n >= num[num.length - 1])
break;
}
return result.toArray(new String[result.size()]);
} finally {
closeQuietly(is);
}
}
/**
* 将文本文件的指定行拼成String返回
*
* @param inName
* @param charset
* @param filter
* @return
* @throws IOException
*/
public static String readLinesAsString(File inName, String charset, LineFilter filter) throws IOException {
BufferedReader is = getReader(inName, charset);
String line = null;
StringBuilder sb = new StringBuilder();
int n = 0;
while ((line = is.readLine()) != null) {
if (filter == null) {
if (sb.length() > 0)
sb.append('\n');
sb.append(line);
} else {
String ll = filter.filter(line, n++);
if (ll != null) {
if (sb.length() > 0)
sb.append('\n');
sb.append(line);
}
}
}
is.close();
return sb.toString();
}
/**
* 行过滤器接口
*
* @author Administrator
*
*/
public interface LineFilter {
/**
* 过滤行
*
* @param line
* 传入:行的内容
* @param num
* 传入:行号
* @return 过滤后的行的内容,如果传出null表示不需要这一行
*/
String filter(String line, int num);
}
/**
* 从文件中读取需要的行
*
* @param inName
* @param filter
* @return
* @throws IOException
*/
public static String[] readLines(File inName, String charset, LineFilter filter) throws IOException {
BufferedReader is = getReader(inName, charset);
String line = null;
List<String> result = new ArrayList<String>();
int n = 0;
while ((line = is.readLine()) != null) {
if (filter != null) {
String ll = filter.filter(line, n++);
if (ll != null) {
result.add(line);
}
} else {
result.add(line);
}
}
is.close();
return result.toArray(new String[result.size()]);
}
/**
* 返回文件路径。<BR>
* getAbsolutePath 并不是唯一的,比如同一个文件, getAbsolutePath()可以返回 C:/TEMP/../book.exe
* ,也可以是 C:/book.exe。 而getCanonicalPath()才可以返回真正的文件路径。 <BR>
* 问题是这个方法需要抛出一个受检异常,很多时候影响 代码风格的简洁美观。
*
* @param file
*/
public static String getPath(File file) {
Assert.notNull(file);
try {
return file.getCanonicalPath();
} catch (IOException e) {
return file.getAbsolutePath();
}
}
/*
* Copies the contents of the given {@link InputStream} to the given {@link
* OutputStream}.
*
* @param pIn The input stream, which is being read. It is guaranteed, that
* {@link InputStream#close()} is called on the stream.
* 关于InputStram在何时关闭的问题,我一直认为应当是成对操作的(即在哪个方法中生成Stream,就要在使用完后关闭),
* 因此不打算在这里使用close方法。 但是后来我又考虑到,InputStream在使用完后,其内部标记已经发生了变化,无法再次使用。
* (reset方法的效果和实现有关,并不能保证回复到Stream使用前的状态。)
* 因此考虑这里统一关闭以防止疏漏,外面再关一次也不会有问题(作为好习惯,还是应该成对打开和关闭)。
*
* @param pOut 输出流,可以为null,此时输入流中的相应数据将丢弃
*
* @param pClose True guarantees, that {@link OutputStream#close()} is
* called on the stream. False indicates, that only {@link
* OutputStream#flush()} should be called finally.
*
* @param pBuffer Temporary buffer, which is to be used for copying data.
*
* @return Number of bytes, which have been copied.
*
* @throws IOException An I/O error occurred.
*/
private static long copy(InputStream in, OutputStream out, boolean inClose, boolean outClose, byte[] pBuffer) throws IOException {
if (in == null)
throw new NullPointerException();
long total = 0;
try {
int res;
while ((res = in.read(pBuffer)) != -1) {
if (out != null) {
out.write(pBuffer, 0, res);
}
total += res;
}
if (out != null)
out.flush();
} finally {
if (outClose)
closeQuietly(out);
if (inClose)
closeQuietly(in);
}
return total;
}
/*
* 同上、READER和Writer之间的拷贝
*/
private static long copy(Reader in, Writer out, boolean inClose, boolean outClose, char[] pBuffer) throws IOException {
if (in == null)
throw new NullPointerException();
long total = 0;
try {
int res;
while ((res = in.read(pBuffer)) != -1) {
if (out != null) {
out.write(pBuffer, 0, res);
}
total += res;
}
if (out != null)
out.flush();
} finally {
if (outClose && out != null)
closeQuietly(out);
if (inClose)
closeQuietly(in);
}
return total;
}
/**
* 流之间拷贝
*
* @param in
* 输入
* @param out
* 输出
* @param inClose
* 关闭输入流?
* @param outClose
* 关闭输出流?
* @return
* @throws IOException
*/
public static long copy(InputStream in, OutputStream out, boolean inClose, boolean outClose) throws IOException {
return copy(in, out, inClose, outClose, new byte[DEFAULT_BUFFER_SIZE]);
}
/**
* 流之间拷贝
*
* @param in
* 输入
* @param out
* 输出
* @param inClose
* 关闭输入流
* @param outClose
* 关闭输出流
* @return
* @throws IOException
*/
public static long copy(Reader in, Writer out, boolean inClose, boolean outClose) throws IOException {
return copy(in, out, inClose, outClose, new char[DEFAULT_BUFFER_SIZE]);
}
/**
* 流之间拷贝
*
* @param in
* 输入
* @param out
* 输出
* @param pClose
* 关闭输出流?
* @return 拷贝长度
* @throws IOException
*/
public static long copy(Reader in, Writer out, boolean pClose) throws IOException {
return copy(in, out, true, pClose, new char[DEFAULT_BUFFER_SIZE]);
}
/**
* 流之间拷贝
*
* @param in
* 输入
* @param out
* 输出
* @param closeOutStream
* 关闭输出流? (输入流默认关闭)
* @return
* @throws IOException
*/
public static long copy(InputStream in, OutputStream out, boolean closeOutStream) throws IOException {
return copy(in, out, true, closeOutStream, new byte[DEFAULT_BUFFER_SIZE]);
}
/**
* 将Reader内容读取到内存中的charArray
*
* @param reader
* 输入
* @return
* @throws IOException
*/
public static char[] asCharArray(Reader reader) throws IOException {
CharArrayWriter cw = new CharArrayWriter(256);
char[] buf = new char[1024];
int n;
try {
while ((n = reader.read(buf)) > -1) {
cw.write(buf, 0, n);
}
} finally {
reader.close();
}
return cw.toCharArray();
}
/**
* 将Reader的内容读取为一个字符串
*
* @param reader
* @return
* @throws IOException
*/
public static String asString(Reader reader) throws IOException {
return asString(reader, true);
}
/**
* 将Reader内容读取为字符串
*
* @param reader
* @param close
* 关闭reader
* @return
* @throws IOException
*/
public static String asString(Reader reader, boolean close) throws IOException {
if (reader == null)
return null;
StringBuilder sb = new StringBuilder(128);
char[] buf = new char[1024];
int n;
try {
while ((n = reader.read(buf)) > -1) {
sb.append(buf, 0, n);
}
} finally {
if (close)
reader.close();
}
return sb.toString();
}
public static String asString(File pStream, String charset) throws IOException {
return asString(getReader(pStream, charset));
}
/**
* 将指定位置的数据读出成为文本
*
* @param url
* 资源位置
* @param charset
* 字符编码,可以传入null
* @return 读到的文本
* @throws IOException
* IO操作异常
**/
public static String asString(URL url, String charset) throws IOException {
if (url == null)
return null;
return asString(url.openStream(), charset, true);
}
/**
* 将输入流转化为String .(使用缺省的字符集)
*
* 最简单的获取系统资源转换为String的写法如下: <code>
* IOUtils.asString(ClassLoader.getSystemResourceAsStream(filename))
* </code>
*/
public static String asString(InputStream pStream) throws IOException {
return asString(pStream, null, true);
}
/**
* 将输入流转化为String
*
* @param pStream
* The input stream to read.
* @param pEncoding
* The character encoding, typically "UTF-8".
* @param close
* close the in stream?
*/
public static String asString(InputStream pStream, String pEncoding, boolean close) throws IOException {
if (pStream == null)
return null;
ByteArrayOutputStream baos = new ByteArrayOutputStream(256);
copy(pStream, baos, close);
if (pEncoding == null) {
return baos.toString();
} else {
return baos.toString(pEncoding);
}
}
/**
* 将制定的URL中的数据读出成byte[]
*
* @param url
* 资源目标位置
* @return 字节数组
* @throws IOException
* IO操作异常
*/
public static byte[] toByteArray(URL url) throws IOException {
return toByteArray(url.openStream());
}
/**
* 读取文件到内存(不可用于大文件)
*
* @param file
* 本地文件
* @return 字节数组
* @throws IOException
* IO操作异常
*/
public static byte[] toByteArray(File file) throws IOException {
InputStream in = (file instanceof URLFile) ? ((URLFile) file).getInputStream() : new FileInputStream(file);
try {
byte[] result = toByteArray(in, (int) file.length());
return result;
} finally {
in.close();
}
}
/**
* 读取文件到内存(不可用于大文件)
*
* @param file
* @return
* @throws IOException
* @deprecated use {@linkp #toByteArray(File)}
*/
public static byte[] asByteArray(File file) throws IOException {
return toByteArray(file);
}
/**
* 读取流数据到内存。注意这个方法会将数据流全部读入到内存中,因此不适用于很大的数据对象
*
* @param in
* @return
* @throws IOException
*/
public static byte[] toByteArray(InputStream in) throws IOException {
try {
byte[] msg = toByteArray(in, -1);
return msg;
} finally {
in.close();
}
}
/**
* @deprecated use {@link #toByteArray(InputStream)}
* @param in
* @return
* @throws IOException
*/
public static byte[] asByteArray(InputStream in) throws IOException {
return toByteArray(in);
}
/**
* 将内存数据转换为流
*
* @param bytes
* @return
*/
public static InputStream asInputStream(byte[] bytes) {
return new ByteArrayInputStream(bytes);
}
/**
* 从流中读取指定的字节,第二个版本,性能比第一版本有明显提升,但比起第三次修改明显不如,仅作参考保留
*
* @param in
* @param length
*
* @return
* @throws IOException
* @deprecated
*/
public static byte[] toByteArray_v2(InputStream in, int length) throws IOException {
ByteArrayOutputStream out;
if (length > 0) {
out = new ByteArrayOutputStream(length);
} else {
out = new ByteArrayOutputStream(1024);
}
byte[] pBuffer = new byte[DEFAULT_BUFFER_SIZE];
int left = (length > 0) ? length : Integer.MAX_VALUE;// 剩余字节数
while (left > 0) {
int n;
if (left < DEFAULT_BUFFER_SIZE) {
n = in.read(pBuffer, 0, left);
} else {
n = in.read(pBuffer);
}
if (n == -1)
break;
left -= n;
out.write(pBuffer, 0, n);
}
out.close();
byte[] message = out.toByteArray();
return message;
}
/**
* asByteArray的旧版本,重写后实际测试发现性能要低5%~10%。 仅为参考保留
*
* @deprecated
* @param in
* @param length
* @return
* @throws IOException
*/
public static byte[] toByteArray_old(InputStream in, int length) throws IOException {
ByteArrayOutputStream out;
if (length > 0) {
out = new ByteArrayOutputStream(length);
} else {
out = new ByteArrayOutputStream(1024);
}
byte[] pBuffer = new byte[DEFAULT_BUFFER_SIZE];
int count = 0;
while (count < length || length < 0) {
if (length < 0 || length - count > DEFAULT_BUFFER_SIZE) {
int n = in.read(pBuffer);
if (n == -1)
break;
count += n;
out.write(pBuffer, 0, n);
} else {
int n = in.read(pBuffer, 0, length - count);
if (n == -1)
break;
count += n;
out.write(pBuffer, 0, n);
}
}
out.close();
byte[] message = out.toByteArray();
return message;
}
/**
* 这个方法是JDK InputStream.read(byte b[], int off, int len) 的代替方法
* 对于网络数据流,可以反复重试,直到读取到足够的数据
*
* @return
*/
public static int readBytes(InputStream in, byte[] data, int offset, int length) throws IOException {
if (length < 0)
throw new IOException("This method just for reading bytes of a expected length from stream.The param length must >=0");
if (length == 0)
return 0;
if (offset + length > data.length) {
throw new IOException("the container byte[] does not enough for the expected length.");
}
int left = length;
int off = offset;
while (left > 0) {
int n;
n = in.read(data, off, left);
if (n == -1)
break;
left -= n;
off += n;
}
return off - offset;
}
/**
* 合并字节数组
*
* @param packages
* @return
*/
public static byte[] mergeBytes(List<byte[]> packages) {
int len = 0;
for (byte[] aPackage : packages) {
len += aPackage.length;
}
byte[] result = new byte[len];
int pos = 0;
for (byte[] aPackage : packages) {
for (int j = 0; j < aPackage.length; j++) {
result[pos++] = aPackage[j];
}
}
return result;
}
/**
* 从流中读取指定的字节,第三个版本,性能再度提升 参考数据,从120M文件中读取前60M,此方法耗时125ms,v2耗时156ms
*
* @param in
* @param length
* 要读取的字节数,-1表示不限制。(注意实际处理中-1的情况下最多读取2G数据,超过2G不会读取)
* @return
* @throws IOException
*/
public static byte[] toByteArray(InputStream in, int length) throws IOException {
ByteArrayOutputStream out;
if (length > 0) {
out = new ByteArrayOutputStream(length);
} else {
out = new ByteArrayOutputStream(1024);
}
int buf = DEFAULT_BUFFER_SIZE;
byte[] pBuffer = new byte[buf];
int left = (length > 0) ? length : Integer.MAX_VALUE;// 剩余字节数
while (left >= buf) {
int n = in.read(pBuffer);
if (n == -1) {
left = 0;
break;
}
left -= n;
out.write(pBuffer, 0, n);
}
while (left > 0) {
int n = in.read(pBuffer, 0, left);
if (n == -1) {
break;
}
left -= n;
out.write(pBuffer, 0, n);
}
out.close();// ByteArrayOut其实是不需要close的,这里close是为了防止一些代码检查工具提出警告
byte[] message = out.toByteArray();
return message;
}
/**
* @deprecated use {@link #toByteArray(InputStream, int)}
* @param in
* @param length
* @return
* @throws IOException
*/
public static byte[] asByteArray(InputStream in, int length) throws IOException {
return toByteArray(in, length);
}
/**
* 将流中的数据读入到BigDataBuffer对象中去。
*
* @param in
* 输入流
* @param limit
* 限制长度
* @return
* @throws IOException
*/
public static BigDataBuffer asBigDataBuffer(InputStream in, long limit) throws IOException {
BigDataBuffer out = new BigDataBuffer();
byte[] pBuffer = new byte[DEFAULT_BUFFER_SIZE];
long size = 0;
try {
while (size < limit || limit < 0) {
int n = processDataRead(pBuffer, in, out, limit, size);
if (n == 0) {
// 继续
} else if (n == -1) {
break; // 完成
} else {
size += n;// 继续
}
}
return out;
} finally {
in.close();
}
}
/*
* @param pBuffer byte[] 的Bufferr,外部初始化一次,免去每次在此方法中创建数组的开销
*
* @param in 数据来源
*
* @param out 数据去向(一般为Buffer或byte[]等)
*
* @param limit 总共要读取的字节数
*
* @param current 当前已读取字节数
*
* @return 本次读取的字节数
*
* @throws IOException
*/
private static int processDataRead(byte[] pBuffer, InputStream in, OutputStream out, long limit, long current) throws IOException {
int n = 0;
if (limit < 0 || limit - current > DEFAULT_BUFFER_SIZE) {
n = in.read(pBuffer);
} else {
int left = (int) (limit - current);
n = in.read(pBuffer, 0, left);
}
if (n < 0) {
return -1; // 没有数据
} else {// 正常读取,返回本次读取字节数
out.write(pBuffer, 0, n);
return n;
}
}
/**
* 将指定的流保存为临时文件
*
* @param is
* @return
* @throws IOException
*/
public static File saveAsTempFile(InputStream is) throws IOException {
File f = File.createTempFile("~tmp", ".io");
saveAsFile(f, is);
return f;
}
/**
* 将数据从来源保存为临时文件
*
* @param reader
* @return
* @throws IOException
*/
public static File saveAsTempFile(Reader reader) throws IOException {
File f = File.createTempFile("~tmp", ".io");
saveAsFile(f, null, reader);
return f;
}
/**
* 将输入流保存为文件
*
* @param is
* @param file
* @throws IOException
*/
public static void saveAsFile(File file, InputStream... iss) throws IOException {
ensureParentFolder(file);
BufferedOutputStream os = new BufferedOutputStream(new FileOutputStream(file));
try {
for (InputStream is : iss) {
copy(is, os, false);
}
} finally {
if (os != null) {
os.flush();
os.close();
}
}
}
/**
* 将reader内容保存为文件
*
* @param reader
* @param file
* @throws IOException
*/
public static void saveAsFile(File file, Charset charset, Reader... readers) throws IOException {
BufferedWriter os = getWriter(file, charset == null ? null : charset.name(), false);
try {
for (Reader reader : readers) {
copy(reader, os, true, false, new char[2048]);
}
} finally {
closeQuietly(os);
}
}
/**
* 将文字写入文件
*
* @param text
* @param file
* @param append
* @throws IOException
*/
public static void saveAsFile(File file, Charset charset, String... texts) throws IOException {
BufferedWriter os = getWriter(file, charset == null ? null : charset.name(), false);
try {
for (String text : texts) {
os.write(text);
}
} finally {
if (os != null) {
os.flush();
os.close();
}
}
}
public static void saveAsFile(File file, String... texts) throws IOException {
saveAsFile(file, null, texts);
}
/**
* 将内存数据块写入文件
*
* @param data
* @param file
* @throws IOException
*/
public static void saveAsFile(File file, boolean append, byte[] data) throws IOException {
ensureParentFolder(file);
OutputStream out = new FileOutputStream(file, append);
try {
out.write(data);
} finally {
if (out != null) {
out.flush();
out.close();
}
}
}
/**
* 将内存数据块写入文件
*
* @param file
* @param data
* @throws IOException
*/
public static void saveAsFile(File file, byte[] data) throws IOException {
saveAsFile(file, false, data);
}
/**
* 将文件分割成size大小的小块文件。 文件切割,使用Channel方式
*
* @param file
* @param output
* @param size
* @return 文件分割的块数
*/
public static int cut(File file, String output, int size) {
try {
if (!file.exists())
return -1;
// 创建文件输入流
FileInputStream fin = (file instanceof URLFile) ? ((URLFile) file).getInputStream() : new FileInputStream(file);
FileChannel fc1 = fin.getChannel();
int cnt = 0;// 存储每次读取的字节数
int nth = 1;
long position = 0;// 位置
long len = file.length();
while (position < len) {
String name = output + "/" + file.getName() + "@" + nth;
FileOutputStream fou = new FileOutputStream(name);
FileChannel fc2 = fou.getChannel();
cnt = (int) fc1.transferTo(position, size, fc2);
fou.close();
fc2.close();
nth++;
position += cnt;
}
fin.close();
fc1.close();
return nth - 1;
} catch (IOException e) {
LogUtil.exception(e);
return -1;
}
}
/**
* 文件合并 实现方式,使用nio操作。
*
* @param list
* @param path
* @param srcFileName
* @return
*/
public static boolean combine(Collection<String> list, String path, String srcFileName) {
File outputFile = new File(path + "/" + srcFileName);
outputFile = escapeExistFile(outputFile);
try {
FileOutputStream fou = new FileOutputStream(outputFile);
FileChannel fco = fou.getChannel();
long position = 0;
for (String i : list) {// 按顺序获得各个文件名
File file = new File(i);// 创建文件
if (!file.exists())
return false;
FileInputStream fin = new FileInputStream(file);
FileChannel fci = fin.getChannel();
long len = file.length();
fco.transferFrom(fci, position, len);// 接收数据到指定的位置
position += len;
closeQuietly(fin);
closeQuietly(fci);
}
closeQuietly(fou);
closeQuietly(fco);
return true;
} catch (Exception ee) {
LogUtil.exception(ee);
return false;
}
}
/**
* 拷贝(目录)拷贝<br>
* 支持目录拷贝,指定目录下的目录结构会被保留,并复制到新的路径上<br>
* 如果有同名文件或文件夹,会自动覆盖。
*
* @param file
* 源文件或文件夹
* @param newFile
* 目标文件或文件夹
* @return true拷贝成功,false表示拷贝过程出现失败
*/
public static boolean copyFile(File file, File newFile) {
return copyFile(file, newFile, CopyStrategy.ALLWAYS_OVERWRITE);
}
/**
* 拷贝(目录)拷贝<br>
* 可以指定拷贝策略<br>
* 支持目录拷贝,指定目录下的目录结构会被保留,并复制到新的路径上
* <p>
* 注意,此方法是在两个完整 文件/目录 路径之间进行拷贝。
* <p>
* 例如 copyFile(new File("c:\temp"),new File("d:\temproot"));
* //c:\temp和d:\temproot都是目录 拷贝后,c:\temp目录下的所有文件都被拷贝到d:'temproot目录下。
* <p>
* copyFile(new File("c:\temp\io.sys"),new File("d:\temproot"));
* 如果d:\temproot不存在,那么拷贝后d:\temproot是一个文件。
*
* @param source
* 源文件或目录
* @param newFile
* 目标文件或目录。
* @param strategy
* 拷贝策略,拷贝策略可以用于指定拷贝中的各种行为
* @return true拷贝成功,false表示拷贝过程出现失败
* @see CopyStrategy
*/
public static boolean copyFile(File source, File newFile, CopyStrategy strategy) {
if (!source.exists())
return false;
if (source.isDirectory()) {// 源为目录时
if (!strategy.processFolder(source, newFile)) {
return false;
}
if (newFile.exists()) {
if (!newFile.isDirectory()) {
throw new IllegalArgumentException("the target " + newFile.getPath() + " has exist, and is not folder.");
}
}
newFile.mkdirs();
for (File f : source.listFiles()) {
File target = strategy.getTargetFile(f, newFile);
if (target != null) {
log.debug("Coping [{}] to [{}]",f,target);
copyFile(f, target, strategy);
}
}
if (strategy.isMove()) {
if (source.list().length == 0) {
source.delete();
}
}
return true;
} else {// 源为文件时
if (newFile.isDirectory()) {
if (strategy.allowFileIntoFolder()) {
return copyFile(source, new File(newFile, source.getName()), strategy);
} else {
throw new IllegalArgumentException("the target " + newFile.getPath() + " has exist, and is a folder.");
}
}
if (newFile.exists() && !strategy.canOverWritten(source, newFile)) {
return false;
}
if (strategy.isMove()) {
if (newFile.exists() && !newFile.delete()) {// 如果目标存在,并且无法删除,则移动失败
return false;
}
return move(source, newFile);
} else {
FileChannel in = null;
FileChannel out = null;
boolean flag = false;
try {
in = ((source instanceof URLFile) ? ((URLFile) source).getInputStream() : new FileInputStream(source)).getChannel();
out = new FileOutputStream(newFile).getChannel();
in.transferTo(0, source.length(), out);
flag = true;
} catch (IOException e) {
LogUtil.exception(e);
} finally {
closeQuietly(out);
closeQuietly(in);
}
return flag;
}
}
}
/**
* 将文件拷贝到指定目录下,文件名保持不变<br>
*
* @param tmpFile
* 需要复制的文件或目录
* @param path
* 文本,要复制的目标路径(必须是一个文件夹),如果目标不存在会自动创建为文件夹。
* @return 拷贝后的文件
* @throws IOException
* 磁盘操作异常时抛出
* @deprecated Please use {@link #copyIntoFolder(File, File)};
*/
public static File copyToFolder(File file, String path) throws IOException {
return copyIntoFolder(file, new File(path));
}
/**
* 将文件拷贝到指定目录下,文件名保持不变<br>
*
* @param tmpFile
* 需要复制的文件或目录
* @param path
* 文本,要复制的目标路径(必须是一个文件夹),如果目标不存在会自动创建为文件夹。
* @return 拷贝后的文件
* @throws IOException
* 磁盘操作异常时抛出
*/
public static File copyIntoFolder(File source, File dir) {
File target = new File(dir, source.getName());
copyFile(source, target);
return target;
}
/**
* 将文件移动到指定目录下
*
* @param file
* 文件
* @param folder
* 目标文件夹
* @param autoEscape
* 如果存在同名文件,则自动改名
* @return
*/
public static boolean moveToFolder(File file, File folder, boolean autoEscape) {
if (folder.exists() && folder.isFile()) {
throw new IllegalArgumentException("Target is a file.(" + folder.getAbsolutePath() + ")");
}
if (!folder.exists())
folder.mkdirs();
File target = new File(folder, file.getName());
if (target.exists()) {
if (autoEscape) {
target = escapeExistFile(target);
} else {
return false;
}
}
if (file.equals(target)) {
return true;
}
return move(file, target);
}
/**
* 文件(目录)重新命名
*
* @param file
* 要处理的文件或目录
* @param newName
* 修改后的文件名(不含路径)。
* @param overwite
* 覆盖模式,如果目标文件已经存在,则删除目标文件后再改名
* @return 如果成功改名,返回改名后的file对象,否则返回null。
*/
public static File rename(File file, String newName, boolean overwite) {
File target = new File(file.getParentFile(), newName);
if (target.exists()) {
if (overwite) {
if (!target.delete())
return null;
} else {
return null;
}
}
return file.renameTo(target) ? target : null;
}
/**
* 将文件移动为指定的新文件
*
* @param oldFile
* @param newFile
* @return
*/
public static boolean move(File oldFile, File newFile) {
Assert.notNull(oldFile, "source file is null!");
Assert.notNull(newFile, "target file is null!");
Assert.isTrue(oldFile.exists(), "source file doesn't exist.");
Assert.isFalse(newFile.exists(), "target file already exist!");
ensureParentFolder(newFile);
return oldFile.renameTo(newFile);
}
/**
* 将制定文件的编码从 from 字符集转换到 to字符集
*
* @param f
* @param from
* @param to
* @throws IOException
*/
public static int converFileEncode(File f, final String from, final String to, String... extPatterns) throws IOException {
TextFileCallback c = new TextFileCallback(from, to, Dealwith.REPLACE);
int n = 0;
if (f.isDirectory()) {
for (File sub : f.listFiles()) {
n += converFileEncode(sub, from, to, extPatterns);
}
} else {
if (extPatterns.length == 0 || ArrayUtils.contains(extPatterns, getExtName(f.getName()))) {
processFile(f, c);
n++;
}
}
return n;
}
/**
* 文件过滤器
*
* @author jiyi
*
*/
public static abstract class FileFilterEx implements FileFilter {
/**
* 是否跳出当前文件夹搜索 每次运行完成accept方法后,程序会执行此方法,如果返回true则将停止在此目录中的搜索。
*
* @return true中断文件搜索,false继续搜索。
*/
protected boolean breakFolder(File root) {
return false;
};
}
/**
* 总是被继承,用于描述对文件夹的处理
*
* @Company: Asiainfo-Linkage Technologies(China),Inc. Hangzhou
* @author Administrator
* @Date 2011-6-15
*/
public abstract static class FolderCallback {
protected abstract void process(File source);
protected boolean breakProcess() {
return false;
}
}
/**
* 用指定的回调方法处理文件夹
*
* @param root
* @param call
* @return
* @throws IOException
*/
public static int processFolders(File root, FolderCallback call) throws IOException {
int n = 0;
for (File f : listFolders(root)) {
call.process(f);
n += processFolders(f, call);
if (call.breakProcess())
break;
}
return n;
}
/**
* 用指定的回调方法处理文本文件(可指定目录并批量处理目录下所有该类型文件)
*
* @param f
* 文件夹
* @param sourceCharset
* 源文件编码
* @param call
* 处理器
* @param extPatterns
* 扩展名过滤
* @throws IOException
*/
public static int processFiles(File f, TextFileCallback call, String... extPatterns) throws IOException {
int n = 0;
if (f.isDirectory()) {
for (File sub : f.listFiles()) {
n += processFiles(sub, call, extPatterns);
}
} else {
if (extPatterns.length == 0 || ArrayUtils.contains(extPatterns, getExtName(f.getName()))) {
processFile(f, call);
n++;
}
}
return n;
}
/**
* 用指定的回调方法处理二进制文件(可指定目录并批量处理目录下所有该类型文件)
*
* @param f
* @param sourceCharset
* @param call
* @throws IOException
*/
public static int processFiles(File f, BinaryFileCallback call, String... extPatterns) throws IOException {
int n = 0;
if (f.isDirectory()) {
for (File sub : f.listFiles()) {
n += processFiles(sub, call, extPatterns);
}
} else {
if (extPatterns.length == 0 || ArrayUtils.contains(extPatterns, getExtName(f.getName()))) {
processFile(f, call);
n++;
}
}
return n;
}
/**
* 处理二进制文件
*
* @param f
* @param call
* @return
* @throws IOException
*/
public static File processFile(File f, BinaryFileCallback call) throws IOException {
InputStream reader = (f instanceof URLFile) ? ((URLFile) f).getInputStream() : new FileInputStream(f);
FileOutputStream w = null;
File target = call.getTarget(f);
if (target != null) {
ensureParentFolder(target);
w = new FileOutputStream(target);
}
call.beforeProcess(w, f, target);
byte[] cache = new byte[call.getMaxBufferSize()];
int next = call.nextBufferSize();
int len = 0;
while ((len = reader.read(cache, 0, next)) > -1) {
byte[] txt = call.process(ArrayUtils.subArray(cache, len));
if (w != null) {
if (txt != null) {
w.write(txt);
}
}
if (call.breakProcess())
break;
}
reader.close();
call.afterProcess(w);
if (w != null)
w.close();
if (call.isSuccess()) {
if (call.replaceSource(f)) {
if (f.delete()) {
File n = new File(f.getPath());
target.renameTo(n);
return n;
}
} else if (call.deleteSource(f)) {
f.delete();
}
return target;
} else {
if (target != null) {
target.delete();
}
return null;
}
}
/**
* 用指定的回调方法处理文本文件
*
* @param f
* 文件
* @param sourceCharset
* 文件编码
* @param call
* 处理器
* @throws IOException
*/
public static File processFile(File f, TextFileCallback call) throws IOException {
if (!call.accept(f)) {
return null;
}
String sourceCharset = call.sourceCharset(f);
BufferedReader reader = getReader(f, sourceCharset);
call.sourceFile = f;
String charSet = call.targetCharset();
BufferedWriter w = null;
File target = call.getTarget(f);
if (target != null) {
w = getWriter(target, charSet == null ? sourceCharset : charSet, false);
}
String line;
call.beforeProcess(f, target, w);
while ((line = reader.readLine()) != null) {
String txt = null;
try {
txt = call.processLine(line);
} catch (Throwable e) {
LogUtil.exception(e);
call.lastException = e;
}
if (w != null) {
if (txt != null) {
w.write(txt);
if (call.wrapLine())
// w.newLine();
w.write("\r\n");
}
}
if (call.breakProcess())
break;
}
reader.close();
call.afterProcess(f, target, w);
if (w != null)
w.close();
if (call.isSuccess() && target != null) {
Dealwith deal = call.dealwithSourceOnSuccess(f);
if (deal == Dealwith.REPLACE) {
if (f.delete()) {
File n = new File(f.getPath());
target.renameTo(n);
return n;
}
} else if (deal == Dealwith.DELETE) {
f.delete();
} else if (deal == Dealwith.BACKUP_REPLACE) {
File backupfile = new File(f.getParentFile(), f.getName() + ".bak");
backupfile = escapeExistFile(backupfile);
if (f.renameTo(backupfile)) {
File n = new File(f.getPath());
target.renameTo(n);
return n;
}
}
return target;
} else {
if (target != null) {
target.delete();
}
return null;
}
}
/**
* 追加行到已有的文本文件
*
* @param file
* @param charset
* @param lines
* @throws IOException
*/
public static void appendToTextFile(File file, String charset, String... lines) throws IOException {
Assert.isTrue(file.exists(), "The file which you want to append is not exist!" + file.getAbsolutePath());
FileOutputStream out = new FileOutputStream(file, true);
String line;
for (int i = 0; i < lines.length - 1; i++) {
line = lines[i];
if (line != null) {
out.write(charset == null ? line.getBytes() : line.getBytes(charset));
out.write(StringUtils.CRLF);
}
}
line = lines[lines.length - 1];
if (line != null)
out.write(charset == null ? line.getBytes() : line.getBytes(charset));
out.close();
}
/**
* 从URL获得reader
*
* @param file
* @param charSet
* @return
* @throws IOException
*/
public static BufferedReader getReader(URL file, String charSet) {
if (file == null)
return null;
try {
InputStream is = file.openStream();
UnicodeReader isr = new UnicodeReader(is, charSet);
return new BufferedReader(isr);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* 获得一个供读取文本的reader, 本方法可以从BOM自动判断utf-8, unicode等类型,因此charset一般可以为null.
* 只有当文件为不带BOM的unicode时才需要指定。
*
* @param source
* @param charSet
* @return
* @throws IOException
*/
public static BufferedReader getReader(File file, String charSet) throws IOException {
if (file == null)
return null;
InputStream is = (file instanceof URLFile) ? ((URLFile) file).getInputStream() : new FileInputStream(file);
UnicodeReader isr = new UnicodeReader(is, charSet);
return new BufferedReader(isr);
}
/**
* 获得Reader
*
* @param is
* @param charSet
* @return
* @throws IOException
*/
public static BufferedReader getReader(InputStream is, String charSet) {
if (is == null)
return null;
UnicodeReader isr = new UnicodeReader(is, charSet);
return new BufferedReader(isr);
}
/**
* 获得相对于一个class的所在路径的相对路径的文件资源
*
* @param source
* class
* @param fileName
* 文件相对路径
* @param charSet
* 编码
* @return BufferedReader 如果文件不存在,返回null
*/
public static BufferedReader getReader(Class<?> source, String fileName, String charSet) {
InputStream is = source.getResourceAsStream(fileName);
if (is == null) {
is = source.getClassLoader().getResourceAsStream(toClassLoaderResourcePath(source, fileName));
}
if (is == null)
return null;
UnicodeReader isr = new UnicodeReader(is, charSet);
return new BufferedReader(isr);
}
/**
* ClassLoader resource不用/开头。这里将c转换过去
*
* @param fileName
* @return
*/
public static String toClassLoaderResourcePath(Class<?> c, String fileName) {
if (fileName.startsWith("/"))
return fileName.substring(1);
String path = c.getPackage().getName().replace('.', '/');
return path.concat("/").concat(fileName);
}
/**
* 类加载器的资源路径url,转换
*
* @param fileName
* @return
*/
public static String toClassResourcePath(String fileName) {
if (fileName.startsWith("/"))
return fileName;
return "/".concat(fileName);
}
public static BufferedInputStream getInputStream(URL url) throws IOException {
URLConnection conn = url.openConnection();
return new BufferedInputStream(conn.getInputStream());
}
/**
* 将URL转化为文件
*
* @param url
* 要转换的URL,必须是file://协议,否则抛出异常。
*/
public static File urlToFile(URL url) {
if (url == null)
return null;
try {
URLFile file = new URLFile(url);
if (file.isLocalFile())
return file.getLocalFile();
return file;
} catch (RuntimeException e) {
LogUtil.error(url.toString() + " is not a valid file:" + e.getMessage());
return null;
}
}
/**
* 将多个URL转换为多个文件对象
*
* @param url
* @return
*/
public static File[] urlToFile(URL[] url) {
File[] result = new File[url.length];
for (int i = 0; i < url.length; i++) {
result[i] = urlToFile(url[i]);
}
return result;
}
/**
* 获得二进制文件写入句柄
*
* @Title: getInputStream
*/
public static BufferedInputStream getInputStream(File file) throws IOException {
return new BufferedInputStream((file instanceof URLFile) ? ((URLFile) file).getInputStream() : new FileInputStream(file));
}
/**
* 获得文本文件写入流
*
* @param target
* @param charSet
* @param append
* @return
* @throws IOException
*/
public static BufferedWriter getWriter(File target, String charSet, boolean append) {
ensureParentFolder(target);
try {
OutputStream os = new FileOutputStream(target, append);
if (charSet == null)
charSet = Charset.defaultCharset().name();
OutputStreamWriter osw = new OutputStreamWriter(os, charSet);
return new BufferedWriter(osw);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* 获得文本文件写入流
*
* @param target
* @param charSet
* @return
* @throws IOException
*/
public static BufferedWriter getWriter(File target, String charSet) {
return getWriter(target, charSet, false);
}
/**
* 将OutputStream封装为BufferedWriter
*
* @param out
* @param charSet
* @return
*/
public static BufferedWriter getWriter(OutputStream out, String charSet) {
if (charSet == null)
charSet = Charset.defaultCharset().name();
OutputStreamWriter osw;
try {
osw = new OutputStreamWriter(out, charSet);
return new BufferedWriter(osw);
} catch (UnsupportedEncodingException e) {
throw new IllegalArgumentException(e);
}
}
/**
* 返回创建文件的流
*
* @param file
* @return
*/
public static BufferedOutputStream getOutputStream(File file) {
return getOutputStream(file, OverWrittenMode.YES);
}
/**
* 返回创建文件的流
*
* @param file
* @param mode
* @return
*/
public static BufferedOutputStream getOutputStream(File file, OverWrittenMode mode) {
if (file.exists()) {
if (mode == OverWrittenMode.NO) {
return null;
} else if (mode == OverWrittenMode.ESCAPE_NAME || mode == OverWrittenMode.AUTO) {
file = IOUtils.escapeExistFile(file);
} else if (mode == OverWrittenMode.YES && file.isDirectory()) {
throw new IllegalArgumentException("the folder " + file.getAbsolutePath() + " is already exists");
}
}
ensureParentFolder(file);
try {
return new BufferedOutputStream(new FileOutputStream(file));
} catch (FileNotFoundException e) {
LogUtil.exception(e);
throw new RuntimeException(e.getMessage());
}
}
/**
* 将对象序列化在内存中
*
* @param obj
* @return
*/
public static byte[] serialize(Serializable obj) {
ByteArrayOutputStream out = new ByteArrayOutputStream(4096);
saveObject(obj, out);
return out.toByteArray();
}
/**
* 将序列化数据还原为对象
*
* @param data
* @return
*/
public static Object deserialize(byte[] data) {
return loadObject(new ByteArrayInputStream(data));
}
/**
* 将可序列化的对象存储到流中
*
* @param obj
* @param output
* @return
*/
public static boolean saveObject(Serializable obj, OutputStream output) {
ObjectOutputStream out = null;
try {
out = new ObjectOutputStream(output);
out.writeObject(obj);
return true;
} catch (IOException ex) {
LogUtil.exception(ex);
return false;
} finally {
closeQuietly(out);
}
}
/**
* 将可序列化的对象转换到字节数组
*
* @param obj
* @return
*/
public static byte[] saveObject(Serializable obj) {
ByteArrayOutputStream bytes = new ByteArrayOutputStream(2048);
try {
ObjectOutputStream out = new ObjectOutputStream(bytes);
out.writeObject(obj);
closeQuietly(out);
return bytes.toByteArray();
} catch (IOException ex) {
LogUtil.exception(ex);
throw new RuntimeException(ex.getMessage());
}
}
/**
* 将可序列化的对象保存到磁盘文件
*
* @param obj
* @param file
* @return
* @throws IOException
*/
public static boolean saveObject(Serializable obj, File file) {
try {
return saveObject(obj, new FileOutputStream(file));
} catch (FileNotFoundException e) {
LogUtil.exception(e);
return false;
}
}
/**
* 将可序列化的对象保存到磁盘文件
*
* @param aaa
* @param filePath
* @return
*/
public static boolean saveObject(Serializable aaa, String filePath) {
return saveObject(aaa, new File(filePath));
}
/**
* 从流读取序列化对象
*
* @param objFile
* @return
*/
public static Object loadObject(InputStream inn) {
try {
ObjectInputStream in = (inn instanceof ObjectInputStream) ? (ObjectInputStream) inn : new ObjectInputStream(inn);
Object obj = in.readObject();
if (obj instanceof JefSerializable) {
((JefSerializable) obj).init();
}
return obj;
} catch (ClassNotFoundException ex) {
LogUtil.exception(ex);
} catch (IOException ex) {
LogUtil.exception(ex);
} finally {
IOUtils.closeQuietly(inn);
}
return null;
}
public static Object loadObject(byte[] objFile) {
return loadObject(new ByteArrayInputStream(objFile));
}
/**
* 从磁盘读取指定的序列化对象
*
* @param objFile
* @return
*/
public static Object loadObject(File file) {
if (!file.exists())
return null;
try {
return loadObject((file instanceof URLFile) ? ((URLFile) file).getInputStream() : new FileInputStream(file));
} catch (IOException e) {
return null;
}
}
/**
* 相对路径计算,计算从folder出发,到达file的相对路径
*
* @param file
* @param folder
* @return
*/
public static String getRelativepath(String file, String folder) {
String[] f1 = StringUtils.split(file.replace('/', '\\'), '\\');
String[] f2 = StringUtils.split(folder.replace('/', '\\'), '\\');
int breakCount = -1;
for (int i = 0; i < f1.length; i++) {
String str = f1[i];
if (i < f2.length && str.equals(f2[i])) {
breakCount = i + 1;
} else {
break;
}
}
if (breakCount == -1)
return file;
StringBuilder sb = new StringBuilder();
sb.append(StringUtils.repeat("../", f2.length - breakCount));
for (int i = breakCount; i < f1.length; i++) {
sb.append(f1[i] + "/");
}
sb.deleteCharAt(sb.length() - 1);
return sb.toString();
}
/**
* 相对路径计算,计算从folder出发,到达file的相对路径
*
* @param file
* @param folder
*/
public static String getRelativepath(File file, File folder) {
String s1 = getPath(file);
String s2 = getPath(folder);
return getRelativepath(s1, s2);
}
/**
* 将ByteBuffer对象脱壳,得到byte[]
*
* @param bf
* ByteBuffer对象
* @return 字节数组
*/
public static byte[] toByteArray(ByteBuffer bf) {
if (bf.position() != 0) {
throw new RuntimeException("This method only allow you to read a ByteBuffer from start");
}
if (bf.limit() == bf.capacity()) {
return bf.array();
}
byte[] bb = new byte[bf.limit()];
bf.get(bb);
return bb;
}
/**
* 在指定目录下搜索单个文件
*
* @Title: findFile
* @param root
* @param filter
* 过滤条件
* @return File 返回文件
*/
public static File findFile(File root, FileFilterEx filter) {
if (root == null || !root.exists())
return null;
boolean breakThisFolder = false;
for (File f : root.listFiles()) {
if (!breakThisFolder) {
if (filter.accept(f)) {
return f;
}
breakThisFolder = filter.breakFolder(root);
}
if (f.isDirectory()) {
File result = findFile(f, filter);
if (result != null)
return result;
}
}
return null;
}
/**
* 在指定目录下搜索文件
*
* @param root
* 要搜索的目录
* @param filter
* 文件过滤器
* @return 搜索到的所有文件
*/
public static Collection<File> findFiles(File root, FileFilterEx filter) {
if (root == null || !root.exists())
return null;
List<File> result = new ArrayList<File>();
boolean breakThisFolder = false;
for (File f : root.listFiles()) {
if (!breakThisFolder) {
if (filter.accept(f)) {
result.add(f);
}
breakThisFolder = filter.breakFolder(root);
}
if (f.isDirectory()) {
result.addAll(findFiles(f, filter));
}
}
return result;
}
/**
* 在指定目录下搜索单个文件
*
* @param root
* 要搜索的目录
* @param name
* 搜索的文件名称(完全匹配)
* @param acceptFolder
* 是否搜索文件夹
* @return File 返回文件
*/
public static File findFile(File root, final String name, final boolean acceptFolder) {
return findFile(root, new FileFilterEx() {
public boolean accept(File pathname) {
if (!acceptFolder && pathname.isDirectory())
return false;
return pathname.getName().equals(name);
}
});
}
// 一段较为粗糙的代码,目的是区分GB18030和几种UTF编码,但不能识别其他复杂编码
// 更精确的处理还是要用chardet包才行
public static String get_charset(File file) {
String charset = "GB18030";
byte[] first3Bytes = new byte[3];
try {
boolean checked = false;
BufferedInputStream bis = new BufferedInputStream((file instanceof URLFile) ? ((URLFile) file).getInputStream() : new FileInputStream(
file));
bis.mark(0);
int read = bis.read(first3Bytes, 0, 3);
if (read == -1) {
bis.close();
return charset;
}
if (first3Bytes[0] == (byte) 0xFF && first3Bytes[1] == (byte) 0xFE) { // 高位在前的unicode
charset = "UTF-16LE";
checked = true;
} else if (first3Bytes[0] == (byte) 0xFE && first3Bytes[1] == (byte) 0xFF) {// 低位在前的unicode
charset = "UTF-16BE";
checked = true;
} else if (first3Bytes[0] == (byte) 0xEF && first3Bytes[1] == (byte) 0xBB && first3Bytes[2] == (byte) 0xBF) {// UTF-8
charset = "UTF-8";
checked = true;
}
if (checked) {
bis.close();
return charset;
}
bis.reset();
// int pos = 0;
while ((read = bis.read()) != -1) {// 每次读取一个字节
// pos++;
if (read >= 0xF0) // 出现0xF0以上,认为是GBK
break;
if (0x80 <= read && read <= 0xBF) // 单独出现BF以下的,也算是GBK
break;
if (0xC0 <= read && read <= 0xDF) { // c0-df之间的,继续读取判断
read = bis.read();
if (0x80 <= read && read <= 0xBF) // 双字节 (0xC0 - 0xDF) -
// (0x80 -
// 0xBF),也可能在GBK编码内
continue;
else
break;
} else if (0xE0 <= read && read <= 0xEF) {// 也有可能出错,但是几率较小
read = bis.read();
if (0x80 <= read && read <= 0xBF) {
read = bis.read();
if (0x80 <= read && read <= 0xBF) {
charset = "UTF-8";
break;
} else
break;
} else
break;
}
}
bis.close();
} catch (Exception e) {
LogUtil.exception(e);
}
return charset;
}
/**
* 比较两个文件/目录是否内容一致。 <br>
* 作为传入参数的文件/目录的名称不会被比较。
*
* @param origin
* 源文件。可传入文件或目录
* @param target
* 目标文件。可传入文件或目录
* @return
*/
public static boolean equals(File origin, File target) {
boolean isFile = origin.isFile();
if (isFile != target.isFile()) {
return false;
}
if (isFile) {
// 文件比较
return FileComparator.LENGTH_SKIP.equals(origin, target);
} else if (origin.isDirectory() && target.isDirectory()) {
File[] ss = origin.listFiles();
File[] ts = target.listFiles();
if (ss.length != ts.length) {
return false;
}
// 目录比较
for (File file : ss) {
File newTarget = new File(target, file.getName());
boolean flag = equals(file, newTarget);
if (!flag) {
return false;
}
}
return true;
} else {
return false;
}
}
/*
* Read in a "logical line" from an InputStream/Reader, skip all comment and
* blank lines and filter out those leading whitespace characters ( , and )
* from the beginning of a "natural line". Method returns the char length of
* the "logical line" and stores the line in "lineBuf".
*/
static final class LineReader {
private char[] inCharBuf;
private char[] lineBuf = new char[1024];
private int inLimit = 0;
private int inOff = 0;
private Reader reader;
public LineReader(Reader reader) {
this.reader = reader;
inCharBuf = new char[8192];
}
int readLine() throws IOException {
int len = 0;
char c = 0;
boolean skipWhiteSpace = true;
boolean isCommentLine = false;
boolean isNewLine = true;
boolean appendedLineBegin = false;
boolean precedingBackslash = false;
boolean skipLF = false;
while (true) {
if (inOff >= inLimit) {
inLimit = reader.read(inCharBuf);
inOff = 0;
if (inLimit <= 0) {
if (len == 0 || isCommentLine) {
return -1;
}
return len;
}
}
c = inCharBuf[inOff++];
if (skipLF) {
skipLF = false;
if (c == '\n') {
continue;
}
}
if (skipWhiteSpace) {
if (c == ' ' || c == '\t' || c == '\f') {
continue;
}
if (!appendedLineBegin && (c == '\r' || c == '\n')) {
continue;
}
skipWhiteSpace = false;
appendedLineBegin = false;
}
if (isNewLine) {
isNewLine = false;
if (c == '#' || c == '!') {
isCommentLine = true;
continue;
}
}
if (c != '\n' && c != '\r') {
lineBuf[len++] = c;
if (len == lineBuf.length) {
int newLength = lineBuf.length * 2;
if (newLength < 0) {
newLength = Integer.MAX_VALUE;
}
char[] buf = new char[newLength];
System.arraycopy(lineBuf, 0, buf, 0, lineBuf.length);
lineBuf = buf;
}
// flip the preceding backslash flag
if (c == '\\') {
precedingBackslash = !precedingBackslash;
} else {
precedingBackslash = false;
}
} else {
// reached EOL
if (isCommentLine || len == 0) {
isCommentLine = false;
isNewLine = true;
skipWhiteSpace = true;
len = 0;
continue;
}
if (inOff >= inLimit) {
inLimit = reader.read(inCharBuf);
inOff = 0;
if (inLimit <= 0) {
return len;
}
}
if (precedingBackslash) {
len -= 1;
// skip the leading whitespace characters in following
// line
skipWhiteSpace = true;
appendedLineBegin = true;
precedingBackslash = false;
if (c == '\r') {
skipLF = true;
}
} else {
return len;
}
}
}
}
}
/*
* Jiyi 2015-9-14日修改。为了兼容windows应用ini的结构(带小节) 故将小节用 | 符号添加在每个key前方
*
* @param lr
*
* @param map
*
* @throws IOException
*/
private static void load0(LineReader lr, Map<String, String> map, Boolean supportSection) throws IOException {
char[] convtBuf = new char[1024];
int limit;
int keyLen;
int valueStart;
char c;
boolean hasSep;
boolean precedingBackslash;
String currentSection = null;
while ((limit = lr.readLine()) >= 0) {
c = 0;
keyLen = 0;
valueStart = limit;
hasSep = false;
precedingBackslash = false;
while (keyLen < limit) {
c = lr.lineBuf[keyLen];
// need check if escaped.
if ((c == '=' || c == ':') && !precedingBackslash) {
valueStart = keyLen + 1;
hasSep = true;
break;
} else if ((c == ' ' || c == '\t' || c == '\f') && !precedingBackslash) {
valueStart = keyLen + 1;
break;
}
if (c == '\\') {
precedingBackslash = !precedingBackslash;
} else {
precedingBackslash = false;
}
keyLen++;
}
while (valueStart < limit) {
c = lr.lineBuf[valueStart];
if (c != ' ' && c != '\t' && c != '\f') {
if (!hasSep && (c == '=' || c == ':')) {
hasSep = true;
} else {
break;
}
}
valueStart++;
}
String key = loadConvert(lr.lineBuf, 0, keyLen, convtBuf);
String value = loadConvert(lr.lineBuf, valueStart, limit - valueStart, convtBuf);
if(supportSection==null) {
supportSection=isSection(key);
}
if (supportSection && value.length() == 0 && isSection(key)) {
currentSection = key.length() > 2 ? key.substring(1, key.length() - 1) : null;
} else {
if (currentSection == null) {
map.put(key, value);
} else {
map.put(currentSection + "|" + key, value);
}
}
}
}
private static boolean isSection(String key) {
if(key==null || key.length()<2) {
return false;
}
return key.charAt(0) == '[' && key.charAt(key.length() - 1) == ']';
}
/*
* Converts encoded \uxxxx to unicode chars and changes special saved
* chars to their original forms
*/
private static String loadConvert(char[] in, int off, int len, char[] convtBuf) {
if (convtBuf.length < len) {
int newLen = len * 2;
if (newLen < 0) {
newLen = Integer.MAX_VALUE;
}
convtBuf = new char[newLen];
}
char aChar;
char[] out = convtBuf;
int outLen = 0;
int end = off + len;
while (off < end) {
aChar = in[off++];
if (aChar == '\\') {
aChar = in[off++];
if (aChar == 'u') {
// Read the xxxx
int value = 0;
for (int i = 0; i < 4; i++) {
aChar = in[off++];
switch (aChar) {
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
value = (value << 4) + aChar - '0';
break;
case 'a':
case 'b':
case 'c':
case 'd':
case 'e':
case 'f':
value = (value << 4) + 10 + aChar - 'a';
break;
case 'A':
case 'B':
case 'C':
case 'D':
case 'E':
case 'F':
value = (value << 4) + 10 + aChar - 'A';
break;
default:
throw new IllegalArgumentException("Malformed \\uxxxx encoding.");
}
}
out[outLen++] = (char) value;
} else {
if (aChar == 't')
aChar = '\t';
else if (aChar == 'r')
aChar = '\r';
else if (aChar == 'n')
aChar = '\n';
else if (aChar == 'f')
aChar = '\f';
out[outLen++] = aChar;
}
} else {
out[outLen++] = (char) aChar;
}
}
return new String(out, 0, outLen);
}
private static String saveConvert(String theString, boolean isKey,int option) {
if(isKey && option<0) {
return theString;
}else if(isKey==false && option<1) {
return theString;
}
int len = theString.length();
int bufLen = len * 2;
if (bufLen < 0) {
bufLen = Integer.MAX_VALUE;
}
StringBuilder outBuffer = new StringBuilder(bufLen);
for (int x = 0; x < len; x++) {
char aChar = theString.charAt(x);
// Handle common case first, selecting largest block that
// avoids the specials below
if ((aChar > 61) && (aChar < 127)) {
if (aChar == '\\') {
outBuffer.append('\\');
outBuffer.append('\\');
continue;
}
outBuffer.append(aChar);
continue;
}
switch (aChar) {
case ' ':
if (x == 0 || isKey)
outBuffer.append('\\');
outBuffer.append(' ');
break;
case '\t':
outBuffer.append('\\');
outBuffer.append('t');
break;
case '\n':
outBuffer.append('\\');
outBuffer.append('n');
break;
case '\r':
outBuffer.append('\\');
outBuffer.append('r');
break;
case '\f':
outBuffer.append('\\');
outBuffer.append('f');
break;
case '=': // Fall through
case ':': // Fall through
case '#': // Fall through
case '!':
outBuffer.append('\\');
outBuffer.append(aChar);
break;
default:
outBuffer.append(aChar);
}
}
return outBuffer.toString();
}
/*
* 内部使用,properties文件读取
*/
static final void loadProperties(Reader in, Map<String, String> map, Boolean supportSecion) {
if (in == null)
return;
try {
load0(new LineReader(in), map, supportSecion);
} catch (Exception e1) {
LogUtil.exception(e1);
} finally {
closeQuietly(in);
}
}
}