测试PreparedStatement的批处理效率

今天研究了一下Java中PreparedStatement的批处理能力. 测试环境如下:

jdk1.6.0_21 +Mysql 5.1
Core 2 Duo T5470 (1.60GHz) + 3G内存

测试的是实现最简单的插入:

INSERT INTO `testdb` (`id`, `value`) VALUES (123456, 'http://www.pptv.com');

共插入20万条数据, 每个测试前都把数据库清空, 然后执行插入, 从开始插入进行计时.每项测试跑3次取平均值.

分为两项测试: 1个是不使用批处理, 逐条插入; 一个是使用addBatch(), 分别按50, 100, 500, 1000, 5000, 10000, 50000, 100000的划分方式进行批量插入. 关键代码如下:

逐行插入:

for (int times = 0; times < TEST_TIMES; times++) {
    cleanDB();
    conn = DBMgr.getDBconn("testdb");
    long starttime = System.currentTimeMillis();
    for (int i = 0; i < MAX_COUNT; i++) {
         pstmt = conn.prepareStatement(SQL);
         pstmt.executeUpdate();
         pstmt.close();
         System.out.println("working: " + i + "/" + MAX_COUNT);
    }
    long endtime = System.currentTimeMillis();
    logger.info("第" + (times + 1) + "次测试, 耗时" + (endtime - starttime) + "ms");
    alltime += endtime - starttime;
}
logger.info("平均耗时: " + (alltime / TEST_TIMES) + "ms");

批处理操作

for (int times = 0; times < TEST_TIMES; times++) {
    cleanDB();
    conn = DBMgr.getDBconn("testdb");
    conn.setAutoCommit(false);
    int num = 0;
    long starttime = System.currentTimeMillis();
    pstmt = conn.prepareStatement(SQL);
    for (int i = 0; i < MAX_COUNT; i++) {
        pstmt.addBatch();
        num++;
        if (num % count == 0 || num == MAX_COUNT) {
            pstmt.executeBatch();
            pstmt.clearBatch();
            pstmt.close();
            pstmt = conn.prepareStatement(SQL);
        }
        System.out.println("working: " + i + "/" + MAX_COUNT);
    }
    long endtime = System.currentTimeMillis();
    logger.info("第" + (times + 1) + "次测试, 耗时" + (endtime - starttime) + "ms");
    alltime += endtime - starttime;
}
logger.info("平均耗时: " + (alltime / TEST_TIMES) + "ms");

记录的log如下:

11:59:25,703 DBTestMulti test  *** 以100000为一次批量操作, 执行批处理语句  ***
11:59:25,703 BaseDBTest cleanDB 初始化数据库
11:59:58,984 DBTestMulti test 第1次测试, 耗时33000ms
11:59:58,984 BaseDBTest cleanDB 初始化数据库
12:01:21,687 DBTestMulti test 第2次测试, 耗时32594ms
12:01:21,687 BaseDBTest cleanDB 初始化数据库
12:02:44,640 DBTestMulti test 第3次测试, 耗时32562ms
12:02:44,640 DBTestMulti test 平均耗时: 32718ms
12:02:47,093 DBTestMulti test  *** 以50000为一次批量操作, 执行批处理语句  ***
12:02:47,093 BaseDBTest cleanDB 初始化数据库
12:03:19,734 DBTestMulti test 第1次测试, 耗时32625ms
12:03:19,734 BaseDBTest cleanDB 初始化数据库
12:04:43,406 DBTestMulti test 第2次测试, 耗时33328ms
12:04:43,406 BaseDBTest cleanDB 初始化数据库
12:06:06,687 DBTestMulti test 第3次测试, 耗时32609ms
12:06:06,687 DBTestMulti test 平均耗时: 32854ms
12:06:09,125 DBTestMulti test  *** 以10000为一次批量操作, 执行批处理语句  ***
12:06:09,125 BaseDBTest cleanDB 初始化数据库
12:06:40,921 DBTestMulti test 第1次测试, 耗时31781ms
12:06:40,921 BaseDBTest cleanDB 初始化数据库
12:08:03,125 DBTestMulti test 第2次测试, 耗时32032ms
12:08:03,125 BaseDBTest cleanDB 初始化数据库
12:09:26,859 DBTestMulti test 第3次测试, 耗时32781ms
12:09:26,859 DBTestMulti test 平均耗时: 32198ms
12:09:29,296 DBTestMulti test  *** 以5000为一次批量操作, 执行批处理语句  ***
12:09:29,296 BaseDBTest cleanDB 初始化数据库
12:10:00,406 DBTestMulti test 第1次测试, 耗时31094ms
12:10:00,406 BaseDBTest cleanDB 初始化数据库
12:11:23,031 DBTestMulti test 第2次测试, 耗时31953ms
12:11:23,031 BaseDBTest cleanDB 初始化数据库
12:12:46,265 DBTestMulti test 第3次测试, 耗时32187ms
12:12:46,265 DBTestMulti test 平均耗时: 31744ms
12:12:48,718 DBTestMulti test  *** 以1000为一次批量操作, 执行批处理语句  ***
12:12:48,718 BaseDBTest cleanDB 初始化数据库
12:13:20,468 DBTestMulti test 第1次测试, 耗时31750ms
12:13:20,468 BaseDBTest cleanDB 初始化数据库
12:14:43,703 DBTestMulti test 第2次测试, 耗时32625ms
12:14:43,703 BaseDBTest cleanDB 初始化数据库
12:16:06,203 DBTestMulti test 第3次测试, 耗时32125ms
12:16:06,203 DBTestMulti test 平均耗时: 32166ms
12:16:08,656 DBTestMulti test  *** 以500为一次批量操作, 执行批处理语句  ***
12:16:08,656 BaseDBTest cleanDB 初始化数据库
12:16:40,906 DBTestMulti test 第1次测试, 耗时32235ms
12:16:40,906 BaseDBTest cleanDB 初始化数据库
12:18:03,625 DBTestMulti test 第2次测试, 耗时32547ms
12:18:03,625 BaseDBTest cleanDB 初始化数据库
12:19:27,343 DBTestMulti test 第3次测试, 耗时33265ms
12:19:27,343 DBTestMulti test 平均耗时: 32682ms
12:19:30,406 DBTestMulti test  *** 以100为一次批量操作, 执行批处理语句  ***
12:19:30,406 BaseDBTest cleanDB 初始化数据库
12:20:53,875 DBTestMulti test 第1次测试, 耗时32782ms
12:20:53,875 BaseDBTest cleanDB 初始化数据库
12:22:17,765 DBTestMulti test 第2次测试, 耗时33687ms
12:22:17,765 BaseDBTest cleanDB 初始化数据库
12:23:41,609 DBTestMulti test 第3次测试, 耗时33531ms
12:23:41,609 DBTestMulti test 平均耗时: 33333ms
12:23:44,421 DBTestMulti test  *** 以50为一次批量操作, 执行批处理语句  ***
12:23:44,421 BaseDBTest cleanDB 初始化数据库
12:25:07,578 DBTestMulti test 第1次测试, 耗时32500ms
12:25:07,578 BaseDBTest cleanDB 初始化数据库
12:26:31,796 DBTestMulti test 第2次测试, 耗时33718ms
12:26:31,796 BaseDBTest cleanDB 初始化数据库
12:27:55,640 DBTestMulti test 第3次测试, 耗时33562ms
12:27:55,640 DBTestMulti test 平均耗时: 33260ms
12:27:58,468 BaseDBTest cleanDB 初始化数据库
14:08:25,484 DBTestSingle test 第1次测试, 耗时5976406ms
14:08:25,484 BaseDBTest cleanDB 初始化数据库
15:47:18,828 DBTestSingle test 第2次测试, 耗时5882735ms
15:47:18,828 BaseDBTest cleanDB 初始化数据库

