YoRuo:想变得更好

爱折腾 - 乐于折腾 - 享受折腾

欢迎来到我的Blog~


一些小问题解决

最近一段时间堕落了。 = =

买了个PS4,天天晚上回家就玩游戏去了。 = =

这篇以后用来记录一些小问题吧。


MySQL 驱动版本不对 导致数据库bit类型字段异常

Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 3
	at com.mysql.cj.mysqla.MysqlaUtils.bitToLong(MysqlaUtils.java:68)
	at com.mysql.cj.core.io.MysqlTextValueDecoder.decodeBit(MysqlTextValueDecoder.java:231)
	at com.mysql.cj.jdbc.ResultSetRow.decodeAndCreateReturnValue(ResultSetRow.java:170)
	at com.mysql.cj.jdbc.ResultSetRow.getValueFromBytes(ResultSetRow.java:269)
	at com.mysql.cj.jdbc.BufferRow.getValue(BufferRow.java:349)
	at com.mysql.cj.jdbc.ResultSetImpl.getNonStringValueFromRow(ResultSetImpl.java:813)
	at com.mysql.cj.jdbc.ResultSetImpl.getBoolean(ResultSetImpl.java:904)
	at com.mysql.cj.jdbc.ResultSetImpl.getBoolean(ResultSetImpl.java:908)

https://bugs.mysql.com/bug.php?id=81755


MyBatis 查询返回插入后主键的值

单个的insert语句 加个selectkey标签

    <insert id="insert" parameterType="mbxc.task.domain.TaskGroup">
        insert into task_group
        <trim prefix="(" suffix=")" suffixOverrides=",">
            `id`,
            `nick`,
            `type`,
            `field`,
        </trim>
        <trim prefix="values (" suffix=")" suffixOverrides=",">
            #{id,jdbcType=BIGINT},
            #{nick,jdbcType=VARCHAR},
            #{type,jdbcType=VARCHAR},
            #{field,jdbcType=CHAR},
        </trim>
        <selectKey resultType="java.lang.Long" keyProperty="id">
            SELECT @@IDENTITY AS id
        </selectKey>
    </insert>

批量插入返回主键值 在 mybatis3.3.1 就已经支持了

https://github.com/mybatis/mybatis-3/pull/547

    <insert id="insertBatch" useGeneratedKeys="true" keyProperty="id" keyColumn="id">
        insert into task
        (
        `params`,
        `status`,
        `exit_code`,
        `group_id`,
        `message`,
        `date_created`,
        `last_updated`
        ) values
        <foreach collection="list" item="item" index="index" separator=" , ">
            (
            #{item.params,jdbcType=LONGVARCHAR},
            #{item.status,jdbcType=CHAR},
            #{item.exitCode,jdbcType=CHAR},
            #{item.groupId,jdbcType=BIGINT},
            #{item.message,jdbcType=LONGVARCHAR},
            #{item.dateCreated,jdbcType=TIMESTAMP},
            #{item.lastUpdated,jdbcType=TIMESTAMP})
        </foreach>
    </insert>

服务报出Broken pipe 异常奔溃

有天凌晨一个重要的服务突然宕机了。

gray2log日志打印的是java-io-ioexception-broken-pipe

org.apache.catalina.connector.ClientAbortException: java.io.IOException: Broken pipe	
	at org.apache.catalina.connector.OutputBuffer.realWriteBytes(OutputBuffer.java:389)
	at org.apache.tomcat.util.buf.ByteChunk.flushBuffer(ByteChunk.java:426)
	at org.apache.catalina.connector.OutputBuffer.doFlush(OutputBuffer.java:338)
	at org.apache.catalina.connector.OutputBuffer.close(OutputBuffer.java:291)
	at org.apache.catalina.connector.CoyoteOutputStream.close(CoyoteOutputStream.java:151)
	at org.apache.struts2.dispatcher.StreamResult.doExecute(StreamResult.java:305)
	at org.apache.struts2.dispatcher.StrutsResultSupport.execute(StrutsResultSupport.java:186)
	at com.opensymphony.xwork2.DefaultActionInvocation.executeResult(DefaultActionInvocation.java:371)
	at com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:275)
	at org.apache.struts2.interceptor.DeprecationInterceptor.intercept(DeprecationInterceptor.java:41)
	at com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:246)
	at org.apache.struts2.interceptor.debugging.DebuggingInterceptor.intercept(DebuggingInterceptor.java:256)
	at com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:246)
	at com.opensymphony.xwork2.interceptor.DefaultWorkflowInterceptor.doIntercept(DefaultWorkflowInterceptor.java:167)
	at com.opensymphony.xwork2.interceptor.MethodFilterInterceptor.intercept(MethodFilterInterceptor.java:98)
	at com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:246)

