1. String format
格式
The format specifiers for general, character, and numeric types have the following syntax: %[argument_index$][flags][width][.precision]conversion The optional argument_index is a decimal integer indicating the position of the argument in the argument list. The first argument is referenced by "1$", the second by "2$", etc. The optional flags is a set of characters that modify the output format. The set of valid flags depends on the conversion. The optional width is a non-negative decimal integer indicating the minimum number of characters to be written to the output. The optional precision is a non-negative decimal integer usually used to restrict the number of characters. The specific behavior depends on the conversion. The required conversion is a character indicating how the argument should be formatted. The set of valid conversions for a given argument depends on the argument's data type.
直接看代码
System.out.println(String.format("|%1$-10s|随便写点什么|%2$10s|再加一个数字|%3$10.2f|", "ha", "bd",66.6666));
打印结果
|ha |随便写点什么| bd|再加一个数字| 66.67|
说明:
%1$-10s :
% 表示后面是一段格式化表达式
1$ 表示占位符,需要用跟在后面的第一个参数替换,1和argument_index相对应
- 表示左对齐,和flags相对应
10 对应于width,代表最小应该占用的宽度,因为需要打印的字符串可能超过width
s 对应于conversion,此处表示打印字符串
对于
%3$10.2f:
2表示打印的float精度到小数点后2位
对于格式化表达式之外的字符,直接打印
2. 对于普通的打印可以通过如下的函数以添加空格方式对齐
public static String padRight(String s, int n) { return String.format("%1$-" + n + "s", s); } public static String padLeft(String s, int n) { return String.format("%1$" + n + "s", s); }
How can I pad a String in Java
这种方法对英文表现良好,但是对于中文打印非常不理想
3. 自定义
考虑一个打印机,英文字符的打印宽度为中文字符的一半;
这种现象和GBK的存储格式比较类似。
但是java内置的是Unicode,
首先需要判断中文和普通的ASCII字符,
看一下Unicode中常用中文的位置:
Block名称 | 开始码位 | 结束码位 | 字符数 |
CJK统一汉字 | 4E00 | 9FBB | 20924 |
参见 Unicode、GB2312、GBK和GB18030中的汉字
那么对取出来的char ch,如果下面的条件成立,可以基本认定是普通的ASCII码
(ch & 0xFFFF00) == 0
现在需要打印出如下的效果
商品名最多占用十个汉字宽度,后面至少有一个ASCII空格;
后面数量占用三个汉字宽度,但最后至少有一个ASCII空格;
最后是金额。
这里主要考虑商品名超出九个汉字宽度的情况,首先需要知道从哪里截断
/** * @param s 待处理的字符串 * @param lenOnByte 最长的ASCII字符宽度,一个汉字=2倍ASCII字符宽度 * @return [0]=-1表示没有超出,否则表示截断的最后一个char的index; * [1]存储了s的ASCII宽度 */ int[] findBreakIdxBasedOnGBK(String s, int lenOnByte) { int res[] = {-1, 0}; int lenCnt = 0; int i = 0; for (; i < s.length(); i++) { char ch = s.charAt(i); //gbk单字节可存储 if ((ch & 0xFFFF00) == 0) { lenCnt += 1; } else { lenCnt += 2; } if (lenCnt >= lenOnByte) { break; } } //表明所有字符没有循环结束已经越界 //由于可能是正好最后一个char到达临界点,所以要减1 if (i < s.length() - 1) { res[0] = i; } res[1] = lenCnt; System.out.println("break point: " + res[0] + " # gbk len: " + res[1]); return res; }
然后添加空格
/** * 右边添加空格用于对齐 * @param s 待处理的字符串 * @param sLenOnByte s的ASCII字符宽度 * @param limitOnByte 最长的ASCII字符宽度 * @return 拼接完成的字符串 */ String padStringRightBasedOnGBK(String s, int sLenOnByte, int limitOnByte) { StringBuilder sb = new StringBuilder(); sb.append(s); //padding for (int i = 0; i < limitOnByte - sLenOnByte; i++) { sb.append(" "); } return sb.toString(); }
应用
List<String> formatGoodsList(List<GoodsItem> goodsItems) { List<String> printData = new ArrayList<>(); StringBuilder sb1 = new StringBuilder(); for (GoodsItem item : goodsItems) { String tmp = item.getGoodsName(); int[] idxs = findBreakIdxBasedOnGBK(tmp, 18); if (idxs[0] == -1) { //如果没有越界 sb1.append(padStringRightBasedOnGBK(tmp, idxs[1], 20)); } else { //如果越界,截取 sb1.append(padStringRightBasedOnGBK(tmp.substring(0, idxs[0] + 1),idxs[1],20)); } sb1.append(padRight(item.getGoodsNum() + "", 6)); sb1.append(item.getGoodsPrice()); printData.add(sb1.toString()); if (idxs[0] != -1) //如果越界,剩下的部分 printData.add(item.getGoodsName().substring(idxs[0] + 1)); sb1.setLength(0); } return printData; }
最后attach一个测试文件