使用java进行文件读写,由于使用的频率不高,加上写起来也没那么简单,常常容易忘记,而后就得去翻阅之前的笔记,或者找寻以前写的文件读写代码,此次决定好好的整理下这块的知识点,并写几个通用的工具类,简化文件读写的操做html
本篇博文将如下面几个点做为研究对象java
文件类型mysql
读取方式git
编码spring
java读写文件的IO流分两大类,字节流和字符流,基类分别是字符:Reader和Writer;字节:InputStream和OutPutStreamsql
字符流分为FileReader和FileWrtier,这两个的父类是InputStreamReader和OutStreamWrtier数据库
字节流分为FileInputStream和FileOutPutStreamapache
继承关系表json
Reader->InputStreamReader->FileReader Reader->BufferedReader Writer->OutputStreamWriter->FileWriter Writer->BufferedWriter InputStream->FileInputStream。FileInputStream 用于读取诸如图像数据之类的原始字节流。要读取字符流,请考虑使用 FileReader。 InputStream->FilterInputStream->BufferedInputStream OutputStream->FileOutputStream。FileOutputStream 用于写入诸如图像数据之类的原始字节的流。要写入字符流,请考虑使用 FileWriter OutputStream->FilterOutputStream->BufferedOutputStream
通常使用流程数组
File file = new File("xxx.txt");
FileReader fr = new FileReader(file);
BufferReader br = new BufferReader(fr);
String s = null; StringBuffer sb = new StringBuffer(); while((s=br.readLine()!=null) { sb.append(s); }
InputStreamReader isr = new InpuStreamReader(InputStream in);
InputStreamReader isr = new InpuStreamReader(InputStream in,Charset cs);
按字节读取文件, 按字符读取文件, 按行读取文件, 随机读取文件
/** * 以字节为单位读取文件,经常使用于读二进制文件,如图片、声音、影像等文件。 * * @param fileName * 文件的名 */ public static InputStream createByteRead(String fileName) throws IOException { File file = new File(fileName); return new FileInputStream(file); } /** * 以字符为单位读取文件,经常使用于读文本,数字等类型的文件 * * @param fileName * 文件名 */ public static Reader createCharRead(String fileName) throws FileNotFoundException { File file = new File(fileName); return new InputStreamReader(new FileInputStream(file), Charset.forName("UTF-8")); } /** * 以行为单位读取文件,经常使用于读面向行的格式化文件 * * @param fileName * 文件名 */ public static BufferedReader createLineRead(String fileName) throws FileNotFoundException { File file = new File(fileName); // return new BufferedReader(new FileReader(file)); return new BufferedReader(new InputStreamReader(new FileInputStream(file), Charset.forName("UTF-8"))); }
上面的方法,主要对读字节,读字符,读行进行简单包装,随机读直接用RandomAccessFile
直接测试用例中给出
/** * 字节方式读取文件 * * @throws IOException */ @Test public void testByteRead() throws IOException { String fileName = "/tmp/test.d"; InputStream in = FileUtil.createByteRead(fileName); int temp; while ((temp = in.read()) != -1) { logger.info("read bytes: {}", temp); } in.close(); // 关闭输入流 in = FileUtil.createByteRead(fileName); byte[] tmpAry = new byte[100]; while ((temp = in.read(tmpAry)) != -1) { logger.info("read 100 bytes: {}, return: {}", tmpAry, temp); } in.close(); } @Test public void testCharRead() throws IOException { String fileName = "/tmp/test.d"; Reader reader = FileUtil.createCharRead(fileName); int temp; while ((temp = reader.read()) != -1) { logger.info("read char: {}", (char) temp); } reader.close(); reader = FileUtil.createCharRead(fileName); char[] tmpAry = new char[100]; while ((temp = reader.read(tmpAry)) != -1) { logger.info("read 100 chars: {}, return: {}", tmpAry, temp); } reader.close(); } @Test public void testLineRead() throws IOException { String fileName = "/tmp/test.d"; BufferedReader bufferedReader = FileUtil.createLineRead(fileName); String temp; while ((temp = bufferedReader.readLine()) != null) { // 会过滤掉换行符 logger.info("read line: >>>{}<<<", temp); } bufferedReader.close(); } /** * 随机读取文件内容 * * 文件名 */ @Test public void testRandomRead() { String fileName = "/tmp/test.d"; RandomAccessFile randomFile = null; try { System.out.println("随机读取一段文件内容:"); // 打开一个随机访问文件流,按只读方式 randomFile = new RandomAccessFile(fileName, "r"); // 文件长度,字节数 long fileLength = randomFile.length(); // 读文件的起始位置 int beginIndex = (fileLength > 4) ? 4 : 0; // 将读文件的开始位置移到beginIndex位置。 randomFile.seek(beginIndex); byte[] bytes = new byte[10]; int byteread = 0; // 一次读10个字节,若是文件内容不足10个字节,则读剩下的字节。 // 将一次读取的字节数赋给byteread while ((byteread = randomFile.read(bytes)) != -1) { System.out.write(bytes, 0, byteread); } } catch (IOException e) { e.printStackTrace(); } finally { if (randomFile != null) { try { randomFile.close(); } catch (IOException e1) { } } } }
test.d 文件内容以下
he你好 world
上面的输出以下
// 按字节读取 10:47:56.754 [main] INFO c.h.h.q.file.test.FileUtilTest - read bytes: 104 10:47:56.773 [main] INFO c.h.h.q.file.test.FileUtilTest - read bytes: 101 10:47:56.775 [main] INFO c.h.h.q.file.test.FileUtilTest - read bytes: 228 10:47:56.781 [main] INFO c.h.h.q.file.test.FileUtilTest - read bytes: 189 10:47:56.783 [main] INFO c.h.h.q.file.test.FileUtilTest - read bytes: 160 10:47:56.783 [main] INFO c.h.h.q.file.test.FileUtilTest - read bytes: 229 10:47:56.785 [main] INFO c.h.h.q.file.test.FileUtilTest - read bytes: 165 10:47:56.786 [main] INFO c.h.h.q.file.test.FileUtilTest - read bytes: 189 10:47:56.787 [main] INFO c.h.h.q.file.test.FileUtilTest - read bytes: 10 10:47:56.787 [main] INFO c.h.h.q.file.test.FileUtilTest - read bytes: 119 10:47:56.789 [main] INFO c.h.h.q.file.test.FileUtilTest - read bytes: 111 10:47:56.790 [main] INFO c.h.h.q.file.test.FileUtilTest - read bytes: 108 10:47:56.792 [main] INFO c.h.h.q.file.test.FileUtilTest - read bytes: 114 10:47:56.794 [main] INFO c.h.h.q.file.test.FileUtilTest - read bytes: 100 10:47:56.794 [main] INFO c.h.h.q.file.test.FileUtilTest - read bytes: 10 10:47:56.794 [main] INFO c.h.h.q.file.test.FileUtilTest - read 100 bytes: [104, 101, -28, -67, -96, -27, -91, -67, 10, 119, 111, 108, 114, 100, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], return: 15 // 按字符读取 10:47:56.721 [main] INFO c.h.h.q.file.test.FileUtilTest - read char: h 10:47:56.736 [main] INFO c.h.h.q.file.test.FileUtilTest - read char: e 10:47:56.736 [main] INFO c.h.h.q.file.test.FileUtilTest - read char: 你 10:47:56.737 [main] INFO c.h.h.q.file.test.FileUtilTest - read char: 好 10:47:56.737 [main] INFO c.h.h.q.file.test.FileUtilTest - read char: 10:47:56.737 [main] INFO c.h.h.q.file.test.FileUtilTest - read char: w 10:47:56.738 [main] INFO c.h.h.q.file.test.FileUtilTest - read char: o 10:47:56.739 [main] INFO c.h.h.q.file.test.FileUtilTest - read char: l 10:47:56.743 [main] INFO c.h.h.q.file.test.FileUtilTest - read char: r 10:47:56.747 [main] INFO c.h.h.q.file.test.FileUtilTest - read char: d 10:47:56.747 [main] INFO c.h.h.q.file.test.FileUtilTest - read char: 10:47:56.748 [main] INFO c.h.h.q.file.test.FileUtilTest - read 100 chars: [h, e, 你, 好, , w, o, l, r, d, , , , , , , , , , , , , , , , , , , , , , , ], return: 11 // 按行读取 10:47:56.801 [main] INFO c.h.h.q.file.test.FileUtilTest - read line: >>>he你好<<< 10:47:56.803 [main] INFO c.h.h.q.file.test.FileUtilTest - read line: >>>wolrd<<< // 随机读取 随机读取一段文件内容: �好 wolrd
小结:
从上面的三中方式能够较明显的感知到,使用不一样的读取类,获取的方式也将不一样
FileInputStream
二进制的读写InputStreamReader
字符的读写BufferedReader
字符读写,支持按行读取FileReader 和 InputStreamReader
FileReader
继承了InputStreamReader
,但并无实现父类中带字符集参数的构造函数,因此使用FileReader
时,可能形成中文乱码,而使用InputStreamReader
则能够根据 new InputStreamReader(new FileInputStream(file), Charset.forName("UTF-8"));
来强制指定编码FileReader
是字符流,用于从文件中读取数据,InputStreamReader
是转换流,能够把字节转换成字符流的方式读入相对路径获取文件, 绝对路径获取文件, 从网络上获取文件
绝对路径读取
File file = new File(fileName);
相对路径读取
InputStream stream = XXX.class.getClassLoader().getResourceAsStream(fileName);
网络读取
URL url = new URL(fileName); InputStream stream = url.openStream();
简单的封装类
public static InputStream getStreamByFileName(String fileName) throws IOException { if (fileName == null) { throw new IllegalArgumentException("fileName should not be null!"); } if (fileName.startsWith("http")) { // 网络地址 URL url = new URL(fileName); return url.openStream(); } else if (fileName.startsWith("/")) { // 绝对路径 Path path = Paths.get(fileName); return Files.newInputStream(path); } else { // 相对路径 return FileUtil.class.getClassLoader().getResourceAsStream(fileName); } }
测试用例, 测试以前在相应的地方准备好图片
@Test public void testReadFile() { try { // 绝对路径读取 String img = "/tmp/img.jpg"; BufferedImage bf1 = ImageIO.read(FileUtil.getStreamByFileName(img)); logger.info("read success!"); } catch (IOException e) { logger.error("read absolute img error!"); } try { // 相对路径 String img2 = "avatar.jpg"; BufferedImage bf2 = ImageIO.read(FileUtil.getStreamByFileName(img2)); logger.info("read success!"); } catch (Exception e) { logger.error("read relative img error!"); } try { String img3 = "http://fanyi.baidu.com/static/translation/img/header/logo_cbfea26.png"; URL url = new URL(img3); BufferedImage bf3 = ImageIO.read(FileUtil.getStreamByFileName(img3)); logger.info("read success!"); } catch (Exception e) { logger.error("read net img error!"); } }
在 JS 语言中,一切都是对象。所以,任何支持的类型均可以经过 JSON 来表示,例如字符串、数字、对象、数组等。可是对象和数组是比较特殊且经常使用的两种类型:
随便写了个json串,基本上包含了json中用到的各类数据格式
{ "user": { "name": [ "yihui", "一灰" ], "info": { "sex": "男", "age": 26 } }, "address": { "phone": null, "qq": true, "email": {} } }
如今有较多的json辅助工具,实现对象和JSON字符串的互转,这里咱们选择fastjson做为测试用例
依赖
<!--json--> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.29</version> </dependency>
工具类
/** * 读取json文件, 并转为特定对象 * @param filename * @param typeReference * @param <T> * @return * @throws IOException */ public static <T> T read(String filename, TypeReference<T> typeReference) throws IOException { BufferedReader bf = FileUtil.createLineRead(filename); try { StringBuilder stringBuffer = new StringBuilder(); String line; while ((line = bf.readLine()) != null) { stringBuffer.append(line); } return JSON.parseObject(stringBuffer.toString(), typeReference); } finally { bf.close(); } }
测试用例
@Getter @Setter @ToString private static class JsonDO { private User user; private Address address; @Getter @Setter @ToString static class User { private List<String> name; private Info info; } @Getter @Setter @ToString static class Info { private Integer age; private String sex; } @Getter @Setter @ToString static class Address { private Long phone; private Boolean qq; private Map<String, String> email; } } @Test public void testJsonRead() throws IOException { String fileName = "info.json"; TypeReference<JsonDO> typeReference = new TypeReference<JsonDO>() {}; JsonDO jsonDO = JsonUtil.read(fileName, typeReference); logger.info("the csv words: {}", jsonDO); }
输出结果
xml文件读写,更常见的使用场景是按照本身的意愿去选择的获取某些节点的值, 没想到什么好的方法来返回这种xml文件的数据对象,这里就给一个简单的使用测试case, 参考来源 : http://www.cnblogs.com/yjmyzz/archive/2012/11/11/2765312.html
主要利用 dom4j 来读写xml文件, 首先添加pom依赖
<dependency> <groupId>dom4j</groupId> <artifactId>dom4j</artifactId> <version>1.6.1</version> </dependency>
测试case以下, 下面为待读取的 demo.xml
<?xml version="1.0" encoding="UTF-8" ?> <address-list> <card name="yangjm" id="1"> <sex>男</sex> <address><![CDATA[中国上海外滩No.01]]> </address> <telephone>13916732212</telephone> </card> <card name="zhangsan" id="2"> <sex>女</sex> <address> <item type="家庭地址"><![CDATA[中国.北京.东直门外大街]]> </item> <item type="单位地址"><![CDATA[中国.上海.田林路888号]]> </item> </address> <telephone>010-123123</telephone> </card> </address-list>
测试代码
@Test public void test() throws IOException { String fileName = "demo.xml"; InputStream inputStream = FileUtil.getStreamByFileName(fileName); try { SAXReader reader = new SAXReader(); Document doc = reader.read(inputStream); //加载xml文件 List peoples = doc.selectNodes("//*[@name]"); //选择全部具备name属性的节点(即demo.xml中的全部card节点) for (Iterator iter = peoples.iterator(); iter.hasNext(); ) { Element card = (Element) iter.next(); List attrList = card.attributes(); //输出每一个card的全部属性 for (Iterator attr = attrList.iterator(); attr.hasNext(); ) { Attribute a = (Attribute) attr.next(); System.out.println(a.getName() + "=" + a.getValue()); } System.out.println( "----------------------------------------------------"); } Element zhangsan = (Element) doc.selectSingleNode("//card[@id='2']"); //查找“id属性”=2的card元素 System.out.println("张三的名称:" + zhangsan.attribute("name").getValue()); //输出zhangsan的name属性 Node addrFamily = zhangsan.selectSingleNode("./address/item[2]"); //选择zhangsan元素下的address节点下的第2个item子节点 System.out.println("张三的单位地址:" + addrFamily.getStringValue()); //输出cdata内容 System.out.println( "----------------------------------------------------"); //为zhangsan下增长二个节点 zhangsan.addElement("email").addAttribute("type", "工做").addText("work@some-domain.com"); zhangsan.addElement("email").addAttribute("type", "私人").addCDATA("private@some-domain.com"); //设置CDATA内容 System.out.println(zhangsan.asXML()); //打印zhangsan节点的xml内容(调试用) System.out.println( "----------------------------------------------------"); } catch (Exception e) { e.printStackTrace(); } inputStream.close(); }
多行缩进 数据结构能够用相似大纲的缩排方式呈现,结构经过缩进来表示,连续的项目经过减号“-”来表示,map结构里面的key/value对用冒号“:”来分隔 注意: 字串不必定要用双引号标识; 在缩排中空白字符的数目并非很是重要,只要相同阶层的元素左侧对齐就能够了(不过不能使用TAB字符); 容许在文件中加入选择性的空行,以增长可读性; 在一个档案中,可同时包含多个文件,并用“——”分隔; 选择性的符号“...”能够用来表示档案结尾(在利用串流的通信中,这很是有用,能够在不关闭串流的状况下,发送结束讯号)。
这是一种以缩进表示数据结构的表现方式,详细说明能够参考百科,解析方法依然使用开源的工具snakeyaml
<dependency> <groupId>org.yaml</groupId> <artifactId>snakeyaml</artifactId> <version>1.17</version> </dependency>
测试文件 test.yaml
house: family: name: Doe parents: - John - Jane children: - Paul - Mark - Simone address: number: 34 street: Main Street city: Nowheretown zipcode: 12345
封装实现类
/** * yaml文件读取 * * @param fileName 文件名 * @param clz 格式化的对象实例 * @param <T> * @return * @throws IOException */ public static <T> T read(String fileName, Class<T> clz) throws IOException { try (InputStream inputStream = FileUtil.getStreamByFileName(fileName)) { Yaml yaml = new Yaml(); return yaml.loadAs(inputStream, clz); } }
测试case
@Getter @Setter @ToString public static class Me { private Integer age; private String name; private Map<String, Object> params; private List<String> favoriteBooks; public Me() { } } @Test public void testYamlRead() throws IOException { String fileName = "test.yaml"; Me me = YamlUtil.read(fileName, Me.class); logger.info("me: {}", me); }
输出结果以下
NI文件由节、键、值组成。 节 [section] 参数(键=值) name=value 注解 注解使用分号表示(;)。在分号后面的文字,直到该行结尾都所有为注解。
ini
文件的格式相似 properties
文件,根据这个规则进行读写也比较简单,即使本身开发一个读写工具类,貌似也不会特别 复杂,固然也彻底没有必要本身造轮子, 直接找个开源工具使用便可
<!--ini--> <dependency> <groupId>org.ini4j</groupId> <artifactId>ini4j</artifactId> <version>0.5.2</version> </dependency>
测试文件 test.ini
[system] program_name=ini4jExample version=1.0 [person_1] name=kanpiaoxue_1 age=30 sex=1 [person_2] name=kanpiaoxue_2 age=31 sex=1 [company] name=company1 address=beijing [company] name=company1 address=beijing
封装代码
/** * ini文件读取 * @param fileName 文件名 * @return * @throws IOException */ public static Ini read(String fileName) throws IOException { InputStream inputStream = FileUtil.getStreamByFileName(fileName); try { Config cfg = new Config(); // 设置Section容许出现重复 cfg.setMultiSection(true); Ini ini = new Ini(); ini.setConfig(cfg); ini.load(inputStream); return ini; } finally { inputStream.close(); } }
测试用例
@Test public void testIniUtilRead() throws IOException { String fileName = "test.ini"; Ini ini = IniUtil.read(fileName); Profile.Section section = ini.get("system"); logger.info("section: {}", section); }
输出结果以下

properties 文件,一般用作配置文件来使用,在spring的工程中有较普遍的应用。定义也比较简单,每一行由
propertyName = propertyValue
这种格式组成, 其中等号前面的为name, 等号后面的value值; # 号后面为注释 properties文件更适用于kv格式的配置项
一个简单的测试case, 下面记录的是数据库的连接信息
driver=com.mysql.jdbc.Driver url=jdbc:mysql://127.0.0.1:3306/wolf?useUnicode=true&characterEncoding=UTF-8 username=root password=
这个读取比较简单, 能够直接使用jdk的 Properties
类来作, 这个类继承自 Hashtable
, 简单的封装代码以下
/** * 读取properties文件的内容 * * @param fileName 文件名 * @return * @throws IOException */ public static Properties read(String fileName) throws IOException { InputStream inputStream = FileUtil.getStreamByFileName(fileName); try { Properties pro = new Properties(); pro.load(inputStream); return pro; } finally { inputStream.close(); } }
测试用例
@Test public void testReadProperties() throws IOException { String fileName = "jdbc.properties"; Properties properties = PropertiesUtil.read(fileName); logger.info("properties: {}", properties); }
输出结果以下

逗号分隔值(Comma-Separated Values,CSV,有时也称为字符分隔值,由于分隔字符也能够不是逗号),其文件以纯文本形式存储表格数据(数字和文本)
一个简单的实例, 一个存储词典的csv文件,共有2列,第一列为单词的id, 第二列为单词内; 第一行表示的头
dicId,"name" 1,"质量" 2,"服务" 3,"发货" 4,"性价比" 5,"尺码"
对于csv文件的读取, 主要借用 apache的开源工具
<!--csv 文件读写--> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-csv</artifactId> <version>1.3</version> </dependency>
简单的封装类以下, 须要注意的是返回值是一个 CSVRecord
对象, 须要对其进行转换; 第一列返回的是头, 须要过滤掉
/** * 读取csv文件, 返回结构话的对象 * @param filename csv 路径 + 文件名, 支持绝对路径 + 相对路径 + 网络文件 * @param headers csv 每列的数据 * @return * @throws IOException */ public static List<CSVRecord> read(String filename, String[] headers) throws IOException { Reader reader = FileUtil.createCharRead(filename); try { CSVParser csvParser = new CSVParser(reader, CSVFormat.INFORMIX_UNLOAD_CSV.withHeader(headers) ); return csvParser.getRecords(); } finally { reader.close(); } }
测试用例以下
@Getter @Setter @ToString private static class WordDO { Integer dicId; String name; } @Test public void testCsvRead() throws IOException { String fileName = "word.csv"; List<CSVRecord> list = CsvUtil.read(fileName, new String[]{"dicId", "name"}); Assert.assertTrue(list != null && list.size() > 0); List<WordDO> words = list.stream() .filter(csvRecord -> !"dicId".equals(csvRecord.get("dicId"))) .map(this::parseDO).collect(Collectors.toList()); logger.info("the csv words: {}", words); } private WordDO parseDO(CSVRecord csvRecord) { WordDO wordDO = new WordDO(); wordDO.dicId = Integer.parseInt(csvRecord.get("dicId")); wordDO.name = csvRecord.get("name"); return wordDO; }
输出结果以下

源码直通车: https://git.oschina.net/liuyueyi/quicksilver/tree/master/silver-file?dir=1&filepath=silver-file