测试的时候都很正常,最近的更新也确定不会引发这个问题。

突然想起之前有一次部署的服务也出现过这个问题,原因是数据库地址是测试库的。

最后发现是获取数据库链接时候获取不到造成的。询问服务商回答说最近机房网络环境不稳定线路在做整改。。。


启动项目成功后又shutdown

接手同事一个项目,要进行功能上的迭代

2017-05-07 21:18:52.696  INFO 17475 --- [           main] mbxc.ShowcaseScheduleApplication         : Started ShowcaseScheduleApplication in 27.189 seconds (JVM running for 27.977)
2017-05-07 21:18:52.697  INFO 17475 --- [       Thread-3] s.c.a.AnnotationConfigApplicationContext : Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@7dfd3c81: startup date [Sun May 07 21:18:46 CST 2017]; root of context hierarchy
2017-05-07 21:18:52.700  INFO 17475 --- [       Thread-3] o.s.c.support.DefaultLifecycleProcessor  : Stopping beans in phase 0
2017-05-07 21:18:52.702  INFO 17475 --- [       Thread-3] o.s.j.e.a.AnnotationMBeanExporter        : Unregistering JMX-exposed beans on shutdown
2017-05-07 21:18:52.703  INFO 17475 --- [       Thread-3] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default'

然后报了这个异常。。 查来查去没发现啥毛病。 Google也没有确切的答案。

然后突然看到 配置类上面的注解 @Profile(“product”)

知道了。。 我本地的application.properties 并没有指定 spring.profiles.active

加载有 @Configuration 这个注解的类就相当于是空的。


MySQL插入的数据量大小

这两天在写一个迁移任务,600w条数据需要迁移。

花了一天写完测试了两遍算是结束了。

在读出数据解析处理后,插入的报了一个异常。这个异常之前也见过。

Cause: com.mysql.cj.jdbc.exceptions.PacketTooBigException: Packet for query is too large (8,425,663 > 4,194,304). You can change this value on the server by setting the 'max_allowed_packet' variable.
; SQL []; Packet for query is too large (8,425,663 > 4,194,304). You can change this value on the server by setting the 'max_allowed_packet' variable.; nested exception is com.mysql.cj.jdbc.exceptions.PacketTooBigException: Packet for query is too large (8,425,663 > 4,194,304). You can change this value on the server by setting the 'max_allowed_packet' variable.
	at org.springframework.jdbc.support.SQLStateSQLExceptionTranslator.doTranslate(SQLStateSQLExceptionTranslator.java:108)
	at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:73)
	at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:81)
	at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:81)
	at org.mybatis.spring.MyBatisExceptionTranslator.translateExceptionIfPossible(MyBatisExceptionTranslator.java:73)
	at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:446)
	at com.sun.proxy.$Proxy75.insert(Unknown Source)
	at org.mybatis.spring.SqlSessionTemplate.insert(SqlSessionTemplate.java:278)
	at org.apache.ibatis.binding.MapperMethod.execute(MapperMethod.java:57)
	at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:59)
	at com.sun.proxy.$Proxy91.insertBatchs(Unknown Source)
	at mbxc.mvc.service.SyncHistoryService.syncHandle(SyncHistoryService.java:67)
	at mbxc.mvc.service.SyncHistoryService$$FastClassBySpringCGLIB$$da69c9f7.invoke(<generated>)
	at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
	at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:738)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157)
	at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:99)
	at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:282)
	at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
	at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
	at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:673)
	at mbxc.mvc.service.SyncHistoryService$$EnhancerBySpringCGLIB$$911bc562.syncHandle(<generated>)
	at java.util.ArrayList.forEach(ArrayList.java:1249)
	at mbxc.task.taskhandler.TaskExecutor.syncHistory(TaskExecutor.java:83)

