发表于: 2018-04-09 11:08:39

1 858




day12



今天完成的事情:


1. 继续测试批处理

      PreparedStatement pstat = con.prepareStatement(sql);
    // 模拟数据
for (int i=0; i<1000000; i++) {
pstat.setString(1, "空见法师"+i);
      pstat.setString(2, "");
      pstat.setString(3, "108");

      if(i % 50000 == 0){
            pstat.addBatch();
            pstat.executeBatch();
         }
}
pstat.executeBatch();
   pstat.clearBatch();

     pstat.close();
     con.close();

在跑了以上代码后,我开心的发现自己的插100万条数据只要4秒,并兴奋的告诉了周围人,后来在数据库执行SELECT COUNT(*) FROM table1;查出来的数据才20条!!

我仔细检查了代码,发现了错误,上面判断能被5万整除才添加一次批处理,而100万刚好是5万的20倍数,所以数据库只有20条数据。这个错误真的是,唉。

每一条记录只有.addBatch()后才会放进批处理中,放完一定量后再

pstat.executeBatch();  执行批处理

pstat.clearBatch(); 清空批处理


进入正式测试,100万数据插入

100万整个一批   27s

50万                 26s

20万                28

10万                19s

5万一批处理    18s

1万                  28s

5000               48s

由上面可知,批处理的速度跟每批处理数据量有关,每次处理太少会慢,但太高也不好,经过上面的测试对于100万的插入每批10万或5万最佳


然后测试插入1000万条数据。用时192s

代码如下

@Test
public void testForRemote() throws Exception {
try {
String sql = "INSERT INTO table1(NAME,gender,age) VALUES(?,?,?)";
Connection con = null;
//加载驱动
       Class.forName("com.mysql.jdbc.Driver");
con = DriverManager.getConnection("jdbc:mysql://39.107.247.104:3306/Jnshu1?useUnicode=true&"+
"characterEncoding=utf-8&useSSL=false&rewriteBatchedStatements=true",
"root","123456xyz");
PreparedStatement pstat = con.prepareStatement(sql);
long start = System.currentTimeMillis();
// 模拟数据
     for (int i=0; i<10000000; i++) {
pstat.setString(1, "空见法师"+i);
pstat.setString(2, "");
pstat.setString(3, "108");
pstat.addBatch();
if(i % 100000 == 0){
pstat.executeBatch();
pstat.clearBatch();
}
}
pstat.executeBatch();
pstat.clearBatch();
long end = System.currentTimeMillis();
pstat.close();
con.close();
System.out.print((end-start)/1000+"秒。");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}

}


2.生成随机数、随机字母随机汉字

public void testForRemote2() throws Exception {
     //(int)(Math.Random()*n) 可以得到一个0n范围的随机数
   //产生一个大于等于0到小于1double型随机数
   double d = Math.random();
     //乘以10然后再强转为int,转为int后会将小数部分弃掉,不是四舍五入
   System.out.println((int)(d*10)+" and "+ d);
     //以下代码可以生成随机的字符
   /**
      * 'z'-'a'=25Math.random()产生的是01的小数[0,1),乘以26得到的是[0,26)double
      * 然后'a'ASCII码表对应的是97,加上[0,26)double得到[97,123)double,但是自动舍弃小数部        分。
    * 从而的到了az的码表值。
    */
     char ca = (char)('a'+Math.random()*('z'-'a'+1));
     System.out.println(ca);
   /**
      * 汉字的unicode范围是:0x4E00~0x9FA5。故可通过以下代码生成随机汉字
    *(char) (0x4e00 + (int) (Math.random() * (0x9fa5 - 0x4e00 + 1)))
     * 其实不用int强转,后面的char强转会弃掉后面的小数位
   */
   System.out.println((char) (0x4e00 + (Math.random() * (0x9fa5 - 0x4e00 + 1))));

}


3.使用新学的随机函数,制造100万个不同的个人信息。

