Java Concurrent CyclicBarrier

Introduction

CyclicBarrier 很久沒用都忘了, 來複習一下..

Description

簡單說就是很多個 thread 去作事情, 作完之後就 await.
CyclicBarrier 等作完的 thread 達到指定的個數後, await 就結束.

Code

public class CyclicBarrierMain {

    public static void main(String[] params) throws InterruptedException {
        int runner = 5;
        CountDownLatch gameOver = new CountDownLatch(runner);
        CyclicBarrier b = new CyclicBarrier(runner, () -> {
            System.out.println("barrier done");
        });
        IntStream.range(0,runner).forEach(idx -> {
            new Thread() {
                @Override
                public void run() {
                    try {
                        System.out.println("wait " + idx);
                        b.await();
                        System.out.println("count down " + idx);
                        gameOver.countDown();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }.start();
        });
        gameOver.await();
        System.out.println("game over");
    }

}

Java Concurrent Phaser

Introduction

今天才知道 Java 7 有提供一個 Phaser class 來處理更複雜的 CountDownLatch 需求.

Simple Phaser

想用 Phaser, 首先你的 threads 工作要有分階段, 一個階段沒做完就不能進行下個階段.
有了這個限制後就可以照下面的流程做事情
  1. 先註冊, 看有幾個 thread 要做事情.
    => 透過呼叫 Phaser#register 去註冊
  2. 做完之後呼叫 arrive, 告訴 Phaser 有事情做完了
  3. 當每個 thread 都把事情做完, Phaser 就會進入下一個階段, 稱做 advance
  4. 當進入下一階段, Phaser 的 onAdvance method 就會被呼叫,
    這個 method 是留給我們 developer 去實做
以上. 簡單的概念, 覺得看好久都沒看懂, 希望看這篇的人可以簡單看懂.
接著是範例, 這範例是

  1. 假設有幾個人賽跑
  2. 首先要到達起跑線 
  3. 都到了以後才開跑 
  4. 每個人都到達終點舊比賽結束.

public class PhaserRacingGame {

    public static void main(String[] params) {
        Random r = new Random();
        Phaser perGamePhase = new Phaser() {
            @Override
            protected boolean onAdvance(int phase, int registeredParties) {
                System.out.println("phase:" + phase + ", registeredParties:" + registeredParties + ", arrived:" + getArrivedParties());
                switch (phase) {
                    case 0:
                        System.out.println("start!");
                        break;
                    case 1:
                        System.out.println("game finish");
                        break;
                    default:
                        System.out.println("unexpected:" + phase);
                }
                return super.onAdvance(phase, registeredParties);
            }
        };
        IntStream.range(0,5).forEach(idx -> {
            perGamePhase.register();
            new Thread(() -> {
                System.out.println(idx + " arrive starting line and wait for start");
                perGamePhase.arriveAndAwaitAdvance();
                System.out.println(idx + " run!! startTime:" + System.currentTimeMillis());
                long spendTime = r.nextInt(1000);
                sleep(spendTime);
                perGamePhase.arrive();
            }).start();
        });
        sleep(5000);
    }

    private static void sleep(long ms) {
        try {
            TimeUnit.MILLISECONDS.sleep(ms);
        } catch (InterruptedException e) {
        }
    }

}

Parent Phaser

再來比較複雜的是 Parent Phaser, Parent Phaser 的目的是減少 synchronization 的時候搶 lock 的碰撞. 
使用 Parent Phaser 的時候要注意的是: advance 是發生在 Parent Phaser 而不是 Child Phaser, 
使用 Parent Phaser 的時候, 是可以確保每個 Child Phaser 準備號都進入下一個 phase 的時候, 已經先到達的 Child Phaser 才可以更進一步 arrive.
比方說世足十六強賽, 要等所有能進入八強賽的隊伍都出線之後才可以開始進行八強賽.
那 Parent Phaser 就是賽程, 十六強賽則是有八個 Child Phaser.
以下範例是看出特性, 方便 copy paste 觀察
public class ParentPhaser {

    public static void main(String[] params) {
        Phaser parent = new Phaser() {
            @Override
            protected boolean onAdvance(int phase, int registeredParties) {
                System.out.println("parent phase:" + phase + ", registeredParties:" + registeredParties);
                return super.onAdvance(phase, registeredParties);
            }
        };
        Phaser child1 = new Phaser(parent, 4) {
            @Override
            protected boolean onAdvance(int phase, int registeredParties) {
                System.out.println("child phase:" + phase + ", registeredParties:" + registeredParties);
                return super.onAdvance(phase, registeredParties);
            }
        };
        Phaser child2 = new Phaser(parent, 3) {
            @Override
            protected boolean onAdvance(int phase, int registeredParties) {
                System.out.println("child phase:" + phase + ", registeredParties:" + registeredParties);
                return super.onAdvance(phase, registeredParties);
            }
        };
        IntStream.range(0,10).forEach(idx -> {
            child1.arrive();
            child1.arrive();
            child1.arrive();
            child1.arrive();
//            child1.arrive(); // cause error, child1 need wait for child2 finish
            child2.arrive();
            child2.arrive();
            child2.arrive();
        });
    }


}

Zip files

In a project, I need zip file and append to separated folders.

public static void main(String[] params) throws IOException {
 List<String> srcFiles = Arrays.asList("E:\\file1.jar", 
   "E:\\file2.jar");
 FileOutputStream fos = new FileOutputStream("E:\\targetfile.zip");
 ZipOutputStream zipOut = new ZipOutputStream(fos);
 for (String srcFile: srcFiles) {
  File fileToZip = new File(srcFile);
  System.out.println(fileToZip + ", canRead:" + fileToZip.canRead());
  ZipEntry zipEntry = new ZipEntry(String.valueOf(srcFiles.indexOf(srcFile)) + "/" + fileToZip.getName());
  zipOut.putNextEntry(zipEntry);

  FileInputStream fis = new FileInputStream(fileToZip);
  IOUtils.copy(fis, zipOut);
  IOUtils.closeQuietly(fis);
 }
 IOUtils.closeQuietly(zipOut);
 IOUtils.closeQuietly(fos);
}

Scan log and paste to Google sheet to know the Flush pressure

Introduction

After a performance issue happen, we have less information about system status at that time.
There is flush log in Cassandra, so I write code to parse it and write to output file.
Thus I can paste the parse result to google sheet or excel to draw charts such as:

Code

public  class  ScanLog  {

  private  static  final  String  fileSufix  =  "10";
  private  static  final  String  LOG_FOLDER  =  "/cassandraLogPath";
  private  static  final  String  OUTPUT_FOLDER  =  "/outputpath";

  public  static  void  main(String[]  params)  throws  IOException,  ParseException  {
    File  folder  =  new  File(LOG_FOLDER);
    Map<String,List<CompactSize>>  cf2CompactSizes  =  toCompactSizes(folder);
    FileUtils.write(new  File(OUTPUT_FOLDER  +  "output-compact-mb-"  +  fileSufix  +  ".csv"),  toCompactSizesString(cf2CompactSizes),  "UTF-8");
    List<CompactSize>  aggCompactSizes  =  aggregate(toCompactSizeList(folder));
    FileUtils.write(new  File(OUTPUT_FOLDER  +  "output-compact-mb-agg-"  +  fileSufix  +  ".csv"),  toCompactSizesString(aggCompactSizes),  "UTF-8");
    List<FlushEvent>  events  =  toEvents(folder);
    FileUtils.write(new  File(OUTPUT_FOLDER  +  "output-queue-"  +  fileSufix  +  ".csv"),  toString(events),  "UTF-8");
    Map<String,  List<Enqueue>>  cfToE  =  cfToE(folder);
    Map<String,  LinkedHashMap<String,Long>>  cfToTimeToAvg  =  cfToTimeToAvg(cfToE);
    FileUtils.write(new  File(OUTPUT_FOLDER  +  "output-"  +  fileSufix  +  ".csv"),  toString(cfToE),  "UTF-8");
    FileUtils.write(new  File(OUTPUT_FOLDER  +  "output-"  +  fileSufix  +  "-avg.csv"),  toAvgString(cfToTimeToAvg),  "UTF-8");
  }

  private  static  List<CompactSize>  aggregate(List<CompactSize>  compactSizes)  {
    LinkedHashMap<String,List<CompactSize>>  minutes2CompactSizes  =  new  LinkedHashMap<>();
    compactSizes.forEach(compactSize  ->  {
      String  timeMinutes  =  compactSize.getTimeMinutes();
      if  (!minutes2CompactSizes.containsKey(timeMinutes))  {
        minutes2CompactSizes.put(timeMinutes,  new  ArrayList<>());
      }
      minutes2CompactSizes.get(timeMinutes).add(compactSize);
    });  
    LinkedHashMap<String,CompactSize>  minutes2AggregatedSize  =  new  LinkedHashMap<>();
    minutes2CompactSizes.entrySet().forEach(entry  ->  {
      String  minutes  =  entry.getKey();
      entry.getValue().forEach(size  ->  {
        if  (!minutes2AggregatedSize.containsKey(size.getTimeMinutes()))  {
          minutes2AggregatedSize.put(size.getTimeMinutes(),new  CompactSize(size.getTime(),size.getCf(),size.getDataSize()));
        }  else  {
          CompactSize  total  =  minutes2AggregatedSize.get(size.getTimeMinutes());
          CompactSize  newTotal  =  new  CompactSize(size.getTime(),size.getCf(),  size.getDataSize()  +  total.getDataSize());
          minutes2AggregatedSize.put(size.getTimeMinutes(),  newTotal);
        }
      });
    });
    List<CompactSize>  result  =  new  ArrayList<>();
    minutes2AggregatedSize.entrySet().forEach(entry  ->  {
      result.add(entry.getValue());
    });
    Collections.sort(result,  Comparator.comparingLong(CompactSize::getTimeMillis));
    return  result;
  }
  
  private  static  String  toCompactSizesString(List<CompactSize>  cf2compactSizes)  {
    StringBuilder  sb  =  new  StringBuilder();
    cf2compactSizes.forEach(size  ->  {
      sb.append(size.getCf()  +  ","  +  size.getTime()  +  ","  +  size.getDataSizeInMB()).append("\n");
    });
    return  sb.toString();
  }
  
  private  static  String  toCompactSizesString(Map<String,List<CompactSize>>  cf2compactSizes)  {
    StringBuilder  sb  =  new  StringBuilder();
    cf2compactSizes.keySet().forEach(cf  ->  {
      cf2compactSizes.get(cf).forEach(size  ->  {
        sb.append(size.getCf()  +  ","  +  size.getTime()  +  ","  +  size.getDataSizeInMB()).append("\n");
      });
    });
    return  sb.toString();
  }

  private  static  List<CompactSize>  toCompactSizeList(File  folder)  {
    List<CompactSize>  result  =  new  ArrayList<>();
    Arrays.stream(folder.listFiles())
        .filter(file  ->  file.getName().contains("system.log"))
        .forEach(file  ->  {
          try  {
            for  (String  line:  FileUtils.readLines(file))  {
              if  (  CompactSize.isMyPattern(line)  )  {
                CompactSize  compactSize  =  new  CompactSize(line);
                result.add(compactSize);
              }
            }
          }  catch  (Exception  ex)  {
            ex.printStackTrace();
          }
        });
    Collections.sort(result,  Comparator.comparingLong(CompactSize::getTimeMillis));
    return  result;
  }
  
  private  static  Map<String,List<CompactSize>>  toCompactSizes(File  folder)  {
    Map<String,List<CompactSize>>  cf2CompactSizes  =  new  HashMap<>();
    Arrays.stream(folder.listFiles())
        .filter(file  ->  file.getName().contains("system.log"))
        .forEach(file  ->  {
          try  {
            for  (String  line:  FileUtils.readLines(file))  {
              if  (  CompactSize.isMyPattern(line)  )  {
                CompactSize  compactSize  =  new  CompactSize(line);
                if  (!cf2CompactSizes.containsKey(compactSize.getCf()))  {
                  cf2CompactSizes.put(compactSize.getCf(),  new  ArrayList<>());
                }
                cf2CompactSizes.get(compactSize.getCf()).add(compactSize);
              }
            }
          }  catch  (Exception  ex)  {
            ex.printStackTrace();
          }
        });
    cf2CompactSizes.values().forEach(compactSizes  ->  
        Collections.sort(compactSizes,  Comparator.comparingLong(CompactSize::getTimeMillis)));
    return  cf2CompactSizes;
  }
  
  private  static  class  CompactSize  {
    private  final  String  time;
    private  final  String  cf;
    private  final  long  dataSize;
    
    CompactSize(String  time,  String  cf,  long  dataSize)  {
      this.time  =  time;
      this.cf  =  cf;
      this.dataSize  =  dataSize;
    }
    
    CompactSize(String  line)  {
      this.time  =  substringBetween(line,  "]  ",",");
      this.cf  =  substringBetween(line,  "/data/ng/db/data/wsg/",  "/");
      this.dataSize  =  NumberUtils.toInt(StringUtils.replace(substringBetween(line,  "].    ",  "  bytes"),",",""));
    }

    public  static  boolean  isMyPattern(String  line)  {
      return  StringUtils.contains(line,  "CompactionTask.java  (line  302)  Compacted")
          &&  StringUtils.contains(line,  "[/data/ng/db/data/wsg/");
    }
    
    public  long  getDataSizeInMB()  {
      return  dataSize  //  bytes  
          /  1024    //  KB
          /  1024;  //  MB
    }
    
    public  long  getDataSize()  {
      return  dataSize;
    }

    public  String  getCf()  {
      return  cf;
    }

    public  String  getTimeMinutes()  {
      long  millis  =  getTimeMillis();
      return  sdfToMinutes.format(new  Date(millis));
    }
    
    public  Long  getTimeMillis()  {
      return  toMillis(getTime());
    }
    
    public  String  getTime()  {
      return  time;
    }
  }
  
  private  static  String  toString(List<FlushEvent>  events)  {
    StringBuilder  sb  =  new  StringBuilder();
    AtomicInteger  waitForWrite  =  new  AtomicInteger();
    AtomicInteger  writing  =  new  AtomicInteger();
    events.forEach(e  ->  {
      switch  (  e.getType()  )  {
        case  Enqueue:
          waitForWrite.incrementAndGet();
          break;
        case  Write:
          writing.incrementAndGet();
          waitForWrite.decrementAndGet();
          break;
        case  Complete:
          writing.decrementAndGet();
          break;
        default:  
          throw  new  RuntimeException("Shouldn't  happen");
      }
      sb.append(e.getTime()  +  ","  +  waitForWrite  +  ","  +  writing).append("\n");
    });
    return  sb.toString();
  }
  
  private  static  List<FlushEvent>  toEvents(File  folder)  {
    List<FlushEvent>  events  =  new  ArrayList<>();
    Arrays.stream(folder.listFiles())
        .filter(file  ->  file.getName().contains("system.log"))
        .forEach(file  ->  {
          try  {
            for  (String  line:  FileUtils.readLines(file))  {
              Optional<FlushEventType>  type  =  FlushEventType.getByPattern(line);
              type.ifPresent(t  ->  {
                events.add(new  FlushEvent(line));
              });
            }
          }  catch  (Exception  ex)  {
            ex.printStackTrace();
          }
          });
    Collections.sort(events,  Comparator.comparingLong(FlushEvent::getTimeMillis));
    return  events;
  }

  private  static  String  toAvgString(Map<String,  LinkedHashMap<String,Long>>  cfToTimeToAvg)  {
    StringBuilder  sb  =  new  StringBuilder();
    cfToTimeToAvg.keySet().forEach(cf  ->  {
      cfToTimeToAvg.get(cf).forEach((time,avg)  ->  {
        sb.append(cf  +  ","  +  time  +  ","  +  avg).append("\n");
      });
    });
    return  sb.toString();
  }

  private  static  String  toString(Map<String,  List<Enqueue>>  cfToE)  {
    StringBuilder  sb  =  new  StringBuilder();
    cfToE.values().stream().forEach(eList  ->  {
      eList.stream().forEach(e  ->  {
        try  {
          sb.append(e.getCf()  +  ","  +  e.getTime()  +  ","  +  e.serializedMB).append("\n");
        }  catch  (Exception  ex)  {
          ex.printStackTrace();
        }
      });
    });
    return  sb.toString();
  }

  private  static  Map<String,  LinkedHashMap<String,Long>>  cfToTimeToAvg(Map<String,  List<Enqueue>>  cfToE)  {
    Map<String,  LinkedHashMap<String,Long>>  cfToTimeToAvg  =  new  HashMap<>();
    cfToE.keySet().forEach(cf  ->  {
      if  (!cfToTimeToAvg.containsKey(cf))  {
        cfToTimeToAvg.put(cf,  new  LinkedHashMap<>());
      }
      LinkedHashMap<String,List<Long>>  timeToMBList  =  new  LinkedHashMap<>();
      cfToE.get(cf).forEach(enqueue  ->  {
        if  (!timeToMBList.containsKey(enqueue.getTimeMinutes()))  {
          timeToMBList.put(enqueue.getTimeMinutes(),  new  ArrayList<>());
        }
        timeToMBList.get(enqueue.getTimeMinutes()).add(enqueue.serializedMB);
      });
      AtomicReference<String>  previousTime  =  new  AtomicReference<>();
      LinkedHashMap<String,Long>  timeToAvg  =  cfToTimeToAvg.get(cf);
      timeToMBList.keySet().forEach(time  ->  {
        try  {
          if  (previousTime.get()  !=  null)  {
            AtomicLong  totalMB  =  new  AtomicLong();
            timeToMBList.get(time).forEach(mb  ->  {
              totalMB.addAndGet(mb);
            });
            long  currentTimeMillis  =  sdfToMinutes.parse(time).getTime();
            long  previousTimeMillis  =  sdfToMinutes.parse(previousTime.get()).getTime();
            long  gapMinutes  =  TimeUnit.MILLISECONDS.toMinutes(currentTimeMillis  -  previousTimeMillis);
            timeToAvg.put(time,  (totalMB.longValue()  /  gapMinutes));
          }
          previousTime.set(time);
        }  catch  (Exception  ex)  {
          ex.printStackTrace();
        }
      });
    });
    return  cfToTimeToAvg;
  }

  private  static  Map<String,  List<Enqueue>>  cfToE(File  folder)  {
    Map<String,List<Enqueue>>  cfToE  =  new  HashMap<>();
    Arrays.stream(folder.listFiles())
        .filter(f  ->  f.getName().contains("system.log"))
        .forEach(file  ->  {
          try  {
            List<String>  lines  =  IOUtils.readLines(new  FileInputStream(file));
            for  (String  line:  lines)  {
              if  (StringUtils.contains(line,  "Enqueuing  flush  of  Memtable-"))  {
                Enqueue  e  =  new  Enqueue(line);
                if  (e.serializedMB  <=  1)  {
                  continue;
                }
                if  (!cfToE.containsKey(e.getCf()))  {
                  cfToE.put(e.getCf(),  new  ArrayList<>());
                }
                cfToE.get(e.getCf()).add(e);
              }
            }
          }  catch  (Exception  ex)  {
            ex.printStackTrace();
          }
        });
    cfToE.keySet().forEach(k  ->  {
      cfToE.get(k).sort((e1,e2)  ->  {
        try  {
          return  (int)(e1.getTimeMillis()-e2.getTimeMillis());
        }  catch  (Exception  ex)  {
          ex.printStackTrace();
          return  0;
        }
      });
    });
    return  cfToE;
  }

  private  enum  FlushEventType  {
    Enqueue("Enqueuing  flush  of  Memtable-"),
    Write("Writing  Memtable-"),
    Complete("Completed  flushing  ");
    private  final  String  pattern;
    FlushEventType(String  pattern)  {
      this.pattern  =  pattern;
    }

    public  String  getPattern()  {
      return  pattern;
    }

    public  static  Optional<FlushEventType>  getByPattern(String  line)  {
      return  Arrays.stream(values())
          .filter(type  ->  StringUtils.contains(line,  type.getPattern()))
          .findFirst();
    }
  }
  
  private  static  class  FlushEvent  {
    private  final  FlushEventType  type;
    private  final  String  time;
    FlushEvent(String  line)  {
      this.time  =  substringBetween(line,  "]  ",",");
      this.type  =  FlushEventType.getByPattern(line).get();  //  must  success
    }

    public  FlushEventType  getType()  {
      return  type;
    }

    public  Long  getTimeMillis()  {
      return  toMillis(getTime());
    }
    
    public  String  getTime()  {
      return  time;
    }
  }
  
  private  static  class  Enqueue  {
    private  final  String  time;
    private  final  String  cf;
    private  final  int  ops;
    private  final  long  serializedMB;

    Enqueue(String  line)  throws  ParseException  {
      this.time  =  substringBetween(line,  "]  ",",");
      this.cf  =  substringBetween(line,  "Enqueuing  flush  of  Memtable-",  "@");
      this.ops  =  NumberUtils.toInt(substringBetween(line,  "serialized/live  bytes,  ",  "  ops)"));
      this.serializedMB  =  NumberUtils.toInt(substringBetween(substringAfter(line,  "Enqueuing  flush  of  Memtable-"),  "(",  "/"))  /  1024  /  1024;
    }

    @Override
    public  String  toString()  {
      return  new  ToStringBuilder(this)
          .append("time",time)
          .append("cf",cf)
          .append("ops",ops)
          .append("serializedMB",  serializedMB)
          .toString();
    }

    public  String  getTimeMinutes()  {
      long  millis  =  getTimeMillis();
      return  sdfToMinutes.format(new  Date(millis));
    }

    public  Long  getTimeMillis()  {
      return  toMillis(getTime());
    }

    public  String  getTime()  {
      return  time;
    }

    public  String  getCf()  {
      return  cf;
    }

  }

  private  static  final  SimpleDateFormat  sdfToMinutes  =  new  SimpleDateFormat("yyyy-MM-dd  HH:mm");
  private  static  final  SimpleDateFormat  sdf  =  new  SimpleDateFormat("yyyy-MM-dd  HH:mm:SS");

  private  static  long  toMillis(String  time)  {
    try  {
      Date  date  =  sdf.parse(time);
      return  date.getTime();
    }  catch  (Exception  ex)  {
      throw  new  RuntimeException(ex);
    }
  }

}

lombok + Orika + Builder Pattern

Reference


Introduction

  1. Builder Pattern is suggested in Effective Java and used to generate immutable object.
  2. Builder Pattern introduces many codes, lombok project makes it easier
  3. We often use Orika to map data when transfer object from UI entity to domain entity or data persistence object to domain entity.
  4. It's hard to map Java Bean to Builder Pattern object because Builder Pattern doesn't follows Java Bean convention. It causes we can't leverage lombok and Orika at the same time

How to fix

  1. Copy and paste the following code: BuilderPropertyResolver
  2. /**
     * This class is used to map from an object to a builder who follows Builder Pattern.
     * 
     * <pre>
     * MapperFactory factory = new DefaultMapperFactory.Builder()
     *  .propertyResolverStrategy(new BuilderPropertyResolver())
     *  .build();
     *  factory.registerObjectFactory((Object o, MappingContext mappingContext) 
     *  -&gt; new MyBuilder(), TypeFactory.valueOf(MyBuilder.class));
     *  </pre>
     */
    public class BuilderPropertyResolver extends IntrospectorPropertyResolver {
    
     private static final Logger logger = LoggerFactory.getLogger(BuilderPropertyResolver.class);
     
     @Override
     protected void collectProperties(Class<?> type, Type<?> referenceType, Map<String, Property> properties) {
      super.collectProperties(type, referenceType, properties);
      if (StringUtils.endsWith(type.getName(), "Builder")) {
       Set<String> fieldNames = Arrays.stream(type.getDeclaredFields()).map(Field::getName).collect(Collectors.toSet());
       Arrays.stream(type.getDeclaredMethods())
         .filter(method -> fieldNames.contains(method.getName()))
         .filter(method -> isWriteMethodInBuilder(type, method))
         .forEach(method -> {
          Property.Builder builder = new Property.Builder();
          builder.expression(method.getName());
          builder.name(method.getName());
          builder.setter(method.getName() + "(%s)");
          Class<?> fieldType = method.getParameterTypes()[0];
          builder.type(this.resolvePropertyType(null, fieldType, type, referenceType));
          Property property = builder.build(this);
          properties.put(method.getName(), property);
         });
      }
     }
    
     private boolean isWriteMethodInBuilder(Class<?> type, Method method) {
      try {
       Class<?> returnType = method.getReturnType();
       Class<?> fieldType = type.getDeclaredField(method.getName()).getType();
       boolean onlyOneParameter = method.getParameterTypes().length == 1;
       boolean methodReturnBuilderType = returnType.equals(type);
       Class<?> methodParamType = method.getParameterTypes()[0];
       boolean isFieldSetter = fieldType.equals(methodParamType);
       return methodReturnBuilderType && onlyOneParameter && isFieldSetter;
      } catch (NoSuchFieldException e) {
       e.printStackTrace();
       return false;
      }
     }
    
    }
    
  3. Leverage this object
    public class Mapper {
     
     public static void main(String[] params) {
      MapperFactory factory = new DefaultMapperFactory.Builder()
        .propertyResolverStrategy(new BuilderPropertyResolver())
        .build();
      factory.registerObjectFactory((Object o, MappingContext mappingContext) 
        -> Person.builder(), TypeFactory.valueOf(Person.PersonBuilder.class));
      
      
      PersonBO bo = new PersonBO();
      bo.setName("TEST");
      factory.classMap(PersonBO.class, Person.class);
      System.out.println(factory.getMapperFacade().map(bo, Person.PersonBuilder.class).build());
      
      Person.PersonBuilder builder = Person.builder().list(Arrays.asList("A"));
      factory.getMapperFacade().map(bo, builder);
      System.out.println(builder);
     }
    
     @Data
     public static class PersonBO {
      private String name;
      private List<String> list;
     }
     
     @ToString
     @Builder
     public static class Person {
      private String name;
      private List<String> list;
     }
     
    }
    

Handle InterruptedException

Reference


How to handle InterruptedException

  • Throw it directly
  • If you can't throw it, call Thread.currentThread().interrupt()
  • Check Thread.currentThread().isInterrupted() every while loop if you catch an InterruptedException within the loop block

Example:

This example shows how to handle InterruptedException, you can change code to check what will happen in other cases.
public class TestMain {

 public static void main(String[] params) {
  ThreadPoolExecutor e = (ThreadPoolExecutor) Executors.newCachedThreadPool();
  MyRunnable r = new MyRunnable();
  e.execute(r);
  System.out.println("shutdown");
  e.shutdownNow();
  System.out.println("shutdown done");
 }

 private static class MyRunnable implements Runnable {

  @Override
  public void run() {
   while (!Thread.currentThread().isInterrupted()) {
    try {
     System.out.println("sleep");
     TimeUnit.HOURS.sleep(1);
    } catch (InterruptedException e) {
     e.printStackTrace();
     Thread.currentThread().interrupt();
    }
   }
  }
 }

}

Java TLS Practice

Introduction

記錄 Java 使用 SSL 的方法 (不要求 client auth)

Reference

Prepare jks files

  1. Install openssl in Ubuntu
    sudo apt-get install openssl
  2. Execute following commands one by one
    openssl req -x509 -newkey rsa:1024 -keyout private/cakey.pem -out private/cacert.pem -days 3650
    openssl x509 -in private/cacert.pem -addtrust clientAuth -setalias "Isaac Test Class 1 CA" -out public/catrust.pem
    keytool -importcert -trustcacerts -noprompt -file private/cacert.pem -alias ca -keystore public/catrust.jks -storepass 123456
    keytool -genkeypair -keyalg RSA -keysize 1024 -validity 730 -keystore public/server.jks
    keytool -certreq -file server-req.pem -keystore public/server.jks
    openssl x509 -req -in server-req.pem -out public/server-cert.pem -CA private/cacert.pem -CAkey private/cakey.pem -extensions v3_usr -days 730 -CAserial private/cacert.srl -CAcreateserial
    cat private/cacert.pem >> public/server-cert.pem
    keytool -importcert -v -file public/server-cert.pem -keystore public/server.jks
    rm server-req.pem
  3. There are public/server.jks and public/catrust.jks

Run server code

public class ServerDontNeedClientAuth {

    private static String SERVER_KEY_STORE = Paths.get("src/main/resources/certs/server.jks").toAbsolutePath().toString();
    private static String SERVER_KEY_STORE_PASSWORD = "123456";

    public static void main(String[] params) throws Exception {
//        System.setProperty("javax.net.debug", "ssl,handshake");
        System.setProperty("javax.net.ssl.trustStore", SERVER_KEY_STORE);
        SSLContext context = SSLContext.getInstance("TLS");
        KeyStore ks = KeyStore.getInstance("jceks");
        ks.load(new FileInputStream(SERVER_KEY_STORE), null);
        KeyManagerFactory kf = KeyManagerFactory.getInstance("SunX509");
        kf.init(ks, SERVER_KEY_STORE_PASSWORD.toCharArray());
        context.init(kf.getKeyManagers(), null, null);

        ServerSocketFactory factory = context.getServerSocketFactory();
        ServerSocket serverSocket = factory.createServerSocket(8443);
        SSLServerSocket sslServerSocket =  (SSLServerSocket) serverSocket;
        sslServerSocket.setNeedClientAuth(false);
        while(true){
            try{
                System.out.println("listen port 8443..");
                Socket socket = sslServerSocket.accept();
                System.out.println("accept:" + socket);
                InputStream is = socket.getInputStream();
                BufferedReader buffer = new BufferedReader(new InputStreamReader(is));
                String readLine = buffer.readLine();
                System.out.println("server receive:" + readLine);
                socket.getOutputStream().write("1234567890".getBytes());
                socket.close();
            }catch(Exception e){
                e.printStackTrace();
            }
        }
    }

}

Run client code

public class ClientWithoutAuth {

    private static String CLIENT_KEY_STORE = Paths.get("src/main/resources/certs/catrust.jks").toAbsolutePath().toString();

    public static void main(String[] params) throws Exception {
        System.setProperty("javax.net.ssl.trustStore", CLIENT_KEY_STORE);
        SocketFactory sf = SSLSocketFactory.getDefault();
        Socket s = sf.createSocket("localhost", 8443);
        PrintWriter writer = new PrintWriter(s.getOutputStream());
        BufferedReader reader = new BufferedReader(new InputStreamReader(s.getInputStream()));
        writer.println("hello\n");
        writer.flush();
        System.out.println("client receive:" + reader.readLine());
        s.close();
    }

}

Server output

listen port 8443..
accept:340f438e[SSL_NULL_WITH_NULL_NULL: Socket[addr=/127.0.0.1,port=50429,localport=8443]]
server receive:hello
listen port 8443..

Client output

1234567890

guava notes

Reference

Optional

Optional 可以用來處理 null value 與避免 NullPointerException
public class OptionalTest {


    @Test
    public void test() {
        try {
            Optional.of(null);
            Assert.fail("Optional.of shouldn't accept null");
        } catch (NullPointerException ex) {
        }
        Optional opt = Optional.of("Test");
        Assert.assertTrue(opt.isPresent());
        Assert.assertEquals("Test", opt.get());
        Assert.assertEquals("Test", opt.or("QQ"));
        Assert.assertEquals("Test", opt.orNull());
        Assert.assertEquals(ImmutableSet.of("Test"), opt.asSet());

        Optional nullableOptional = Optional.fromNullable(null);
        Assert.assertFalse(nullableOptional.isPresent());
        try {
            Assert.assertNull(nullableOptional.get());
            Assert.fail();
        } catch (IllegalStateException ex) {
        }
        Assert.assertEquals("QQ", nullableOptional.or("QQ"));
        Assert.assertNull(nullableOptional.orNull());
        Assert.assertEquals(ImmutableSet.of(), nullableOptional.asSet());
        Assert.assertEquals(Optional.absent(), nullableOptional);
    }

}

ComparisonChain

ComparisonChain 可以用比較漂亮的程式實作 compare
public class ComparisonChainTest {

    @Test
    public void test() {
        NumbersObject n1 = new NumbersObject(1,2,3);
        NumbersObject n2 = new NumbersObject(1,2,4);
        NumbersObject n3 = new NumbersObject(1,3,4);
        NumbersObject n4 = new NumbersObject(2,2,4);

        List<NumbersObject> list = Arrays.asList(n4, n3, n2, n1);
        Collections.sort(list, (o1, o2) -> ComparisonChain.start()
                .compare(o1.a,o2.a)
                .compare(o1.b,o2.b)
                .compare(o1.c,o2.c)
                .result());
        Assert.assertTrue(n1 == list.get(0));
        Assert.assertTrue(n2 == list.get(1));
        Assert.assertTrue(n3 == list.get(2));
        Assert.assertTrue(n4 == list.get(3));
    }

    final static class NumbersObject {
        private final int a ;
        private final int b;
        private final int c;
        NumbersObject(int a, int b, int c) {
            this.a = a;
            this.b = b;
            this.c = c;
        }
    }

}

Ordering

Ordering 其實就是 Comparator, 可以傳給 Collections. 例如: Collections.sort(list, Ordering)

usingToString

用 toString 的結果排序
@Test
public void usingToString() {
    NumbersObject n1 = new NumbersObject(1, 2, 3);
    NumbersObject n2 = new NumbersObject(1, 2, 4);
    NumbersObject n3 = new NumbersObject(-1, 2, -3);
    NumbersObject n4 = new NumbersObject(-2, 3, 5);
    List<NumbersObject> numbers = Arrays.asList(n1, n2, n3, n4);
    Collections.sort(numbers, Ordering.usingToString());
    Assert.assertEquals(n3, numbers.get(0));
    Assert.assertEquals(n4, numbers.get(1));
    Assert.assertEquals(n1, numbers.get(2));
    Assert.assertEquals(n2, numbers.get(3));
}

private final class NumbersObject {

    public final Integer a;
    public final Integer b;
    public final Integer c;

    public NumbersObject(Integer a, Integer b, Integer c) {
        this.a = a;
        this.b = b;
        this.c = c;
    }

    @Override
    public String toString() {
        return MoreObjects.toStringHelper(this)
                .add("a",a)
                .add("b",b)
                .add("c",c)
                .toString();
    }
}

nullsFirst & nullsLast

排序的時候, null 放在最前面位置(或最後).
物件本身是 Comparable, 可以直接拿 nullsFirst 回傳的 Ordering 來排序, 若物件本身不是 Comparable 就要提供排序的方式
@Test
public void nullsFirst_NotComparable() {
    NumbersObject n1 = new NumbersObject(1, 2, 3);
    NumbersObject n2 = new NumbersObject(1, 2, 4);
    NumbersObject n3 = new NumbersObject(-1, 2, -3);
    NumbersObject n4 = new NumbersObject(-2, 3, 5);
    NumbersObject nullNumber = new NumbersObject(null,null,null);
    List<NumbersObject> numbers = Arrays.asList(n1, n2, n3, n4, nullNumber);
    Ordering ordering = Ordering.natural().nullsFirst().onResultOf(new Function<NumbersObject, Integer>() {
        @Override
        public Integer apply(NumbersObject numbersObject) {
            return numbersObject.a;
        }
    });
    Collections.sort(numbers, ordering);
    Assert.assertEquals(nullNumber, numbers.get(0));
    Assert.assertEquals(n4, numbers.get(1));
    Assert.assertEquals(n3, numbers.get(2));
    Assert.assertEquals(n1, numbers.get(3));
    Assert.assertEquals(n2, numbers.get(4));
}

@Test
public void nullsFirst_Comparable() {
    List<Integer> list = Arrays.asList(5, 2, 6, 7, 3, null);
    Collections.sort(list, Ordering.natural().nullsFirst());
    Assert.assertEquals((Integer) null, list.get(0));
    Assert.assertEquals((Integer) 2, list.get(1));
    Assert.assertEquals((Integer) 3, list.get(2));
    Assert.assertEquals((Integer) 5, list.get(3));
    Assert.assertEquals((Integer) 6, list.get(4));
    Assert.assertEquals((Integer) 7, list.get(5));
}

@Test
public void nullsLast_Comparable() {
    List<Integer> list = Arrays.asList(5, 2, 6, 7, 3, null);
    Collections.sort(list, Ordering.natural().nullsLast());
    Assert.assertEquals((Integer) 2, list.get(0));
    Assert.assertEquals((Integer) 3, list.get(1));
    Assert.assertEquals((Integer) 5, list.get(2));
    Assert.assertEquals((Integer) 6, list.get(3));
    Assert.assertEquals((Integer) 7, list.get(4));
    Assert.assertEquals((Integer) null, list.get(5));
}

@Test
public void nullsLast_NotComparable() {
    NumbersObject n1 = new NumbersObject(1, 2, 3);
    NumbersObject n2 = new NumbersObject(1, 2, 4);
    NumbersObject n3 = new NumbersObject(-1, 2, -3);
    NumbersObject n4 = new NumbersObject(-2, 3, 5);
    NumbersObject nullNumber = new NumbersObject(null,null,null);
    List<NumbersObject> numbers = Arrays.asList(n1, n2, n3, n4, nullNumber);
    Ordering ordering = Ordering.natural().nullsLast().onResultOf(new Function<NumbersObject, Integer>() {
        @Override
        public Integer apply(NumbersObject numbersObject) {
            return numbersObject.a;
        }
    });
    Collections.sort(numbers, ordering);
    Assert.assertEquals(n4, numbers.get(0));
    Assert.assertEquals(n3, numbers.get(1));
    Assert.assertEquals(n1, numbers.get(2));
    Assert.assertEquals(n2, numbers.get(3));
    Assert.assertEquals(nullNumber, numbers.get(4));
}

private final class NumbersObject {

    public final Integer a;
    public final Integer b;
    public final Integer c;

    public NumbersObject(Integer a, Integer b, Integer c) {
        this.a = a;
        this.b = b;
        this.c = c;
    }

    @Override
    public String toString() {
        return MoreObjects.toStringHelper(this)
                .add("a",a)
                .add("b",b)
                .add("c",c)
                .toString();
    }
}

compound

可以組合多個 Comparator, 在上一個 Comparator 無法決定時就交給下一個 Comparator 判斷.
在排列的屬性有優先順序的時候可以讓程式變簡單.
例如: 先排 a, 再 b, 再 c. null 的話就排前面
@Test
public void compound() {
    NumbersObject n1 = new NumbersObject(1, 2, 3);
    NumbersObject n2 = new NumbersObject(1, 2, 4);
    NumbersObject n3 = new NumbersObject(-1, 2, -3);
    NumbersObject n4 = new NumbersObject(-2, 3, 5);
    NumbersObject nullNumber = new NumbersObject(null,null,null);
    List<NumbersObject> numbers = Arrays.asList(n1, n2, n3, n4, nullNumber);
    Comparator<NumbersObject> byNullFirstA = Ordering.natural().nullsFirst().onResultOf((t) -> t.a);
    Comparator<NumbersObject> byNullFirstB = Ordering.natural().nullsFirst().onResultOf((t) -> t.b);
    Comparator<NumbersObject> byNullFirstC = Ordering.natural().nullsFirst().onResultOf((t) -> t.c);
    Ordering orderByABC = Ordering.compound(Arrays.asList(byNullFirstA, byNullFirstB, byNullFirstC));
    Collections.sort(numbers, orderByABC);
    Assert.assertEquals(nullNumber, numbers.get(0));
    Assert.assertEquals(n4, numbers.get(1));
    Assert.assertEquals(n3, numbers.get(2));
    Assert.assertEquals(n1, numbers.get(3));
    Assert.assertEquals(n2, numbers.get(4));
}

private final class NumbersObject {

    public final Integer a;
    public final Integer b;
    public final Integer c;

    public NumbersObject(Integer a, Integer b, Integer c) {
        this.a = a;
        this.b = b;
        this.c = c;
    }

    @Override
    public String toString() {
        return MoreObjects.toStringHelper(this)
                .add("a",a)
                .add("b",b)
                .add("c",c)
                .toString();
    }
}

lexicographical

跟 Collections.sort 不同, loxicographical 是用來替很多個 iterable 作排序
@Test
public void lexicographically() {
    ImmutableList<String> empty = ImmutableList.of();
    ImmutableList<String> a = ImmutableList.of("a");
    ImmutableList<String> aa = ImmutableList.of("a", "a");
    ImmutableList<String> ab = ImmutableList.of("a", "b");
    ImmutableList<String> b = ImmutableList.of("b");
    List<ImmutableList<String>> all = Arrays.asList(b,a,ab,aa,empty);
    Ordering<Iterable<String>> c = Ordering.<String>natural().lexicographical();
    Collections.sort(all, c);
    Assert.assertEquals(empty, all.get(0));
    Assert.assertEquals(a, all.get(1));
    Assert.assertEquals(aa, all.get(2));
    Assert.assertEquals(ab, all.get(3));
    Assert.assertEquals(b, all.get(4));
}

@Test
public void lexicographicallyReverse() {
    ImmutableList<String> empty = ImmutableList.of();
    ImmutableList<String> a = ImmutableList.of("a");
    ImmutableList<String> aa = ImmutableList.of("a", "a");
    ImmutableList<String> ab = ImmutableList.of("a", "b");
    ImmutableList<String> b = ImmutableList.of("b");
    List<ImmutableList<String>> all = Arrays.asList(b,a,ab,aa,empty);
    Ordering<Iterable<String>> c = Ordering.<String>natural().lexicographical().reverse();
    Collections.sort(all, c);
    Assert.assertEquals(empty, all.get(4));
    Assert.assertEquals(a, all.get(3));
    Assert.assertEquals(aa, all.get(2));
    Assert.assertEquals(ab, all.get(1));
    Assert.assertEquals(b, all.get(0));
}

@Test
public void reverseLexicographically() {
    ImmutableList<String> empty = ImmutableList.of();
    ImmutableList<String> a = ImmutableList.of("a");
    ImmutableList<String> aa = ImmutableList.of("a", "a");
    ImmutableList<String> ab = ImmutableList.of("a", "b");
    ImmutableList<String> b = ImmutableList.of("b");
    List<ImmutableList<String>> all = Arrays.asList(b,a,ab,aa,empty);
    Ordering<Iterable<String>> c = Ordering.<String>natural().reverse().lexicographical();
    Collections.sort(all, c);
    System.out.println(all);
    Assert.assertEquals(empty, all.get(0));
    Assert.assertEquals(b, all.get(1));
    Assert.assertEquals(a, all.get(2));
    Assert.assertEquals(ab, all.get(3));
    Assert.assertEquals(aa, all.get(4));
}

onResultOf

就是自訂排序方式, 直行完後會再往前呼叫其他排序法
@Test
public void onResultOf() {
    Comparator<Integer> c = Ordering.<Integer>natural().onResultOf((x) -> Optional.ofNullable(x).orElse(4)%5);
    List<Integer> list = Arrays.asList(5, 11, 4, 1, 2, 5, 7, 9, null);
    Collections.sort(list, c);
    System.out.println(list);
    Assert.assertEquals(5, list.get(0).intValue());
    Assert.assertEquals(5, list.get(1).intValue());
    Assert.assertEquals(11, list.get(2).intValue());
    Assert.assertEquals(1, list.get(3).intValue());
    Assert.assertEquals(2, list.get(4).intValue());
    Assert.assertEquals(7, list.get(5).intValue());
    Assert.assertEquals(4, list.get(6).intValue());
    Assert.assertEquals(9, list.get(7).intValue());
    Assert.assertEquals(null, list.get(8));
}

@Test
public void onResultOf_reverse() {
    Comparator<Integer> c = Ordering.<Integer>natural().reverse().onResultOf((x) -> Optional.ofNullable(x).orElse(4)%5);
    List<Integer> list = Arrays.asList(5, 11, 4, 1, 2, 5, 7, 9, null);
    Collections.sort(list, c);
    System.out.println(list);
    Assert.assertEquals(5, list.get(8).intValue());
    Assert.assertEquals(5, list.get(7).intValue());
    Assert.assertEquals(11, list.get(5).intValue());
    Assert.assertEquals(1, list.get(6).intValue());
    Assert.assertEquals(2, list.get(3).intValue());
    Assert.assertEquals(7, list.get(4).intValue());
    Assert.assertEquals(4, list.get(0).intValue());
    Assert.assertEquals(9, list.get(1).intValue());
    Assert.assertEquals(null, list.get(2));
}

greatestOf 與 leastOf

拿排序過資料的最後幾筆與最前幾筆.
@Test
public void greatestOf() {
    NumbersObject n1 = new NumbersObject(1, 2, 3);
    NumbersObject n2 = new NumbersObject(1, 2, 4);
    NumbersObject n3 = new NumbersObject(-1, 2, -3);
    NumbersObject n4 = new NumbersObject(-2, 3, 5);
    NumbersObject nullNumber = new NumbersObject(null,null,null);
    List<NumbersObject> numbers = Arrays.asList(n1, n2, n3, n4, nullNumber);
    List<NumbersObject> greatestOf = Ordering.natural().greatestOf(numbers,3);
    Assert.assertEquals(n4, greatestOf.get(0));
    Assert.assertEquals(n3, greatestOf.get(1));
    Assert.assertEquals(n1, greatestOf.get(2));
}

@Test
public void leastOf() {
    NumbersObject n1 = new NumbersObject(1, 2, 3);
    NumbersObject n2 = new NumbersObject(2, 2, 4);
    NumbersObject n3 = new NumbersObject(-1, 2, -3);
    NumbersObject n4 = new NumbersObject(-2, 3, 5);
    NumbersObject nullNumber = new NumbersObject(null,null,null);
    List<NumbersObject> numbers = Arrays.asList(n1, n2, n3, n4, nullNumber);
    List<NumbersObject> leastOf = Ordering.natural().leastOf(numbers,3);
    Assert.assertEquals(nullNumber, leastOf.get(0));
    Assert.assertEquals(n2, leastOf.get(1));
    Assert.assertEquals(n1, leastOf.get(2));
}

private final class NumbersObject implements Comparable<NumbersObject> {

    public final Integer a;
    public final Integer b;
    public final Integer c;

    public NumbersObject(Integer a, Integer b, Integer c) {
        this.a = a;
        this.b = b;
        this.c = c;
    }

    @Override
    public String toString() {
        return MoreObjects.toStringHelper(this)
                .add("a",a)
                .add("b",b)
                .add("c",c)
                .toString();
    }

    @Override
    public int compareTo(NumbersObject o) {
        if (a == null && o.a == null) {
            return 0;
        } else if (a == null) {
            return -1;
        } else if (o.a == null) {
            return 1;
        } else {
            return Ints.compare(o.a,a);
        }
    }
}

ImmutableXXX

沒有 ImmutableSet 的時候會用 Collections.unmodifiableSet, 差別是 ImmutableSet 有很多好用的 api 讓程式簡單很多.
另外 ImmutableXXX 在能夠避免線性成長的 copy time 時候會盡量避免. 例如 ImmutableList.copyOf(ImmutableSet)
@Test(expected = UnsupportedOperationException.class)
public void of() {
    ImmutableSet.of("a","b").add("a");
}

@Test(expected = UnsupportedOperationException.class)
public void copyOf() {
    ImmutableSet.copyOf(Arrays.asList("a","b","c")).add("");
}

@Test
public void listCopyOfSet() {
    ImmutableList immutableList = ImmutableList.copyOf(ImmutableSet.of("a","b"));
}

Multipleset

像是 set 一樣, 差別是一個同樣的資料可以存多筆. 也可以記錄某種資料總共有幾筆.
@RunWith(Parameterized.class)
public class MultisetTest {

    @Parameterized.Parameters
    public static Collection<Object[]> data() {
        return Arrays.asList(new Object[][] {
                { HashMultiset.create() },
                {TreeMultiset.create() },
                {LinkedHashMultiset.create() },
                {ConcurrentHashMultiset.create()},
        });
    }

    private final Multiset<String> testee;

    public MultisetTest(Multiset<String> testee) {
        this.testee = testee;
    }

    @Test
    public void test() {
        testee.addAll(Arrays.asList("A","B","A"));
        Assert.assertEquals(2, testee.count("A"));
        Assert.assertEquals(1, testee.count("B"));
    }

}

public class ImmutableMultisetTest {

    @Test
    public void count() {
        ImmutableMultiset set = ImmutableMultiset.of("A","B","A");
        Assert.assertEquals(2,set.count("A"));
        Assert.assertEquals(1,set.count("B"));
    }

}

function 參數先排位置再對參數名稱

如果沒有指定要丟的參數名稱, python 就會照順序排. 如果順序對不起來就會 error.
>>> def f(a,b,c):
 print 'a:', a, 'b:', b, 'c:', c

 
>>> f(1,2,3)
a: 1 b: 2 c: 3
>>> f(1,c=3,b=2)
a: 1 b: 2 c: 3
>>> f(1,2,c=3)
a: 1 b: 2 c: 3
>>> f(1,2,b=2) # 第二個參數已經指定了, 所以就不能再指定 b

Traceback (most recent call last):
  File "", line 1, in 
    f(1,2,b=2)
TypeError: f() got multiple values for keyword argument 'b'

>>> f(c=3,2,a=1)
SyntaxError: non-keyword arg after keyword arg

>>> f(a=1,2,3)
SyntaxError: non-keyword arg after keyword arg

字串 format

帶入 dict 的值, 如果用 format api, dictionary 要帶 ** 去展開 key-value
>>> m = {'a':1.1234, "b":"qq"}
>>> '%(a)1.1f %(a)s %(b)s' %m
'1.1 1.1234 qq'


>>> '{a:1.1f} {a:s} {b:s}'.format(m) # illegal, must use **m

Traceback (most recent call last):
  File "", line 1, in 
    '{a:1.1f} {a:s} {b:s}'.format(m)
KeyError: 'a'

>>> '{a:1.1f} {a:s} {b:s}'.format(**m) # illegal, a is float type

Traceback (most recent call last):
  File "", line 1, in 
    '{a:1.1f} {a:s} {b:s}'.format(**m)
ValueError: Unknown format code 's' for object of type 'float'

>>> '{a:1.1f} {b:s}'.format(**m)
'1.1 qq'

tuple 就是不可變的 list

tuple 就是不可變的 list.
>>> list = ['c',[1,2,3]]
>>> tuple = ('c',(1,2,3))
>>> list[0]
'c'
>>> list[1]
[1, 2, 3]
>>> tuple[0]
'c'
>>> tuple[1]
(1, 2, 3)
>>> list[0] = 'q'
>>> list[0]
'q'
>>> list[1]
[1, 2, 3]
>>> tuple[0] = 'q'

Traceback (most recent call last):
  File "", line 1, in 
    tuple[0] = 'q'
TypeError: 'tuple' object does not support item assignment

function is an object

python 的 function 是一個物件, 所以可以放進 collection 中等以後被呼叫.
>>> def a():
 print 'a';

 
>>> def b():
 print 'b';

 
>>> def c():
 print 'c';

 
>>> list = [a,b,c];
>>> for f in list:
 f();

 
a
b
c

split large file

I use Notepad++ to check log and debug.
When log file is large, I need to split large file to smaller ones.
I can't find tool so write codes to split file in Java every time.
I use Python to write a tool to split file when studying python.
Welcome to use this tool if you also need to split large log file.

Shell 筆記


  1. cd /d %~dp0
    不管執行 script 時候路徑在哪, cd /d %~dp0 後 script 的路徑就到該 script 路徑下
    script, 放在 f:/test/test.bat
    cd /d %~dp0
    dir
    
    execute: f:/test/test.bat
    f:\>test\test.bat
    
    f:\>cd /d f:\test\
    
    f:\test>dir
     磁碟區 F 中的磁碟是 新增磁碟區
     磁碟區序號:  B0DF-F1C2
    
     f:\test 的目錄
    
    2015/07/14  上午 11:41    <DIR>          .
    2015/07/14  上午 11:41    <DIR>          ..
    2015/07/14  上午 11:42                16 test.bat
                   1 個檔案               16 位元組
                   2 個目錄  224,189,362,176 位元組可用
    

Git 筆記


  1. 從 branch merge 回 master, 導致 pom.xml conflict, 想 reset pom.xml 因為這不是我要 merge 的內容. (stackoverflow)
     git reset pom.xml
     git checkout pom.xml
    
  2. 想清掉 untrack file
    git clean -f
    
  3. 想清掉 untrack folder
    git clean -f -d
    
  4. 有次一個 branch 太久沒 pull 了, 後來不知道誰改了甚麼, 要再 pull 都 conflict 一堆.
    反正我也沒有要保留 local 的東西, 同事就教我強制把 local 的檔案 reset 到某個版本
    git reset comm_id --hard
    
  5. 在 pull 之前就 commit, git status 出現 "Your branch is ahead of 'origin/master' by 25 commits" 的訊息, 用 reset 還原
    git reset --hard origin/master
    
  6. 要把一個 branch 傳送到另一個 repository
    # new remote
    git remote add remotename git@git.abc.com:test/test.git
    
    # push branch to remote
    git push -u remotename branchname
    

Basic Perl 筆記


  1. 單引號的 \n 不會換行, 雙引號內的 \n 會換行
    print("line1\nline2\n\n\n\n");
    print('line3:abc\nabc');
    
    d:\>test.pl
    input string:abc
    input number:4
    abcabcabcabc
    d:\>test.pl
    equal:1
    d:\>test.pl
    line1
    line2
    
    
    
    line3:abc\nabc
    
  2. 用 . 連字串
    $a = 'abc\nabc';
    $b = "def\ndef";
    $c = $a . $b . "QQQ";
    print("$c");
    
    d:\>test.pl
    abc\nabcdef
    defQQQ
    
  3. 數字 0 是 false, 其他都是 true.
    空字串是 false, 其他都是 true.
    undef 是 false.
    print($abc == 0); # print 1, $abc is undef and is false, false is 0
    $abc = '';
    print($abc == 0); # print 1, $abc is empty string and is false, false is 0
    $abc = "0";
    print($abc == 0); # print 1, $abc is string 0, Perl transfer to number 0
    
    d:\>test.pl
    111
    
  4. 使用 lt,le,eq,ge,gt 作字串的比較, perl 會用 ASCII 或 Unicode 作為順序參考排大小.
    $t1 = "a";
    $t2 = "a";
    if ($t1 eq $t2) {
        print("same");
    } else {
        print("different");
    }
    
    d:\>test2.pl
    same
    
    $t1 = "a";
    $t2 = "b";
    if ($t1 eq $t2) {
        print("same");
    } else {
        print("different");
    }
    
    d:\>test2.pl
    different
    
  5. 取得使用者輸入: <STDIN>
    print("What's your name?\n");
    $name = <STDIN>;
    print("$name, how are you?");
    
  6. d:\>test.pl
    What's your name?
    isaac
    isaac
    , how are you?
    



  7. 這時候就發現 <STDIN> 取得的資料會包含換行, 換行字元是不需要的, 就用 chomp 去掉
    print("What's your name?\n");
    $name = <STDIN>;
    chomp($name);
    print("$name, how are you?");
    

  8. d:\>test.pl
    What's your name?
    isaac
    isaac, how are you?
    



  9. 如果輸入的時候按 Ctrl + C, 沒有輸入, <STDIN> 會回傳 undef. 這時候可以用 defined 來判斷是否為 undef
    $name = <STDIN>;
    if (defined($name)) {
     chomp($name);
     print("input:$name");
    } else {
     print("no input\n");
    }
    

  10. d:\>test.pl
    no input
    Terminating on signal SIGINT(2)
    

  11. 使用陣列
    while ($i < 10) {
     $i += 1;
     $names[$i] = "p$i";
     print("$names[$i]\n");
    }
    $names[100] = "qq";
    print("$names[100]");
    
    d:\>test.pl
    p1
    p2
    p3
    p4
    p5
    p6
    p7
    p8
    p9
    p10
    qq
    
  12. 取陣列最後一個值的 index: $#names
    $names[100] = "aaa";
    print("$#names\n"); 
    print($names[$#names]); 
    
    d:\>test.pl
    100
    aaa
    
  13. 列出陣列全部值 (這裡是要注意 $#names 的值是最後一個 index 而不是長度. )
    想得到長度要再加 1, 因為還有第 0 個.
    $i = 0;
    while ($i < 10) {
     $i += 1;
     $names[$i] = $i;
    }
    $i = 0;
    while ($i <= $#names) {
     $i += 1;
     print("$names[$i]");
    }
    
    d:\>test.pl
    12345678910
    
  14. index 可以指定負數, -1 就是最後一個值
    $names[0]="0";
    $names[1]="1";
    $names[2]="2";
    $names[3]="3";
    print($names[0]); #0
    print($names[-1]); #3
    print($names[-2]); #2
    print($names[-3]); #1
    print($names[-4]); #0
    
  15. 用 @串列變數 = (用逗號分隔的串列值) 宣告串列
    會置換變數, \n 會換行, 就跟雙引號宣告的變數一樣
    @a = (1,2,3); 
    print("\@a:@a\n");
    
    @b = (1..3); # 使用 .. 會 +1
    print("\@b:@b\n");
    
    @c = (1.4...5.6); # 使用 .. 會無條件捨去小數
    print("\@c:@c\n");
    
    @d = (2,6...10,43); 
    print("\@d:@d\n");
    
    $e = 10;
    $f = 20;
    @g = ($e...$f); 
    print("\@g:@g\n");
    
    @h = ("a", "b\n", "c");  #有換行效果
    print("\@h:@h\n");
    
    d:\>test.pl
    @a:1 2 3
    @b:1 2 3
    @c:1 2 3 4 5
    @d:2 6 7 8 9 10 43
    @g:10 11 12 13 14 15 16 17 18 19 20
    @h:a b
     c
    
  16. 用 @串列變數=qw(用空白分隔的串列值) 宣告串列,
    不會置換變數, \n 不會換行, 跟單引號宣告的變數一樣
    @a = qw(1 2 3); #可以用 qw()
    print("\@a:@a\n"); 
    
    @b = qw<1 data-blogger-escaped-..3="">; #也可以 qw<>, 但 1..3 會直接印出來
    print("\@b:@b\n");
    
    @c = qw/1.4...5.6/; #也可以 qw//
    print("\@c:@c\n");
    
    @d = qw!2 6...10 43!; 
    print("\@d:@d\n");
    
    $e = 10;
    $f = 20;
    @g = qw($e...$f); #沒有換變數的效果
    print("\@g:@g\n");
    
    @h = qw("a" "b\n" "c");  #無換行效果, 雙引號也會被印出來
    print("\@h:@h\n");
    
    d:\>test.pl
    @a:1 2 3
    @b:1..3
    @c:1.4...5.6
    @d:2 6...10 43
    @g:$e...$f
    @h:"a" "b\n" "c"
    
  17. 一次 assign 值給多個變數
    ($a,$b,$c) = (1,2,3);
    print("a:$a\n");
    print("b:$b\n");
    print("c:$c\n");
    
    d:\>test.pl
    a:1
    b:2
    c:3
    
  18. 換值
    $a[0] = 0;
    $a[1] = 1;
    ($a[0],$a[1]) = ($a[1],$a[0]);
    print("a[0]:$a[0]\n");
    print("a[1]:$a[1]\n");
    
    d:\>test.pl
    a[0]:1
    a[1]:0
    
  19. 如果移除括號也不改變原本意思, 就可以移除括號
    @a = 1...3;
    print(@a);
    
    d:\>test.pl
    123
    
  20. 如果一個串列值是另一個串列, 被包含在串列裡的會被展開
    @a = 1..3;
    $b = 4;
    @c = ();
    #d is undefined
    @e = (@a,$b,@c,@d);
    print(@e);
    
    d:\>test.pl
    1234
    
  21. 用 pop 可以從串列取值出來, 沒有值的話會取出 undef
    @a = 1..3;
    while (defined($val = pop(@a))) {
     print("$val\n");
    }
    
    d:\>test.pl
    3
    2
    1
    
  22. 用 push 可以把值放進串列
    @a = 1..3;
    push(@a,4);
    while (defined($val = pop(@a))) {
     print("$val\n");
    }
    
    d:\>test.pl
    4
    3
    2
    1
    
  23. 串列可以複製全部的值 (不是 reference, 所以複製後對串列的修改不會互相影響)
    @a = 1...3;
    @b = @a;
    print("a:\n");
    while (defined($val = pop(@a))) {
     print("$val\n");
    }
    print("b:\n");
    while (defined($val = pop(@b))) {
     print("$val\n");
    }
    
    d:\>test.pl
    a:
    3
    2
    1
    b:
    3
    2
    1
    
  24. 一次 assign 值給多個變數
    ($a,$b,$c) = (1,2,3);
    print("a:$a\n");
    print("b:$b\n");
    print("c:$c\n");
    
    d:\>test.pl
    a:1
    b:2
    c:3
    
  25. qw 透過空白來區分值, 也可以 assign 值給多個變數
    ($google,$yahoo,$linkedin) = qw {
     http://www.google.com
     http://www.yahoo.com
     http://www.linkedin.com
    };
    print("google:$google\n");
    print("yahoo:$yahoo\n");
    print("linkedin:$linkedin\n");
    
    ($google,$yahoo,$linkedin) = qw !
     http://www.google.com
     http://www.yahoo.com
     http://www.linkedin.com
    !;
    print("google:$google\n");
    print("yahoo:$yahoo\n");
    print("linkedin:$linkedin\n");
    
    d:\>test.pl
    google:http://www.google.com
    yahoo:http://www.yahoo.com
    linkedin:http://www.linkedin.com
    google:http://www.google.com
    yahoo:http://www.yahoo.com
    linkedin:http://www.linkedin.com
    
  26. shift 從 index 0 取值, unshift 從 index 0 放值
    @a = ();
    unshift(@a,"1");
    unshift(@a,"2");
    unshift(@a,"3");
    print("@a\n"); #321
    print(shift(@a)); #3
    print(shift(@a)); #2
    print(shift(@a)); #1
    
  27. pop 從 index 最後取值, push 從 index 最後放值
    @a = ();
    unshift(@a,"1");
    unshift(@a,"2");
    unshift(@a,"3");
    print("@a\n"); #321
    push(@a,"4"); #3214
    push(@a,"5"); #32145
    push(@a,"6"); #321456
    print(shift(@a)); #3
    print(shift(@a)); #2
    print(shift(@a)); #1
    print(pop(@a)); #6
    print(pop(@a)); #5
    print(pop(@a)); #4
    
    d:\>test.pl
    3 2 1
    321654
    
  28. pop,push,shift,unshift 可以一次處理整個串列
    @a = ();
    unshift(@a, qw/ 1 2 3 /); #123
    unshift(@a, qw/ 4 5 6 /); #456123
    push(@a, qw/ 7 8 9 /); #456123789
    push(@a, qw/ 10 11 12 /); #456123789101112
    print(shift(@a)); #4
    print(shift(@a)); #5
    print(shift(@a)); #6
    print(shift(@a)); #1
    print(shift(@a)); #2
    print(shift(@a)); #3
    print(pop(@a)); #12
    print(pop(@a)); #11
    print(pop(@a)); #10
    print(pop(@a)); #9
    print(pop(@a)); #8
    print(pop(@a)); #7
    defined(pop(@a)) ? print("value") : print(" no val"); # no val
    
    d:\>test.pl
    456123121110987 no val
    
  29. 切串列: splice
    @a = 1..9;
    splice(@a,1); # 從 index 1 之後全切掉
    print("@a\n"); #1
    @a = 1..9;
    @removed = splice(@a,1,3); # 從 index 1 切掉三個
    print("@a\n"); #156789
    print("@removed\n"); #234
    @a = 1..9;
    @b = qw (- 9 8 7 6 5 4 3 2 1 -);
    splice(@a,1,3,@b); # 從 index 1 切掉三個之後加上 b 串列
    print("@a\n"); #1-987654321-56789
    @a = 1..9;
    splice(@a,1,0,@b); # 從 index 1 加上 b 串列, 完全不切掉任何值
    print("@a\n"); #1-987654321-23456789
    
    d:\>test.pl
    1
    1 5 6 7 8 9
    2 3 4
    1 - 9 8 7 6 5 4 3 2 1 - 5 6 7 8 9
    1 - 9 8 7 6 5 4 3 2 1 - 2 3 4 5 6 7 8 9
    
  30. print 的時候用 \@ 來跳脫串列的 @
    @yahoo = qw { yahoo hohoho };
    print("yahoo:@yahoo\n");
    print("mail:test@yahoo.com\n"); #@沒跳脫, 會換成串列內容
    print("mail:test\@yahoo.com\n"); #@跳脫了, 不會換成串列內容
    
    d:\>test.pl
    yahoo:yahoo hohoho
    mail:testyahoo hohoho.com
    mail:test@yahoo.com
    
  31. 串列可以當成陣列用
    @names = qw (a b c);
    print("index 0:$names[0]\n"); #a
    print("index 1:$names[1]\n"); #b
    print("index 2:$names[2]\n"); #c
    
    d:\>test.pl
    index 0:a
    index 1:b
    index 2:c
    
  32. 如果緊接著串列變數要印[index]的字串, 串列變數就要別處理
    @names = qw (a b c);
    print("index 0:${names[0]}[0]\n"); #用 {} 把變數圈起來
    print("index 1:$names[1]"."[1]\n"); #用 . 把字串分開
    print("index 2:$names[2]\[2\]\n"); #用 \ 跳脫 [ 與 ]
    
    d:\>test.pl
    index 0:a[0]
    index 1:b[1]
    index 2:c[2]
    
  33. foreach iterate 串列
    @names = qw (a b c);
    foreach $name (@names) {
     print("$name\n");
    }
    
    d:\>test.pl
    a
    b
    c
    
  34. foreach 裡面宣告的變數不會影響外部的變數
    $name = "hello";
    @names = qw (a b c);
    foreach $name (@names) {
     print("$name\n");
    }
    print("$name\n"); #hello
    
    d:\>test.pl
    a
    b
    c
    hello
    
  35. 預設變數 $_, 比方說在 foreach 的時候沒宣告變數就可以使用 $_
    foreach (qw / a b c /) {
     print("$_\n");
    }
    
    d:\>test.pl
    a
    b
    c
    
  36. reverse 把串列反過來
    @a = (1,2,3,4,5);
    print("a:@a\n");
    print("reverse a:".reverse(@a)."\n");
    
    d:\>test.pl
    a:1 2 3 4 5
    reverse a:54321
    
  37. 用 each iterate 串列, each 會一次回傳 index 與 value.
    @a = (1,2,3,4,5);
    while (($index,$value) = each(@a)) {
     print("index:$index, value:$value\n");
    }
    
    d:\>test.pl
    index:0, value:1
    index:1, value:2
    index:2, value:3
    index:3, value:4
    index:4, value:5
    
  38. 當進行字串的運算時, 就得到字串的結果. 當執行數字的計算時, 就得到數字的結果. 是字串還是數字是由運算符號決定.
    print(3*3 ."\n");
    print(3x3 ."\n");
    @a = qw{1 100 3 4 5}; #長度5
    print(3*@a ."\n"); #3*5=15
    print(3x@a ."\n");
    
    C:\Users\isaac>test.pl
    9
    333
    15
    33333
    
  39. 在字串的運算時, 串列會印出字串. 在數字的運算時, 串列會印出個數.
    @a = qw {e f d c b a};
    print(2*@a."\n"); #@a 是數字5, 印出 10 (2*5=10)
    print(2x@a."\n"); #@a 是數字5, 印出 22222
    print(sort(@a)); #印出排序過的字串
    
    C:\Users\isaac>test.pl
    12
    222222
    abcdef
    
  40. 運算串列的時候會印出串列, 但有時候運算串列的時候需要印出串列的 size. 這時候要用 scalar 這個假函式讓它變串列的 size
    @list = qw /a b c/;
    print("list:",@list,", size:",scalar @list);
    
    d:\>test.pl
    list:abc, size:3
    
  41. 可以在 console 多行資料給串列, 在 windows 下按 Ctrl+Z 結束, 在 Linux 下按 Ctrl+D 結束
    @commands = <STDIN>;
    print("commands:",@commands);
    
  42. d:\>test.pl
    a
    b
    c
    d
    e
    ^Z
    commands:a
    b
    c
    d
    e
    




  43. STDIN 輸入資料進串列, 每一行都會加上換行符號, 這不一定是我們要的, 可以用 chomp 去掉換行符號
    @commands = <STDIN>;
    chomp(@commands);
    print("commands:",@commands);
    

  44. d:\>test.pl
    a
    b
    c
    d
    e
    ^Z
    commands:abcde
    




  45. 可以簡化寫法
    chomp(@commands = <STDIN>);
    print("commands:",@commands);
    

  46. d:\>test.pl
    a
    b
    c
    d
    e
    ^Z
    commands:abcde
    




  47. 定義副常式 subroutine, 呼叫的方式是用 &副常式名稱 來呼叫.
    &hellosubroutine;
    
    sub hellosubroutine {
     print("hello subroutine");
    }
    
    d:\>test.pl
    hello subroutine
    
  48. subroutine 存取的變數都是全域變數
    &changeto5;
    print($n,"\n");
    &changeto10;
    print($n,"\n");
    
    sub changeto5 {
     $n = 5;
    }
    
    sub changeto10 {
     $n = 10;
    }
    
    d:\>test.pl
    5
    10
    
  49. subroutine 的最後一行計算就是回傳值
    print(&changeto5,"\n");
    print(&print,"\n");
    print(&add1ToN,"\n");
    print($n,"\n");
    
    sub add1ToN {
     $n + 1;
    }
    
    sub changeto5 {
     $n = 5;
    }
    
    sub print {
     print("");
    }
    
    d:\>test.pl
    5
    1
    6
    5
    
  50. subroutine 加參數
    sub test {
     print("arg[0]:$_[0]\n");
     print("arg[1]:$_[1]\n");
     print("arg[2]:$_[2]\n");
     print("arg[3]:$_[3]\n");
    }
    
    print("======3 args============\n");
    &test(1,2,3);
    print("======4 args============\n");
    &test(1,2,3,4);
    
    d:\>test.pl
    ======3 args============
    arg[0]:1
    arg[1]:2
    arg[2]:3
    arg[3]:
    ======4 args============
    arg[0]:1
    arg[1]:2
    arg[2]:3
    arg[3]:4
    
  51. 參數傳入 subroutine 後會存在 @_ 這個預設串列
    sub test {
     print("@_");
    }
    
    &test(1,2,3,4,5);
    
    d:\>test.pl
    1 2 3 4 5
    
  52. 用 my 可以宣告 subroutine 裡的區域變數
    sub test {
     $a = "a";
     my $b = "qq";
    }
    
    &test;
    print("a:$a\n");
    if (!defined($b)) {
     print("b is undef");
    }
    
    d:\>test.pl
    a:a
    b is undef
    
    sub max {
     my $max = shift @_;
     for (@_) {
      if ($max < $_) {
       $max = $_;  
      }
     }
     $max; #return
    }
    
    print(&max(1,2,3,4,5),"\n");
    if (!defined($max)) {
     print("\$max is undef");
    }
    
    d:\>test.pl
    5
    $max is undef
    
  53. 一個 subroutine 中本來就有一個變數, 又透過 my 宣告區域變數, subroutine 在 my 宣告後, 會以 my 宣告的變數值為主, 但又不影響原本的全域變數值
    sub max {
     $max = 333;
     my $max = shift @_;
     for (@_) {
      if ($max < $_) {
       $max = $_;  
      }
     }
     print($max,"\n"); #max=5
     $max; #return 5
    }
    
    print(&max(1,2,3,4,5),"\n");
    print($max); #max=333
    
    d:\>test.pl
    5
    5
    333
    
  54. 用 my 一次宣告多個變數來接外來的參數
    sub max {
     my($a,$b,$c,$d) = @_;
     print("a:$a,b:$b,c:$c,d:$d\n");
    }
    
    &max(1,2,3); 
    &max(1,2); 
    
    d:\>test.pl
    a:1,b:2,c:3,d:
    a:1,b:2,c:,d:
    
    
  55. 檢查陣列長度是否符合預期
    sub max {
     if (@_ != 2) {
      print("argument size should be 2\n");
     }
     my($a,$b) = @_;
     if ($a > $b) { 
      $a;
     } else {
      $b;
     }
    }
    
    print("max:",&max(1,2,3));
    
    D:\>test.pl
    argument size should be 2
    max:2
    
  56. use strict 強迫程式碼用比較好的方式撰寫 原本的範例
    sub test {
     foreach $qq (qw /a b c/) {
      print("$qq\n");
     }
    }
    
    $qq = 5;
    &test;
    print("qq:",$qq);
    
    d:\>test.pl
    a
    b
    c
    qq:5
    
    加上 use strict 之後
    use strict;
    sub test {
     foreach $qq (qw /a b c/) {
      print("$qq\n");
     }
    }
    
    $qq = 5;
    &test;
    print("qq:",$qq);
    
    d:\>test.pl
    Global symbol "$qq" requires explicit package name at D:\test.pl line 3.
    Global symbol "$qq" requires explicit package name at D:\test.pl line 4.
    Global symbol "$qq" requires explicit package name at D:\test.pl line 8.
    Global symbol "$qq" requires explicit package name at D:\test.pl line 10.
    Execution of D:\test.pl aborted due to compilation errors.
    
  57. return 回傳值
    原本 subroutine 的最後一行程式就是該 subroutine 的回傳值, 不過使用 return 就可以在最後一行之前回傳
    sub indexOf {
        my($keyword,@texts) = @_;
        foreach (0...$#texts) {
            if ($keyword eq $texts[$_]) {
                return $_;
            }
        }
        -1;
    }
    
    print(&indexOf("test",qw/ ab r ewr /),"\n");
    print(&indexOf("test",qw/ ab r ewr test/),"\n");
    
    d:\>test.pl
    -1
    3
    
  58. 當呼叫 subroutine 時需要用 & 來呼叫, 這是透過 & 來告訴 perl 這是一個 subroutine. 不過如果呼叫的時候有加參數,讓 perl 知道這是個 subroutine, 就不需要 & 了.
    sub say {
        print("say:",@_);
    }
    
    say("hello");
    
    d:\>test.pl
    say:hello
    
  59. 但是如果 subroutine 的名稱跟 perl 預設的 function 同名, 那還是需要透過 & 來告訴 perl 這是 subroutine 而不是預設的 function.
    sub print {
        print("print:",@_);
    }
    print("hello\n");
    &print("hello");
    
    d:\>test.pl
    hello
    print:hello
    
  60. 使用 my 宣告的區域變數在 subroutine 結束後值就不在了, 使用 state 宣告的話, 變數的狀態會記在 subroutine 中. 不過要宣告 use 5.010 才可以使用這個功能.
    use 5.010;
    
    sub test {
        my $localn = 0;
        $localn = $localn+1;
        print("test.localn:",$localn,"\n");
        
        state $n = 0;
        $n = $n+1;
        print("test.n:",$n,"\n");
    }
    
    sub test2 {
        state $n = 0;
        $n = $n+1;
        print("test2.n:",$n,"\n");
    }
    
    &test;
    &test;
    &test2;
    
    d:\>test.pl
    test.localn:1
    test.n:1
    test.localn:1
    test.n:2
    test2.n:1
    
    use 5.010;
    
    sub append {
        state @list;
        foreach (@_) {
            push(@list,$_);
        }
        print("list:",@list,"\n");
    }
    
    &append(qw/a b c/);
    &append(qw/1 2 3/);
    &append(qw/Q R T/);
    
    d:\>test.pl
    list:abc
    list:abc123
    list:abc123QRT
    
  61. console 輸入
    $line = <STDIN>;
    chomp($line);
    print($line);
    
    d:\>test.pl
    test
    test
    
    while (defined($line = <STDIN>)) {
        print($line);
    }
    
    d:\>test.pl
    test
    test
    qq
    qq
    BB
    BB
    ^Z
    
    d:\>
    
    while(<STDIN>) {
        print($_);
    }
    
    d:\>test.pl
    test
    test
    qq
    qq
    bb
    bb
    QQ
    QQ
    ^Z
    
    
    foreach (<STDIN>) {
        print($_,"\n");
    }
    
    d:\>test.pl
    a
    b
    c
    ^Z
    a
    
    b
    
    c
    
    這裡值得說明的是: perl 在 while 迴圈中使用 <STDIN> 做了特別處理, 使用 while (<STDIN>) 的效果會變這樣
    while (defined($_ = <STDIN>)) {
        print($_);
    }
    
    d:\>test.pl
    test
    test
    bb
    bb
    ^Z
    
    不過使用 foreach 則會把 STDIN 的結果全都讀進來才用 foreach iterate.
    這代表著如果 STDIN 的 input 量很大, 使用 while 沒關係因為每次換行都會輸出一次.
    使用 foreach 來讀大資料的話有可能一次佔用很多記憶體.
  62. 在程式中使用 while (<>) 可以讀取開啟程式時參數指定的檔案, 或者用 - 來當成標準輸入
    test2.txt
    {"test2":"test2","a": 1, "b": [1, 2, 3, 4, 5, 6]}
    
    julie.txt
    2.59,2.11,2:11,2:23,3-10,2-23,3:10,3.21,3-21
    
    test.pl
    while (<>) {
        print("print:$_\n");
    }
    
    d:\>test.pl test2.txt julie.txt
    print:{"test2":"test2","a": 1, "b": [1, 2, 3, 4, 5, 6]}
    print:2.59,2.11,2:11,2:23,3-10,2-23,3:10,3.21,3-21
    
    在參數指定 - 可以加上 STDIN 的效果
    test.pl
    while (<>) {
        print("print:$_\n");
    }
    
    d:\>test.pl test2.txt - julie.txt
    print:{"test2":"test2","a": 1, "b": [1, 2, 3, 4, 5, 6]}
    qq
    print:qq
    
    bb
    print:bb
    
    ^Z
    print:2.59,2.11,2:11,2:23,3-10,2-23,3:10,3.21,3-21
    
    看到 - 的處理都會多一個換行, 可以用 chomp 去掉.
    另外 perl 鼓勵我們少打字, 呼叫 function 的時候不用加括號也可以
    while (<>) {
        chomp;
        print "print:$_\n";
    }
    
    d:\>test.pl test2.txt - julie.txt
    print:{"test2":"test2","a": 1, "b": [1, 2, 3, 4, 5, 6]}
    testt
    print:testt
    ^Z
    print:2.59,2.11,2:11,2:23,3-10,2-23,3:10,3.21,3-21
    
    
    如果沒指定參數, <> 就會從 STDIN 讀取輸入
    while (<>) {
        chomp;
        print "print:$_\n";
    }
    
    d:\>test.pl
    a
    print:a
    b
    print:b
    c
    print:c
    ^Z
    
    
  63. while(<>) 其實是處理 @ARGV, @ARGV 是 perl 的特殊陣列, 裡面會放起動程式的參數, 進入程式後可以像一般陣列一樣使用
    foreach (@ARGV) {
        print("arg:$_\n");
    }
    
    d:\>test.pl a b c
    arg:a
    arg:b
    arg:c
    
    
    @ARGV = qw/a b c/;
    foreach (@ARGV) {
        print("arg:$_\n");
    }
    
    d:\>test.pl d d d
    arg:a
    arg:b
    arg:c
    
  64. print <> 作出 linux 下 cat 的效果 data1.txt
    a
    b
    c
    d
    e
    
    data2.txt
    d
    d
    c
    b
    a
    e
    
    執行 cat
    [root@Platform-151-ninja Isaac]# cat data1.txt data2.txt 
    a
    b
    c
    d
    e
    d
    d
    c
    b
    a
    e
    
    執行 perl
    print <>;
    
    在 linux 執行
    [root@Isaac]# perl test.pl data1.txt data2.txt 
    a
    b
    c
    d
    e
    d
    d
    c
    b
    a
    e
    
    在 windows 執行結果跟在 Linux 執行不太一樣
    d:\>test.pl data1.txt data2.txt
    a
    b
    c
    d
    ed
    d
    c
    b
    a
    e
    
  65. 待續...

Scrum Q&A


Q: 我們用用 SCRUM 想幹嘛? 
A: 我們都曾經遇過在某個專案已經花費很多時間跟人力做得一團亂. 但在某個機緣下少少幾個人展現強大的戰鬥力把待辦清單做完, 成為膾炙人口的佳話. 這個佳話也成為團隊員日後美好的回憶. 會有佳話是因為: 
  1. 待辦事項清單清楚
  2. 有人排出優先順序, 先做重要的, 其他丟掉
  3. 隊員各有擅長的事情, 壓力下大家挑出擅長的事情互相合作完成
  4. 跟使用者端緊密合作, 確保大家做的事情可以上線
就好像玩美式足球. 賽前規劃戰略, 比賽中一球一球緊密配合, 賽後檢討準備下場比賽.
使用 SCRUM, 最終希望在這個組織工作的人都能擁有美好的回憶, 達到卓越的狀態.

Q: 為甚麼要 Daily Scrum? 
A: 因為我 (Scrum Master) 需要知道每個人的狀況, 是否有人被卡住需要我去協調.
此外當隊員都很好溝通, 彼此熟悉, 那當然很好. 如果隊員溝通情況沒那麼好, 或是個性沒那麼搭, Daily Scrum 是個管道強迫大家互相溝通進度或向外求援

Q: 為甚麼要 retrospective meeting?
A: 大家在執行上有甚麼問題, Scrum Master 需要知道, 然後趕快改善, 這是很重要的部分

Q: 為甚麼要 demo?
A: 做出來的東西要快點讓使用者看看是否可用, 還是說應該要快點修正. 
不過 demo 的時候, 使用者的回饋不用照單全收, 比方說他可能會說: 請給我全部欄位都可以排序.
我們可以在 demo 的時候吸收 feedback, 然後分析 feedback 再改善, 而不是把使用者的話當 spec.

Q: 為甚麼要 Story?
A: Story 會描述情境, 別人看了比較知道要滿足甚麼. 你都短短的寫技術或 component 名詞, 只有你現在知道要做甚麼, 以後看 ticket 都需要去爬程式回想才知道想幹嘛. 更糟的是程式不一定能反應當下的情境.

Q: 為甚麼要 Story Point?
A: 為了評估團隊的 througput, 看進步或退步了, 看是否可持續改善.
另外用來跟公司收到的利益結合, 計算 Story 替公司爭取多少利益. 這當成 product owner 的 KPI. 亂開 Story 的 product owner 績效會很差.

Q: Plan Meeting 怎麼進行?
A: 讓每個 member 知道每個 Story 就好了.
"千萬"不要討論實做細節, 那不是 plan meeting 該做的事. 就算實做的"方向"也不該討論.
那是 member 自行決定的東西.
plan meeting 後, member 可以留下來繼續討論怎麼在這個 sprint 內"攻下"這些 story.
照戰略攻克每個 story 很爽...

User Story Template

Reference


User story format:

“As a (user type), I want to (goal), so that (reason).”
Attributes of good user story: INVEST
Independent
Negotiable
Valuable
Estimable
Sized Appropriately
Testable

Best Practices

  • Readability:
    • "One sentence" part is for reader to catch up in 5 seconds
    • The entire user story is for reader to understand in 30 seconds
    • The extra information can be added in notes section. For example, detail requirement, special conditions, and key decisions.
    • Use your best way to communication. For example, for functional requirement, please put it in UI prototype which is more clear than thousand words.
  • Prioritization:
    • Always need to have priority
    • Force ranking from 1 (the top priority) in each category
  • How to improve user story quality:
    • Write often and review with colleagues
    • Share with different stakeholders like customers, NOC, BD, or CEO. Try to get their feedbacks.

[Study Group] Java Performance - JVM Overview

Reference

Notes

  1. HotSpot VM 三個主要元件: VM Runtime, JIT Compiler, memory manager
  2. JIT Compiler 與 Garbage Collector 是 pluggable.
  3. HotSpot VM Runtime 提供 service 與 Common API 給 JIT Compiler 與 Garbage Collector 使用
  4. HotSpot VM Runtime 還提供了諸如啟動程序, thread management, JNI等功能
  5. 32位元的 HotSpot VM 最多只能使用4GB Ram, 而隨著 OS 不同實際上只能使用 1.5-3.3GB Ram
  6. 64位元的系統引進後, HotSpot VM可以使用更大的記憶體, 但隨之而來的是由於java object 的 representation (稱為 ordinary object pointers, or oops)的 size 變大產生的效能問題. 換句話說就是其寬度從32位元變為64位元時跟著一起變大了.
  7. oops 寬度變大導致能放進 CPU cache line 的 oops 減少也使 CPU 效能跟著減少 (跟32位元比起來約減少約8%-15%).
  8. 在 Java6 HotSpot VM, OpenJDK 中增加新功能: compressed oops, 透過 -XX:+UseCompressedOops 就能起動, 使能擁有64位元的 heap 與32位元相同甚至更好的效能 

HotSpot VM Runtime

Command Line Options

Command Line Option 有三種: standard, nonstandard, developer.
  1. Standard: JVM Spec 定的 option, 所以每個 JVM 實作都會支援. 穩定, 有可能在之後的 release 被 deprecated.
  2. Nonstandard: -X 開頭的 option. 不保證每個 JVM 實作都會支援. 也可能在沒告知的情況下被移除. 
  3. Developer: -XX 開頭的 option. 需在特定的系統需求與權限下才能被正確使用. 也可能在沒告知的情況下被移除.
Command Line Option 控制著 HotSpot VM 內部變數的值.
  1. + 或 - 可指定一個 boolean 變數為 true 或 false.
    Ex. -XX:+AggressiveOpts 設定 AggressiveOpts 為 true 去 enable 額外的效能優化
    Ex. -XX:-AggressiveOpts 設定 AggressiveOpts 為 false 去 disable 額外的效能優化
  2. Developer command line 也可以指定nonboolean value.
    Ex. -XX:OptionName=<N>
  3. 幾乎所有 value 為數值的 command line option 都可以使用 k,m,g 結尾來代表 kilo-, mega-, giga-

VM Life Cycle

HotSpot VM Runtime負責開啟與關閉 HotSpot VM
  1. launcher: 開啟 HotSpot VM 的 component.
    Ex. 
    1. java (Linux)
    2. javaw (Windows)
    3. JNI_CreateJavaVM (Embedded JVM through JNI interface)
    4. javaws (Java Web Start)
  2. Launcher 開啟 HotSpot VM 的流程
    1. Parse command line option: 有些 option  會拿來決定如何開啟 HotSpot VM 如
      -server or -client. 有些則會丟給開啟後的 HotSpot VM.
    2. 如果 java heap size 與 JIT compiler type 沒透過 option 指定, 則 Launcher 會在這時候根據環境計算 & 決定
    3. 建立環境變數如 CLASSPATH
    4. 如果 option 沒指定 Main-Class, Launcher 在這時候會去找 Jar file 的 manifest 指定的 Main-Class
    5. 在新建立的非原生(nonprimordial)的 thread 使用 JNI_CreateJavaVM 啟動 HotSpot VM. (原生的 thread 是作業系統建立的 thread)
    6. HotSpot VM 開啟後, 載入 Main-Class, 取得要丟給 Main-Class 的參數
    7. 解析參數後, 透過 JNI method CallStaticVoidMethod 傳入被呼叫的 Main-Class 的 main method.
  3. Java program 或 Java main method 執行完, HotSpot VM 要檢查與清除所有在程式執行中產生的 exception 然後回傳 exit status 給 caller.
  4. 呼叫 JNI method DetachCurrentThread 去 detach Java Main method.
  5. 當 DetachCurrentThread 被呼叫, thread count 會減少, 使 JNI 知道何時可以安全的關閉 HotSpot VM 並確保沒有 thread 在沒有 java frames or stacks 的時候還在 thread 裡面作業.

Class Loader Delegation

一個 class loader 可以叫另一個 class loader 去 load class 就叫做 Class Loader Delegation.
Class Loaders 被階層式的定義, 每個 class loader 有個 delegation parent. 這個 delegation 定義了呈現 class 的方法. Java SE class loader 會 search bootstrap classloader => extention class loader => system class loader (system class loader 就是預設的 application class loader, 也就是從 classpath 載入 class 與 Java main method 的 class loader). application class loader 可以是 Java SE 的 class loader, 也可以是 developer 自訂的 class loader. Java SE 的 class loader 實作了會從 JRE 的 lib/ext 載 class 的 extenstion class loader.

Bootstrap Class Loader

HotSpot VM 實作了 Bootstrap Class Loader, 會從 HotSpot VM BOOTCLASSPATH 載入 class 例如 rt.jar (rt.jar 包含了 Java SE class library).

Type Safety

Java class 或 Java interface 包含 package name 在 class loader 中必須是 unique. 這表示兩個不同的 class loader 內的同樣名字的 class 代表著不同的 class. HotSpot VM 要負責保證 extension customer class loader 不會破壞這個 type safety. => HotSpot 要確保當 class A 呼叫 B.someMethod() 時, A 的 class loader 與 B 的 class loader 透過 class loader 的 tracking constraint 都同意 someMethod 的 parameter 與 return type.

Byte Code Verification

在對於 Java 6 之前 compile 的 class (version number 50), HotSpot VM 使用 type inference 檢查 class file. Java 6 之後, HotSpot VM 使用新的, 效率較好的 type verification 機制.

Class Data Sharing

在 Java 5 之後為了優化啟動速度並減少記憶體使用 & 增加能同時開啟的 JVM 引進的功能. 原理是有些 class 是能夠跨 JVM 共用的, 放在 read-only memory mapped space 可以分享給不同的 JVM, 使不用重新載入 class.

HotSpot VM Garbage Collector

Generational Garbage Collection

HotSpot VM 使用 Generational Garbage Collection, 這個方式依賴兩種觀察:
  1. 大部分物件會很快變 unreachable
  2. 很少從老物件到新物件的關連
這兩個觀察就是 weak generational hypothesis. 根據這個假說, HotSpot VM 將 heap 分成幾塊: 
  • The young generation: 大部分新物件都會 allocate 在 young generation, 在 java heap 中這一塊相對小且很快會被回收, 因為大部分物件被預期會很快變成 unreachable, 存在 young generation 的然後被 minor garbage collection 的物件被期望要很少. 通常 minor garbage collection 會比較有效率因為它處理的空間比較小且包含很多要回收的物件.
  • The old generation: 活比較久的物件會被 promote 到 old generation. 這個區塊比 young generation 大, 成長的也比較慢. GC 較不頻繁, 但時間較久.
  • The permanent generation: 雖說這是個 generation, 但 user allocate 的物件不會被移到這裡. 這個區塊是給 HotSpot VM 使用的. 例如 metadata, internal string..etc
為了確保 GC 時間短, garbage collector 必須不用 scan 整個 old generation 就能從 young generation 指出 live object. 為此 HotSpot VM 使用 card table 來完成這件事. old generation 每 512 bytes 被分成一個 card. card table 是一個 array, 每個 card 有一個 byte 做為 entry. 每個 reference 欄位的更新必須確保包含該 reference 欄位的 card 被註記成 dirty. (透過設定 card table 的 entry). 在 minor GC 的時候只有被註記成 dirty 的 card 會被 scan 來發現從 old generation 連到 young generation 的 reference.

HotSpot VM 與 bytecode interpreter, JIT compiler 互動的時候使用 write barrier 的技巧維護 card table. 這個 barrier 就是一段去改寫 card table entry 為 dirty 的程式. 當 interpreter 執行 bytecode 修改 reference 的時候會跑一次 write barrier. JIT Compiler 也會在更新 reference 的時候去跑 write barrier.

generational garbage collection 的好處是每個 generation 可以依照特性使用不同的 GC algorithm. base 在 young generation 的特性 (空間小, 物件很快被回收), 比較快速的 garbage collector 做為 minor GC 可以浪費一些空間以較快速度處理 young generation. 而空間使用上較有效率的 garbage collector 則拿來處理佔了大部分  java heap 的 old generation. 這個 GC 比較慢但由於 full GC 的頻率比較少所以影響有限.

The Young Generation

Young Generation 分為三塊: The eden & two survivor spaces

  • The eden. 大部分新物件都放這, 在 minor GC 後 eden space 通常是空的
  • The two survivor spaces. 活過至少一次 minor GC 的物件就會進入 survivor space. 但還有機會在進入 old generation 前變成 unreachable.
在 minor GC 的時候, 不保證 survivor space 有足夠的空間放物件, 如果 overflow 了, 物件會直接放到 old generation. 這會造成 old generation 因為存放短暫存活的物件而變大造成效能問題. 當這現象持續發生導致 old generation 滿了, 就會導致 minor GC 之後就開始 full GC. 

Fast Allocation

object allocator 與 garbage collector 的操做緊密相關, garbage collector 必須記錄回收過的 free space 在哪裡, allocator 必須去找 heap 裡面能滿足 allocation request 的 free space 在哪. 回收 eden space 的 garbage collector 有個優勢就是每次回收之後 eden space 就是空的. 這讓 eden space 的 allocation 透過 bump-the-pointer 的技巧能很有效率. bump-the-pointer 這個技巧是去追蹤最後一個 allocated object, 把位置放在 top, 當新的 allocation request 來的時候, allocator 只需要檢查 eden 的 top 與 end 是否能滿足該 allocation request, 滿足的話, top 就會被調整到新 allocate object 的尾巴.

在 multi-threaded 的環境中, bump-the-pointer 為了做對需要 lock, 如果只有一個 lock (global lock) 會有效能問題, HotSpot VM 使用 Thread-Local Allocation Buffers (TLABs) 的技巧, 在 eden space 中切一小塊給每個 thread 專屬的能夠 allocate 的 buffer 來提升 multi-thread 時候 allocation 的效能. 由於每個 TLAB 都只會有一個 thread 在使用, 就不需要 lock 的機制就能執行 bump-the-pointer 的動作. 然而當 TLAB 滿了, 一個 thread 需要新的 TLAB 就需要取得 lock 取得 TLAB 才安全. 在 HotSpot VM 中 new Object() 通常會有大約 10 行 assembly code, 就是為了清空 eden space 並啟動 fast allocation.

Garbage Collectors: Spoiled for Choice

HotSpot VM 有四種 garbage collector, 不同 garbage collector 針對不同型態的 application 設計.

The Serial GC

old generation 由 sliding compacting mark-sweep 也就是 mark-compact garbage collector 管理,  Serial GC 負責 young generation. minor 與 full GC 會在 stop-the-world 時進行, 整個 application 在 GC 結束前動作都會停下來.
mark-compact collector 會先指出哪些物件還活在 old generation, 把它們轉到 heap 一開始的地方, 在 heap 的尾端騰出連續的空間. 這讓之後的物件使用快速的 bump-the-pointer 的技巧從 young generation promote 到 old generation.
大部分沒有 "暫停時間一定要很短" 需求或是在 client machine 執行的 application 選用 Serial GC. 好處是只有一個 virtual processor 會執行 garbage collection 的工作. 在今天的機器上, Serial GC 可以有效率只要很短的暫停時間處理 100MB 以下的 heap size.
另一個使用 Serial GC 的 case 是在一台機器上跑多個 JVM. (JVM 的數量比 processor 還多) 在這樣的環境上, 即使 garbage collection 會跑更久, 一個 JVM 最好只跑在一個 processor 來減少對其他 JVM 的影響.

The Parallel GC: Throughput Matters!

很多 java application 執行的環境有許多的實體記憶體與 processor. 理想上 garbage collector 會善用這些資源執行 GC 的工作. 為了加強跑在這種 server style 機器上 application 的 throughput, HotSpot VM 提供 Parallel GC, 也叫做 Throughput GC.
Parallel GC 的行為跟 Serial GC 一樣, 將物件從 young generation 搬到 old generation 的時候要 stop-the-world. 但是 minor 與 full GC 可以使用 available processors 同時進行.
有需求 throughput 要好且 pause time 不能低 (stop-the-world 的時間不能久)的 application, 如果跑在多個 processor 的機器上可以使用 Parallel GC. 

The Mostly-Concurrent GC: Latency Matters!

對某些 application 而言, 一段一段 throughput 的重要性比不上快速的 response time. 在 stop-the-world 模式下 application 在 GC 完成前會無法服務. minor GC 的影響不大, 但在 full GC 的時候, 即使頻率不高, 但每次的 stop-the-world 都可能造成下次更久的 stop-the-world. 
為了解決這個問題, HotSpot VM 提供 Mostly-Concurrent GC, 也叫做 Concurrent Mark-Sweep GC (CMS). 這個機制對 minor GC 的處理不變, 但對於 old generation, CMS 用一個演算方式讓大部分 GC 的工作同時進行, 每次 GC 只需要兩次短暫的暫停.
CMS 的流程是: 
  1. 開始於一個短暫的暫停 (整個 JVM stop-the-world), 稱做 initial mark, 指出可立即從 old generation 外部碰觸到的物件. 
  2. 在 concurrent marking phase 的時候標記這些物件是可接觸到的. 
  3. 由於在標記的時候可能 reference fields 就被更新了 (CMS 正在 iterate 的 object tree 會更新), 所以不保證所有的物件在 concurrent marking phase 結束後都會被標記完. 為了解決這個問題, application 會再暫停一下子, 稱為 remark pause, CMS 在這個 phase 中會再訪問一次 concurrent marking phase 中有更動過的物件並給予最終狀態. 為了追蹤物件狀態這時候會重複使用 card table.
  4. 由於 remark pause比 initial mark 更重, 為了增加效率被設計成同步進行 (parallelized)
  5. 為了更進一步減少 remark pause的工作, HotSpot VM 引進 concurrent pre-cleaning phase. 這個 phase 在 concurrent marking phase 後以及 remark pause之前發生. 這時候會做些在 remark pause 的事情, 例如重新拜訪一次被更動的物件, 如此在 remark pause 的時候工作就不會那麼多. (有些需要 finalize mark 的物件在 pre-cleaning 的時候就處理過了)
  6. 在 remark pause 的最後, 保證所有 java heap 裡面的 live object 都會被標記.
    整體來說, 比起 Parallel GC, CMS 的工作量比較多. 這是因為 garbage collector 要做的事情就是那麼多, 為了增加效率與減少 pause time 只好增加工作量.
  7. 登記完 old generation 的所有物件後, 最後一個 phase 就是 concurrent sweeping phase, 也就是把垃圾物件清除. 與 Serial GC & Parallel GC 不同的是, CMS 清除垃圾物件後, free space 並不是連續的. CMS 會去記錄 free space 位置在哪. 這導致在 old generation 定位物件的成本比使用 bump-the-pointer 技巧的 Serial GC or Parallel GC 來的昂貴.
  8. 這也對 minor GC 帶來額外的負擔因為 old generation 的 allocation 會發生在物件在 minor GC 被 promote 的時候.
  9. CMS 另一個 Serial GC 與 Parallel GC 沒有的缺點是: CMS 需要更多的 Java heap.
    1. 這是因為 concurrent marking cycle 延續的時間比 stop-the-world 還長, 而且只有在 sweeping phase 的時候 space 才真正的被回收.
    2. 在 marking phase 的時候 application 還是可以繼續執行, 所以也可以繼續在 old generation 佔用新的空間, 而這些空間又只會在 sweeping phase 的時候減少
    3. 此外, 儘管 GC 保證在 marking phase 的時候 identify 所有的 live object, 但這不保證它會 identify 所有的 garbage 物件. 物件有可能在 marking phase 的時候被偵測到是 garbage 但也可能不會, 如果沒被偵測到就只能等下次的 marking phase.
    4. 在 GC 時候沒被偵測到的 garbage object 稱作 floating garbage (漂浮垃圾)
  10. 最後, 由於缺乏 compaction 造成的碎片問題 (fragmentation issues) 可能使 garbage collector 無法盡可能有效使用 free space. 如果 old generation 在回收有效空間之前就滿了, 如同 Serial GC 與 Parallel GC, CMS 會回到 stop-the-world compackting 的階段.

The Garbage-First GC: CMS Replacement

  1. G1 使用跟其他 GC 機制不同的 Java heap layout.
  2. 它把 Java heap 分成多組同樣大小的 chunk, 稱作 region.
  3. 雖然 G1 是 generational, 但沒有實體上區分 young 與 old generation. 而是每個 generation 有多個 regions. (這些 regions 不一定是連續的). 這讓 G1 能有彈性的調整 young generation 的 size.
  4. G1 是在從 region 中評估 surviving object 的時候回收資源, 大部分時候 G1 回收 young regions (也就是 G1 的 young generation) 就像 minor GC 一樣.
  5. G1 也會周期性的進行 concurrent marking cycles 來 identify 哪些 non-young regions 是空的或大部分是空的 (empty: 看文章應該是說沒空間 promote 物件到此). 這些 region 屬於回收起來 CP 值最高的 regions. 這些 region 會被丟進排程進行回收. 

HotSpot VM JIT Compilers

Compilation Policy

  1. 由於 JIT 沒有足夠的時間 compile 每個 method, 所有的程式最初都從 interpreter 開始執行, 一旦 code 變夠 hot 了就會被 schedule 去 compile.
  2. 這在 HotSpot VM 裡面是由兩個 counter 來控制: invocation counter 與 backedge counter.
    1. invocation counter: 每次 method 被呼叫就 +1
    2. backedge counter: 每次 control flow 從較高的 bytecode index 執行到較低的 bytecode index 就 +1.
      backedge counter 用來偵測有 loop 的 method 來提前 compile.
  3. counter 無論何時增加都會檢查一個 threadhold, 如果超過 threadhold, interpreter 就會要求 compile 這個 method. 
  4. invocation counter 的 threadhold 名字叫 CompileThreadhold.
  5. backedge counter 的 threadhold 公式比較複雜:
    CompileThreadhold * OnStackReplacePercentage / 100
  6. compilation request 會被 enqueue 進一個一或多個 compiler threads 監控著的 list, 當 compiler thread 不忙的時候就會將 compilation request 從 list 移掉然後開始進行 compilation.
  7. 通常 interpreter 不會等 compiler 完成, 而是 reset invocation counter 後繼續執行 interpreter 裡面的 method. 當 compilation 完成後, compile 過的 method 會被關連到那個 method, 讓下一個 method caller 使用那個 compiled code. (當 interpreter 偵測到一個 method 是 compiled 過的, 下次就不會執行 interpreter 裡面的 code 而會 dispatch 到 compiled 的 code 去執行)
  8. 然而, 當一個 java code 是跑一個很長而且只跑一次的 loop, 比方說一個程式會執行無限迴圈直到 process 關掉, 這時候 method 雖然被偵測到要 compile, 但是 compile 結束卻不會被使用, 而是一直使用 interpreter 的 code, 因為正常情況 long-running loop method 要等到下次 method 被呼叫的時候才會使用 compiled 過的程式. 這時候 HotSpot VM 可以執行一個特別的 compiles call "On Stack Replacement" 或稱為 "OSRs" 可以解決這個問題.
  9. 當 backedge counter 溢位 (猜是超過 threadhold 的意思), interpreter 會發出 compile request, 這會開始執行 backedge 的 bytecode 而不是 method 第一次被呼叫的 bytecode. 這導致 compile 完產生出來的程式會把 interpreter frame 當成 input 來用並執行. 這作法使 long-running loop 可以使用 compiled 過的 bytecode. 
  10. On Stack Replacement 因此而得名: compile 完產生的 code 拿一個 interpreter frame 來執行.

Client JIT Compiler Oberview

  1. HotSpot VM Client JIT Compiler 目標是快速啟動與 compilation.
  2. 為了給予 Java 合理的啟動效能避免太多的複雜度, Client JIT Compiler 被當成一個快速簡單的 code generator 程式啟動, 
  3. 這概念上跟 interpreter 有點像因為它會針對不同種類的 bytecode 建立 template 並維護一個像 interpreter frame 的 stack layout.
  4. Java 1.4 之前只支援 field accessor 的 inline.
  5. Java 1.4 之後支援 inline method, 還支援 Class Hieracrchy Analysis 與 deoptimization.
  6. Java 6 後為了提升效能有很多改變, 其中一個是把 intermediate representation 改為 SSA style representation, 且原本 simple local register allocator 改為 linear scan register allocator.
  7. 此外 Java 6 支援數字可以跨 block. 在 x86 支援使用 SSE 增加浮點數的操作, 提升浮點運算效能提升.

Server SIT Compiler Overview

  1. HotSpot VM Server JIT Compiler 目標是好的效能, throughput 與優化
  2. 比起 Client JIT Compiler, Server JIT Compiler 在 compile 的時候需要更多的 space 與時間.
  3. Server JIT Compiler 會更積極作 inline, 通常也帶來更大的 method, 更大的 method 要花更長的時間 compile.

SSA - Program Dependence Graph

program dependence graph

  1. Server JIT Compiler 的 intermediate representation (IR) 是一個 SSA like IR, 但它使用了不同的 representing 流程處理方式, 稱為 "program dependence graph"
  2. 為了能夠積極的重新排列 operation 與 global value 來減少多餘的計算, "program dependence graph" 會試著在 operation 進行中截取最小的 constraint, 
  3. "program dependence graph"擁有 rick type system 而能夠取得所有 java type 執行的細節, 再投入之後的優化中. 

methodDataOop

  1. interpreter 在執行的時候會蒐集 profile information 給 Server JIT Compiler 使用.
  2. 執行 bytecode 的時候, 一個 method 如果執行了足夠的次數, interpreter 就會建立一個 methodDataOop 的物件來裝這個 method 的 profile information, 資料包含哪些 type 被呼叫以及呼叫幾次.
  3. 所有 control flow 的 bytecode 也都會紀錄自己有多常被使用以及後續的動作.
  4. 所有這些資訊都被 Server JIT Compiler 用來基於 common types 與執行頻率找到 inline 的機會, 這會驅動 block layout 與 register allocation.

uncommon trap

  1. 所有 Java bytecode 的 JIT Compiler 都需要面對 unloaded 或 uninitialized classes 的可能性. 
  2. 在 Server JIT compiler 包含 unresolved constant pool entries 時,Server JIT compiler 會把 unloaded 或 uninitialized classes 當成碰不到的路徑 (unrechable path)
  3. 遇到這種情況, Server JIT compiler 會替該 bytecode 送出一個 uncommon trap 並停止透過該 method parse 的 path.
  4. 一個 uncommon trap 是一個給 HotSpot VM 的 request, 這個 request 會要求 HotSpot VM deoptimize 目前的 compiled method, 然後將程式退回到 constant entry pool 能被解析與 process 為止, 退回的部分改在  interpreter 執行. 
  5. HotSpot VM 收到 uncommon trap 後, 原本 compiled 過的 code 會被丟掉, 程式會回到 interpreter 執行, 直到下次 compile 又被觸發為止, 下一次執行時由於 path 都被 resolved 過, 所以下次可以正常的 compile.
  6. uncommon traps 也會用來處理沒碰觸到的 path, 所以 compiler 沒有 generate code 給沒有用過的 method. 這樣 code 會比較小而且更直接的程式通常更好優化.
  7. Server JIT Compiler 還會拿 uncommon trap 來做更樂觀的優化, 當 Server JIT Compiler 認為某些行只會執行一次, 就會放個 dynamic check 去檢查, 如果檢查結果認為是 uncommon 就送出 uncommon trap 然後在 interpreter 執行這段程式. 當 uncommon trap 發生次數夠多, 就會被當成其實不是 uncommon, 就不再懷疑這可能是 uncommon 而直接使用 generated code. 這個情況發生在: 當 call site 從一個 profile information 只會看到一個類別, Server JIT Compiler 就會假設只看到一個類別, 然後加入檢查. 如果大部分情況看到一個類別, 少部分情況看到別的類別, Server JIT Compiler 就不會送出 uncommon trap 而是使用 compiled code.

optimizations on loops

  1. Server JIT Compiler 在 loop 上有很多優化, 透過拆解 iteration 做到如 loop unswitching, loop unrolling, range check elimination 等優化.
  2. 拆解 iteration 的方式是把一個 loop 轉成三個: preloop, main loop, post loop.
  3. 這個 idea 是去計算每個 loop 的邊界使 main loop 不用做任何的 range check, 只有 preloop 與 post loop 需要做 range check.
  4. 大部分情況 preloop 與 post loop 都只需要跑少數幾次, 甚至很多情況下都可以被完全淘汰.
  5. 當 range check 被移除, 就可以被 unroll. Loop unroll 就是讓 loop body 變單純, 讓被 loop 的程式攤開來不要跑 loop, 減少 loop 的次數.
  6. unroll 之後可以減少執行 loop 的成本, 通常可以更簡化 loop 的內容, 使 loop 可以在更短時間做更多事. 有時候幾次的 unroll 後可以讓 loop 完全消失.
  7. Loop unrolling 可以導致開始另一個優化叫做 superword, superword 是 vectorization 的一種形式. 
  8. Unrolling 可以在 loop body 建立一些平行的 operations, 如果那些 operations 照順序放在記憶體, 就可以被蒐集成 vector 裡面的 operations. 使一個指令就能在同樣的一段時間內執行多個 operation. 
一旦所有的高階優化都完成, IR (intermediate representation) 會被轉換成 machine dependent 的格式. 這時候可以享有所有特別的 instructions 與 processor address mode 的好處.

compound interest

Reference

Description

不想工作, 最好投資可以每年幫你賺 200萬, 就是在不工作的情況下還是可以賺200萬.
怎麼做呢? 我先做個網頁, 用複利的方式去替你算每年的目標是多少.
二三十年的規畫下來, 定好目標之後, 要怎麼執行則是另一個 story..

Android APP: GameVolumn

Reference

Description

昨晚才做完 BrightImage, 想說再做一個甚麼東西, 想到上次有朋友提個需求:
由於每次進到不同的 APP 都會需要調整不同的音量, 重點是每次調整的音量都變來變去.
有時是鈴聲, 有時是電話, 有時鬧鐘, 有時音樂...etc.
他希望有個 APP 可以只調整遊戲音量. 所以就做了 GameVolumn.

Development Note

  1. 首先查一下調整音量那個左右滑動的叫甚麼 => 結果叫做 SeekBar, OK.
  2. 希望 Activity 開起之後只出線 SeekBar 就好 => 把 Activity 設定為透明. OK.
  3. 看一下怎麼讀取跟設定音量 => 使用 AudioManager
  4. 設定好想要 beep 一下 => 使用 ToneGenerator
  5. 設定好, 當 user 想要離開, 點 SeekBar 以外的地方就關掉 Activity => 用 onTouchEvent
  6. 結束上傳
有點粗糙, 不過功能算是完成了. 

Fight with Bitmap OOM problem.

Reference

Description

為了買大樂透不想每次都開 APP 顯示 QR Code 很慢, 所以做了 MAX Brightness 這個 Widget.
做好之後, 的確可以方便的把亮度調最大再開 QR Code 的 screenshot, 掃完圖後也可以方便的恢復原本亮度.
朋友聽完這個 Widget 之後就問: 幹嘛不乾脆做一個功能直接把圖秀出來亮度最亮就好?
真是一語驚醒夢中人... 於是就開始做 BrightImage 這個 APP.
需求就很簡單: 選一張圖顯示, 顯示的時候調最亮, 選過一次就不用重選.

Development Note

整個 APP 開發起來很簡單, 比較麻煩的是 Bitmap 這個東西.
由於功能包含可以選圖呈現, 所以測試的時候就也亂選圖, 結果選到一張手機拍的照片後程式就 crash 了... Orz
看 log 發現是 OutOfMemory, 引發 OutOfMemory 的圖有 3264*1826 這麼大.
程式的寫法是

Bitmap bitmap = BitmapFactory.decodeFile(picturePath);
imageView.setImageBitmap(bitmap);

這樣的寫法, 小圖沒問題, 大圖就爆了. 就算沒爆, 只要橫放&直放手機或者重複幾次就會爆.
後來上網查, 發現要 recycle, 所以我就在 onPause 去把 Bitmap recycle 掉, 結果沒那麼簡單. recycle 的時機很重要, 有時候 recycle 了, 底層還沒 recycle 完, 有時是 imageView 預設的圖在佔空間.
試了多種組合都沒用. 由於每天大概只做半小時吧, 加上最近忙家裡有事, 這個問題擺了幾週..

這幾週中有試到一種方式不會 OutOfMemory.

Display display = getWindowManager().getDefaultDisplay();
imageView.setImageBitmap(Bitmap.createScaledBitmap(BitmapFactory.decodeFile(picturePath),display.getWidth(), display.getHeight(), true));

這樣的寫法, 雖然圖片在螢幕翻轉的時候圖片會被 scale 成怪怪的樣子, 但不會 OOM.
當時沒發現原因, 直到今天才突然想到: 啊! 其實存在 imageView 裡面的 Bitmap size 比較小所以不會 OOM.
加 log 去檢查發現果然沒錯 => 雖然很簡單的規則卻過很久才想到 Orz

一開始想說是不是要自己算寬高, 可是這樣程式會比較雜. 想說難道沒有相關的 API 嗎?
看 Bitmap 有個 API: Bitmap#getScaledWidth(targetDensity:int), 就去查 Density 甚麼意思.
然後發現 Bitmap#getScaledWidth(targetDensity:int) 似乎可以達到把圖縮小的目的.
重要的是不用自己去算寬高, 感覺太雜了.

一開始縮小圖片的方式只有

bitmap.getScaledHeight(DisplayMetrics.DENSITY_LOW);

一開始測試沒問題, 結果上傳到 Google Play 後自己測試又遇到 OOM!!
看 log 發現原來原本的 density 是 240, DENSITY_LOW 是 120, 用 DENSITY_LOW 去縮小圖卻沒用, 因為圖片還是太大, 還是會遇到 OOM.
想是不是還是得自己算寬高的時候, 突然想到一個很瞎的方式: catch OOM 然後縮小 targetDensity 來縮小記憶體用量.

for ( int i = 1; i < 10; i++ ) {
  int targetDensity = bitmap.getDensity() / i;
  try {
    int h = bitmap.getScaledHeight(DisplayMetrics.DENSITY_LOW);
    int w = bitmap.getScaledWidth(targetDensity);
    Log.i(getClass().getName(), "reduce density to " + targetDensity);
    imageView.setImageBitmap(Bitmap.createScaledBitmap(bitmap, w, h, true));
    break;
  } catch (OutOfMemoryError e) {
    Log.w(getClass().getName(), "OOM when targetDensity:" + targetDensity);
  }    
}

結果這個方法有用. 想想應該不會有情況是把圖片的精細度縮 1024 倍還看不到, 就設定只要測10次就好了.

PS. 這次有加可以選圖的功能, 上網查詢後發現意外的簡單.

private void selectPicture() {
  Intent i = new Intent(Intent.ACTION_PICK, android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
  startActivityForResult(i, RESULT_LOAD_IMAGE);
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
  super.onActivityResult(requestCode, resultCode, data);
  if (requestCode == RESULT_LOAD_IMAGE && resultCode == RESULT_OK && null != data) {
    Uri selectedImage = data.getData();
    String[] filePathColumn = { MediaStore.Images.Media.DATA };
  
    Cursor cursor = getContentResolver().query(selectedImage, filePathColumn, null, null, null);
    cursor.moveToFirst();
  
    int columnIndex = cursor.getColumnIndex(filePathColumn[0]);
    String picturePath = cursor.getString(columnIndex);
    cursor.close();
                       
    loadImage();
  }
}

別名演算法 Alias Method

 題目 每個伺服器支援不同的 TPM (transaction per minute) 當 request 來的時候, 系統需要馬上根據 TPM 的能力隨機找到一個適合的 server. 雖然稱為 "隨機", 但還是需要有 TPM 作為權重. 解法 別名演算法...