插入的数据大小太大了。

  1. 更改数据库max_allowed_packet的配置大小 https://dev.mysql.com/doc/refman/5.7/en/packet-too-large.html

    max_allowed_packet 默认为 16MB

  2. 对数据进行切分。


对List均匀拆分

以前写过一个拆分List的Util。

     public static List<List<Long>> splitList(List<Long> list, int len) {
       if (list == null || list.size() == 0 || len < 1) {
         return null;
       }

       List<List<Long>> result = new ArrayList<>();

       int size = list.size();
       int count = (size + len - 1) / len;
       for (int i = 0; i < count; i++) {
         List<Long> subList = list.subList(i * len, ((i + 1) * len > size ? size : len * (i + 1)));
         result.add(subList);
       }
       return result;
     }

并不是很好。

在Guava中Lists有拆分的方法

Lists.partition(list, size);

附源码

     /**
      * Returns consecutive {@linkplain List#subList(int, int) sublists} of a list,
      * each of the same size (the final list may be smaller). For example,
      * partitioning a list containing {@code [a, b, c, d, e]} with a partition
      * size of 3 yields {@code [[a, b, c], [d, e]]} -- an outer list containing
      * two inner lists of three and two elements, all in the original order.
      *
      * <p>The outer list is unmodifiable, but reflects the latest state of the
      * source list. The inner lists are sublist views of the original list,
      * produced on demand using {@link List#subList(int, int)}, and are subject
      * to all the usual caveats about modification as explained in that API.
      *
      * @param list the list to return consecutive sublists of
      * @param size the desired size of each sublist (the last may be
      *     smaller)
      * @return a list of consecutive sublists
      * @throws IllegalArgumentException if {@code partitionSize} is nonpositive
      */
     public static <T> List<List<T>> partition(List<T> list, int size) {
       checkNotNull(list);
       checkArgument(size > 0);
       return (list instanceof RandomAccess)
           ? new RandomAccessPartition<>(list, size)
           : new Partition<>(list, size);
     }

     private static class Partition<T> extends AbstractList<List<T>> {
       final List<T> list;
       final int size;

       Partition(List<T> list, int size) {
         this.list = list;
         this.size = size;
       }

       @Override
       public List<T> get(int index) {
         checkElementIndex(index, size());
         int start = index * size;
         int end = Math.min(start + size, list.size());
         return list.subList(start, end);
       }

       @Override
       public int size() {
         return IntMath.divide(list.size(), size, RoundingMode.CEILING);
       }

       @Override
       public boolean isEmpty() {
         return list.isEmpty();
       }
     }

     private static class RandomAccessPartition<T> extends Partition<T> implements RandomAccess {
       RandomAccessPartition(List<T> list, int size) {
         super(list, size);
       }
     }

对原有的list 根据下标进行拆分。最后返回的List中的 List中的 对象地址 还是 和传入的List相同。如果你对拆分前的List .clear()拆分拿到的List也会为空。


查询order by 后返回的数据

一开始很随意的用了这个语句 = = 还没有 使用 MAX()

这样会返回 date_created 相同时 找到的第一条数据。

select
last_id
from sync_info
order by date_created desc limit 1

然后我改成了 这个 当然也可以使用 MAX()

select
last_id
from sync_info
order by id desc limit 1

对象转Map

调用淘宝开发平台接口,有个接口请求参数很多很多,Content-Typeapplication/x-www-form-urlencoded

