Java数据文件映射工具

一 思路

1. 最近公司需要开发许多文件数据同步的程序, 将数据写入文件, 通过SFTP协议同步给其他平台, 本人太懒, 就想弄个一劳永逸的工具.

2. 系统启动时, 创建一个Map结构的容器, 容器中存储文件生成规则与数据Entity的映射属性配置.

3. 文件生成时, 根据根据配置Key查询配置的值(配置值包括文件编码, 文件是否存在文件头, 文件每一列的分隔符), 使用反射机制反射Entity拿到值, 根据配置的规则写入文件.

4. 读取文件时, 根据根据配置Key查询配置的值, 创建List集合, 读取到文件, 根据配置中的Entity属性与文件属性映射关系将每一行数据都转换为Entity对象, 存入List集合.

二 开撸

1. 创建一个XML配置文件 fileMapper.xml

<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<!DOCTYPE context [
   <!ELEMENT context (fileMapper | fileTemplate)*>
   <!ELEMENT fileMapper (mapper+)>
   <!ELEMENT mapper EMPTY>
   <!ATTLIST fileMapper id ID #REQUIRED>
   <!ATTLIST fileMapper isFileHead (true|false) "true">
   <!ATTLIST fileMapper separator CDATA #REQUIRED>
   <!ATTLIST fileMapper charEncod CDATA "UTF-8">
   <!ATTLIST fileMapper fileTemplate CDATA #IMPLIED >
   <!ATTLIST mapper entityAttribute CDATA #REQUIRED>
   <!ATTLIST mapper fileAttribute CDATA #REQUIRED>
]>

<context>
    <!--
        一, fileMapper属性注释:
            id = "配置的唯一值, 根据ID可从容器中获取该条配置"
            isFileHead = "是否存在文件头 true存在, false不存在"
            separator = "每一列数据的分隔符"
            charEncod = "文件编码"
        二, mapper属性注释
            entityAttribute = "Java类的属性名"
            fileAttribute = "文件的列名"
    -->
    <fileMapper id="userInfo_MIIT" isFileHead="false" separator="," charEncod="UTF-8" >
        <mapper entityAttribute="name" fileAttribute="fullName" />
        <mapper entityAttribute="age" fileAttribute="age" />
        <mapper entityAttribute="sex" fileAttribute="gender" />
        <mapper entityAttribute="phone" fileAttribute="phoneNumber" />
        <mapper entityAttribute="company" fileAttribute="organize" />
        <mapper entityAttribute="department" fileAttribute="department" />
        <mapper entityAttribute="location" fileAttribute="province" />
    </fileMapper>

</context>

2. 创建配置信息模型对象 Context.Java , 用于在系统启动时, 将 fileMapper.xml 配置文件中的数据写入内存的载体

    /**
     * @功能描述 文件映射模型
     * @author 索睿君
     * @date 2019年7月5日10:01:21
     */
    @XmlAccessorType(XmlAccessType.FIELD)
    @XmlRootElement(name = "context")
    public static class Context {
        @XmlElement(name = "fileMapper")
        public List<FileMapper> fileMapperList;
        @XmlAccessorType(XmlAccessType.FIELD)
        @XmlRootElement(name = "fileMapper")
        public static class FileMapper {
            /** 唯一标识 */
            @XmlAttribute
            private String id;
            /** 是否拥有文件头 (true是false否) */
            @XmlAttribute
            private boolean isFileHead;
            /** 每列的分隔符 */
            @XmlAttribute
            private String separator;
            /** 字符编码 */
            @XmlAttribute
            private String charEncod = "UTF-8";/** Java属性名与文件头名称映射 */
            @XmlElement(name = "mapper")
            private List<Mapper> mapperList;
            private Map<String, String> mapper = new LinkedHashMap<>();

            @XmlAccessorType(XmlAccessType.FIELD)
            @XmlRootElement(name = "mapper")
            public static class Mapper {
                @XmlAttribute
                private String entityAttribute;
                @XmlAttribute
                private String fileAttribute;
            }
            @Override
            public String toString() {
                return "FileMapper [id=" + id + ", isFileHead=" + isFileHead + ", separator=" + separator
                        + ", charEncod=" + charEncod + ", fileTemplate=" + fileTemplate + ", filePath=" + filePath
                        + ", mapperList=" + mapperList + ", mapper=" + mapper + "]";
            }
        }
    }