其中批处理都是执行3次取平均值, 而逐行操作, 一次执行时间就要1个半小时, 所以只跑了两次. 日志筛选如下:

每次100000条, 分2次处理: 32718ms
每次50000条, 分4次处理: 32854ms
每次10000条, 分20次处理: 32198ms
每次5000条, 分40次处理: 31744ms
每次1000条, 分200次处理: 32166ms
每次500条, 分400次处理: 32682ms
每次100条, 分2000次处理: 33333ms
每次50条, 分4000次处理: 33260ms

而最后的两条是没有使用批处理跑出来的 用时近600000ms, 近1小时40分钟, 完全不是一个数量级的.

相比较而言, 批处理语句在量的划分上差别并不明显, 在千级的划分只有一点点的减少.

vim解决/bin/bash^M: bad interpreter错误

可能是你的脚本文件是DOS格式的, 即每一行的行尾以\r\n来标识, 其ASCII码分别是_0x0D_, 0x0A. 而Unix只有\n. 可以有很多种办法看这个文件是DOS格式的还是UNIX格式的, 还是MAC格式的. 比如

vim filename

然后用命令:set ff?可以看到dos或unix的字样.

如果确实是dos的换行方式, 出现这个问题的原因, 通常是该文本文件由windows通过ftp上传, 或者直接从windows下copy的一段代码, 导致了换行方式不同. 只要把文件中的\r换行符删除就好了.

可以在上传的时候选择ascii text模式

或者手动转换

方法1:

sed -i "s/\r//" <filename>

方法2:

Vim中执行:%s/^M//g

^M中的^不是Shift+6, 而是先按Ctr-V 再按 Ctrl-M

Java循环遍历不定长数组链表

功能描述 一个链表中保存多个不同长度的数组, 根据一个索引值, 能取到对应的元素所在的数组下标

public static int[] calcIndexArr(final List<String[]> list, int index) {
    int listSize;
    if (list == null || (listSize = list.size()) == 0) {
        return new int[0];
    }

    int max = 1;
    for (int i = 0; i < list.size(); i++) {
        max *= list.get(i).length;
    }
    while (index >= max) {
        index -= max;
    }

    int[] indexArr = new int[list.size()];

    for (int i = 0; i < listSize; i++) {
        int b = 1;
        for (int j = i + 1; j < listSize; j++) {
            b *= list.get(j).length;
        }
        int a = index / b;
        indexArr[i] = a;
        index = index - a * b;
    }
    return indexArr;
}

测试方法:

public void testCalcIndexArr() {
    List<String[]> list = new ArrayList<String[]>();
    list.add(new String[2]);
    list.add(new String[4]);
    list.add(new String[1]);
    for (int i = 0; i < 30; i++) {
        int arr[] = RandName.calcIndexArr(list, i);
        assertEquals(3, arr.length);
        System.out.println(arr[0] + "," + arr[1] + "," + arr[2]);
    }
}

测试结果:

0,0,0
0,1,0
0,2,0
0,3,0
1,0,0
1,1,0
1,2,0
1,3,0
0,0,0
0,1,0
0,2,0
0,3,0
1,0,0
1,1,0
1,2,0
1,3,0
0,0,0
0,1,0
0,2,0
0,3,0
1,0,0
1,1,0
1,2,0
1,3,0
0,0,0
0,1,0
0,2,0
0,3,0
1,0,0
1,1,0
outlook联系人批量导入google

现在Android平台的手机非常流行, 很多人都选择了功过google帐号来同步和管理联系人. 手机上对联系人管理效率非常的低, 在web上管理速度又很慢. 我下面说说我自己的解决方案.

从用智能手机开始, 我就习惯了用Outlook对我的联系人进行管理, 随着这几年换过很多操作系统的手机, 都是支持和Outlook直接进行同步的, 包括windows smartphone, windows pocket pc, blackberry, palm os, ios, symbian…唯独碰到android这朵奇葩, 默认只支持google帐号同步, 于是就牵扯到从outlook -> google 和 google -> outlook双向同步的问题.

