本人在使用jdk中遇到过一个小问题,该部分的内容大概为,通过dlm4j解析xml文档,在读文档的时候解析xml文档是正常的,但是一旦通过FIleWriter对xml写入(修改)的时候就会出问题,会报文件提前结束的错误。虽然问题我已经解决了,但是还是要写一篇来标记下,以加强印象
目录
代码运行环境声明
- java 环境:oracle jdk1.8
- 系统运行环境:windows10, 版本:1909
- 代码编写工具:intellij IDEA community ,版本:2019.3.3
- java编译辅助工具:apache maven 3.6.2
- dom4j 依赖包版本:1.6.1
问题关键
- 理解FileWriter这个后来出现的java io系统的接口类,本质上还是只是一个OutputSteam
- FileWriterWriter在初始化的时候可以传入一个boolean参数,代表是否追加,传入true表示追加文件
- 在创建FIleWriter对象的时候如果不传入true,那么初始化时就会立马清空文件内容
正文部分
本文代码以模拟的方式,以重现当时的情景
一、初遇问题代码
代码内容出现顺序:
- xml文件person.xml
- 可重用类Person.java
- 人员管理类PersonManager.java
- 入口类EntranceClass.java
xml文件person.xml
<persons>
<person id="1">
<name>lison</name>
<age>23</age>
<japan_name>铭じょ</japan_name>
</person>
</persons>
可重用实体类Person.java
public class Person {
private int id;
private String name;
private int age;
private String japan_name;
//geter serter 省略。。。。
@Override
public String toString() {
return "Person{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
", japan_name='" + japan_name + '\'' +
'}';
}
}
人员管理类PersonManager.java
public class PersonManager {
//扫描器
Scanner sc = new Scanner(System.in);
//储存所有人员的list
List<Person> personList;
/**
* 保存人员的方法
*
* @param p 已有信息的person对象
* @return 保存成功返回true ,否则返回false
*/
boolean savePerson(Person p) {
loadPersons();
//添加空person,则返回添加失败信号
if (p == null)
return false;
personList.add(p);
SAXReader reader = new SAXReader();
try (Writer writer = new FileWriter(getThisProjectResourcePath("./person.xml"))) {
Document document = reader.read(getThisProjectResourcePath("./person.xml"));
Element root = document.getRootElement();
Element element = root.addElement("person");
element.addAttribute("id", Integer.toString(p.getId()));
element.addElement("name").setText(p.getName());
element.addElement("age").setText(Integer.toString(p.getAge()));
element.addElement("japan_name").setText(p.getJapan_name());
document.write(writer);
writer.flush();
} catch (DocumentException e) {
e.printStackTrace();
return false;
} catch (IOException e) {
e.printStackTrace();
return false;
}
return true;
}
/**
* 返回当前项目的文件资源路径的方法
*
* @param path
* @return
*/
public String getThisProjectResourcePath(String path) {
return PersonManager.class.getClassLoader().getResource(path).getPath();
}
/**
* 用于获取用户输入的辅助方法
*
* @param tips 提示语
* @return 返回用户输入
*/
String inputMethod(String tips) {
while (true) {
System.out.print(tips + "\n>>");
String inputValue = sc.nextLine();
if (inputValue.length() < 0) {
System.out.println("请输入内容");
continue;
}
return inputValue;
}
}
/**
* 加载储存有用户人员xml
*
* @return 加载成功则返回true ,否则返回false
*/
boolean loadPersons() {
if (personList != null)
return true;
personList = new ArrayList<>();
try {
Document document = new SAXReader().read(getThisProjectResourcePath("./person.xml"));
Element root = document.getRootElement();
List<Element> elements = root.elements();
for (Element el : elements) {
Person p = new Person();
p.setId(Integer.valueOf(el.attributeValue("id")));
p.setName(el.element("name").getStringValue());
p.setAge(Integer.valueOf(el.element("age").getStringValue()));
p.setJapan_name(el.element("japan_name").getStringValue());
personList.add(p);
}
return true;
} catch (DocumentException e) {
e.printStackTrace();
return false;
}
}
/**
* 添加人员的方法
*
* @return 返回是否添加成功
*/
boolean addPerson() {
Person p = new Person();
String id = inputMethod("输入id");
String name = inputMethod("中文名");
String age = inputMethod("年龄");
String japan_name = inputMethod("日文名");
p.setId(Integer.valueOf(id));
p.setName(name);
p.setAge(Integer.valueOf(age));
p.setJapan_name(japan_name);
return savePerson(p);
}
/**
* 查看人员名单的方法
*
* @return 显示成功返回true
*/
boolean showPersons() {
loadPersons();
for (Person p : personList) {
System.out.println(p);
}
return true;
}
}
入口类EntranceClass.java
public class EntranceClass {
public static void main(String[] args) {
PersonManager pm = new PersonManager();
if (pm.loadPersons())
System.out.println("加载成功");
else System.out.println("加载失败");
while (true) {
System.out.println("1添加人员\n2查看人员");
String option = pm.inputMethod("请输入你的选项");
switch (option) {
case "1":
pm.addPerson();
break;
case "2":
pm.showPersons();
break;
default:
System.exit(0);
}
}
}
}
二、问题出现过程
在不知道FileWriter内部使用OutputSreamWriter的情况下,使用以上代码,但凡调用过PersonManager.savePerson(Person) ,之后的一切操作加载或者写入操作均会报错,之后的提示报错提示为:文件提前结束。
出现文件提前结束,说明文件是为空的。这反应出保存的方法出现了文件清空的操作,才引发了这个错误。
三、问题追踪
要说明这个问题,要从FileWriter 的内部结构说起,本质上FileWriter就是个outputStreamWriter,是一个转换流,FileWriter是继承这个OutputSreamWriter的。他本身不提供任何实现方法,都是在内部调用outputStreamWriter实现的,所以FileWriter就是个包装类而已。
断点在FileWriter初始化的时间
多次debug后,我将断点落在FileWriter新建时,发现只要FileWriter初始化完成,person.xml文件就被清空了,所以dom4j读取文件的时候会出现错误,是因为在dom4j读取文件时,文件已经被清空了。所以必须研究下FileWriter是怎么把文件内容洗白的
FileWriter解析
FileWriter的源码结构:
FileWriter部分源码:
public class FileWriter extends OutputStreamWriter {
public FileWriter(String fileName) throws IOException {
super(new FileOutputStream(fileName));
}
public FileWriter(String fileName, boolean append) throws IOException {
super(new FileOutputStream(fileName, append));
}
以下省略......
}
在我的代码中,保存人员的方法体中实例化的方法是使用的FileWriter的第一个构造方法,构造方法又新建了一个FIleOutputSteam类传给父类。
FileOutputStream解析
这里FileWritern新建了一个FileOutputSteam,通过构造器的方式发送给了父类(OutputSteamWriter),那么父类就会基于该流(FileOutputSteam)对象来操作文件内容。
根据文档来看,FileOutputSteam是有第二个参数的,且参数类型为布尔类型,如果传入true代表增加文件内容,但是如果传入false的话,那么就是将文件内容完全重写,这个时候FileOuitputStream会将文件清空。
但是如果新建FIleOutputSteam的时候不传入第二个参数的布尔值,那么默认他会使用文件重写的方式去写文件。所以先前dom4j读取文件会出现文件提前结束的问题,根源就找到了。
四、解决
文件既然是在FileWriter初始化的时候被清空的,那么我们可以先让dom4j框架操作完文档之后,将内容写入到文件时,再实例化一个改文件的FileWriter,以解决这个问题。
当然也不能通过在FileWriter实例化的时候采用添加(追加)内容的方式,这会直接导致文件内容不规范。因为dom4j不会比较前后变化,而是将内容写入文件的时候,一股脑的将全部内容写入文件中。
所以如果让dom4j写入文件时候,采用追加内容的FIleWriter对象,那么文件中会出现旧的内容和旧的内容+最新的内容。简单来说就会导致出现两个根标签,下次dom4j读取的时候会读不出内容来,那么这个程序就废了。
改正后的保存人员方法:
/**
* 保存人员的方法
*
* @param p 已有信息的person对象
* @return 保存成功返回true ,否则返回false
*/
boolean savePerson(Person p) {
loadPersons();
//添加空person,则返回添加失败信号
if (p == null)
return false;
personList.add(p);
SAXReader reader = new SAXReader();
try {
Document document = reader.read(getThisProjectResourcePath("./person.xml"));
Element root = document.getRootElement();
Element element = root.addElement("person");
element.addAttribute("id", Integer.toString(p.getId()));
element.addElement("name").setText(p.getName());
element.addElement("age").setText(Integer.toString(p.getAge()));
element.addElement("japan_name").setText(p.getJapan_name());
Writer writer = new FileWriter(getThisProjectResourcePath("./person.xml"));
document.write(writer);
writer.flush();
writer.close();
} catch (DocumentException e) {
e.printStackTrace();
return false;
} catch (IOException e) {
e.printStackTrace();
return false;
}
return true;
}