用java把通达信导出的交易记录文件高速批量导入mysql数据库

有段时间需要把通达信的交易记录导入到mysql数据库,主要是1分钟和5分钟数据。对比测试过innoDB和MyISAM引擎,不论从数据访问速度还是数据文件空间占用上比较,MyISAM都大占优势。 Mysql数据版本为:5.5.47Ā

导入mysql数据库性能最好的应该使用load data,但是为了通用性,同时也想通过java程序实现更极致的性能。故记录下过程。

程序性能:在我笔记本上测试下来,导入速度大约在2.5万3万每秒。

电脑配置:双核四线程,比较低配了。

处理思路:

读取导出交易数据文件目录中的所有文件名,存放到String[]中

启动4个线程读取文件,把数据加工成:insert into <table> values(),(),() 这样的语句统一放入BlockingQueue<String>中。

当BlockingQueue<String>存放的数据量达到600,开始执行批量insert到数据库。直到处理完所有文件。

建表:

--  一分钟数据表名为trade1f,五分钟数据表名为trade5f,字段完全一致。

CREATE TABLE `trade1f` (

  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `dttime` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00' COMMENT '时间',
  `code` char(6) DEFAULT NULL COMMENT '股票代码',
  `openPrc` float DEFAULT NULL COMMENT '开盘价',
  `maxPrc` float DEFAULT NULL COMMENT '最高价',
  `minPrc` float DEFAULT NULL COMMENT '最低价',
  `closePrc` float DEFAULT NULL COMMENT '收盘价',
  `vol` bigint(20) DEFAULT NULL COMMENT '成交量',
  `volMoney` bigint(20) DEFAULT NULL COMMENT '成交金额',
  `bg` enum('SH','SZ') DEFAULT NULL COMMENT 'SH OR SZ',
  `createDt` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  KEY `dttime` (`dttime`),
  KEY `code` (`code`)
) ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=utf8

 

源代码如下:

package itil.stock.bs;

import java.io.File;
import java.io.FilenameFilter;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;

import com.jfinal.plugin.activerecord.Db;
import com.jfinal.plugin.activerecord.DbKit;
import com.jfinal.plugin.activerecord.Record;

import itil.stock.common.Kit;