一个一个的 @Field塞好蠢也好麻烦就用了@FieldMap,前端传过来的是一个json,那么@RequestBody转为对象后需要再转为Map就写了一个Util类。

package mbxc.mvc.util;

import com.google.common.base.CaseFormat;
import com.google.common.base.Preconditions;
import com.google.common.collect.Maps;

import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Map;


/**
 * Created by LXC on 2017/5/10.
 */
public class MapUtils {

  private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

  public static Map<String, Object> objTransformMap(Object object, List<String> skipStrs) throws Exception {
    Preconditions.checkNotNull(object, "object must not be null !");

    Map<String, Object> map = Maps.newHashMap();

    BeanInfo info = Introspector.getBeanInfo(object.getClass());
    for (PropertyDescriptor pd : info.getPropertyDescriptors()) {
      Method reader = pd.getReadMethod();
      if (reader != null
          && (skipStrs == null || !skipStrs.contains(pd.getName()))
          && !pd.getName().equals("class")) {

        Object value = reader.invoke(object);
        value = value instanceof LocalDateTime ? ((LocalDateTime) value).format(DATE_TIME_FORMATTER) : value;
        if (value != null) {
          map.put(CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, pd.getName()), value);
        }
      }
    }
    return map;
  }
}

String 的 replace 和 replaceAll

字符串替换的时候报错了一个错。

