【java实现控制台打印表格】

背景

判断给定数据库是否可以连接,习惯做法是安装一个客户端,输入连接信息后连接测试。但是客户现场通常只提供一个linux系统,没有相关客户端。因此,需要一个能在linux上运行的数据库连接测试工具。我的实现思路:使用jdbc连接目标服务器,并执行一条给定的sql语句,能够连接成功则在控制台输出执行结果,连接失败则打印异常信息。
在实现该功能的过程中,对我来说最麻烦的是要在控制台输出表格,难点在于控制表格列宽相等(涉及中英文长度不一致)以及表格内容要居中对齐

效果

最终实现效果如下:
在这里插入图片描述

核心代码分享

分享此内容的目的有二:

  1. 为要实现同样功能的童鞋提供参考
  2. 请大佬们从实现思路或者具体方法上指点一下是否有更佳实现方式

主要写了一个PrintTable类:
在这里插入图片描述

定义了一个Table内部类,实现以下方法:

  1. buildTable(List<List< String>> content): 传入二维list,构建表格
  2. getLimitTable():限制宽度,最大条数后的表格
  3. getMaxWidthLenList(Table table):得到表格的每列最大宽度,用于实现列宽相等
  4. getFormatTable(Table table, String symbol):根据指定分隔符得到最终格式化后的表格
  5. printTable(String… symbols):打印表格,指定分隔符

PrintTable:

package com.sw.utils;

import lombok.Data;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

@Data
public class PrintTable {

    private Table table;
    //最大列宽:sql查询结果某列内容可能过大,不想完全显示,因此限制最大列宽
    private Integer maxWidth;
    //最大条数:sql查询结果可能有非常多,通常不必完全显示,因此限制最大条数
    private Integer maxLength;

    public PrintTable(List<List<String>> content, Integer maxWidth, Integer maxLength) {
        this.table = buildTable(content);
        this.maxLength = maxLength;
        this.maxWidth = maxWidth;
    }


    public PrintTable(List<List<String>> content) {
        this.table = buildTable(content);
        this.maxLength = 10;
        this.maxWidth = 40;
    }

    /**
     * 创建Table实例
     *
     * @param content
     * @return
     */

    private Table buildTable(List<List<String>> content) {
        return new Table(content);
    }

    /**
     * 打印表格
     */
    public void printTable(String... symbols) {
        String symbol = symbols.length == 0 ? "|" : symbols[0];
        //按照最大列宽、最大数据量过滤后的表格
        Table limitTable = getLimitTable();
        //设置表格的最大宽度:得到每列宽度,再求和
        List<Integer> originMaxWidthList = getMaxWidthLenList(limitTable);
        limitTable.setMaxWidthList(originMaxWidthList);

        //得到格式化后的表格数据
        Table formatTable = getFormatTable(limitTable, symbol);
        Integer totalColSize = formatTable.getTotalColSize();
        //打印首行分割符号
        System.out.println(StringUtils.getRepeatChar("-", totalColSize));
        formatTable.getContent()
                .forEach(row -> {
                    row.forEach(System.out::print);
                    System.out.println();
                    //打印每行分割符号
                    System.out.println(StringUtils.getRepeatChar("-", totalColSize));
                });
    }