public class GatherFromTdxFile3 {
    private static final int batchSize=30;
    private static final int valueSize=20;
    private static final String sdfStr="yyyy-MM-dd HH:mm:ss";
    private static final String insert="INSERT into <table>(dttime,code,openPrc,maxPrc,minPrc,closePrc,vol,volMoney,bg)values";
    private static final BlockingQueue<String> insertQueue = new LinkedBlockingQueue<String>(100000);
    //此逻辑是使用insert into values(),(),()....一个sql插入多条,一批次含有多条sql
    //这里不按文件来执行插入,直到对列里满足待插入的数据数量才执行插入数据库,故一次提交会包含多个文件的数据
    public static void hdTradeData(String fileName){
        final String fname = fileName.substring(fileName.lastIndexOf("\\")+1);//SZ#300737.txt
        String bg = fname.substring(0,2);// SZ or SH
        
        List<String> listData = Kit.readFileByLines(fileName, "GBK");
        String info = listData.get(0);
        String tableName = "trade5f";
        if(info.contains("1分钟线")){
            tableName="trade1f";
        }else if(info.contains("日线 ")){
            tableName="tradeday";
        }
        boolean isNotDayData = !(tableName.equals("tradeday"));
        
        String[] sk = info.split("\\s");
        String code = sk[0];
        //String name = info.replace(code,"").replace("1分钟线 不复权", "").replace("5分钟线 不复权", "").trim();
        String openPrc,maxPrc,minPrc,closePrc;//用批量方式插入,生成sql语句,不需要管数据类型
        String vol,volMoney;
        StringBuilder sbValues = null;
        int all=0;
//        String lastDtStr=null;
//        Timestamp dbLastDttime = null;
//        boolean ckNone = false;
        for(int line=2;line<listData.size()-1;line++){//跳过前两行与最后一行
            String lineStr = listData.get(line);
            String[] data = lineStr.split("\\s");
            final String dtStr = data[0];
            String dttimeStr = null;
            if(isNotDayData){
                String time = data[1];
                dttimeStr = dtStr+" "+time.substring(0, 2)+":"+time.substring(2)+":"+"00";
            }else{
                dttimeStr = dtStr+" 00:00:00";
            }
            
            
            //**********检查当前code与日期是存否在**********
            //本逻辑只检测已导入的当天日期最后一条,只有时间在最后一条之后才插入数据库
            //如果能保证数据文件不存在重复,注释此段代码,将大大提升导入效率
            //使用ThreadLocal提升初始化SimpleDateFormat效率
            /*Date dttime = Kit.parse(dttimeStr,sdfStr);
            if(!dtStr.equals(lastDtStr)){
                lastDtStr=dtStr;
                Record ckRec = Db.findFirst("select max(dttime) dttime from "+tableName+" t "
                        + "where t.code='"+code+"' and t.dttime='"+dttimeStr+"'");
                if(ckRec.get("dttime")!=null){
                    dbLastDttime=ckRec.getTimestamp("dttime");
                }else{
                    ckNone=true;
                }
                if(dbLastDttime!=null && !dttime.after(dbLastDttime)){
                    continue;
                }
            }
            
            if(!ckNone && dbLastDttime!=null && !dttime.after(dbLastDttime)){
                continue;
            }*/
            //**********检查当前code与日期是存否在**********
            
            all++;
            if(all%valueSize==1){
                String tmp = insert.replace("<table>", tableName);
                sbValues = new StringBuilder(200*valueSize);
                sbValues.append(tmp);
            }
            if(isNotDayData){
                openPrc=data[2];
                maxPrc=data[3];
                minPrc=data[4];
                closePrc=data[5];
                vol = data[6];
                volMoney = data[7];
            }else{
                openPrc=data[1];
                maxPrc=data[2];
                minPrc=data[3];
                closePrc=data[4];
                vol = data[5];
                volMoney = data[6];
            }
            sbValues.append("(\'").append(dttimeStr).append("\',");
            sbValues.append('\'').append(code).append("\',");
            sbValues.append(openPrc).append(",");
            sbValues.append(maxPrc).append(",");
            sbValues.append(minPrc).append(",");
            sbValues.append(closePrc).append(",");
            sbValues.append(vol).append(",");
            sbValues.append(volMoney).append(",");
            sbValues.append('\'').append(bg).append("\'),");
            
            if(all%valueSize==0){
                putQueue(sbValues.substring(0, sbValues.length()-1).toString());
                sbValues = null;
                if(insertQueue.size()>600){
                    saveListToDb(getListForSave(false));
                }
            }
        }
        if(sbValues!=null){
            putQueue(sbValues.substring(0, sbValues.length()-1).toString());
            sbValues = null;
        }
    }
    public static void saveListToDb(List<String> sqlList){
        if(sqlList!=null && sqlList.size()>0){
            Db.batch(sqlList, batchSize);
            //System.out.println("saved:"+sqlList.size()*valueSize);
            sqlList.clear();
        }
    }
    public static void putQueue(String sql){
        try {
            insertQueue.put(sql);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    public static synchronized List<String> getListForSave(boolean isforceSave){
        if(!isforceSave && insertQueue.size()<10){
            return null;
        }
        List<String> sqlList = new ArrayList<String>(insertQueue.size()*batchSize*200);
        while (insertQueue.size()>0) {
            final String sql = insertQueue.poll();
            if(sql!=null){
                sqlList.add(sql);
            }
        }
        return sqlList;
    }
    //检查一个文件第一条数据是否存在
    public static Boolean checkExist(String dir,String[] fileList){
        Boolean chk = true;
        for(String rec:fileList){
            chk = checkExist(dir+"\\"+rec);
            if(chk!=null){
                return chk;
            }
        }
        return true;
    }
    public static Boolean checkExist(String dir){
        List<String> listData = Kit.readFileByLines(dir, "GBK");
        if(listData.size()<4){
            return null;//对于被检测的文件,数据不合法将返回null,重新检测下一个文件
        }
        String info = listData.get(0);
        String tableName = "trade5f";
        if(info.contains("1分钟线")){
            tableName="trade1f";
        }
        String lineStr = listData.get(2);//取第一行数据
        String[] data = lineStr.split("\\s");
        final String dtStr = data[0];
        String time = data[1];
        final String dttimeStr = dtStr+" "+time.substring(0, 2)+":"+time.substring(2)+":"+"00";
        Record rec = Db.findFirst("select 1 from "+tableName+" where dttime='"+dttimeStr+"' limit 1");
        System.out.println("检查时间:"+dttimeStr+" 是否存在记录");
        if(rec==null){
            return false;
        }
        return true;
    }
    //下载数据保存到数据库
    public static void hdFileData(final String dir) {
        File file = new File(dir);
        if(!file.isDirectory()){
            if(checkExist(dir)){
                System.out.println("已存在记录,程序退出........");
                return;
            }
            hdTradeData(dir);
            return;
        }
        String[] fileList = file.list(new FilenameFilter(){
            public boolean accept(File dir,String name){
                return name.startsWith("SH#")||name.startsWith("SZ#");
            }
        });
        if(checkExist(dir,fileList)){
            System.out.println("已存在记录,程序退出........");
            return;
        }
        BlockingQueue<String> queue = new LinkedBlockingQueue<String>(5000);
        for(String rec:fileList){
            try {
                queue.put(dir+"\\"+rec);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        
        int threadNum = Integer.parseInt(Kit.readPropByKey("threadNum", 4));
        System.out.println("待处理文件数:"+queue.size()+" 处理线程数:"+threadNum);
        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(threadNum);
        long begin = System.currentTimeMillis();
        while (queue.size()>0) {
            final String rec = queue.poll();
            fixedThreadPool.execute(new Runnable() {
                public void run() {
                    hdTradeData(rec);
                }
            });
        }
        
        // 关闭服务后, 阻塞到所有任务被执行完毕或者超时发生,或当前线程被中断
        try {
            fixedThreadPool.shutdown(); // 平缓关闭服务
            fixedThreadPool.awaitTermination(10, TimeUnit.HOURS);
        } catch (InterruptedException e) {
            e.printStackTrace();
            fixedThreadPool.shutdownNow();
        }
        saveListToDb(getListForSave(true));//最后一次强制执行
        long end = System.currentTimeMillis();
        System.out.println("处理完成,时间:"+(end-begin)/1000.0+"秒");
    }
    
    public static void gen30fData(){
        Record t5fMaxRec = Db.findFirst("select max(dttime) dttime from trade5f");
        Date t5fMaxDate = t5fMaxRec.getDate("dttime");
        if(!Kit.format(t5fMaxRec.getTimestamp("dttime"), sdfStr).endsWith(" 15:00:00")){
            System.out.println("5f数据不是15点结束");
            return;
        }
        Record t30fMaxRec = Db.findFirst("select max(dttime) dttime from trade30f");
        String sql = null;
        if(t30fMaxRec.get("dttime")==null){
            sql = "select dttime,code,openPrc,maxPrc,minPrc,closePrc,vol,volMoney,bg from trade5f order by code,dttime";
        }else if(t5fMaxDate.getTime()<=(t30fMaxRec.getDate("dttime")).getTime()){
            System.out.println("数据已存在");
            return;
        }else if(t5fMaxDate.after(t30fMaxRec.getDate("dttime"))){
            Calendar cld = Calendar.getInstance();
            cld.setTime(t30fMaxRec.getDate("dttime"));
            cld.add(Calendar.DATE, 1);
            sql = "select dttime,code,openPrc,maxPrc,minPrc,closePrc,vol,volMoney,bg from trade5f where dttime>='"+Kit.format(cld.getTime(), "yyyy-MM-dd")+"' and dttime<='"+Kit.format(t5fMaxDate, sdfStr)+"' order by code,dttime";
        }else{
            System.out.println("else case.........");
            return;
        }
        long begin = System.currentTimeMillis();
        List<Record> list = new ArrayList<Record>(batchSize);
        int all=0;
        try{
            Connection conn = DbKit.getConfig().getConnection();
            PreparedStatement ps = conn.prepareStatement(sql,
                    ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY); 
            ps.setFetchSize(Integer.MIN_VALUE);
            ResultSet rs = ps.executeQuery();
            
            Record preRec = new Record();
            preRec.set("code", null);
            float openPrc = 0;
            float closePrc = 0;
            float minPrc = 0;
            float maxPrc = 0;
            long vol=0;
            long volMoney=0;
            
            while(rs.next()){
                String dttimeStr = Kit.format(rs.getTimestamp("dttime"), sdfStr);
                String code = rs.getString("code");
                if(dttimeStr.endsWith(":35:00")||dttimeStr.endsWith(":05:00")){//取整点和30分时的下一5f作为初始化
                    openPrc=rs.getFloat("openPrc");
                    minPrc = rs.getFloat("minPrc");
                    maxPrc = rs.getFloat("maxPrc");
                    vol = rs.getLong("vol");
                    volMoney = rs.getLong("volMoney");
                }
                if(code.equals(preRec.getStr("code"))){
                    if(rs.getFloat("minPrc")<minPrc){
                        minPrc = rs.getFloat("minPrc");
                    }
                    if(rs.getFloat("maxPrc")>maxPrc){
                        maxPrc = rs.getFloat("maxPrc");
                    }
                    vol+=rs.getLong("vol");
                    volMoney+=rs.getLong("volMoney");
                    if(dttimeStr.endsWith(":00:00")||dttimeStr.endsWith(":30:00")){
                        closePrc = rs.getFloat("closePrc");
                        
                        Record t30f = new Record();
                        t30f.set("dttime", rs.getTimestamp("dttime"));
                        t30f.set("code", code);
                        t30f.set("openPrc", openPrc);
                        t30f.set("maxPrc", maxPrc);
                        t30f.set("minPrc", minPrc);
                        t30f.set("closePrc", closePrc);
                        t30f.set("vol", vol);
                        t30f.set("volMoney", volMoney);
                        t30f.set("bg", rs.getString("bg"));
                        
                        list.add(t30f);
                        all++;
                        if(all%batchSize==0){
                            Kit.batchSave("trade30f", list);
                            list.clear();
                            System.out.println("saved:"+all);
                        }
                    }
                }
                preRec.set("code", code);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        Kit.batchSave("trade30f", list);
        list.clear();
        System.out.println("saved:"+all);
        long end = System.currentTimeMillis();
        System.out.println("处理结束,用时:"+(end-begin)/1000.0+"秒");
    }
    public static void main(String[] args) {
        int cnt = 10000000;
        long begin = System.currentTimeMillis();
        for(int i=0;i<cnt;i++){
            SimpleDateFormat sdf = Kit.getDateFormat(sdfStr);//new SimpleDateFormat(sdfStr);
            try {
                sdf.parse("2018-02-11 16:36:39");
            } catch (ParseException e) {
                e.printStackTrace();
            }
        }
        long end = System.currentTimeMillis();
        System.out.println((end-begin)/1000.0);
        
        begin = System.currentTimeMillis();
        for(int i=0;i<cnt;i++){
            Kit.parse("2018-02-11 16:36:39",sdfStr);
        }
        end = System.currentTimeMillis();
        System.out.println((end-begin)/1000.0);
    }
}
 

为了方便,以上程序使用的是jfinal框架的数据库操作,使用JDBC也是同样道理,批量提交的逻辑大概是:

Connection conn=xxxx

Statement st = conn.createStatement();

st.addBatch(sql语句1);st.addBatch(sql语句2);st.addBatch(sql语句3);st.addBatch(sql语句n); …………

st.executeBatch();

程序里也包含了用5分钟数据高效生成30分钟数据的逻辑,可参考。

如果电脑性能好,请修改线程池中的线程数,比如8线程可修改为7:

ExecutorService fixedThreadPool = Executors.newFixedThreadPool(4);

程序中的初始化数字请自行修改批次大小:

batchSize=30;

valueSize=20;

 

Kit.readFileByLines是把文件按行读到List中,代码为:

public static List<String> readFileByLines(String fileName,String encode) {

List<String> list = new ArrayList<String>();
try {
InputStreamReader isr = new InputStreamReader(new FileInputStream(
fileName), encode);
BufferedReader reader = new BufferedReader(isr);
String line = null;
while ((line = reader.readLine()) != null) {
if (line.length() > 0)
list.add(line);
}
reader.close();
isr.close();
} catch (Exception e) {
e.printStackTrace();
}
return list;

}

其它调用到Kit啥的大家都看得懂,自行实现。

程序为了实现更高性能,对于数据已存在的检测只对第一个有效文件的第一行进行检测,如果已存在数据,将不再进行导入。

mysql几个关键的配置:

myisam_max_sort_file_size=40G

key_buffer_size=2048M

innodb_buffer_pool_size=4096M

java调用程序:

GatherFromTdxFile3.hdFileData("C:\\new_tdx2\\T0002\\export");

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值