java.util.regex.PatternSyntaxException: Unclosed character class near index 0
[
^
	at java.util.regex.Pattern.error(Pattern.java:1955)
	at java.util.regex.Pattern.clazz(Pattern.java:2548)
	at java.util.regex.Pattern.sequence(Pattern.java:2063)
	at java.util.regex.Pattern.expr(Pattern.java:1996)
	at java.util.regex.Pattern.compile(Pattern.java:1696)
	at java.util.regex.Pattern.<init>(Pattern.java:1351)
	at java.util.regex.Pattern.compile(Pattern.java:1028)
	at java.lang.String.replaceAll(String.java:2223)

很奇怪 = = 简单替换个字符串而已

然后看了一下 replace 和 **replaceAll **的源码,

如下。

    /**
     * Replaces each substring of this string that matches the given <a
     * href="../util/regex/Pattern.html#sum">regular expression</a> with the
     * given replacement.
     *
     * <p> An invocation of this method of the form
     * <i>str</i>{@code .replaceAll(}<i>regex</i>{@code ,} <i>repl</i>{@code )}
     * yields exactly the same result as the expression
     *
     * <blockquote>
     * <code>
     * {@link java.util.regex.Pattern}.{@link
     * java.util.regex.Pattern#compile compile}(<i>regex</i>).{@link
     * java.util.regex.Pattern#matcher(java.lang.CharSequence) matcher}(<i>str</i>).{@link
     * java.util.regex.Matcher#replaceAll replaceAll}(<i>repl</i>)
     * </code>
     * </blockquote>
     *
     *<p>
     * Note that backslashes ({@code \}) and dollar signs ({@code $}) in the
     * replacement string may cause the results to be different than if it were
     * being treated as a literal replacement string; see
     * {@link java.util.regex.Matcher#replaceAll Matcher.replaceAll}.
     * Use {@link java.util.regex.Matcher#quoteReplacement} to suppress the special
     * meaning of these characters, if desired.
     *
     * @param   regex
     *          the regular expression to which this string is to be matched
     * @param   replacement
     *          the string to be substituted for each match
     *
     * @return  The resulting {@code String}
     *
     * @throws  PatternSyntaxException
     *          if the regular expression's syntax is invalid
     *
     * @see java.util.regex.Pattern
     *
     * @since 1.4
     * @spec JSR-51
     */
    public String replaceAll(String regex, String replacement) {
        return Pattern.compile(regex).matcher(this).replaceAll(replacement);
    }

    /**
     * Replaces each substring of this string that matches the literal target
     * sequence with the specified literal replacement sequence. The
     * replacement proceeds from the beginning of the string to the end, for
     * example, replacing "aa" with "b" in the string "aaa" will result in
     * "ba" rather than "ab".
     *
     * @param  target The sequence of char values to be replaced
     * @param  replacement The replacement sequence of char values
     * @return  The resulting string
     * @since 1.5
     */
    public String replace(CharSequence target, CharSequence replacement) {
        return Pattern.compile(target.toString(), Pattern.LITERAL).matcher(
                this).replaceAll(Matcher.quoteReplacement(replacement.toString()));
    }

replace : 是直接是纯字符替换。

repalceAll : 是正则表达式替换,换进行转义。


Excel解析工具类

依赖jar

compile group: 'org.apache.poi', name: 'poi-ooxml', version: '3.16'
package mbxc.mvc.util;


import com.google.common.collect.Lists;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellType;
import org.apache.poi.xssf.usermodel.XSSFRow;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.InputStream;
import java.text.DecimalFormat;
import java.util.List;

/**
 * Created by LXC on 2017/5/10.
 */

public class ExcelUtils {

  private static final String OFFICE_EXCEL_2003_POSTFIX = "xls";

  private static final String OFFICE_EXCEL_2010_POSTFIX = "xlsx";

  private static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat("0");

  public static List<Long> readItemIdFromExcel(HttpServletRequest request) throws IOException {
    String typeNameFix = request.getHeader("X-File-Name");
    String type = MapUtils.getFileExt(typeNameFix);

    List<Long> list = null;
    if (OFFICE_EXCEL_2003_POSTFIX.equals(type.toLowerCase())) {
      list = readXls(request.getInputStream());

    } else if (OFFICE_EXCEL_2010_POSTFIX.equals(type.toLowerCase())) {
      list = readXlsx(request.getInputStream());
    }
    return list;
  }

  /**
   * .xls结尾的文件
   *
   * @param is 文件流
   * @return
   * @throws IOException
   */
  private static List<Long> readXlsx(InputStream is) throws IOException {
    XSSFWorkbook xssfWorkbook = new XSSFWorkbook(is);
    List<Long> list = Lists.newArrayList();

    for (int numSheet = 0; numSheet < xssfWorkbook.getNumberOfSheets(); numSheet++) {
      XSSFSheet xssfSheet = xssfWorkbook.getSheetAt(numSheet);
      if (xssfSheet == null) {
        continue;
      }
      for (int rowNum = 1; rowNum <= xssfSheet.getLastRowNum(); rowNum++) {
        XSSFRow xssfRow = xssfSheet.getRow(rowNum);
        if (xssfRow != null) {
          String result = getValue(xssfRow.getCell(0));
          if (StringUtils.isNotEmpty(result)) {
            list.add(Long.valueOf(result));
          }
        }
      }
    }
    return list;
  }

  /**
   * .xls结尾的文件
   *
   * @param is 文件流
   * @return
   * @throws IOException
   */
  private static List<Long> readXls(InputStream is) throws IOException {
    HSSFWorkbook hssfWorkbook = new HSSFWorkbook(is);
    List<Long> list = Lists.newArrayList();

    for (int numSheet = 0; numSheet < hssfWorkbook.getNumberOfSheets(); numSheet++) {
      HSSFSheet hssfSheet = hssfWorkbook.getSheetAt(numSheet);
      if (hssfSheet == null) {
        continue;
      }
      for (int rowNum = 1; rowNum <= hssfSheet.getLastRowNum(); rowNum++) {
        HSSFRow hssfRow = hssfSheet.getRow(rowNum);
        if (hssfRow != null) {
          String result = getValue(hssfRow.getCell(0));
          if (StringUtils.isEmpty(result)) {
            list.add(Long.valueOf(result));
          }
        }
      }
    }
    return list;
  }

  @SuppressWarnings("static-access")
  private static String getValue(Cell cell) {
    if (cell.getCellTypeEnum() == CellType.BOOLEAN) {
      return String.valueOf(cell.getBooleanCellValue());
    } else if (cell.getCellTypeEnum() == CellType.NUMERIC) {
      return String.valueOf(DECIMAL_FORMAT.format(cell.getNumericCellValue()));
    } else {
      return String.valueOf(cell.getStringCellValue());
    }
  }
}

求小于当前数字的斐波那契数列

  @Test
  public void fibonacci() {
    //1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233,377,610,987,1597,2584,4181,6765,10946,17711,28657,46368
    System.out.println("0 --- > " + fibonacciForLessThanNumber(0));
    System.out.println("1 --- > " + fibonacciForLessThanNumber(1));
    System.out.println("2 --- > " + fibonacciForLessThanNumber(2));
    System.out.println("3 --- > " + fibonacciForLessThanNumber(3));
    System.out.println("4 --- > " + fibonacciForLessThanNumber(4));
    System.out.println("5 --- > " + fibonacciForLessThanNumber(5));
    System.out.println("6 --- > " + fibonacciForLessThanNumber(6));
    System.out.println("7 --- > " + fibonacciForLessThanNumber(7));
    System.out.println("8 --- > " + fibonacciForLessThanNumber(8));
    System.out.println("9 --- > " + fibonacciForLessThanNumber(100));

    System.out.println("index 0  --- > " + fibonacciForIndex(0));
    System.out.println("index 1  --- > " + fibonacciForIndex(1));
    System.out.println("index 2  --- > " + fibonacciForIndex(2));
    System.out.println("index 3  --- > " + fibonacciForIndex(3));
    System.out.println("index 4  --- > " + fibonacciForIndex(4));
    System.out.println("index 5  --- > " + fibonacciForIndex(5));
    System.out.println("index 6  --- > " + fibonacciForIndex(6));
    System.out.println("index 7  --- > " + fibonacciForIndex(7));
    System.out.println("index 8  --- > " + fibonacciForIndex(8));
    System.out.println("index 9  --- > " + fibonacciForIndex(9));
    System.out.println("index 10  --- > " + fibonacciForIndex(10));
    System.out.println("index 11  --- > " + fibonacciForIndex(11));
  }

  private List<Long> fibonacciForLessThanNumber(long l) {
    List<Long> list = new ArrayList<>();

    if (l >= 0) {
      list.add(0L);
    }

    if (l >= 1) {
      LongStream.range(0, 2).forEach(i -> list.add(1L));
    }

    long current;
    long prePre = 1;
    long pre = 1;

    boolean flag = l > 1;

    while (flag) {
      if (l >= 2) {
        current = prePre + pre;
        prePre = pre;
        pre = current;
        if (current <= l) {
          list.add(current);
        } else {
          flag = false;
        }
      }
    }
    return list;
  }

  private long fibonacciForIndex(long n) {
    if (n <= 0) {
      return 0;
    }

    if (n <= 2) {
      return 1;
    }

    long pre = 1;

    long next = 1;

    long c = 2;

    for (int i = 3; i <= n; i++) {
      c = pre + next;
      pre = next;
      next = c;
    }
    return c;
  }

Html代码转图片并裁剪

突然遇到这个需求,找了很多jar都不尽如意。

最后还是找到了一个,还走了一个不是坑的坑。 = =

jar包:html2image

错误的jar地址。http://mvnrepository.com/artifact/com.github.xuwei-k/html2image/0.1.0

正确的jar地址。https://github.com/e-ucm/eadventure/tree/master/etc/repository/gui/ava/html2image

方法调用:

  private static List<BufferedImage> resizeToHeight(BufferedImage image) {
    int width = image.getWidth();
    int height = image.getHeight() / 20;

    List<BufferedImage> images = Lists.newArrayList();

    IntStream.range(0, 20).forEach(i -> {
      BufferedImage ret = image.getSubimage(0, i * height, width, height);
      images.add(ret);
    });
    return images;
  }

  public static void main(String[] args) throws IOException {
    String htmlTxt = "<p style=\"text-align:center;\"><span style=\"font-size:15.0pt;\">0702-13</span></p>\n" +
        "\n" +
        "<p style=\"text-align:center;\"><span style=\"font-size:15.0pt;\">尺码:SML</span></p>\n" +
        "\n" +
        "<p style=\"text-align:center;\"><span style=\"font-family: lisu;\"><span style=\"color: rgb(69, 129, 142);\"><strong><span style=\"font-size: 24px;\">面料;衬衣弹力棉+黑色带弹力西装棉&nbsp;</span></strong></span></span><br />\n" +
        " <span style=\"font-size:15.0pt;\">尺码;SML</span><br />\n" +
        " <span style=\"font-size:15.0pt;\">小码;衬衣 胸围84 衣长59 肩34 袖56 半裙;裤腰66 臀围84 裙长50 马甲随衬衣尺寸</span><br />\n" +
        " <span style=\"font-size:15.0pt;\">中码;衬衣 胸围88 衣长60 肩35 袖57 半裙;裤腰70 臀围88 裙长51 马甲随衬衣尺寸</span><br />\n" +
        " <span style=\"font-size:15.0pt;\">大码;衬衣 胸围88 衣长60 肩36 袖58 半裙;裤腰74 臀围92 裙长52 马甲随衬衣尺寸</span></p>"+
        "<p><img alt=\"undefined\" src=\"https://img.alicdn.com/imgextra/i1/93733667/TB2V0xIt9FjpuFjSspbXXXagVXa_!!93733667.jpg\" /><br />\n" +
        " <img alt=\"undefined\" src=\"https://img.alicdn.com/imgextra/i4/93733667/TB2szRMt88kpuFjSspeXXc7IpXa_!!93733667.jpg\" /><br />\n" +
        " <img alt=\"undefined\" src=\"https://img.alicdn.com/imgextra/i3/93733667/TB2tZOqt9FjpuFjSszhXXaBuVXa_!!93733667.jpg\" /><br />\n" +
        " <img alt=\"undefined\" src=\"https://img.alicdn.com/imgextra/i3/93733667/TB2j4EvtMNlpuFjy0FfXXX3CpXa_!!93733667.jpg\" /><br />\n" +
        " <img alt=\"undefined\" src=\"https://img.alicdn.com/imgextra/i4/93733667/TB2RAsStHtlpuFjSspfXXXLUpXa_!!93733667.jpg\" /><br />\n" +
        " <img alt=\"undefined\" src=\"https://img.alicdn.com/imgextra/i4/93733667/TB2uYAZtR0kpuFjy1zdXXXuUVXa_!!93733667.jpg\" /><br />\n" +
        " <img alt=\"undefined\" height=\"384.8481308411215\" src=\"https://img.alicdn.com/imgextra/i3/93733667/TB2rPRMt88kpuFjSspeXXc7IpXa_!!93733667.jpg\" width=\"790\" /><br />\n" +
        " <img alt=\"undefined\" src=\"https://img.alicdn.com/imgextra/i4/93733667/TB2iyITtHXlpuFjSszfXXcSGXXa_!!93733667.jpg\" /><br />\n" +
        " <img alt=\"undefined\" src=\"https://img.alicdn.com/imgextra/i3/93733667/TB2KOITtHXlpuFjSszfXXcSGXXa_!!93733667.jpg\" /><br />\n" +
        " <img alt=\"undefined\" src=\"https://img.alicdn.com/imgextra/i2/93733667/TB27wBetYJkpuFjy1zcXXa5FFXa_!!93733667.jpg\" /><br />\n" +
        " <img alt=\"undefined\" src=\"https://img.alicdn.com/imgextra/i1/93733667/TB2dpJFt80kpuFjSsppXXcGTXXa_!!93733667.jpg\" /><br />\n" +
        " <img alt=\"undefined\" src=\"https://img.alicdn.com/imgextra/i4/93733667/TB2qQ7FtNXlpuFjSsphXXbJOXXa_!!93733667.jpg\" /><br />\n" +
        " <img alt=\"undefined\" src=\"https://img.alicdn.com/imgextra/i3/93733667/TB2m7wStHtlpuFjSspfXXXLUpXa_!!93733667.jpg\" /><br />\n" +
        " <img alt=\"undefined\" src=\"https://img.alicdn.com/imgextra/i4/93733667/TB2.odstYXlpuFjy1zbXXb_qpXa_!!93733667.jpg\" /><br />\n" +
        " <img alt=\"undefined\" src=\"https://img.alicdn.com/imgextra/i2/93733667/TB2fGcOtH0kpuFjy0FjXXcBbVXa_!!93733667.jpg\" /><br />\n" +
        " <img alt=\"undefined\" src=\"https://img.alicdn.com/imgextra/i4/93733667/TB2RyIltRNkpuFjy0FaXXbRCVXa_!!93733667.jpg\" /></p>";
    HtmlImageGenerator imageGenerator = new HtmlImageGenerator();
    imageGenerator.loadHtml(htmlTxt);
    BufferedImage imageNew = imageGenerator.getBufferedImage();
    
    List<BufferedImage> imageList = resizeToHeight(imageNew);

    for (int i = 0; i < imageList.size(); i++) {
      File outFile = new File("/Users/lxc/Desktop/temp/" + i + ".png");
      ImageIO.write(imageList.get(i), "png", outFile);
    }
  }

第一个方法为裁剪图片,并未对原来的图片进行修改。而是重新创建了一个 BufferedImage 对象

    /**
     * Returns a subimage defined by a specified rectangular region.
     * The returned <code>BufferedImage</code> shares the same
     * data array as the original image.
     * @param x the X coordinate of the upper-left corner of the
     *          specified rectangular region
     * @param y the Y coordinate of the upper-left corner of the
     *          specified rectangular region
     * @param w the width of the specified rectangular region
     * @param h the height of the specified rectangular region
     * @return a <code>BufferedImage</code> that is the subimage of this
     *          <code>BufferedImage</code>.
     * @exception RasterFormatException if the specified
     * area is not contained within this <code>BufferedImage</code>.
     */
    public BufferedImage getSubimage (int x, int y, int w, int h) {
        return new BufferedImage (colorModel,
                                  raster.createWritableChild(x, y, w, h,
                                                             0, 0, null),
                                  colorModel.isAlphaPremultiplied(),
                                  properties);
    }

还有一个通过url拼接图片的方法:

  public static List<File> mergeAndCutPic(List<String> picList) {
    int length = picList.size();
    if (length < 1) {
      return null;
    }
    List<File> files = Lists.newArrayList();
    try {
      BufferedImage[] images = new BufferedImage[length];
      int[][] imageArr = new int[length][];

      for (int i = 0; i < length; i++) {
        images[i] = ImageIO.read(new URL(picList.get(i)).openStream());
        int width = images[i].getWidth();
        int height = images[i].getHeight();
        imageArr[i] = new int[width * height];
        imageArr[i] = images[i].getRGB(0, 0, width, height, imageArr[i], 0, width);
      }

      int tempHeight = 0;
      int tempWidth = images[0].getWidth();

      for (BufferedImage image : images) {
        tempWidth = tempWidth > image.getWidth() ? tempWidth : image.getWidth();
        tempHeight += image.getHeight();
      }

      if (tempHeight < 1) {
        return null;
      }

      BufferedImage imageNew = new BufferedImage(tempWidth, tempHeight,
          BufferedImage.TYPE_INT_RGB);
      int height = 0;
      for (int i = 0; i < images.length; i++) {
        imageNew.setRGB(0, height, tempWidth, images[i].getHeight(), imageArr[i], 0, tempWidth);
        height += images[i].getHeight();
      }

      List<BufferedImage> imageList = resizeToHeight(imageNew);

      for (BufferedImage image : imageList) {
        File outFile = new File("/Users/lxc/Desktop/temp/"
            + System.currentTimeMillis() + UUID.randomUUID() + ".png");
        ImageIO.write(image, "png", outFile);
        files.add(outFile);
      }
    } catch (Exception e) {
      return null;
    }
    return files;
  }