3. 创建 FileMapperContext.Java 类, 在系统启动时读取 fileMapper.xml 配置信息, 转化为 Context.Java 对象, 存入Map集合

    /**
     * @功能描述 文件映射容器
     * @author 索睿君
     * @date 2019年7月5日14:55:35
     */
    @Component public static class FileMapperContext{
        private final static String CLASSPATH = "com/config/fileMapper/fileMapper.xml";
        private final static Log Log = LogFactory.getLog(FileMapperContext.class);
        public final static Map<String, Context.FileMapper> MAPPER_CONTEXT = new HashMap<>();static{
            try {
                File file = new File(Context.class.getClassLoader().getResource(CLASSPATH).getFile());
                JAXBContext jaxbContext = JAXBContext.newInstance(Context.class);
                Context context = (Context) jaxbContext.createUnmarshaller().unmarshal(file);
                context.fileMapperList.forEach(fileMapper -> {
                    fileMapper.mapperList.forEach(mapper -> {
                        fileMapper.mapper.put(mapper.entityAttribute, mapper.fileAttribute);
                    });
                    MAPPER_CONTEXT.put(fileMapper.id, fileMapper);
            Log.info("=============文件映射配置[" + fileMapper.id + "]加载完毕!~");
                });
            } catch (Exception e) {
                Log.error("文件映射配置加载异常, 嵌套异常: \n" + e.toString());
            }
        }
    }

4. 生成文件的函数, 使用时传参数配置的Key(也就是 fileMapper.xml 配置文件中的ID属性值), 文件路径, 数据集合

    /**
     * @功能描述 数据写入文件
     * @param mapperKey
     * @param filePath
     * @param dataList
     * @throws ServiceException
     */
    public static <T> void writeFile(String mapperKey, String filePath, List<T> dataList) throws ServiceException {
        // 参数验证
        if (mapperKey == null || mapperKey.trim().length() < 1 || filePath == null || !PathHandler.initPath(filePath).isFile()) {
            throw new ServiceException("数据写入文件异常, 异常信息: 非法参数!~");
        }
        long startTime = System.currentTimeMillis();
        Log.info("开始写入文件["+PathHandler.getFileName(filePath)+"]");
        // 获取映射
        Context.FileMapper fileMapperObject = FileMapperContext.MAPPER_CONTEXT.get(mapperKey);
        if (fileMapperObject == null) {
            throw new ServiceException("数据写入文件异常, 异常信息: 无效的文件映射键!~");
        }
        Charset charEncod = Charset.forName(fileMapperObject.charEncod);                //字符编码
        boolean isFileHead = fileMapperObject.isFileHead;                               //是否拥有文件头
        String separator = fileMapperObject.separator;                                  //列分隔符
        Map<String, String> mapper = fileMapperObject.mapper;                           //属性文件映射
        String fileHeads = mapper.values().stream().collect(Collectors.joining(separator));   //文件头
        List<String> variableName = mapper.keySet().stream().collect(Collectors.toList());    //属性名
        try (BufferedWriter writer = Files.newBufferedWriter(Paths.get(filePath),charEncod)) {
            if(isFileHead) writer.write(fileHeads + System.getProperty("line.separator"));
            if(dataList == null || dataList.size() < 1) return;
            for (T data : dataList) {
                ArrayList<String> variableValueList = new ArrayList<>();
                Class<? extends Object> clazz = data.getClass();
                for (String variable : variableName) {
                    Field declaredField = clazz.getDeclaredField(variable);
                    declaredField.setAccessible(true);
                    Object object = declaredField.get(data);
                    if(object == null){
                        variableValueList.add("");
                    }else if(declaredField.getType().getName().equals(Date.class.getName())){
                        String format = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(object);
                        variableValueList.add(format);
                    }else{
                        variableValueList.add(String.valueOf(object));
                    }
                }
                String fileLine = variableValueList.stream().collect(Collectors.joining(separator));
                writer.write(fileLine + System.getProperty("line.separator"));
            }
            Long endTime = System.currentTimeMillis();
            Log.info("文件["+PathHandler.getFileName(filePath)+"]写入结束, 耗时: " + (endTime - startTime) + "ms");
        } catch (IOException e) {
            throw new ServiceException("数据写入文件IO异常, 嵌套异常: " + e.toString());
        } catch (NoSuchFieldException | SecurityException e) {
            throw new ServiceException("数据写入文件反射属性异常, 嵌套异常: " + e.toString());
        } catch (IllegalArgumentException | IllegalAccessException e) {
            throw new ServiceException("数据写入文件读取属性异常, 嵌套异常: " + e.toString());
        }
    }