他们的姓名不同,因为我设定的三个随机汉字,顾不上遵守百家姓的规则了

他们的性别也是随机的 '男' 或 '女' ,我还分配了 ‘人妖’这一性别,尽在程序出错是出现

它们的qq号我用的10位随机数模拟,年龄也是两位的随机数来模拟。代码如下

@Test
public void testForRemote() throws Exception {
try {
String sql = "INSERT INTO table1(NAME,gender,age,qq) VALUES(?,?,?,?)";
       Connection con = null;
       //加载驱动
     Class.forName("com.mysql.jdbc.Driver");
       con = DriverManager.getConnection("jdbc:mysql://39.107.247.104:3306/Jnshu1?useUnicode=true&"+
"characterEncoding=utf-8&useSSL=false&rewriteBatchedStatements=true",
               "root","123456xyz");
       PreparedStatement pstat = con.prepareStatement(sql);
       list2 = getPersonWithArg();
       System.out.println(list2.size());

       long start = System.currentTimeMillis();
       for (int i=0; i<list2.size(); i++) {

         Person person = list2.get(i);
         pstat.setString(1, person.getNAME());
         pstat.setString(2, person.getGender());
         pstat.setString(3, person.getAge());
         pstat.setString(4, person.getQq());

         pstat.addBatch();
         if(i % 100000 == 0){
             pstat.executeBatch();
             pstat.clearBatch();
         }
}
        pstat.executeBatch();
         pstat.clearBatch();
           long end = System.currentTimeMillis();
       pstat.close();
       con.close();
       System.out.print((end-start)/1000+"秒。");
   } catch (ClassNotFoundException e) {
e.printStackTrace();
   }

}

// 模拟数据
public List<Person> getPersonWithArg(){

for (int r = 0; r < 1000000 ; r++) {
       String qq = "";
       String sex = "";
       String age = "";
       String name = "";
       Person person = new Person();

       //生成3个随机汉字来模拟姓名
     for (int i = 0; i < 3; i++) 
           name += (char) (0x4e00 + (Math.random() * (0x9fa5 - 0x4e00 + 1)));
       }
       person.setNAME(name);

       //性别
     int sexf = (int)Math.random() * 2;
       if(sexf==0){
           sex = "";
       }else if( sexf == 1){
           sex = "";
       }else {
           sex = "人妖";
       }
       person.setGender(sex);

       //年龄
     int d;
       for (int i = 0; i < 2; i++) {
           d = (int)(Math.random()*10);
           age += d;
       }
       person.setAge(age);

       // qq
       int j;
       for (int i = 0; i < 10; i++) {
           d = (int)(Math.random()*10);
           qq += d;
       }
       person.setQq(qq);

       list.add(person);
   }
   return list;
}

最终结果数据库里出现了100万个各不相同的个人信息(模拟)。由于姓名是随机的汉字,而全体汉字中其实大部分都是繁体字,所以可以看到姓名都是那样的,注意,那不是乱码。

用时20秒,偶尔抽风会飙到40秒。但多次测试是20秒。


小课堂

String 字符串常量

StringBuffer 字符串变量(线程安全)

StringBuilder 字符串变量(非线程安全)

 简要的说, String 类型和 StringBuffer 类型的主要性能区别其实在于 String 是不可变的对象, 因此在每次对 String 类型进行改变的时候其实都等同于生成了一个新的 String 对象,然后将指针指向新的 String 对象,所以经常改变内容的字符串最好不要用 String ,因为每次生成对 象都会对系统性能产生影响,特别当内存中无引用对象多了以后, JVM 的 GC 就会开始工作,那速度是一定会相当慢的。

 而如果是使用 StringBuffer 类则结果就不一样了,每次结果都会对 StringBuffer 对象本身进行操作,

 而不是生成新的对象,再改变对象引用。所以在一般情况下我们推荐使用 StringBuffer ,特别

 是字符串对象经常改变的情况下。

 例:

 String S1 = “This is only a” + “ simple” + “ test”;

 StringBuffer Sb = new StringBuilder(“This is only a”).append(“ simple”).append(“ test”);






