精华内容
下载资源
问答
  • 我们导出三个简单的符号: with-raw-io 、 read-char和read-line 。 显然, read-char 和 read-line 会影响现有的 CL 符号。 我们只是在with-raw-io内部调用它们。 with-raw-io是一个宏,它采用&body并通过修改...
  • 导出导出redis里面的key值

    万次阅读 2016-07-18 15:17:49
    导出redis3.0集群查询key的值:echo "hget StockCodeKey:1 600284" | /data/redis3.0/bin79/redis-cli -c > /data/redis3.0/bin79/xcc.txt
    导出redis3.0集群查询key的值:
    
    echo "hget StockCodeKey:1 600284" | /data/redis3.0/bin79/redis-cli -c > /data/redis3.0/bin79/xcc.txt

    使用redic-cli导入的两种方法:
    有兴趣看原文的,参见 http://redis.io/topics/mass-insert

    语法
    redis-cli –pipe命令通常用于向redis中导入数据,表现形式为从管道接收指定格式数据,写批量入到redis中

    # 注意每个语句后以\r\n结尾
    echo -en '*3\r\n$3\r\nSET\r\n$4\r\nname\r\n$6\r\nzlikun\r\n' | redis-cli --pipe  
    格式说明 
    * *3表示语句有3个参数(包含SET命令本身) 
    * $3表示接下来的参数字符数,如:SET长度为3 
    * SET第一个参数(要执行的命令) 
    * $4表示接下来的参数字符数 
    * name是第二个参数(缓存键) * $6表示接下来的参数字符数 
    * zlikun是第三个参数(缓存值) 
    * ...

    案例
    将MySQL中数据直接导入到Redis中

    mysql -h 127.0.0.1 -uroot -p123456 --skip-column-names --raw < export.data.sql | redis-cli --pipe  
    或分成两步来做
    
    mysql -h 127.0.0.1 -uroot -p123456 --skip-column-names --raw < export.data.sql > export.data.log  
    cat export.data.log | redis-cli --pipe  

    export.data.sql是一个用于生成指定格式数据的查询SQL文件,如:

    -- 生成用户ID:用户名数据,每条记录以\r结尾,否则报错(测试后得出的结论)
    SELECT CONCAT(  
        '*3\r\n'
        ,'$',LENGTH(r_cmd) ,'\r\n'
        ,r_cmd ,'\r\n'
        ,'$',LENGTH(r_key) , '\r\n'
        ,r_key ,'\r\n'
        ,'$',LENGTH(r_val) ,'\r\n'
        ,r_val ,'\r'
    ) AS 'data'
    FROM (  
        SELECT 'SET' AS r_cmd ,CONCAT('user:' ,ID) AS r_key ,NAME AS r_val FROM TBL_USER LIMIT 100
    ) r_tmp ;

    方法2: 逐条执行命令

    目的: 导入  user3  ,  password3 到一个hashset。
    week05-c.txt文件 的内容是:
    
    hset user3 password pwd3
    
    使用命令 cat week05-c.txt | ./redis-cli --pipe  导入
    展开全文
  • 《玩转Redis》系列文章主要讲述Redis的基础及中高级应用。本文是《玩转Redis》系列第...Redis生产环境安全高效导出大量数据 Redis导入导出所有数据 Redis导出指定前缀(指定通配符)数据 Redis生产环境安全高效删除数据

      《玩转Redis》系列文章主要讲述Redis的基础及中高级应用。本文是《玩转Redis》系列第【13】篇,最新系列文章请前往公众号“zxiaofan”(点我点我)查看,或百度搜索“玩转Redis zxiaofan”即可。

    本文关键字:玩转Redis、Redis数据导入、Redis大量插入、Redis数据导出、Redis导出指定通配符、Redis数据删除、Redis批量删除、Redis删除指定前缀key、Redis删除指定通配符key;

    往期精选:《玩转Redis-删除了两百万key,为什么内存依旧未释放?》

    大纲

    • Redis生产环境安全高效导入大量数据
      • 使用shell脚本构造测试数据
      • Redis非集群模式导入大量数据
      • Redis集群模式导入大量数据
    • Redis生产环境安全高效导出大量数据
      • Redis导入导出所有数据
      • Redis导出指定前缀(指定通配符)数据
    • Redis生产环境安全高效删除数据
      • Redis删除已知的指定key
      • Redis删除指定前缀(指定通配符)数据
    • 实用小技巧
      • Redis统计指定通配符key的数量
      • 免输密码连接Redis脚本
      • 思考题:Linux可以设置脚本可执行但不可读吗

    概述:

      本文将模拟生产环境,构造大量测试数据,并完成高效安全的数据导入、数据导出、数据删除。

      众所周知,Redis是单线程的,一旦一个命令发生长时间阻塞,极易导致大量命令阻塞,从而导致生产环境雪崩,所以本文着重强调 【生产环境、安全高效】 操作。

      Let’s Go!

    1、Redis生产环境安全高效导入大量数据

    1.1、使用shell脚本构造测试数据

      linux环境创建dataBuild.sh脚本,此脚本用于生成测试数据,脚本的入参是测试数据的数量,脚本内容如下:

    #!/bin/sh
    # init Test Data.
    # Build By @zxiaofan.com
    
    echo "构造数量:$1";
    rm -f ./testdata.txt;
    touch testdata.txt;
    
    starttime=`date +'%Y-%m-%d %H:%M:%S'`;
    echo "Start: $starttime";
    
    for((i=0; i< $1 ;i++))
    do
    	str='set key-';
    	name=${str}${i}' value'${i};
    	echo  $name>> testdata.txt;
    done
    
    endtime=`date +'%Y-%m-%d %H:%M:%S'`;
    echo "End: $endtime";
    
    start_seconds=$(date --date="$starttime" +%s);
    end_seconds=$(date --date="$endtime" +%s);
    echo "本次运行时间: "$((end_seconds-start_seconds))"s"
    #echo 'show testdata';
    #cat testdata.txt;
    

      建议在linux环境创建脚本,否则可能执行shell脚本时报错$’\r’: command not found。解决方案:vim acc.conf.base,然后执行 “:set fileformat=unix” ,并wq保存即可。

      接下来就可以执行脚本生成测试数据了,此处生成30W数据,脚本执行完毕后当前目录会生成测试数据文件testdata.txt。

    # By @zxiaofan
    
    # 执行脚本生成30W测试数据;
    
    root@xxx:/study/redis# ./dataBuild.sh 300000
    构造数量:300000
    Start: 2020-10-11 17:30:45
    End: 2020-10-11 17:34:54
    本次运行时间: 249s
    
    # 脚本内容示例,30W数据大约有7.5M:
    set key-0 value0
    set key-1 value1
    set key-2 value2
    set key-3 value3
    set key-4 value4
    set key-5 value5
    ...
    
    

    1.2、Redis非集群模式导入大量数据

      Redis官方已经为我们提供了解决方案【mass-insert】:https://redis.io/topics/mass-insert

    Using a normal Redis client to perform mass insertion is not a good idea for a few reasons: the naive approach of sending one command after the other is slow because you have to pay for the round trip time for every command. It is possible to use pipelining, but for mass insertion of many records you need to write new commands while you read replies at the same time to make sure you are inserting as fast as possible.

    Only a small percentage of clients support non-blocking I/O, and not all the clients are able to parse the replies in an efficient way in order to maximize throughput. For all of these reasons the preferred way to mass import data into Redis is to generate a text file containing the Redis protocol, in raw format, in order to call the commands needed to insert the required data.

    翻译过来就是:

    • 每个Redis客户端的命令从执行到返回结果都需要一定的时间;
    • 只有少部分客户端支持异步I/O处理,所以即使使用多个Redis客户端并发插入也不能提高吞吐量;
    • 建议解决方案是:将待插入数据以Redis协议生成文本文件再导入。

      Redis 2.6以上版本,redis-cli支持管道模式 pipe mode(管道模式),该模式专门为执行大规模插入而设计。

      我们来实战一下,管道模式导入命令如下:

    cat testdata.txt | redis-cli --pipe
    

      我们可以在命令前加time指令用于统计命名执行时间。

    # By @zxiaofan
    
    [redis@xxx]$ time cat testdata.txt | ./redis-cli --pipe
    Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
    All data transferred. Waiting for the last reply...
    Last reply received from server.
    errors: 0, replies: 300000
    
    real	0m1.778s
    user	0m0.039s
    sys	0m0.016s
    
    
    # 连上Redis确认数据是否导入成功。
    
    127.0.0.1:6379> get key-0
    "value0"
    127.0.0.1:6379> get key-299999
    "value299999"
    127.0.0.1:6379> get key-300000
    (nil)
    

      30W数据,1.7秒完成导入。需要注意的是,如果 “redis-cli” 命令 没有设置为全局命令,需要先进入到 redis-cli 所在目录,然后执行以下命令:

    # redis-cli 变成了 ./redis-cli
    cat testdata.txt | ./redis-cli --pipe
    

      如果需要指定其他参数,可以如下操作:

    • 指定端口:-p 6379
    • 指定远程主机:-h 192.168.1.1
    • 需要密码认证:-a redisPasword
    # By @zxiaofan
    
    # Redis导入数据完整命令:
    
    cat testdata.txt | ./redis-cli -h 192.168.1.1 -p 6379 -a redisPasword --pipe
    

    1.3、Redis集群模式导入大量数据

      以上数据导入方式针对的是非集群模式,那么集群模式又该如何导入数据呢?

    cat testdata.txt | redis-cli -c --pipe
    

      如上所示,使用“-c”即可启动集群模式,但是由于集群模式存在多个节点,此处极有可能会出现数据处理失败的场景。

      Redis集群模式有16384个hash槽(hash slots),假设集群有3个节点,node1分配 0~5460的槽位,node2分配 5461~10922的槽位,node3分配 10923~16383的槽位。集群中的每个主节点只会操作对应的hash槽,根据“CRC16(key) mod 16384”计算出每个key对应的槽位,然后到槽位对应的Redis节点执行操作即可。

    HASH_SLOT = CRC16(key) mod 16384
    

    注意:

      集群模式下节点可能动态增加或删除,所以执行以上命令时需确保集群节点的稳定性,同时做好结果检查。

    2、Redis生产环境安全高效导出大量数据

    2.1、Redis导入导出所有数据

      这种场景下,可使用Ruby开源工具redis-dump导出,官网:https://github.com/delano/redis-dump。

    # redis-dump使用示例
    
    $ redis-dump -u 127.0.0.1:6371 > db_full.json
    $ redis-dump -u 127.0.0.1:6371 -d 15 > db_db15.json
    
    $ < db_full.json redis-load
    $ < db_db15.json redis-load -d 15
    # OR
    $ cat db_full | redis-load
    $ cat db_db15.json | redis-load -d 15
    
    # 带密码操作
    $ redis-dump -u :password@host:port -d 0 > db_full.json
    $ cat db_full.json | redis-load -u :password@host:port -d 0
    

    2.2、Redis导出指定前缀(指定通配符)数据

      安全高效导出大量数据,生产环境多是需要导出“指定通配符”数据,用于数据或者业务分析,难点是【安全高效导出大量指定通配符数据】,核心在“指定通配符”。面对数据量较大的生产环境,由于Redis是单线程的,所以不能使用阻塞性的操作指令。

    完整命令示例:

    # By @zxiaofan
    
    ./redis-cli -h 192.168.1.1 -p 6379 -a password --raw --scan --pattern '自定义pattern' > out.txt
    

      需要注意的是:这里不能使用“keys”指令,因为此命令是阻塞式的。网上有很多文章写的是 “keys”指令,切忌慎用,不然一不小心就真的是从删库到跑路了。

    2.3、redis-cli命令–raw参数作用

    • 输出数据是原始数据,不包含类型;
    • 输出数据的中文正常显示,显示内容不是编码后的内容;

      redis-cli检测到标准输出是tty(终端)时,返回结果会加类型,比如(integer);
    标准输出不是tty,例如,数据被重定向到管道或者文件时,会自动默认开启–raw选项,便不会增加类型信息。官方说明:https://redis.io/topics/rediscli,查看“Command line usage”模块。

    # 标准输出是tty(终端),注意结果有个(integer):
    $ redis-cli incr mycounter
    (integer) 7
    
    # 标准输出不是tty(重定向到了文件):
    $ redis-cli incr mycounter > /tmp/output.txt
    $ cat /tmp/output.txt
    8
    
    # 加入--raw参数,结果没有(integer)
    $ redis-cli --raw incr mycounter
    9
    

    3、Redis生产环境安全高效删除数据

    3.1、Redis删除已知的指定key

      看了如何快速导入大量数据,其实通过其原理我们不难发现,本质上是执行了Redis协议格式的文本,那么当我们能提前构造好待删除数据的脚本时,也同样可以使用 “–pipe”完成大量数据的高效删除;

    # By @zxiaofan
    
    cat deletedata.txt | redis-cli -c --pipe
    
    # deletedata.txt 数据示例:
    
    del key-0
    del key-1
    del key-2
    del key-3
    del key-4
    del key-5
    del key-6
    del key-7
    del key-8
    del key-9
    ...
    

    3.2、删除指定前缀(指定通配符)数据

    命令如下:

    # By @zxiaofan
    
    ./redis-cli -h 127.0.0.1 -p 6379 -a password --scan --pattern 'key-*' | xargs ./redis-cli -h 127.0.0.1 -p 6379 -a password del {}
    

    输出结果形如:

    Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
    Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
    (integer) 12322
    Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
    (integer) 12326
    

    命令核心是:

    • 【–scan --pattern ‘key-*’】:利用通配符匹配指定key;
    • 【| xargs】:用于传递参数;
    • 【del {}】:接收key并删除;

    Linux xargs 命令:

      xargs(英文全拼: eXtended ARGuments)是给命令传递参数的一个过滤器,也是组合多个命令的一个工具。
    xargs 可以将管道或标准输入(stdin)数据转换成命令行参数,也能够从文件的输出中读取数据。

      xargs 也可以将单行或多行文本输入转换为其他格式,例如多行变单行,单行变多行。

      xargs 默认的命令是 echo,这意味着通过管道传递给 xargs 的输入将会包含换行和空白,不过通过 xargs 的处理,换行和空白将被空格取代。

      xargs 是一个强有力的命令,它能够捕获一个命令的输出,然后传递给另外一个命令。

      由于很多命令不支持|管道来传递参数,所以就有了 xargs 命令。

    以上命令介绍源自:https://www.runoob.com。

    xargs 一般是和管道一起使用,命令格式:

    somecommand |xargs -item  command
    

    4、实用小技巧

    4.1、Redis统计指定通配符key的数量

      前面我们教会大家在生产环境导出或者删除数据,那我们如何验证数据量是否正确呢?

    借助“ | wc -l”即可:

    # By @zxiaofan
    # 命令的核心是“ | wc -l”
    
    >./redis-cli -h 127.0.0.1 -p 6379 -a password --scan --pattern 'id:*' | wc -l
    66666
    

    4.2、免输密码连接Redis脚本

      在生产环境每次操作都输入密码肯定是非常不安全、非常low的操作,那么我们有什么方式可以不用输入密码就连接redis吗。

      将以下内容保存于脚本“openredis.sh”中,使用 “chmod u+x openredis.sh” 授予脚本可执行权限。此处的免输密码实际上是将密码保存到脚本中,所以请注意此脚本的安全性。

    # By @zxiaofan
    # 连接Redis脚本,如有密码,请将脚本中的“password”换成实际密码
    # 第一个参数是端口号,第二个参数是待执行命令;
    # 如果需要指定主机,则加入 -h 即可;
    
    Port=$1
    CMD=$2
    /usr/local/bin/redis-cli -p $Port -a password $CMD
    

    使用方式:

    # 使用 openredis.sh 查看Clients连接信息
    
    
    [redis@xxx redis]# ./openredis.sh 6379 'info  Clients'
    Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
    # Clients
    connected_clients:1
    client_recent_max_input_buffer:51
    client_recent_max_output_buffer:0
    blocked_clients:0
    
    # 使用 openredis.sh 存入及查询数据
    
    [redis@redis]# ./openredis.sh 6379 'set key1 v1 '
    Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
    OK
    [redis@redis]# ./openredis.sh 6379 'get key1 '
    Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
    "v1"
    
    # 使用 openredis.sh 统计数据
    # 注意包裹指令的是单引号,'--scan --pattern 'k*''
    
    [redis@redis]# ./openredis.sh 6379 '--scan --pattern 'id:*'' | wc -l
    66666
    
    # 使用 openredis.sh 导入数据
    # testdata.txt 存放的是 大量“set key-0 value0”数据
    
    cat testdata.txt | ./openredis.sh 6379 --pipe
    
    # 使用 openredis.sh 导出数据
    
    [redis@redis]# ./openredis.sh 6379 '--scan --pattern 'id:*'' > out.txt
    66666
    

    4.3、思考题:Linux可以设置脚本可执行但不可读吗

      “免输密码连接Redis脚本”我们提到,密码保存于脚本“openredis.sh”中,那么我们可以将本“openredis.sh”权限设置为“可执行但不可读”吗,以此来保证脚本中私密信息的安全。

    # 设置权限为:所有者可读可写可执行;其他用户仅可执行;
    
    chmod 711 openredis.sh
    
    # redis用户仅有可执行权限
    
    [redis@redis]$ ll openredis.sh 
    -rwx--x--x 1 root root 186 Nov  8 22:20 openredis.sh
    
    [redis@redis]$ cat openredis.sh 
    cat: openredis.sh: Permission denied
    
    [redis@redis]$ ./openredis.sh 6379 '--scan --pattern 'id:*'' | wc -l
    bash: ./openredis.sh: Permission denied
    0
    

      从上面的执行情况看,当脚本设置为“可执行但不可读”时,该脚本是无法执行的,因为“解释器还需要读取脚本”。

    知识延伸:

      如果权限设置为“可执行但不可读”的对象不是脚本,而是一个二进制文件,此时该二进制文件是可执行的。

    # 将JDK的bin目录下的java文件设置权限为仅可执行,实际发现是可执行的;
    
    [admin@xxx bin]$ ll java
    ---x--x--x 1 10 143 7734 Dec 13  2016 java
    [admin@xxx bin]$ java -version
    java version "1.8.0_121"
    Java(TM) SE Runtime Environment (build 1.8.0_121-b13)
    Java HotSpot(TM) 64-Bit Server VM (build 25.121-b13, mixed mode)
    

      那脚本有什么方式可以实现“可执行但不可读”吗?欢迎留言探讨。

    【玩转Redis系列文章 近期精选 @zxiaofan】

    《玩转Redis-删除了两百万key,为什么内存依旧未释放?》

    《玩转Redis-Redis中布隆过滤器的使用及原理》

    《玩转Redis-HyperLogLog原理探索》

    《玩转Redis-HyperLogLog统计微博日活月活》

    《玩转Redis-京东签到领京豆如何实现》


    扫码关注公众号查阅最新文章。

    Life is all about choices!

    将来的你一定会感激现在拼命的自己!

    CSDN】【GitHub】【OSCHINA】【掘金】【语雀】【微信公众号
    欢迎订阅zxiaofan的微信公众号,扫码或直接搜索zxiaofan


    展开全文
  • Java之Excel导出工具类使用教程

    千次阅读 多人点赞 2019-03-20 17:34:02
    本工具类经过PostMan和web页面严格测试可用,经过了多个版本迭代优化,可以直接使用,也方便大家根据自己的业务需求,修改定制自己的导出工具。 市面上有很多封装好的导出工具(如:阿里的easyExcel,GitHub上xxl-...

    前言:

    本工具类经过PostMan和前端页面严格测试可用,经过了多个版本迭代优化,可以直接使用,也方便大家根据自己的业务需求,修改定制自己的导出工具。
    市面上有很多封装好的导出工具(如:阿里的easyExcel,GitHub上xxl-excel等),但如果直接引用依赖,扩展性和定制性比较差,所以博主通过apache.poi,自己实现一款Excel导出工具,方便定制使用。本工具类支持SpringMVC等主流的Java框架,支持RESTful接口,代码全部通过测试。

    一.功能介绍:

    • 支持多个Excel一次性导出并压缩成zip
    • 支持List<Entity>实体类导出
    • 支持List<Map>列数不固定的数据导出
    • 支持多Sheet页导出
    • 支持导出文件名为URL编码,防止乱码
    • 支持文件名、sheet名特殊字符自动替换
    • 支持Excel2007以上版本
    • 支持有数据的文本框描边
    • 支持表头字体加大
    • 表头数据单元格内换行
    • 支持标题栏
    • 支持选择null空字段是否导出
    • 支持将日期格式数据转换为自定义格式的时间字符串
    • 支持单元格类型区分数值、字符串,单元格对齐方式不同对待
    • 支持将Excel导出到HttpServletResponse流中(用做对外提供接口)

    二.导出工具类源码:

    注: 此代码可直接在项目中使用。

    package com.xxx.util;
    
    
    import java.io.*;
    import java.lang.reflect.Field;
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    import java.net.URLEncoder;
    import java.text.SimpleDateFormat;
    import java.util.ArrayList;
    import java.util.Collection;
    import java.util.Date;
    import java.util.Iterator;
    import java.util.List;
    import java.util.ListIterator;
    import java.util.Map;
    import java.util.regex.Matcher;
    import java.util.regex.Pattern;
    
    import javax.servlet.ServletOutputStream;
    import javax.servlet.http.HttpServletResponse;
    
    import lombok.extern.slf4j.Slf4j;
    import org.apache.commons.io.FileUtils;
    import org.apache.poi.ss.usermodel.BorderStyle;
    import org.apache.poi.ss.usermodel.HorizontalAlignment;
    import org.apache.poi.ss.util.CellRangeAddress;
    import org.apache.poi.xssf.usermodel.*;
    
    /**
     * 导出Excel工具类
     *
     * @author 大脑补丁
     *
     */
    @Slf4j
    public class ExportExcelUtil<T> {
    
    	// 新版Excel文件后缀
    	private static final String EXCEL_SUFFIX = ".xlsx";
    
    	/**
    	 * 导出多Sheet的Excel到HttpServletResponse流中
    	 *
    	 * @param fileName
    	 *            另存为文件名
    	 * @param sheetNames
    	 *            工作簿中的多张Sheet工作表的名称列表
    	 * @param titleName
    	 *            表格的标题名称(没有标题,则传null)
    	 * @param headers
    	 *            表头列表
    	 * @param dataList
    	 *            要导出的数据源
    	 * @param HttpServletResponse
    	 *            Http响应
    	 * @param pattern
    	 *            时间类型数据的格式,默认UTC格式
    	 * @param isExportNullField
    	 *            空字段是否导出(true:导出,false:不导出)
    	 * @see 导出Excel格式:表头内容居中,字体略大于正文,颜色深灰色。正文文本类型对齐方式居左,数字类型对齐方式居右。仅有数据* 的单元格,有边框环绕,实体类的属性顺序即为表头顺序
    	 */
    	public static <T> void exportExcel(String fileName, List<String> sheetNames, String titleName, List<String> headers,
    			List<List<T>> dataLists, HttpServletResponse response, String pattern, boolean isExportNullField) {
    		XSSFWorkbook wb = exportAllExcel(sheetNames, titleName, headers, dataLists, pattern, isExportNullField);
    		setResponseHeader(response, replaceSpecStr(fileName));
    		ServletOutputStream out = null;
    		try {
    			out = response.getOutputStream();
    			wb.write(out);
    		} catch (IOException e) {
    			e.printStackTrace();
    		} finally {
    			try {
    				out.flush();
    				out.close();
    				wb.close();
    			} catch (IOException e) {
    				e.printStackTrace();
    			}
    		}
    	}
    
    	/**
    	 * 导出多Sheet动态列的Excel到HttpServletResponse流中
    	 *
    	 * @param fileName          另存为文件名
    	 * @param sheetNames        工作簿中的多张Sheet工作表的名称列表
    	 * @param titleName         表格的标题名称(没有标题,则传null)
    	 * @param headersList       多个sheet页的表头列表
    	 * @param dataLists         要导出的数据源
    	 * @param response          Http响应
    	 * @param pattern           时间类型数据的格式,默认UTC格式
    	 * @param isExportNullField 空字段是否导出(true:导出,false:不导出)
    	 */
    	public static void exportDynamicExcel(String fileName, List<String> sheetNames, String titleName,
    			List<List<String>> headersList, List<List<Map<String, Object>>> dataLists, HttpServletResponse response,
    			String pattern, boolean isExportNullField) {
    		XSSFWorkbook wb = exportDynamicExcelImpl(sheetNames, titleName, headersList, dataLists, pattern,
    				isExportNullField);
    		setResponseHeader(response, replaceSpecStr(fileName));
    		ServletOutputStream out = null;
    		try {
    			out = response.getOutputStream();
    			wb.write(out);
    		} catch (IOException e) {
    			e.printStackTrace();
    		} finally {
    			try {
    				out.flush();
    				out.close();
    				wb.close();
    			} catch (IOException e) {
    				e.printStackTrace();
    			}
    		}
    	}
    
    
    	/**
    	 * 导出多个Excel并压缩到zip包中
    	 *
    	 * @param response          HttpServletResponse
    	 * @param excelList         多个Excel数据源
    	 * @param filePath          导出路径(文件夹)
    	 * @param zipName           压缩包名,如(ABC)
    	 * @param pattern           Excel中的时间格式
    	 * @param isExportNullField 是否导出空单元格(空单元格显示为"--")
    	 */
    	public static void exportExcelsToZip(HttpServletResponse response, List<Map<String, Object>> excelList,
    			String filePath, String zipName, String pattern, boolean isExportNullField) {
    		setZipResponseHeader(response, replaceSpecStr(zipName), true, ".zip");
    		long timestamp = System.nanoTime();
    		// 本次导出,生成一个excel文件的上级文件夹
    		String targetPath = filePath + File.separator + "temp" + File.separator + timestamp + File.separator + zipName;
    		for (Map<String, Object> excelMap : excelList) {
    			XSSFWorkbook wb = exportDynamicExcelImpl((List<String>) excelMap.get("sheetNames"), null,
    					(List<List<String>>) excelMap.get("headers"),
    					(List<List<Map<String, Object>>>) excelMap.get("dataLists"), pattern, isExportNullField);
    
    			// Excel输出路径示例:/usr/local/tomcat/excels/temp/1649149721911900/报表20210416/xx名称-20210416.xlsx
    			String excelPath =
    					targetPath + File.separator + replaceSpecStr(excelMap.get("fileName").toString()) + EXCEL_SUFFIX;
    			try {
    				File file = new File(excelPath);
    				FileUtils.forceMkdirParent(file);
    				FileOutputStream outputStream = new FileOutputStream(file);
    				wb.write(outputStream);
    				outputStream.close();
    				wb.close();
    				log.info("成功导出Excel:" + file.getName());
    			} catch (IOException e) {
    				log.warn("导出Excel时,创建文件出错");
    				e.printStackTrace();
    			} finally {
    				try {
    					wb.close();
    				} catch (IOException e) {
    					e.printStackTrace();
    				}
    			}
    		}
    		// 将所有Excel压缩,并写入到response中
    		ServletOutputStream out = null;
    		FileInputStream fileInputStream = null;
    		try {
    			// zip输出路径示例:/usr/local/tomcat/excels/temp/1649149721911900/日报20210416.zip
    			String outputPath =
    					filePath + File.separator + "temp" + File.separator + timestamp + File.separator + zipName
    							+ ZipUtils.ZIP_SUFFIX;
    			ZipUtils.compress(targetPath, outputPath);
    			File zipFile = new File(outputPath);
    			fileInputStream = new FileInputStream(zipFile);
    			BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream);
    			out = response.getOutputStream();
    			byte[] bytes = new byte[bufferedInputStream.available()];
    			// 必须加此行代码,否则下载的Zip包损坏
    			int read = bufferedInputStream.read(bytes);
    			out.write(bytes);
    		} catch (IOException e) {
    			e.printStackTrace();
    		} finally {
    			try {
    				out.flush();
    				out.close();
    				fileInputStream.close();
    			} catch (IOException e) {
    				e.printStackTrace();
    			}
    		}
    	}
    
    	/**
    	 * 设置响应的类型、编码和文件名称
    	 *
    	 * @param response
    	 * @param fileName
    	 */
    	public static void setResponseHeader(HttpServletResponse response, String fileName) {
    		try {
    			response.reset();
    			response.setContentType("application/msexcel");// 设置生成的文件类型
    			response.setCharacterEncoding("UTF-8");// 设置文件头编码方式和文件名
    			// 在浏览器中测试生效,postman中文件名为response,无法修改
    			response.setHeader("Content-disposition", "attachment;filename="
    					.concat(String.valueOf(URLEncoder.encode(replaceSpecStr(fileName) + EXCEL_SUFFIX, "UTF-8"))));
    			// 此设置,可保证web端可以取到文件名
    			response.setHeader("Access-Control-Expose-Headers", "Content-Disposition");
    		} catch (Exception ex) {
    			ex.printStackTrace();
    		}
    	}
    
    	/**
    	 * 设置Zip下载的响应的类型、编码和文件名称
    	 *
    	 * @param response
    	 * @param fileName
    	 * @param urlEncode
    	 * @param zipSuffix
    	 */
    	public static void setZipResponseHeader(HttpServletResponse response, String fileName, boolean urlEncode,
    			String zipSuffix) {
    		try {
    			if (zipSuffix == null) {
    				zipSuffix = ".zip";
    			}
    			String downloadName = urlEncode == true ?
    					String.valueOf(URLEncoder.encode(replaceSpecStr(fileName) + zipSuffix, "UTF-8")) :
    					String.valueOf(replaceSpecStr(fileName) + zipSuffix);
    			response.reset();
    			// 设置生成的文件类型
    			response.setContentType("application/x-zip-compressed");
    			//response.setContentType("application/octet-stream");
    			// 设置文件头编码方式和文件名
    			response.setCharacterEncoding("UTF-8");
    			response.setHeader("Content-Disposition", "attachment;filename=".concat(downloadName));
    			response.setHeader("Access-Control-Expose-Headers", "Content-Disposition");
    		} catch (Exception ex) {
    			ex.printStackTrace();
    		}
    	}
    
    	/**
    	 * 设置响应的类型、编码和文件名称
    	 *
    	 * @param response
    	 * @param fileName
    	 */
    	public static void setResponseHeader(HttpServletResponse response, String fileName, boolean urlEncode) {
    		try {
    			String downloadName = urlEncode == true
    					? String.valueOf(URLEncoder.encode(replaceSpecStr(fileName) + EXCEL_SUFFIX, "UTF-8"))
    					: String.valueOf(replaceSpecStr(fileName) + EXCEL_SUFFIX);
    			response.reset();
    			response.setContentType("application/msexcel");// 设置生成的文件类型
    			response.setCharacterEncoding("UTF-8");// 设置文件头编码方式和文件名
    			// 在浏览器中测试生效,postman中文件名为response,无法修改
    			response.setHeader("Content-Disposition", "attachment;filename=".concat(downloadName));
    			response.setHeader("Access-Control-Expose-Headers", "Content-Disposition");
    		} catch (Exception ex) {
    			ex.printStackTrace();
    		}
    	}
    
    	/**
    	 * 多Sheet导出实现
    	 *
    	 * @param sheetNames
    	 * @param titleName
    	 * @param headers
    	 * @param dataLists
    	 * @param pattern
    	 * @param isExportNullField
    	 * @return
    	 */
    	private static <T> XSSFWorkbook exportAllExcel(List<String> sheetNames, String titleName, List<String> headers,
    			List<List<T>> dataLists, String pattern, boolean isExportNullField) {
    		// 创建一个工作薄
    		XSSFWorkbook workbook = new XSSFWorkbook();
    		for (int i = 0; i < dataLists.size(); i++) {
    			// 创建一个工作表
    			XSSFSheet sheet = workbook.createSheet(replaceSpecStr(sheetNames.get(i)));
    			// 设置单元格列宽度为16个字节
    			sheet.setDefaultColumnWidth((short) 16);
    			// 创建表头样式
    			XSSFCellStyle headersStyle = workbook.createCellStyle();
    			headersStyle.setBorderTop(BorderStyle.THIN);
    			headersStyle.setBorderBottom(BorderStyle.THIN);
    			headersStyle.setBorderLeft(BorderStyle.THIN);
    			headersStyle.setBorderRight(BorderStyle.THIN);
    			// 表头内容对齐方式:居中
    			headersStyle.setAlignment(HorizontalAlignment.CENTER);
    			XSSFFont headersFont = workbook.createFont();
    			// 设置字体格式
    			headersFont.setColor(new XSSFColor(java.awt.Color.DARK_GRAY));
    			headersFont.setFontHeightInPoints((short) 14);
    			// 表头样式应用生效
    			headersStyle.setFont(headersFont);
    			XSSFCellStyle dataSetStyle = workbook.createCellStyle();
    			// 正文单元格边框样式
    			dataSetStyle.setBorderBottom(BorderStyle.THIN);
    			dataSetStyle.setBorderRight(BorderStyle.THIN);
    			dataSetStyle.setBorderLeft(BorderStyle.THIN);
    			// 数据内容对齐方式:居左
    			// dataSetStyle.setAlignment(HorizontalAlignment.CENTER_SELECTION);
    			XSSFFont dataSetFont = workbook.createFont();
    			// 正文字体颜色
    			dataSetFont.setColor(new XSSFColor(java.awt.Color.BLACK));
    			// 为正文设置样式
    			dataSetStyle.setFont(dataSetFont);
    			if (titleName != null && titleName != "") {
    				XSSFCellStyle titleStyle = workbook.createCellStyle();
    				// 将首行合并居中作为标题栏
    				sheet.addMergedRegion(new CellRangeAddress(0, 0, 0, headers.size() - 1));
    				XSSFFont titleFont = workbook.createFont();
    				// 设置标题字体大小
    				titleFont.setFontHeightInPoints((short) 20);
    				// 设置标题字体样式
    				titleStyle.setFont(titleFont);
    				// 创建标题行并设置样式
    				XSSFRow titleRow = sheet.createRow(0);
    				XSSFCell titleCell = titleRow.createCell(0);
    				titleCell.setCellStyle(titleStyle);
    				titleCell.setCellValue(titleName);
    			}
    			int index = titleName == null || titleName.equals("") ? 0 : 1;
    			// 创建表头并设置样式
    			XSSFRow row = sheet.createRow(index);
    			for (short j = 0; j < headers.size(); j++) {
    				XSSFCell cell = row.createCell(j);
    				cell.setCellStyle(headersStyle);
    				XSSFRichTextString text = new XSSFRichTextString(headers.get(j));
    				cell.setCellValue(text);
    			}
    			// 导出正文数据,并设置其样式
    			Iterator<?> it = dataLists.get(i).iterator();
    			while (it.hasNext()) {
    				index++;
    				row = sheet.createRow(index);
    				Object entity = it.next();
    				// 利用反射,根据实体类属性的先后顺序,动态调用其getXxx()方法,得到属性值
    				Field[] fields = entity.getClass().getDeclaredFields();
    				for (short k = 0; k < fields.length; k++) {
    					XSSFCell cell = row.createCell(k);
    					Field field = fields[k];
    					String fieldName = field.getName();
    					String getMethodName = "get" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
    					try {
    						@SuppressWarnings("rawtypes")
    						Class entityClass = entity.getClass();
    						@SuppressWarnings("unchecked")
    						Method getMethod = entityClass.getMethod(getMethodName, new Class[] {});
    						Object value = getMethod.invoke(entity, new Object[] {});
    						String textValue = null;
    						// 如果是时间类型,格式化
    						if (value instanceof Date) {
    							Date date = (Date) value;
    							pattern = pattern == null || pattern.equals("") ? "yyyy-MM-dd'T'HH:mm:ss'Z'" : pattern;
    							SimpleDateFormat sdf = new SimpleDateFormat(pattern);
    							textValue = sdf.format(date);
    						} else {
    							// 若字段为空且允许导出空字段,则将null导出为""
    							textValue = value == null && isExportNullField ? "" : value.toString();
    						}
    						if (!textValue.equals("")) {
    							// 有数据时边框环绕
    							cell.setCellStyle(dataSetStyle);
    							// 正则判断是否为数值
    							Pattern p = Pattern.compile("^\\d+(\\.\\d+)?$");
    							Matcher matcher = p.matcher(textValue);
    							if (matcher.matches()) {
    								// 是数字当作double处理,整型也不会补充小数点
    								cell.setCellValue(Double.parseDouble(textValue));
    							} else {
    								// 不是数字类型作为文本输出
    								cell.setCellValue(textValue);
    							}
    						}
    					} catch (SecurityException e) {
    						e.printStackTrace();
    					} catch (NoSuchMethodException e) {
    						e.printStackTrace();
    					} catch (IllegalArgumentException e) {
    						e.printStackTrace();
    					} catch (IllegalAccessException e) {
    						e.printStackTrace();
    					} catch (InvocationTargetException e) {
    						e.printStackTrace();
    					}
    				}
    			}
    		}
    		return workbook;
    	}
    
    	/**
    	 * 多Sheet导出动态列到Excel实现
    	 *
    	 * @param sheetNames
    	 * @param titleName
    	 * @param headersList       多个Sheet页的表头
    	 * @param dataLists
    	 * @param pattern           时间格式
    	 * @param isExportNullField 是否导出空字段
    	 * @return
    	 */
    	private static XSSFWorkbook exportDynamicExcelImpl(List<String> sheetNames, String titleName,
    			List<List<String>> headersList, List<List<Map<String, Object>>> dataLists, String pattern,
    			boolean isExportNullField) {
    		// 创建一个工作薄
    		XSSFWorkbook workbook = new XSSFWorkbook();
    		for (int i = 0; i < dataLists.size(); i++) {
    			// 创建一个工作表
    			XSSFSheet sheet = workbook.createSheet(replaceSpecStr(sheetNames.get(i)));
    			// 设置单元格列宽度为16个字节
    			sheet.setDefaultColumnWidth((short) 16);
    			// 创建表头样式
    			XSSFCellStyle headersStyle = workbook.createCellStyle();
    			headersStyle.setBorderTop(BorderStyle.THIN);
    			headersStyle.setBorderBottom(BorderStyle.THIN);
    			headersStyle.setBorderLeft(BorderStyle.THIN);
    			headersStyle.setBorderRight(BorderStyle.THIN);
    			// 表头内容对齐方式:居中
    			headersStyle.setAlignment(HorizontalAlignment.CENTER);
    			XSSFFont headersFont = workbook.createFont();
    			// 设置字体格式
    			headersFont.setColor(new XSSFColor(java.awt.Color.DARK_GRAY, new DefaultIndexedColorMap()));
    			headersFont.setFontHeightInPoints((short) 12);
    			// 表头样式应用生效
    			headersStyle.setFont(headersFont);
    			// 设置单元格内内容换行
    			headersStyle.setWrapText(true);
    			XSSFCellStyle dataSetStyle = workbook.createCellStyle();
    			// 正文单元格边框样式
    			dataSetStyle.setBorderBottom(BorderStyle.THIN);
    			dataSetStyle.setBorderRight(BorderStyle.THIN);
    			dataSetStyle.setBorderLeft(BorderStyle.THIN);
    			// 数据内容对齐方式:居左
    			// dataSetStyle.setAlignment(HorizontalAlignment.CENTER_SELECTION);
    			XSSFFont dataSetFont = workbook.createFont();
    			// 正文字体颜色
    			dataSetFont.setColor(new XSSFColor(java.awt.Color.BLACK, new DefaultIndexedColorMap()));
    			// 为正文设置样式
    			dataSetStyle.setFont(dataSetFont);
    			// 获取当前Sheet页的表头
    			List<String> headers = headersList.get(i);
    			if (titleName != null && titleName != "") {
    				XSSFCellStyle titleStyle = workbook.createCellStyle();
    				// 将首行合并居中作为标题栏
    				sheet.addMergedRegion(new CellRangeAddress(0, 0, 0, headers.size() - 1));
    				XSSFFont titleFont = workbook.createFont();
    				// 设置标题字体大小
    				titleFont.setFontHeightInPoints((short) 20);
    				// 设置标题字体样式
    				titleStyle.setFont(titleFont);
    				// 创建标题行并设置样式
    				XSSFRow titleRow = sheet.createRow(0);
    				XSSFCell titleCell = titleRow.createCell(0);
    				titleCell.setCellStyle(titleStyle);
    				titleCell.setCellValue(titleName);
    			}
    			int index = titleName == null || titleName.equals("") ? 0 : 1;
    			// 创建表头并设置样式
    			XSSFRow row = sheet.createRow(index);
    			for (short j = 0; j < headers.size(); j++) {
    				XSSFCell cell = row.createCell(j);
    				cell.setCellStyle(headersStyle);
    				XSSFRichTextString text = new XSSFRichTextString(headers.get(j));
    				cell.setCellValue(text);
    			}
    			// 导出正文数据,并设置其样式
    			ListIterator<Map<String, Object>> it = dataLists.get(i).listIterator();
    			while (it.hasNext()) {
    				try {
    					index++;
    					row = sheet.createRow(index);
    					Map<String, Object> map = it.next();
    					headers = new ArrayList<String>(map.keySet());
    					List<Object> values = new ArrayList<Object>(map.values());
    					for (int k = 0; k < map.keySet().size(); k++) {
    						try {
    							XSSFCell cell = row.createCell(k);
    							cell.setCellStyle(dataSetStyle);
    							String textValue = null;
    							Object value = values.get(k);
    							// 如果是时间类型,格式化
    							if (value instanceof Date) {
    								Date date = (Date) value;
    								pattern = pattern == null || pattern.equals("") ? "yyyy-MM-dd'T'HH:mm:ss'Z'" : pattern;
    								SimpleDateFormat sdf = new SimpleDateFormat(pattern);
    								textValue = sdf.format(date);
    							} else {
    								// 若字段为空且用户允许导出空字段,则将null导出为"--"
    								textValue = value == null && isExportNullField ? "--" : value.toString();
    							}
    							if (!textValue.equals("")) {
    								// 有数据时边框环绕
    								//cell.setCellStyle(dataSetStyle);
    								// 正则判断是否为数值
    								Pattern p = Pattern.compile("^\\d+(\\.\\d+)?$");
    								Matcher matcher = p.matcher(textValue);
    								if (matcher.matches()) {
    									// 是数字当作double处理,整型也不会补充小数点
    									cell.setCellValue(Double.parseDouble(textValue));
    								} else {
    									// 不是数字类型作为文本输出
    									cell.setCellValue(textValue);
    								}
    							}
    						} catch (Exception e) {
    							e.printStackTrace();
    						}
    					}
    				} catch (Exception e) {
    					e.printStackTrace();
    				}
    			}
    		}
    		return workbook;
    
    	}
    
    	/**
    	 * 导出Excel到HttpServletResponse流中
    	 *
    	 * @param fileName
    	 *            另存为文件名
    	 * @param sheetName
    	 *            工作簿中的一张工作表的名称
    	 * @param titleName
    	 *            表格的标题名称(无需创建,则传null)
    	 * @param headers
    	 *            表头列表
    	 * @param dataList
    	 *            要导出的数据源
    	 * @param response
    	 *            Http响应
    	 * @param pattern
    	 *            时间类型数据的格式,默认UTC格式
    	 * @param isExportNullField
    	 *            空字段是否导出(true:导出,false:不导出)
    	 */
    	@Deprecated
    	public static void exportOneSheetExcel(String fileName, String sheetName, String titleName, List<String> headers,
    			Collection<Collection<? extends Object>> dataList, HttpServletResponse response, String pattern,
    			boolean isExportNullField) {
    		XSSFWorkbook wb = exportExcelToWorkbook(sheetName, titleName, headers, dataList, pattern, isExportNullField);
    		setResponseHeader(response, replaceSpecStr(fileName));
    		ServletOutputStream out = null;
    		try {
    			out = response.getOutputStream();
    			wb.write(out);
    		} catch (IOException e) {
    			e.printStackTrace();
    		} finally {
    			try {
    				out.flush();
    				out.close();
    				wb.close();
    			} catch (IOException e) {
    				e.printStackTrace();
    			}
    		}
    	}
    
    	/**
    	 * 导出核心实现
    	 *
    	 * @param sheetName
    	 * @param titleName
    	 * @param headers
    	 * @param dataList
    	 * @param pattern
    	 * @param isExportNullField
    	 * @return XSSFWorkbook
    	 */
    	@Deprecated
    	private static XSSFWorkbook exportExcelToWorkbook(String sheetName, String titleName, List<String> headers,
    			Collection<?> dataList, String pattern, boolean isExportNullField) {
    		// 创建一个工作薄
    		XSSFWorkbook workbook = new XSSFWorkbook();
    		// 创建一个工作表
    		XSSFSheet sheet = workbook.createSheet(replaceSpecStr(sheetName));
    		// 设置单元格列宽度为16个字节
    		sheet.setDefaultColumnWidth((short) 16);
    		// 创建表头样式
    		XSSFCellStyle headersStyle = workbook.createCellStyle();
    		headersStyle.setBorderTop(BorderStyle.THIN);
    		headersStyle.setBorderBottom(BorderStyle.THIN);
    		headersStyle.setBorderLeft(BorderStyle.THIN);
    		headersStyle.setBorderRight(BorderStyle.THIN);
    		// 表头内容对齐方式:居中
    		headersStyle.setAlignment(HorizontalAlignment.CENTER);
    		XSSFFont headersFont = workbook.createFont();
    		// 设置字体格式
    		headersFont.setColor(new XSSFColor(java.awt.Color.DARK_GRAY));
    		headersFont.setFontHeightInPoints((short) 14);
    		// 表头样式应用生效
    		headersStyle.setFont(headersFont);
    		XSSFCellStyle dataSetStyle = workbook.createCellStyle();
    		// 正文单元格边框样式
    		dataSetStyle.setBorderBottom(BorderStyle.THIN);
    		dataSetStyle.setBorderRight(BorderStyle.THIN);
    		dataSetStyle.setBorderLeft(BorderStyle.THIN);
    		// 数据内容对齐方式:居左
    		// dataSetStyle.setAlignment(HorizontalAlignment.CENTER_SELECTION);
    		XSSFFont dataSetFont = workbook.createFont();
    		// 正文字体颜色
    		dataSetFont.setColor(new XSSFColor(java.awt.Color.BLACK));
    		// 为正文设置样式
    		dataSetStyle.setFont(dataSetFont);
    		if (titleName != null && titleName != "") {
    			XSSFCellStyle titleStyle = workbook.createCellStyle();
    			// 将首行合并居中作为标题栏
    			sheet.addMergedRegion(new CellRangeAddress(0, 0, 0, headers.size() - 1));
    			XSSFFont titleFont = workbook.createFont();
    			// 设置标题字体大小
    			titleFont.setFontHeightInPoints((short) 20);
    			// 设置标题字体样式
    			titleStyle.setFont(titleFont);
    			// 创建标题行并设置样式
    			XSSFRow titleRow = sheet.createRow(0);
    			XSSFCell titleCell = titleRow.createCell(0);
    			titleCell.setCellStyle(titleStyle);
    			titleCell.setCellValue(titleName);
    		}
    		int index = titleName == null || titleName.equals("") ? 0 : 1;
    		// 创建表头并设置样式
    		XSSFRow row = sheet.createRow(index);
    		for (short i = 0; i < headers.size(); i++) {
    			XSSFCell cell = row.createCell(i);
    			cell.setCellStyle(headersStyle);
    			XSSFRichTextString text = new XSSFRichTextString(headers.get(i));
    			cell.setCellValue(text);
    		}
    		// 导出正文数据,并设置其样式
    		Iterator<?> it = dataList.iterator();
    		while (it.hasNext()) {
    			index++;
    			row = sheet.createRow(index);
    			Object entity = it.next();
    			// 利用反射,根据实体类属性的先后顺序,动态调用其getXxx()方法,得到属性值
    			Field[] fields = entity.getClass().getDeclaredFields();
    			for (short i = 0; i < fields.length; i++) {
    				XSSFCell cell = row.createCell(i);
    				Field field = fields[i];
    				String fieldName = field.getName();
    				String getMethodName = "get" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
    				try {
    					@SuppressWarnings("rawtypes")
    					Class entityClass = entity.getClass();
    					@SuppressWarnings("unchecked")
    					Method getMethod = entityClass.getMethod(getMethodName, new Class[] {});
    					Object value = getMethod.invoke(entity, new Object[] {});
    					String textValue = null;
    					// 如果是时间类型,格式化
    					if (value instanceof Date) {
    						Date date = (Date) value;
    						pattern = pattern == null || pattern.equals("") ? "yyyy-MM-dd'T'HH:mm:ss'Z'" : pattern;
    						SimpleDateFormat sdf = new SimpleDateFormat(pattern);
    						textValue = sdf.format(date);
    					} else {
    						// 若字段为空且允许导出空字段,则将null导出为""
    						textValue = value == null && isExportNullField ? "" : value.toString();
    					}
    					if (!textValue.equals("")) {
    						// 有数据时边框环绕
    						cell.setCellStyle(dataSetStyle);
    						// 正则判断是否为数值
    						Pattern p = Pattern.compile("^\\d+(\\.\\d+)?$");
    						Matcher matcher = p.matcher(textValue);
    						if (matcher.matches()) {
    							// 是数字当作double处理,整型也不会补充小数点
    							cell.setCellValue(Double.parseDouble(textValue));
    						} else {
    							// 不是数字类型作为文本输出
    							cell.setCellValue(textValue);
    						}
    					}
    				} catch (SecurityException e) {
    					e.printStackTrace();
    				} catch (NoSuchMethodException e) {
    					e.printStackTrace();
    				} catch (IllegalArgumentException e) {
    					e.printStackTrace();
    				} catch (IllegalAccessException e) {
    					e.printStackTrace();
    				} catch (InvocationTargetException e) {
    					e.printStackTrace();
    				}
    			}
    		}
    		return workbook;
    	}
    
    	/**
    	 * 正则替换所有特殊字符
    	 *
    	 * @param orgStr
    	 * @return
    	 */
    	public static String replaceSpecStr(String orgStr) {
    		if (null != orgStr && !"".equals(orgStr.trim())) {
    			String regEx = "[\\\\|:/\"<>?*\\[\\] ]";
    			Pattern p = Pattern.compile(regEx);
    			Matcher m = p.matcher(orgStr);
    			return m.replaceAll("_");
    		}
    		return null;
    	}
    
    }
    
    

    压缩工具类:

    注: 如果不需要导出后,将Excel压缩成Zip包功能,可以不导出此类。

    package com.xxx.util;
    
    /**
     * @description: 压缩工具类
     * @author: 大脑补丁
     * @create: 2021-04-16 15:51
     */
    
    import java.io.*;
    import java.util.Enumeration;
    import java.util.zip.*;
    
    public class ZipUtils {
    	static final int BUFFER = 8192;
    	static final String ZIP_SUFFIX = ".zip";
    
    	/**
    	 * 压缩文件
    	 *
    	 * @param srcPath 要压缩的文件或文件夹(如:/usr/local/目录)
    	 * @param zipPath 输出的zip文件路径(如:/usr/local/abc.zip)
    	 * @throws IOException
    	 */
    	public static void compress(String srcPath, String zipPath) throws IOException {
    		File srcFile = new File(srcPath);
    		File zipFile = new File(zipPath);
    		if (!srcFile.exists()) {
    			throw new FileNotFoundException(srcPath + "不存在!");
    		}
    		FileOutputStream out = null;
    		ZipOutputStream zipOut = null;
    		try {
    			out = new FileOutputStream(zipFile);
    			CheckedOutputStream cos = new CheckedOutputStream(out, new CRC32());
    			zipOut = new ZipOutputStream(cos);
    			String baseDir = "";
    			compress(srcFile, zipOut, baseDir);
    		} finally {
    			if (null != zipOut) {
    				zipOut.close();
    				out = null;
    			}
    
    			if (null != out) {
    				out.close();
    			}
    		}
    	}
    
    	private static void compress(File file, ZipOutputStream zipOut, String baseDir) throws IOException {
    		if (file.isDirectory()) {
    			compressDirectory(file, zipOut, baseDir);
    		} else {
    			compressFile(file, zipOut, baseDir);
    		}
    	}
    
    	/** 压缩一个目录 */
    	private static void compressDirectory(File directory, ZipOutputStream zipOut, String baseDir) throws IOException {
    		File[] files = directory.listFiles();
    		for (int i = 0; i < files.length; i++) {
    			compress(files[i], zipOut, baseDir + directory.getName() + "/");
    		}
    	}
    
    	/** 压缩一个文件 */
    	private static void compressFile(File file, ZipOutputStream zipOut, String baseDir) throws IOException {
    		if (!file.exists()) {
    			return;
    		}
    
    		BufferedInputStream bis = null;
    		try {
    			bis = new BufferedInputStream(new FileInputStream(file));
    			ZipEntry entry = new ZipEntry(baseDir + file.getName());
    			zipOut.putNextEntry(entry);
    			int count;
    			byte data[] = new byte[BUFFER];
    			while ((count = bis.read(data, 0, BUFFER)) != -1) {
    				zipOut.write(data, 0, count);
    			}
    
    		} finally {
    			if (null != bis) {
    				bis.close();
    			}
    		}
    	}
    
    	/**
    	 * 解压文件
    	 *
    	 * @param zipFile
    	 * @param dstPath
    	 * @throws IOException
    	 */
    	public static void decompress(String zipFile, String dstPath) throws IOException {
    		File pathFile = new File(dstPath);
    		if (!pathFile.exists()) {
    			pathFile.mkdirs();
    		}
    		ZipFile zip = new ZipFile(zipFile);
    		for (Enumeration entries = zip.entries(); entries.hasMoreElements(); ) {
    			ZipEntry entry = (ZipEntry) entries.nextElement();
    			String zipEntryName = entry.getName();
    			InputStream in = null;
    			OutputStream out = null;
    			try {
    				in = zip.getInputStream(entry);
    				String outPath = (dstPath + "/" + zipEntryName).replaceAll("\\*", "/");
    				;
    				//判断路径是否存在,不存在则创建文件路径
    				File file = new File(outPath.substring(0, outPath.lastIndexOf('/')));
    				if (!file.exists()) {
    					file.mkdirs();
    				}
    				//判断文件全路径是否为文件夹,如果是上面已经上传,不需要解压
    				if (new File(outPath).isDirectory()) {
    					continue;
    				}
    
    				out = new FileOutputStream(outPath);
    				byte[] buf1 = new byte[1024];
    				int len;
    				while ((len = in.read(buf1)) > 0) {
    					out.write(buf1, 0, len);
    				}
    			} finally {
    				if (null != in) {
    					in.close();
    				}
    
    				if (null != out) {
    					out.close();
    				}
    			}
    		}
    		zip.close();
    	}
    
    	public static void main(String[] args) throws Exception {
    		String targetFolderPath = "/Users/test/zipFile/zipFolder";
    		String rawZipFilePath = "/Users/test/zipFile/raw.zip";
    		String newZipFilePath = "/Users/test/zipFile/new.zip";
    
    		//将Zip文件解压缩到目标目录
    		decompress(rawZipFilePath, targetFolderPath);
    
    		//将目标目录的文件压缩成Zip文件
    		compress(targetFolderPath, newZipFilePath);
    
    	}
    }
    
    
    

    所需jar包依赖org.apache.poi版本:

     <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi</artifactId>
            <version>4.1.0</version>
     </dependency>
    <dependency>
                <groupId>org.apache.poi</groupId>
                <artifactId>poi-ooxml</artifactId>
                <version>4.1.0</version>
                <exclusions>
                    <exclusion>
                        <artifactId>poi</artifactId>
                        <groupId>org.apache.poi</groupId>
                    </exclusion>
                    <exclusion>
                        <artifactId>xmlbeans</artifactId>
                        <groupId>org.apache.xmlbeans</groupId>
                    </exclusion>
                </exclusions>
     </dependency>
    

    三.数据源数据结构解释:

    1.导出方法参数数据源:List<List<T>> dataLists

    第一层List:多个sheet页。
    第二层List:一个Sheet页下的多条数据。
    T:实体类,对应一个Sheet页下的一行数据。

    2.导出方法参数数据源:List<List<Map<String, Object>>> dataLists

    第一层List:多个Sheet页数据。
    第二层List:一个Sheet页下的多条数据
    Map<String, Object>:一个Sheet页下的一行数据,其中key:表头,value:对应单元格的值。

    四.功能和使用方法:

    1.导出实体类方式

    使用场景: 适合Excel列的名称顺序和列数是固定的业务场景。

    注意事项: 实体类的属性声明的顺序,即为Excel导出后列的顺序,想调整列的顺序,需调整实体类属性声明的顺序即可。

    导出方法:

    /**
    	 * 导出多Sheet的Excel到HttpServletResponse流中
    	 *
    	 * @param fileName
    	 *            另存为文件名
    	 * @param sheetName
    	 *            工作簿中的多张Sheet工作表的名称列表
    	 * @param titleName
    	 *            表格的标题名称(没有标题,则传null)
    	 * @param headers
    	 *            表头列表
    	 * @param dataList
    	 *            要导出的数据源
    	 * @param HttpServletResponse
    	 *            Http响应
    	 * @param pattern
    	 *            时间类型数据的格式,默认UTC格式
    	 * @param isExportNullField
    	 *            空字段是否导出(true:导出,false:不导出)
    	 */
    	public static <T> void exportExcel(String fileName, List<String> sheetNames, String titleName, List<String> headers,
    			List<List<T>> dataLists, HttpServletResponse response, String pattern, boolean isExportNullField){……}
    

    使用示例:

    实体类示例:

    package com.entity.dto;
    
    import java.io.Serializable;
    
    /**
     * 数据转Excel对象 注:属性名的顺序和Excel列顺序必须相同
     */
    @SuppressWarnings("serial")
    public class EventDTO implements Serializable {
    	// 列字段1
    	private String name;
    	// 列字段2
    	private String code;
    
    	public String getName() {
    		return name;
    	}
    
    	public void setName(String name) {
    		this.name = name;
    	}
    
    	public String getCode() {
    		return code;
    	}
    
    	public void setCode(String code) {
    		this.code = code;
    	}
    }
    
    

    注: 文件名和sheet名:注意不要含有Excel不支持的特殊字符。

    使用示例:
    在SpringMVC的RESTful接口中,控制器中调用示例

    @GetMapping(value = "/myevent/export/{code}")
    	public void exportEvent(HttpServletResponse response, @PathVariable("code") String code) {
    		List<List<EventDTO>> dataLists = eventService.exportEvent(stationCode);
    		if (dataLists != null && dataLists.size() > 0) {
    			try {
    				List<String> sheetNames = new ArrayList<String>(
    						Arrays.asList("sheet页1, "sheet页2", "sheet页3"));
    				List<String> headers = new ArrayList<String>(
    						Arrays.asList("类型", "型号", "编号", "参数", "描述", "级别"));
    				String excelName = "你的Excel名称";
    				ExportExcelUtil.exportExcel(excelName, sheetNames, null, headers, dataLists, response, null, true);
    			} catch (Exception e) {
    				log.info("导出Excel出错", e);
    			}
    		}
    	}
    

    2.导出Map对象的方式

    使用场景: 适合Excel列的名称和顺序和列数是不固定的,如每次导出的列数可能不一致的场景。

    注意事项: Excel导出后列的顺序,为Map中的键值对加入的顺序,要想导出后列的顺序固定,可将Map实例化为LinkedHashMap即可使导出后的列顺序不会改变。

    例: 如下方式缓存导出数据,导出后的“名称”列,会在“类型”列的左侧。

    Map<String, Object> tempMap = new LinkedHashMap<String, Object>();
        tempMap.put("名称", device.getName());
    	tempMap.put("类型", device.getDeviceType());
    

    导出方法:

    /**
    	 * 导出多Sheet动态列的Excel到HttpServletResponse流中
    	 *
    	 * @param fileName          另存为文件名
    	 * @param sheetNames        工作簿中的多张Sheet工作表的名称列表
    	 * @param titleName         表格的标题名称(没有标题,则传null)
    	 * @param headersList       多个sheet页的表头列表
    	 * @param dataLists         要导出的数据源
    	 * @param response          Http响应
    	 * @param pattern           时间类型数据的格式,默认UTC格式
    	 * @param isExportNullField 空字段是否导出(true:导出,false:不导出)
    	 */
    	public static void exportDynamicExcel(String fileName, List<String> sheetNames, String titleName,
    			List<List<String>> headersList, List<List<Map<String, Object>>> dataLists, HttpServletResponse response,
    			String pattern, boolean isExportNullField) {……}
    

    使用示例:
    在SpringMVC的RESTful接口中,控制器中调用示例:

    @PostMapping(value = "/history/export")
    	public void exportHistory(HttpServletResponse response, @RequestBody Params params) {
    		List<List<Map<String, Object>>> datalists = deviceService.exportHistory(params);
    		if (!datalists.isEmpty()) {
    			try {
    				List<String> headers1 = new ArrayList<String>("表头列名1","表头列名2","表头列名3");
    				List<String> headers2 = new ArrayList<String>("表头列名1","表头列名2","表头列名3");
    				String fileName ="你的文件名,若包含Excel不支持的特殊字符,会自动处理成下划线";
    				ExportExcelUtil.exportDynamicExcel(fileName, Arrays.asList("用户历史数据"), null, Arrays.asList(headers1 ,headers2), datalists, response,
    						null, true);
    			} catch (Exception e) {
    				e.printStackTrace();
    			}
    		}
    	}
    

    3.多个Excel同时导出并压缩成zip包示例

    ①:Seivice层组装数据源

    看清楚数据结构的嵌套,组装好数据源。这里展示两个Excel同时导出的数据源结构。

    public class YourService{
    
    public List<Map<String, Object>>  export (ExcelInput input){
    
    		// 多个Excel的数据源
    		List<Map<String, Object>> resultList = new ArrayList<>();
    		
    		// 多个Sheet页数据源,每个List<Map<String, Object>>为一个Sheet页的数据源
    		List<List<Map<String, Object>>> deviceDataLists = new LinkedList<>();
    		// 第1个Excel数据源
    		Map<String, Object> powerExcelMap = new HashMap<>();
    		powerExcelMap.put("dataLists", deviceDataLists);
    		powerExcelMap.put("fileName", "excel文件名字1");
    		//多个Sheet页名字
    		powerExcelMap.put("sheetNames", Arrays.asList("第1个sheet页名字","第2个sheet页名字"));
    		// 第一个Sheet页的表头
    		List<String> powerHeaders1 = new LinkedList<>();
    		// 第二个Sheet页的表头
    		List<String> powerHeaders2 = new LinkedList<>();
    		powerExcelMap.put("headers", Arrays.asList(powerHeaders1,powerHeaders2 ));
    		resultList.add(powerExcelMap);
    
    		// 第2个Excel数据源
    		List<List<Map<String, Object>>> weatherDataLists= new LinkedList<>();
    		List<String> weatherHeaders = new LinkedList<>();
    		Map<String, Object> weatherExcelMap = new HashMap<>();
    		weatherExcelMap.put("dataLists", weatherDataLists);
    		weatherExcelMap.put("fileName",  "excel文件名字2");
    		powerExcelMap.put("sheetNames", Arrays.asList("第1个sheet页名字","第2个sheet页名字"));
    		// 第一个Sheet页的表头
    		List<String> weatherHeaders1 = new LinkedList<>();
    		// 第二个Sheet页的表头
    		List<String> weatherHeaders2 = new LinkedList<>();
    		weatherExcelMap.put("headers", Arrays.asList(weatherHeaders1,weatherHeaders2));
    		resultList.add(weatherExcelMap);
    	}
    }
    

    ②.Contorller层调用Excel工具类导出多个Excel到Response中

    @PostMapping("/export")
    public void export (HttpServletResponse response, @RequestBody ExcelInput input) {
    	// 获取①中的数据源
    	List<Map<String, Object>> excelList = yourService.export(input);
    	if (ObjectUtils.isEmpty(excelList) || ObjectUtils.isEmpty(input.getTime())) {
    		return;
    	}
    	// 导出多个Excel到zip包中
    	ExportExcelUtil.exportExcelsToZip(response, excelList, filePath,  "压缩包名称", null, true);
    }
    

    五.导出效果测试:

    1.PostMan测试:

    导出多个Excel到Zip包中示例:

    注意:文件名中的中文都经过URLEncode,需要请前端同学URLDecode一下,即可展示出中文名。
    在这里插入图片描述

    2.网页测试:

    3.导出效果图:

    看下导出的效果吧,边框、表头、对齐方式、字体大小、单元格数据换行、时间字符串格式转换等。效果满意的话点个赞吧!

    Excel实用教程集锦

    以下是我写的关于Java操作Excel的所有教程,基本包含了所有场景。

    1.如果简单导出推荐使用工具类的方式,这种配置最简单。

    2.如果对导出样式要求极高的还原度,推荐使用Freemarker方式,FreeMarker模板引擎可以通吃所有Excel的导出,属于一劳永逸的方式,项目经常导出推荐使用这种方式。

    3.Freemarker导出的Excel为xml格式,是通过重命名为xls后,每次会打开弹框问题,我在《Freemarker整合poi导出带有图片的Excel教程》也已经完美解决,本教程将直接导出真正的xls格式,完美适配新版office和wps。Freemarker是无法导出带有图片的Excel,通过其他技术手段,也在本教程中完美导出带有图片的Excel。

    4.下列教程中的代码都经本人和网友多次验证,真实有效!

    ↓↓↓↓一揽子Excel解决方案,赶快收藏吧↓↓↓↓

    《Java导入Excel工具类使用教程》

    《Java之Excel导出工具类使用教程》

    《Freemarker导出复杂Excel图文教程》

    《Freemarker整合poi导出带有图片的Excel教程》

    展开全文
  • android 中导出数据

    千次阅读 2014-01-13 18:34:02
    随时随地阅读更多技术实战干货,充分利用闲暇时间,请关注源代码社区公众号(ydmsq666)和技术...下面以导出系统联系人为例: MainActivity: package com.home.exportcontact; import java.io.File; import java....

    随时随地阅读更多技术实战干货,充分利用闲暇时间,请关注源代码社区公众号(ydmsq666)和技术交流群。

    思路很简单:

    1、将数据库的数据读取出来,并根据需要作相应处理。

    2、将数据写入SD卡

    下面以导出系统联系人为例:

    MainActivity:

    package com.home.exportcontact;
    
    import java.io.File;
    import java.io.FileWriter;
    import java.io.IOException;
    import android.app.Activity;
    import android.app.AlertDialog;
    import android.content.Context;
    import android.content.DialogInterface;
    import android.database.Cursor;
    import android.os.Bundle;
    import android.os.Environment;
    import android.os.Handler;
    import android.os.Message;
    import android.provider.ContactsContract;
    import android.provider.ContactsContract.CommonDataKinds.Phone;
    import android.provider.ContactsContract.CommonDataKinds.StructuredName;
    import android.provider.ContactsContract.Contacts.Data;
    import android.view.View;
    import android.view.View.OnClickListener;
    import android.widget.Button;
    import android.widget.TextView;
    
    public class MainActivity extends Activity implements OnClickListener {
    	private Button exportBtn;// 导出按钮
    	private TextView showText;// 显示进度的文本
    	// 导出txt的存放位置
    	private final static String OUTPUT_PATH = Environment
    			.getExternalStorageDirectory() + "/我的联系人.txt";
    	private static final int OUTPUT_FAIL = 0;// 导出失败标识
    	private static final int OUTPUT_SUCCESS = 1;// 导出成功标识
    	private static int count;// 导出联系人的计数
    	private Thread mOutputThread;// 导出联系人线程
    	// 空格
    	private static final String SPACE_1 = " ";
    	private static final String SPACE_2 = SPACE_1 + SPACE_1;
    	private static final String SPACE_4 = SPACE_2 + SPACE_2;
    	private static final String SPACE_8 = SPACE_4 + SPACE_4;
    	private static final String SPACE_11 = SPACE_8 + SPACE_2 + SPACE_1;
    
    	@Override
    	protected void onCreate(Bundle savedInstanceState) {
    		super.onCreate(savedInstanceState);
    		setContentView(R.layout.main);
    		exportBtn = (Button) findViewById(R.id.main_btn_export);
    		exportBtn.setOnClickListener(this);
    		showText = (TextView) findViewById(R.id.main_tv_show);
    	}
    
    	@Override
    	public void onClick(View v) {
    		if (v == exportBtn) {
    			outputContact();
    		}
    	}
    
    	/**
    	 * 导出联系人入口
    	 */
    	private void outputContact() {
    		File file = new File(OUTPUT_PATH);
    		if (!file.exists()) {
    			startOutputContact();
    		} else {
    			createDialog(this, "警告", "我的联系人.txt已经存在,是否覆盖?");
    		}
    	}
    
    	/**
    	 * 创建提示对话框
    	 * 
    	 * @param context
    	 * @param title
    	 * @param message
    	 */
    	private void createDialog(Context context, String title, String message) {
    		AlertDialog.Builder builder = new AlertDialog.Builder(context);
    		builder.setTitle(title);
    		builder.setMessage(message);
    		builder.setPositiveButton("确定", new DialogInterface.OnClickListener() {
    			public void onClick(DialogInterface dialog, int whichButton) {
    				startOutputContact();
    			}
    		});
    		builder.setNeutralButton("取消", new DialogInterface.OnClickListener() {
    			public void onClick(DialogInterface dialog, int whichButton) {
    				dialog.cancel();
    			}
    		});
    		builder.show();
    	}
    
    	/**
    	 * 开启导出联系人子线程
    	 */
    	private void startOutputContact() {
    		setOutputWidgetEnabled(false);
    		showText.setText("正在导出联系人...");
    		if (mOutputThread != null) {
    			mOutputThread.interrupt();
    			mOutputThread = null;
    		}
    		mOutputThread = new Thread(new OutputRunnable(this));
    		if (mOutputThread != null) {
    			mOutputThread.start();
    		}
    	}
    
    	/**
    	 * 设置导出组件的可用性
    	 */
    	private void setOutputWidgetEnabled(boolean enabled) {
    		exportBtn.setEnabled(enabled);
    		if (!enabled) {
    			showText.setText("");
    		}
    	}
    
    	class OutputRunnable implements Runnable {
    		private Context context;
    
    		public OutputRunnable(Context context) {
    			this.context = context;
    		}
    
    		@Override
    		public void run() {
    			boolean result = doOutputContact(context);
    			if (result) {
    				handler.sendEmptyMessage(OUTPUT_SUCCESS);
    			} else {
    				handler.sendEmptyMessage(OUTPUT_FAIL);
    			}
    		}
    	}
    
    	/**
    	 * 处理UI提示相关
    	 */
    	private Handler handler = new Handler() {
    		public void handleMessage(Message msg) {
    			if (msg.what == OUTPUT_SUCCESS) {
    				showText.setText((String.format("已成功导出 %d 条联系人记录", count)));
    				setOutputWidgetEnabled(true);
    			} else if (msg.what == OUTPUT_FAIL) {
    				showText.setText("导入联系人失败");
    				setOutputWidgetEnabled(true);
    			}
    		}
    	};
    
    	/**
    	 * 从数据库中导出联系人
    	 * 
    	 * @param context
    	 * @return 是否成功
    	 */
    	private boolean doOutputContact(Context context) {
    		count = 0;
    		try {
    			String result = readFromContactDatabase(context);
    			writeFile(OUTPUT_PATH, result);
    		} catch (Exception e) {
    			return false;
    		}
    		return true;
    	}
    
    	/**
    	 * 从数据库中读取联系人相关信息
    	 * 
    	 * @param context
    	 * @return 信息结果
    	 */
    	private String readFromContactDatabase(Context context) {
    		StringBuilder resultBuilder = new StringBuilder();
    		Cursor cursor = context
    				.getContentResolver()
    				.query(ContactsContract.Data.CONTENT_URI,
    						new String[] { StructuredName.DISPLAY_NAME,
    								Data.RAW_CONTACT_ID }, Data.MIMETYPE + "= ?",
    						new String[] { StructuredName.CONTENT_ITEM_TYPE }, null);
    		while (cursor.moveToNext()) {
    			// 得到名称和每一行记录的ID
    			String displayName = cursor.getString(0);
    			int id = cursor.getInt(1);
    			// 得到电话号码的游标
    			Cursor mobileCursor = context.getContentResolver().query(
    					ContactsContract.Data.CONTENT_URI,
    					new String[] { Phone.NUMBER },
    					Data.RAW_CONTACT_ID + " = " + id + " AND " + Data.DATA2
    							+ " = " + 2, null, null);
    			String mobileNum = "";
    			if (mobileCursor.moveToNext()) {
    				mobileNum = mobileCursor.getString(0);
    			}
    			mobileCursor.close();
    			// 得到家庭电话
    			Cursor homeCursor = context.getContentResolver().query(
    					ContactsContract.Data.CONTENT_URI,
    					new String[] { Phone.NUMBER },
    					Data.RAW_CONTACT_ID + " = " + id + " AND " + Data.DATA2
    							+ " = " + 1, null, null);
    			String homeNum = "";
    			if (homeCursor.moveToNext()) {
    				homeNum = homeCursor.getString(0);
    			}
    			homeCursor.close();
    			if (displayName != null && !"".equals(displayName)) {
    				String result = displayName + SPACE_4;
    				if ("".equals(mobileNum)) {
    					result += SPACE_11;
    				} else {
    					result += mobileNum;
    				}
    				result += SPACE_8 + homeNum + '\n';
    				String checkString = resultBuilder.toString();
    				if (!checkString.contains(result)
    						&& ("".equals(mobileNum) || !checkString
    								.contains(mobileNum))) {
    					resultBuilder.append(result);
    					count++;
    				}
    			}
    		}
    		cursor.close();
    		return resultBuilder.toString();
    	}
    
    	/**
    	 * 将联系人信息写入SD
    	 * 
    	 * @param path
    	 * @param info
    	 */
    	private void writeFile(String path, String info) {
    		try {
    			File file = new File(path);
    			FileWriter writer = new FileWriter(file, false);
    			writer.write(info);
    			writer.close();
    		} catch (IOException e) {
    			e.printStackTrace();
    		}
    	}
    }
    

    布局:

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical" >
    
        <Button
            android:id="@+id/main_btn_export"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="导出联系人" />
    
        <TextView
            android:id="@+id/main_tv_show"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
    
    </LinearLayout>

    权限:

     <uses-permission android:name="android.permission.READ_CONTACTS" />
     <uses-permission android:name="android.permission.WRITE_CONTACTS" />
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />




     

    展开全文
  • Android导出Excel

    千次阅读 2016-10-21 10:42:09
    import java.io.File; import java.text.DecimalFormat; import java.text.SimpleDateFormat; import java.util.Date; import java.util.List; import com.szx.util.Constant; import android.content.Context; ...
  • 最近需要做一个导出word的功能,在网上搜索了一下,发现了各种方法,但是在这个过程中遇到不少问题,各种报错,各种调试,先整理各种总结,希望能对大家有所帮助,少走弯路。欢迎大家留言交流。同时感谢网络中的大神...
  • Redis 数据导入导出

    2020-12-02 18:07:39
    Redis 数据导入导出redis-dump方式安装redis-dump工具redis-dump导出redis-load导入错误处理彻底解决【“curl: (7) Failed to connect to raw.githubusercontent.com port 443: Connection refused”】 redis-dump...
  • 导出前后端实现不同方式 后端实现的导出: Java Excel是一开放源码项目,通过它Java开发人员可以读取Excel文件的内容、创建新的Excel文件、更新已经存在的Excel文件。jxl 由于其小巧 易用的特点, 逐渐已经取代了 POI...
  • freemarker导出word文档

    2017-10-26 20:06:50
    很多web项目需要添加导出word文档的功能,使用POI似乎比freemarker麻烦一些,事实上POI导出word文档我还没有学习,这里记录一下使用freemarker导出word文档。 查询了很多资料,也查询了很多方法,大神们的方法都...
  • dataset 导出Excel

    2014-10-10 17:29:00
    /// <summary> /// /// </summary> /// <param name="dataSet">要导出的数据来源</param> /// <param name="fileName">导出的Excel名称</...
  • 1.用户要把页面上创建的管理关系(比如部门关系/人员关系/产品关系等)导出,以便离线查看/分析/备份等作用; 2.用户要把本地的关系数据导入到系统 这里我把业务逻辑做了简化;介绍一下如何把数据导出成Excel...
  • Sqoop2 MySQL与HDFS导入导出

    千次阅读 2017-09-14 23:04:21
    本篇博客主要介绍sqoop2中如何进行数据的导入导出,笔者使用的版本为1.99.5,安装及配置请参考博客:http://blog.csdn.net/hg_harvey/article/details/77803520Sqoop 命令使用官网: ...
  • Android VCard通讯录导入导出详解

    千次阅读 2013-10-16 14:20:45
    详细介绍Android通讯录的导出和导入 通讯录导出到SD卡时,会在SD卡中生成一个vcf文件,用于保存联系人姓名,手机号码。 vCard 规范容许公开交换个人数据交换 (Personal Data Interchange PDI)信息,在传统纸质商业...
  • springboot+freemarker导出word

    千次阅读 2019-05-31 17:42:43
    后来......,反正就是不行,必须用后台导出。没办法只能写一个,直接上代码吧。 项目准备 核心html解析包 <dependency> <groupId>org.jsoup</groupId> <artifactId>jsoup<...
  • SheetJS中文文档-js导出Excel脚本库

    万次阅读 2021-01-08 11:33:13
    社区版本的主要关注点在正确的数据转换,即从任意一个兼容的数据表示中提取数据,导出适用于任意第三方接口的各种数据格式。 解析工作簿 对于解析,第一步是读取文件。这一步包括获取数据并且导入数据库。这里有一些...
  • 这三个按钮分别是:从线上导入MD文件、从本机导入MD文件、导出到本地。 CSDN-markdown编辑器的导入功能让我们很方便的基于已有的Markdown文件进行重新编辑修改,然后发表博文。 CSDN-markdown编辑器的导出
  • 导出Unity场景的所有游戏对象信息,一种是XML一种是JSON。本篇文章我们把游戏场景中游戏对象的、旋转、缩放、平移与Prefab的名称导出在XML与JSON中。然后解析刚刚导出的XML或JSON通过脚本把导出的游戏场景还原。在...
  • 【Dubbo源码分析】服务导出

    千次阅读 2019-06-04 16:51:12
    Dubbo 服务导出过程始于 Spring 容器发布刷新事件,Dubbo 在接收到事件后,会立即执行服务导出逻辑。整个逻辑大致可分为三个部分,第一部分是前置工作,主要用于检查参数,组装 URL。第二部分是导出服务,包含导出...
  • 导出CSV文件

    2015-03-08 17:59:35
    在mvc的程序中添加Common文件夹,添加CsvSerializer.cs类,以后就调用这个就行了。// CsvSerializer.cs using System; using System.Collections.Generic; using System.ComponentModel;...using System.IO; usi
  • raw.replace("=3D", "=").replace("=", "=3D" ); } public String getRawHandledDocBodyBlock() { String raw = doc.getElementsByTag("body" ).html(); return raw.replace("=3D", "=").replace("=", "=...
  • sqoop详细导入导出数据步骤

    千次阅读 2020-07-24 02:35:34
    sqoop的作用前面也说过主要用于做数据迁移,它用于从关系数据库(如MySQL,Oracle)导入数据到Hadoop HDFS,并从Hadoop文件系统导出到关系数据库的互相导入。 Sqoop功能 将关系型数据库数据导入HDFS,解决HDFS的...
  • swagger 导出apiDownload CreateExcelFile - 8.9 KB 下载CreateExcelFile-8.9 KB Download LogProvider - 2.6 KB 下载LogProvider-2.6 KB Download MikesBank - 239.6 KB 下载MikesBank-239.6 KB Download Southwind...
  • 创建名为crypto_ethereum , crypto_ethereum_raw , crypto_ethereum_temp新数据集 创建Google Cloud Storage存储桶 创建一个新的Google Storage存储桶以存储导出的文件 创建Google Cloud Composer环境 创建一个新...
  • 根据一个数组导出Excel表格。 Demo 地址:https://xrkffgg.github.io/Kvue/#/Js/003 2 实现原理 2.1 引入工具库 file-saver、xlsx、script-loader npm install -S file-saver xlsx npm install -D script-loader ...
  • Unity3d Assetbundle文件的导出与加载

    千次阅读 2015-08-17 16:23:50
    Unity3d Assetbundle文件的导出与加载 Assetbundle文件导出 首先在Assets中创建Editor文件夹,添加如下脚本 ExportAssetbundle.cs using UnityEngine; using System.Collections....using System.IO; public cl
  • 在上一篇文章《用sqoop进行mysql和hdfs系统间的数据互导》中,提到sqoop...要么将HBase导出到HDFS平面文件,要么将其导出到Hive中,再导出到mysql。本篇讲从hive中导出到mysql。从hive将数据导出到mysql 一、创建m...
  • ext.net 2.5 导出excel的用法

    千次阅读 2015-07-14 16:04:23
    前台页面的导入,导出

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 6,372
精华内容 2,548
关键字:

drawio导出