5. 读取文件的函数

    /**
     * @param <T>
     * @功能描述 文件读取数据
     * @param mapperKey
     * @param filePath
     * @return
     * @throws ServiceException
     */
    public static < T> List<T> readFile(String mapperKey,String filePath,Class<T> javaClass) throws ServiceException{
        //参数验证
        if(mapperKey == null || mapperKey.trim().length() < 1 || !new File(filePath).isFile()){
            throw new ServiceException("文件读取数据异常, 异常信息: 非法参数!~");
        }
        long startTime = System.currentTimeMillis();
        Log.info("开始读取文件["+PathHandler.getFileName(filePath)+"]");
        try {
            //获取映射
            Context.FileMapper fileMapperObject = FileMapperContext.MAPPER_CONTEXT.get(mapperKey);
            if(fileMapperObject == null){
                throw new ServiceException("文件读取数据异常, 异常信息: 无效的文件映射键!~");
            }
            Charset charEncod = Charset.forName(fileMapperObject.charEncod);             //获取字符编码
            Map<String, String> fileMapperMap = fileMapperObject.mapper;                 //获取文件对象映射
            String[]  variableArray = fileMapperMap.keySet().toArray(new String[]{});    //获取属性数组
            String separator = fileMapperObject.separator;                               //获取文件列分隔符
            boolean isFileHead = fileMapperObject.isFileHead;                            //文件是否存文件头
            //读取文件
            List<String> fileContext = Files.readAllLines(Paths.get(filePath),charEncod);
            if(fileContext == null || fileContext.size() < 1) return null;
            List<T> dataList = new LinkedList<>();
            //开始解析
            for (int lineIndex = isFileHead ? 1 : 0; lineIndex < fileContext.size(); lineIndex++) {
                //获取行内容
                String fileLine =  fileContext.get(lineIndex);
                System.out.println("正在读取第["+lineIndex+"]行数据:" + fileLine);
                T newInstance = javaClass.newInstance();
                if(fileLine == null || fileLine.trim().length() < 1) continue;
                String[] fileColumn = fileLine.split("["+separator+"]");
                for (int columnIndex = 0; columnIndex < fileColumn.length; columnIndex++) {
                    Field declaredField = javaClass.getDeclaredField(variableArray[columnIndex]);
                    declaredField.setAccessible(true);
                    declaredField.set(newInstance, baseClass(fileColumn[columnIndex], declaredField.getType()) );
                    declaredField.setAccessible(false);
                }
                dataList.add(newInstance);
            }
            fileContext.clear();
            long endTime = System.currentTimeMillis();
            Log.info("文件["+PathHandler.getFileName(filePath)+"]读取完毕, 耗时: " + (endTime - startTime) + "ms");
            return dataList;
        } catch (IOException e) {
            throw new ServiceException("文件读取数据IO读取异常, 异常信息: " + e.toString());
        } catch (InstantiationException | IllegalAccessException e) {
            throw new ServiceException("文件读取数据反射对象异常, 异常信息: " + e.toString());
        }  catch (NoSuchFieldException | SecurityException e) {
            throw new ServiceException("文件读取数据反射属性异常, 异常信息: " + e.toString());
        }
    }

6. 文件生成测试

    @Test
    public void 生成文件(){
        try {
            // 指定文件路径
            String filePath = "E:/开发需求/25-谢绝来电/04 需求测试/WISH_DATA_A_20190716.txt.删除";
            // 创建List, 存放用户意愿数据
            List<UserWish> dataList = new ArrayList<>();       //生成100个手机号
            List<String> createPhones = TestUtil.createPhones(100);
            for (String phone : createPhones) {
                UserWish userWish = new UserWish();
                userWish.setOperateType(3);
                userWish.setPhone(Des3Util.encryptBase64(phone, ybef_key, yber_keyIpv));    //手机号加密
                userWish.setWish("1");
                userWish.setRegistTime("2019-06-27 04:54:28");
                userWish.setUpdateTime(new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date()));
                userWish.setLocation("BJ");
                dataList.add(userWish);
            }
            //调用工具生成文件
            FileMapping.writeFile(FileMapper.KEY_UserWish_BJDX, filePath, dataList);
            System.out.println("用户意愿数据生成完毕!~");
        } catch (Exception e) {
            System.out.println("异常: "+e);
        }
    }

7. 文件读取测试

    @Test
    public void 读取数据(){
        String 数据文件 = "E:/开发需求/25-谢绝来电/04 需求测试/WISH_DATA_A_20190716.txt.删除";
        try {
            List<UserWish> readFile = FileMapping.readFile(FileMapper.KEY_UserWish_BJDX, 数据文件, UserWish.class);
            readFile.forEach(System.out::println);
            System.out.println("共 [" + readFile.size() + "] 条记录!~");
        } catch (ServiceException e) {
            System.out.println("读取异常:" + e.toString());
        }
    }

三 总结

生成500万数据大概需要消耗2分钟时间

读取500万数据大概在4到5分钟之间

欢迎各位大神指正以上程序的不足之处和优化点

原文地址:https://www.cnblogs.com/suoruijun/p/suibi.html