    /**
     * 格式化表格
     *
     * @param symbol 定义每列间隔符号
     * @return
     */
    private Table getFormatTable(Table table, String symbol) {
        //获取原表每列最大宽度
        List<Integer> originMaxWidthList = table.getMaxWidthList();
        //除了间隔符号外,固定在每个单元格前后加两个空格
        int symbolLen = symbol.length() + 2;
        //遍历原table,将每个单元格填充到该列最大长度
        List<List<String>> formatList = table.getContent().stream().map(
                row -> {
                    //用于流在遍历每行的过程中,获取列序号
                    AtomicInteger atomicInteger = new AtomicInteger(0);
                    return row.stream().map(cell -> {
                        //当前遍历的列序号
                        int j = atomicInteger.getAndIncrement();
                        //原表该列的最大宽度+间隔符号宽度-双字节出现的次数
                        int cellSize = originMaxWidthList.get(j) + symbolLen - StringUtils.getZHCharCount(cell);
                        //如果是首行,还需要再前面加一个分割符号|,故长度加1
                        cellSize = j == 0 ? cellSize + 1 : cellSize;
                        //返回原始字符串按照指定symbol填充到指定长度cellSize,并居中对齐的字符
                        return StringUtils.getPadString(cell, cellSize, symbol, j);
                    }).collect(Collectors.toList());
                }
        ).collect(Collectors.toList());
        //存储格式化后的表格数据
        Table formatTable = buildTable(formatList);
        //设置格式化表格的总宽度:原始宽度+自定义分割符号的总宽度(列数*符号宽度)+首列前面的符号宽度
        int totalColSize = table.getTotalColSize() + table.getColCount() * symbolLen + 1;
        formatTable.setTotalColSize(totalColSize);
        return formatTable;
    }

 	/**
     * @return 获取经过条件过滤的表格
     */
    private Table getLimitTable() {
        List<List<String>> limitContent = table.getContent().stream()
                .limit(maxLength)
                .map(row -> row.stream()
                	//去除内容中含制表符时对结果展示的影响
                        .map(cell -> cell == null ? null : cell.replaceAll("\t", " "))
                        .map(cell -> cell != null && cell.length() > maxWidth ? cell.substring(0, maxWidth) : cell)
                        .collect(Collectors.toList())
                ).collect(Collectors.toList());
        return buildTable(limitContent);
    }

    /**
     * 计算table每行的最大宽度
     * 要使列宽相等,就需要将每个单元格宽度设置为该列最大宽度,二计算每行最大宽度相对容易些
     * 故将content转置后得到的每行最大宽度即为所求
     * 需要考虑单双字节的情况,比如有数组arr:{"aabb","sql表格","编程学习"},
     * 按照String.length计算,arr[1]最长,但是实际上arr[2]看起来才是最宽的
     * 因此计算宽度时,将双字节字符看做2个单位长度,即:每出现一个双字节字符,长度+1
     *
     * @return
     */
    private List<Integer> getMaxWidthLenList(Table table) {
        //得到转置数组每个元素的长度,一个中文算两个长度
        return Arrays.stream(table.transpose())
                .map(rows -> Arrays.stream(rows)
                        .mapToInt(s -> {
                            //sql查询结果如果为null,则认为长度为4
                            if (s == null) {
                                return 4;
                            } else {
                                //加上双字节字符出现的次数,最短为null,四个字符
                                return s.length() + StringUtils.getZHCharCount(s);
                            }
                        }).max().orElse(0)
                ).collect(Collectors.toList());
    }

    @Data
    private class Table {
        /**
         * 表格内容(含表头)
         */
        private List<List<String>> content = new ArrayList<>();

        /**
         * 表格列总字符长度:便于打印行分割符号
         */
        private Integer totalColSize;
        /**
         * 每列最大宽度
         */
        private List<Integer> maxWidthList;


        Integer getTotalColSize() {
            if (totalColSize == null && maxWidthList != null && maxWidthList.size() != 0) {
                this.totalColSize = maxWidthList.stream().reduce(Integer::sum).get();
            }
            return totalColSize;
        }

        //private限制只能通过外部类构造
        private Table(List<List<String>> content) {
            this.content = content;
        }

        //获取表格行数
        int getRowCount() {
            return content.size();
        }

        //获取表格列数,0行代表表头,默认认为content中至少含有表头
        int getColCount() {
            return content.get(0).size();
        }

        /**
         * 转置二维数组
         *
         * @return
         */
        private String[][] transpose() {
            int rowCount = getRowCount();
            int colCount = getColCount();
            String[][] result = new String[colCount][rowCount];

            for (int i = 0; i < rowCount; i++) {
                for (int j = 0; j < colCount; j++) {
                    result[j][i] = content.get(i).get(j);
                }
            }
            return result;
        }
    }

}

用到的工具类:StringUtils

package com.sw.utils;

import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.IntStream;