虽然outlook支持contacts导出为csv而且google联系人也支持导入csv, 但是我非常不推荐这种格式, 她会把你联系人信息里的头像都丢失了, 这里推荐使用vCard名片格式导入.

那么接下来就是要解决outlook的联系人批量导出和google批量导入的问题了. 会涉及一点点编程的东西, 很简单, 一点一点说. 以outlook 2010为例. 其他版本的outlook类似.

准备工作

启动outlook的开发工具.

outlook 2010默认是隐藏了这个功能的, 我们需要开启它.

outlook菜单栏 -> 文件 -> 选项, 打开outlook选项, 在自定义功能区主选项卡中勾选开发工具.

outlook选项

这个时候在主选项卡中应该可以看到’开发工具’选项卡了.

outlook开发工具

outlook批量导出联系人

1. 先在c:根目录创建一个文件夹contacts, 用来保存导出的联系人文件.

2. 在开发工具 -> 中创建一个宏, export.

outlook宏

3. 填入代码

Sub export()
Dim MyContacts As Outlook.MAPIFolder
Dim ContItem As Outlook.ContactItem
Dim SaveIndex As Integer

Set MyContacts = Application.GetNamespace("MAPI").GetDefaultFolder(olFolderContacts) 

SaveIndex = 0
For Each ContItem In MyContacts.Items
    FileName = "c:\contacts\" & SaveIndex & ".vcf"
    ContItem.SaveAs FileName, olVCard
    SaveIndex = SaveIndex + 1
    Next

End Sub

4. 执行这段脚本.

执行outlook宏

网上也有一些教程提供了类似的代码, 无非都是用联系人名称作为导出到的vcf文件名, 我不推荐这样做, 因为谁也难保自己的联系人里会不会有重名的现象, 而且我们后期的目标是批量上传这批文件, 所以对这些文件的名称没有要求. 我直接用递增的数字作为文件名, 以保证每个联系人都能准确输出.

5. 检查一下看看c:\contacts是不是已经保存了你所有的联系人, 如果保存成功, 第一步的导出工作已经成功.

google批量导入联系人

这一步是为了将上一步导出的所有联系人导入到google. 我们观察一下google 联系人的导入功能就会发现, 它一次只能导入一个vcf文件, 下面我们就要解决将多个vcf文件合并的操作. 可以尝试用记事本打开一个vsf看看, 就会发现其实vcf就是一个文本文件, 以BEGIN:VCARD开始, 以END:VCARD结束, 这样就方便了, 我们可以尝试合并这些文本文件了.

  • 开始菜单 -> 运行, 输入”cmd”打开命令行程序. 执行下面的操作:
    cd c:\contacts
    c:
    copy *.vcf all.vcf

这一步执行了将所有vcf文件合并到all.vcf的操作, 下面就可以一次性导入google联系人了.

gmail导入联系人

关于google导出和outlook导入联系人

这里又是一个很麻烦的东西, 我研究过google的导出的功能, 虽然支持csv和vcard(vcf)两种方式, 但是都会导致联系人头像等信息丢失的发生, 所以我依然不推荐使用google联系人来管理你的通讯录, 我更倾向于, 使用outlook来管理, 然后清空google的联系人以后, 重新导入到google.

Maven打包非web项目时包含第三方jar包

非web项目中, 默认使用maven install时, 并不会将项目依赖的jar包打包放进项目中, 需要对pom.xml做如下修改:

<build>
  <plugins>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-dependency-plugin</artifactId>
      <executions>
        <execution>
          <id>copy-dependencies</id>
          <phase>package</phase>
          <goals>
            <goal>copy-dependencies</goal>
          </goals>
          <configuration>
            <outputDirectory>${project.build.directory}/lib</outputDirectory>
            <overWriteReleases>false</overWriteReleases>
            <overWriteSnapshots>false</overWriteSnapshots>
            <overWriteIfNewer>true</overWriteIfNewer>
          </configuration>
        </execution>
      </executions>
    </plugin>
  </plugins>
</build>