明天计划的事情


1.再用框架测试数据插入

2.maven在服务器跑一遍


遇到的问题


1. 在用随机信息填充数据库时,发生了内存溢出的报错,执行以下代码导致内存溢出

现在想想,应该是太多的字符串修改用了String而没有用StringBuffer,100万的数据没对字符串修改一次就多一个指针,光3个汉字就要出来300万没用的字符串指针,所以这种频繁操作字符串场景要用StringBuffer。


2.在插入完数据后,出现了第一个姓名3个汉字,第二个6个,第三个9个......还有qq、年龄等如下图

正常的该是这样的,分别是id、name、gender、age、qq 字段。


可却出现了这样的情况:


越到后面越会发生雪崩,插100万更不得了。


最终找到了问题原因,可先看我下面代码,错误处已被我标红

public List<Person> getPersonWithArg(){

Person person = new Person();
   String qq = "";
   String sex = "";
   String age = "";
   String name = "";

   for (int r = 0; r < 50 ; r++) {

//生成3个随机汉字来模拟姓名
       for (int i = 0; i < 3; i++) {

           name += (char) (0x4e00 + (Math.random() * (0x9fa5 - 0x4e00 + 1)));
       }
       person.setNAME(name);

       //性别
       int sexf = (int)Math.random() * 2;
       if(sexf==0){
           sex = "";
       }else if( sexf == 1){
           sex = "";
       }else {
           sex = "人妖";
       }
       person.setGender(sex);

       //年龄
       int d;
       for (int i = 0; i < 2; i++) {
           d = (int)(Math.random()*10);
           age += d;
       }
       person.setAge(age);

       // qq
       int j;
       for (int i = 0; i < 10; i++) {
           d = (int)(Math.random()*10);
           qq += d;
       }
       person.setQq(qq);

       list.add(person);
   }
return list;
}

关于name、gender、age、qq 的字符串声明在了循环外面,意味着每一次循环后string内部的字符串没有被释放或置空,而是加在前面的内容上,于是出现了上图的效果,如果插100万条数据,那么第100万条数据的姓名将是300万字的姓名,好可怕。string在循环外声明,那么循环执行完后相当于在给它加了n个后缀,然后有set到了实体类的属性中。


3. 今天还发生了一个错误,这个错误是紧接着上一个的,当我乖乖把那四个String的声明放到了for循环体内后,插入完数据去看数据库,发现上一个问题解决了,但是还有一个问题。同样可以参考上面那段代码那个被我标绿的new Person()的声明,那个person对象同样声明在循环体外。首先字段内的信息不会叠加了,但是全部一样:

这个问题原因也在我不断调试中找到了,没错,就是因为person在循环外声明,也就是说这个方法内就一个person对象,虽然后面后 list.add(person) ,但不过以一个对象add 了一百万遍,更怪异的是数据库数据全一样,这就揭示了list的内部实现原理,首先我们知道每创一个对象就在内存中开辟了一块内存空间,然后声明一个变量可以指向它的首地址,同样list集合在add对象时,也是将建一个索引指向该对象的内存首地址,list每执行一次add,就是将一个对象的地址加进来,最终由于list集合中的所有元素都是同一个per送的首地址,所以你get出来的都是一个,那么数据库里插入的信息自然都是一样的。那问题又来了,我前面循环了100万次,又给person对象set过100万次属性,数据库里的信息是哪一次的呢? 没错,就是最后一次的,最后一次会覆盖前面的值,所以上图中的信息是 "最后一个person" 的信息。不信?我们来演示下,很简单,将循环调到10次,然后把person和string的声明都放循环外面如下

跑一次程序看看数据库如下,可以看到,姓名刚好 3*10 30个汉字


附一张我画的List集合所有元素执行一个对象示意图



收获


     

      凌晨四点钟,看到海棠花未眠



 



返回列表 返回列表
评论

    分享到