本人在使用jdk中遇到过一个小问题,该部分的内容大概为,通过dlm4j解析xml文档,在读文档的时候解析xml文档是正常的,但是一旦通过FIleWriter对xml写入(修改)的时候就会出问题,会报文件提前结束的错误。虽然问题我已经解决了,但是还是要写一篇来标记下,以加强印象
目录
代码运行环境声明
问题关键
正文部分
一、初遇问题代码
xml文件person.xml
可重用实体类Person.java
人员管理类PersonManager.java
入口类Entrance.java
二、问题出现过程
三、问题追踪
断点在FileWriter初始化的时间
FileWriter解析
FileOutputStream解析
四、解决
本文代码以模拟的方式,以重现当时的情景
代码内容出现顺序:
lison
23
铭じょ
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 + '\'' +
'}';
}
}
public class PersonManager {
//扫描器
Scanner sc = new Scanner(System.in);
//储存所有人员的list
List 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 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;
}
}
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就是个包装类而已。
多次debug后,我将断点落在FileWriter新建时,发现只要FileWriter初始化完成,person.xml文件就被清空了,所以dom4j读取文件的时候会出现错误,是因为在dom4j读取文件时,文件已经被清空了。所以必须研究下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类传给父类。
这里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;
}