偶遇的jdk使用小事件分享——Writer(i/o)的子类FileWriter相关

本人在使用jdk中遇到过一个小问题,该部分的内容大概为,通过dlm4j解析xml文档,在读文档的时候解析xml文档是正常的,但是一旦通过FIleWriter对xml写入(修改)的时候就会出问题,会报文件提前结束的错误。虽然问题我已经解决了,但是还是要写一篇来标记下,以加强印象

目录

代码运行环境声明

问题关键

正文部分

一、初遇问题代码

xml文件person.xml

可重用实体类Person.java

人员管理类PersonManager.java

入口类Entrance.java

二、问题出现过程

三、问题追踪

断点在FileWriter初始化的时间

FileWriter解析

FileOutputStream解析

四、解决


代码运行环境声明

  • 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,那么初始化时就会立马清空文件内容

正文部分

本文代码以模拟的方式,以重现当时的情景

一、初遇问题代码

代码内容出现顺序:

  1. xml文件person.xml
  2. 可重用类Person.java
  3. 人员管理类PersonManager.java
  4. 入口类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类结构
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;
    }

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值