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一个测试文件