public class StringUtils {

    /**
     * 判断字符串是否为空
     *
     * @param str
     * @return
     */
    public static boolean isEmpty(String str) {
        return str == null || "".equals(str);
    }

    /**
     * 将content按照正则匹配,返回可以匹配的字符串列表
     *
     * @param reg
     * @param content
     * @return
     */
    public static List<String> extractMessage(String reg, String content) {
        Pattern compile = Pattern.compile(reg, Pattern.CASE_INSENSITIVE);
        Matcher matcher = compile.matcher(content);
        List<String> list = new ArrayList<>();
        while (matcher.find()) {
            list.add(matcher.group());
        }
        return list;
    }


    /**
     * 将str重复count次,返回结果
     *
     * @param str
     * @param count
     * @return
     */
    public static String getRepeatChar(String str, int count) {
        StringBuilder res = new StringBuilder();
        IntStream.range(0, count).forEach(i -> res.append(str));
        return res.toString();
    }

    /**
     * 将字符串填充到指定长度并居中对齐
     *
     * @param str
     * @param len
     * @return
     */
    public static String getPadString(String str, Integer len) {
        StringBuilder res = new StringBuilder();
        str = str.trim();
        if (str.length() < len) {
            int diff = len - str.length();
            int fixLen = diff / 2;
            String fix = getRepeatChar(" ", fixLen);
            res.append(fix).append(str).append(fix);
            if (res.length() > len) {
                return res.substring(0, len);
            } else {
                res.append(getRepeatChar(" ", len - res.length()));
                return res.toString();
            }
        }
        return str.substring(0, len);
    }

    /**
     * 此方法主要为表格的单元格数据按照指定长度填充并居中对齐并带上分割符号
     *
     * @param str    原始字符串
     * @param len    输出字符串的总长度
     * @param symbol 分割符号
     * @param index  传入的cell在list的索引,如果为第一个则需要在前面增加分割符号
     * @return
     */
    public static String getPadString(String str, Integer len, String symbol, int index) {
        String origin = str + "  ";
        if (index == 0) {
            String tmp = getPadString(origin, len - 2);
            return symbol + tmp + symbol;
        } else {

            String tmp = getPadString(origin, len - 1);
            return tmp + symbol;
        }
    }

    /**
     * 得到一个字符串中单字节出现的次数
     *
     * @param cell
     * @return
     */
    public static Integer getENCharCount(String cell) {
        if (cell == null) {
            return 0;
        }
        String reg = "[^\t\\x00-\\xff]";
		cell = cell.replaceAll(reg, "");
		//把·当做中文字符两个宽度
        return cell.replaceAll("·", "").length();
    }

    /**
     * 得到制表符长度,每个\t显示四个长度
     *
     * @param cell
     * @return
     */
    public static Integer getTableCount(String cell) {
        if (cell == null) {
            return 0;
        }
        String reg = "\t";
//        String reg = "|[^\t\\x00-\\xff]";
        return cell.length() - cell.replaceAll(reg, "").length();
    }

    /**
     * 得到一个字符串中双字节出现的次数
     *
     * @param cell
     * @return
     */
    public static Integer getZHCharCount(String cell) {
        if (cell == null) {
            return 0;
        }
        return cell.length() - getENCharCount(cell);
    }

    public static void main(String[] args) {
        String test = "ab\t哈哈嘻嘻";
        String reg = "[^\t\\x00-\\xff]";
        System.out.println(test.replaceAll(reg, "").length());
        test.replaceAll("\t|[^\\x00-\\xff]", "");
        System.out.println(test.length());
        System.out.println(StringUtils.getZHCharCount(test));
        System.out.println(StringUtils.getENCharCount(test));
    }
}

调用方法:
将sql得到的rsultSet封装成二维list,再调用PrintTable即可完成控制台打印
在这里插入图片描述

更新

20220715:修改外国人名中的特殊符号宽度,把它当做中文处理,如:兹维·博迪
在这里插入图片描述
工具源码连接:https://download.csdn.net/download/qq_31076523/88664572

评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值