时间: 2024-10-09 07:00:43

Java数据文件映射工具的相关文章

Java class文件分析工具 -- Classpy

Classpy Classpy是一个图形化的class文件分析工具,功能和javap类似,界面主要参考了Java Class Viewer: 为什么要重新创造轮子? 写这个工具花了将近一周的时间,那么为什么要浪费时间重新发明一个轮子呢?主要是因为下面几点原因: 通过自己写一个class解析器,可以彻底理解class文件格式和字节码 尝鲜Java8和JavaFX 8 Java Class Viewer比较老,不支持新的class文件格式 可以结合javap和Java Class Viewer的优点

Java 压缩文件夹工具类(包含解压)

依赖jar <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-compress</artifactId> <version>1.18</version> </dependency> CompressUtils.java package utils; import java.io.BufferedInputStream;

Oracle快速导出数据文件

数据文件生成方式有多种,但是大多效率低下,对于大型数据仓库来说,高效导出文件是迫切需求: 这里通过shell+java的方式集成老熊写的数据文件生成工具,效率比普通的导出方法高70%: shell文件生成脚本: Java调用脚本:这里implement Callable接口,实现了多线程的调用方式: /** * @Title: Db2FileSync.java * @Package etl.etlUtils * @Description: TODO * @author Lubin Su * @d

MongoDB数据文件内部结构

有人在Quora上提问:MongoDB数据文件内部的组织结构是什么样的.随后10gen的工程师Jared Rosoff出来做了简短的回答. 每一个数据库都有自己独立的文件.如果你开启了directoryperdb选项,那你每个库的文件会单独放在一个文件夹里. 数据库文件在内部会被切分成单个的块,每个块只保存一个名字空间的数据.在MongoDB中,名字空间用于区分不同的存储类别.比如每个collection有一个独立的名字空间,每个索引也有自己的名字空间. 在一个块中,会保存多条记录,每条记录是B

MS SQL Server数据库修复/MDF数据文件数据恢复/MDF质疑/mdf无法附加

微软的SQL Server 数据库最常用的有两种类型的文件: 1.主要数据文件,文件后缀一般是.MDF: 2.事务日志文件,文件后缀一般是.LDF. 用户数据表.视图.存储过程等等数据,都是存放在MDF文件里,LDF文件是存放MS SQL Server操作过程中的日志记录. MDF文件必读附加到MS SQL Server数据库环境后,才能正常读取其中的数据.当由于某种原因数据库附加不上,MDF数据库文件的内容就没办法读取,修复的方法有两种: 1. 使用MS SQL Server环境进行修复,先以

Java NIO内存映射---上G大文件处理

林炳文Evankaka原创作品.转载请注明出处http://blog.csdn.net/evankaka 摘要:本文主要讲了java中内存映射的原理及过程,与传统IO进行了对比,最后,用实例说明了结果. 一.java中的内存映射IO和内存映射文件是什么? 内存映射文件非常特别,它允许Java程序直接从内存中读取文件内容,通过将整个或部分文件映射到内存,由操作系统来处理加载请求和写入文件,应用只需要和内存打交道,这使得IO操作非常快.加载内存映射文件所使用的内存在Java堆区之外.Java编程语言

Java NIO内存映射---上G大文件处理(转)

林炳文Evankaka原创作品.转载请注明出处http://blog.csdn.net/evankaka 摘要:本文主要讲了java中内存映射的原理及过程,与传统IO进行了对比,最后,用实例说明了结果. 一.java中的内存映射IO和内存映射文件是什么? 内存映射文件非常特别,它允许Java程序直接从内存中读取文件内容,通过将整个或部分文件映射到内存,由操作系统来处理加载请求和写入文件,应用只需要和内存打交道,这使得IO操作非常快.加载内存映射文件所使用的内存在Java堆区之外.Java编程语言

Java实体映射工具MapStruct的使用

官网地址:http://mapstruct.org/ MapStruct 是一个代码生成器,简化了不同的 Java Bean 之间映射的处理,所谓的映射指的就是从一个实体变化成一个实体.例如我们在实际开发中,DAO 层的实体(PO)和一些数据传输对象(DTO),大部分属性都是相同的,只有少部分的不同,通过 mapStruct,可以让不同实体之间的转换变的简单.我们只需要按照约定的方式进行配置即可. MapStruct 是一个可以处理注解的 Java 编译器插件,可以在命令行中使用,也可以在 ID

Java使用内存映射实现大文件的上传

在处理大文件时,如果利用普通的FileInputStream 或者FileOutputStream 抑或RandomAccessFile 来进行频繁的读写操作,都将导致进程因频繁读写外存而降低速度.如下为一个对比实验. package test; import java.io.BufferedInputStream; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOExc