TotalCommander 进阶指南之自定义工具栏图标全攻略

张开发
2026/5/3 23:18:52 15 分钟阅读
TotalCommander 进阶指南之自定义工具栏图标全攻略
1. TotalCommander自定义工具栏的必要性第一次打开TotalCommander时那个默认的灰色工具栏简直让我怀疑人生。作为每天要和上百个文件打交道的程序员每次都要通过层层目录才能访问常用文件夹效率低得令人发指。直到我发现工具栏可以自定义整个工作流才发生了质的变化。自定义工具栏绝不只是换个皮肤那么简单。想象一下把每天要访问的20个目录、常用的15个工具软件全都变成一眼就能识别的图标按钮点击即达。我实测下来这样配置后文件操作效率至少提升3倍。特别是处理紧急任务时再也不用在层层文件夹里捉迷藏了。最让我惊喜的是这个功能对新手出奇地友好。你不需要懂编程甚至不需要知道dll文件是什么只要会点鼠标就能完成配置。下面我就把自己折腾了三个周末才摸透的配置技巧用最直白的方式分享给大家。2. 图标配置前的准备工作2.1 图标资源获取渠道好的工具栏从选对图标开始。我试过几十种图标方案总结出这几个靠谱来源系统内置资源Windows自带的shell32.dll和imageres.dll里藏着上千个高质量图标。路径通常在C:\Windows\System32下用TC直接导航到这个目录就能预览。这些图标风格统一适合基础功能按钮。专业图标网站推荐Iconfont和Flaticon搜索toolbar icons能找到成套的免费素材。下载时注意选择32x32或64x64尺寸PNG格式带透明通道的效果最好。程序自带图标很多软件的exe文件里都内置图标资源。比如Notepad的图标就藏在安装目录的notepad.exe里。用TC的文件→打开→内部查看器可以直接提取。2.2 图标管理的最佳实践刚开始我把所有图标都堆在桌面结果重装系统全丢了。现在我的方案是在TC目录下新建Icons文件夹按功能建立子目录System、Tools、Folders等每个图标文件按功能_颜色_尺寸命名比如RecycleBin_Blue_32px.ico这样管理后配置新电脑时只要把整个Icons文件夹复制过去所有工具栏设置都能完美还原。实测这个习惯让我至少省了20小时的重配置时间。3. 分步配置工具栏图标3.1 基础配置步骤给TC添加一个新按钮其实就5步右键工具栏→配置工具栏点击添加按钮新建条目在命令栏输入功能代码如cm_OpenDesktop点击图标文件旁的箭头选择图标在提示文字里输入鼠标悬停说明但魔鬼藏在细节里。我踩过的坑包括图标路径要用绝对路径相对路径换电脑就失效带空格的路径必须加引号比如C:\Program Files\App\icon.ico系统dll文件选择图标时记住图标编号更方便后期修改3.2 高级命令配置除了打开文件夹TC工具栏还能直接执行复杂操作。这是我的几个实用配置# 快速清空回收站 命令cm_EmptyRecycleBin 图标shell32.dll,31 # 用VSCode打开当前目录 命令cmd /c code %P 图标C:\Icons\VSCode.ico # 一键同步文件夹 命令cm_SyncDirs 图标imageres.dll,102这些命令配合自定义图标把原本需要多次点击的操作变成一键完成。特别是同步文件夹功能做备份时简直救命。4. 工具栏布局优化技巧4.1 视觉分组方案当按钮超过15个时合理的分组能大幅提升效率。我的布局方案是左侧高频系统功能回收站、桌面、快速访问中部项目相关目录按客户名称分组右侧开发工具链IDE、数据库客户端、终端最右侧用分隔符隔开不常用但重要的功能每个分组之间添加分隔符在配置里选---作为命令视觉上更清晰。实测这种布局让我的误点击率降低了70%。4.2 响应式布局策略不同显示器尺寸需要不同布局。我的方案是笔记本小屏幕只保留10个核心按钮其他通过子菜单组织外接大显示器展开所有30个按钮按功能分三行排列4K高分屏使用64x64大图标同时增加按钮间距TC的工具栏配置可以保存为多个方案通过快捷键快速切换。我常用的组合是Ctrl1精简工作模式Ctrl2全功能开发模式Ctrl3文件整理专用布局5. 疑难问题解决方案5.1 图标显示异常处理遇到过好几次图标变成白板的情况总结出这些解决方法检查图标路径是否包含中文或特殊字符确认图标尺寸不超过128x128像素系统dll图标需要管理员权限才能读取尝试把图标复制到TC目录再重新关联最稳妥的方案是把所有图标都转成ico格式。我用的是免费的Greenfish Icon Editor批量转换时还能统一调整色调。5.2 配置备份与迁移重装系统最怕丢配置。我现在用这个自动化方案用TC的配置→保存配置功能导出INI文件编写批处理脚本自动复制这些目录%APPDATA%\GHISLER\TC安装目录下的Icons文件夹工具栏配置注册表项HKEY_CURRENT_USER\Software\Ghisler# 1. 概述本文分享Dubbo 的线程池策略。在 《精尽 Dubbo 源码分析 —— 线程池》 一文中我们已经看到了多种线程池和线程队列那么 Dubbo 是如何给我们设置最合适的线程池的呢这就是本文的目的。Dubbo 的线程池策略基于dubbo-common模块的threadpool包实现整体类图如下2. ThreadPoolcom.alibaba.dubbo.common.threadpool.ThreadPool线程池接口。代码如下SPI(fixed) public interface ThreadPool { /** * 获得对应线程池 * * param url URL 对象 * return 线程池 */ Adaptive({Constants.THREADPOOL_KEY}) Executor getExecutor(URL url); }SPI(fixed)注解Dubbo SPI拓展点默认为fixed。Adaptive({Constants.THREADPOOL_KEY})注解基于 Dubbo SPI Adaptive 机制加载对应的线程池实现使用URL.threadpool属性。#getExecutor(url)方法获得线程池对象。2.1 FixedThreadPoolcom.alibaba.dubbo.common.threadpool.support.fixed.FixedThreadPool实现 ThreadPool 接口固定大小线程池启动时建立线程不关闭一直持有。代码如下public class FixedThreadPool implements ThreadPool { public Executor getExecutor(URL url) { // 线程名 String name url.getParameter(Constants.THREAD_NAME_KEY, Constants.DEFAULT_THREAD_NAME); // 线程数 int threads url.getParameter(Constants.THREADS_KEY, Constants.DEFAULT_THREADS); // 队列数 int queues url.getParameter(Constants.QUEUES_KEY, Constants.DEFAULT_QUEUES); // 创建执行器 return new ThreadPoolExecutor(threads, threads, 0, TimeUnit.MILLISECONDS, queues 0 ? new SynchronousQueueRunnable() : (queues 0 ? new LinkedBlockingQueueRunnable() : new LinkedBlockingQueueRunnable(queues)), new NamedThreadFactory(name, true), new AbortPolicyWithReport(name, url)); } }目前和fixed是等价的配置方式dubbo:protocol namedubbo threadpoolfixed /可通过dubbo:parameter keythreads value123 /配置线程数。可通过dubbo:parameter keyqueues value123 /配置队列数。第 10 行默认线程数为200。第 12 行默认队列数为0。第 14 至 16 行根据不同的队列数使用不同的队列实现queues 0 SynchronousQueue 对象。queues 0 LinkedBlockingQueue 对象。queues 0带队列数的 LinkedBlockingQueue 对象。第 17 行创建 NamedThreadFactory 对象用于生成线程名。第 18 行创建 AbortPolicyWithReport 对象用于当任务添加到线程池中被拒绝时。2.2 CachedThreadPoolcom.alibaba.dubbo.common.threadpool.support.cached.CachedThreadPool实现 ThreadPool 接口缓存线程池空闲一定时长自动删除需要时重建。代码如下public class CachedThreadPool implements ThreadPool { public Executor getExecutor(URL url) { // 线程名 String name url.getParameter(Constants.THREAD_NAME_KEY, Constants.DEFAULT_THREAD_NAME); // 核心线程数 int cores url.getParameter(Constants.CORE_THREADS_KEY, Constants.DEFAULT_CORE_THREADS); // 最大线程数 int threads url.getParameter(Constants.THREADS_KEY, Integer.MAX_VALUE); // 队列数 int queues url.getParameter(Constants.QUEUES_KEY, Constants.DEFAULT_QUEUES); // 线程存活时长 int alive url.getParameter(Constants.ALIVE_KEY, Constants.DEFAULT_ALIVE); // 创建执行器 return new ThreadPoolExecutor(cores, threads, alive, TimeUnit.MILLISECONDS, queues 0 ? new SynchronousQueueRunnable() : (queues 0 ? new LinkedBlockingQueueRunnable() : new LinkedBlockingQueueRunnable(queues)), new NamedThreadFactory(name, true), new AbortPolicyWithReport(name, url)); } }目前和cached是等价的配置方式dubbo:protocol namedubbo threadpoolcached /可通过dubbo:parameter keythreads value123 /配置线程数。可通过dubbo:parameter keyqueues value123 /配置队列数。可通过dubbo:parameter keyalive value123 /配置线程存活时长。第 10 行默认核心线程数为0。第 12 行默认最大线程数为Integer.MAX_VALUE。第 14 行默认队列数为0。第 16 行默认线程存活时长为60 * 1000毫秒。第 18 至 20 行根据不同的队列数使用不同的队列实现queues 0 SynchronousQueue 对象。queues 0 LinkedBlockingQueue 对象。queues 0带队列数的 LinkedBlockingQueue 对象。第 21 行创建 NamedThreadFactory 对象用于生成线程名。第 22 行创建 AbortPolicyWithReport 对象用于当任务添加到线程池中被拒绝时。2.3 LimitedThreadPoolcom.alibaba.dubbo.common.threadpool.support.limited.LimitedThreadPool实现 ThreadPool 接口可伸缩线程池但池中的线程数只会增长不会收缩。只增长不收缩的目的是为了避免收缩时突然来了大流量引起的性能问题。代码如下public class LimitedThreadPool implements ThreadPool { public Executor getExecutor(URL url) { // 线程名 String name url.getParameter(Constants.THREAD_NAME_KEY, Constants.DEFAULT_THREAD_NAME); // 核心线程数 int cores url.getParameter(Constants.CORE_THREADS_KEY, Constants.DEFAULT_CORE_THREADS); // 最大线程数 int threads url.getParameter(Constants.THREADS_KEY, Constants.DEFAULT_THREADS); // 队列数 int queues url.getParameter(Constants.QUEUES_KEY, Constants.DEFAULT_QUEUES); // 创建执行器 return new ThreadPoolExecutor(cores, threads, Long.MAX_VALUE, TimeUnit.MILLISECONDS, queues 0 ? new SynchronousQueueRunnable() : (queues 0 ? new LinkedBlockingQueueRunnable() : new LinkedBlockingQueueRunnable(queues)), new NamedThreadFactory(name, true), new AbortPolicyWithReport(name, url)); } }目前和limited是等价的配置方式dubbo:protocol namedubbo threadpoollimited /可通过dubbo:parameter keythreads value123 /配置线程数。可通过dubbo:parameter keyqueues value123 /配置队列数。第 10 行默认核心线程数为0。第 12 行默认最大线程数为200。第 14 行默认队列数为0。第 16 至 18 行根据不同的队列数使用不同的队列实现queues 0 SynchronousQueue 对象。queues 0 LinkedBlockingQueue 对象。queues 0带队列数的 LinkedBlockingQueue 对象。第 19 行创建 NamedThreadFactory 对象用于生成线程名。第 20 行创建 AbortPolicyWithReport 对象用于当任务添加到线程池中被拒绝时。3. AbortPolicyWithReportcom.alibaba.dubbo.common.threadpool.support.AbortPolicyWithReport实现java.util.concurrent.ThreadPoolExecutor.AbortPolicy拒绝策略实现类。打印 JStack 分析线程状态。3.1 属性/** * 线程名 */ private final String threadName; /** * URL 对象 */ private final URL url; /** * 最后打印时间 */ private static volatile long lastPrintTime 0; /** * 信号量用于保证同一个时间段只一个打印 JStack */ private static Semaphore guard new Semaphore(1); /** * 已注册的线程池集合 * * key线程池地址 * value线程池 */ private static final MapString, Executor executors new ConcurrentHashMapString, Executor(); /** * 打印 JStack 的周期单位毫秒 */ private final long dumpExpectionGap 10 * 60 * 1000;guard属性信号量用于保证同一个时间段只一个打印 JStack 。executors静态属性已注册的线程池集合。dumpExpectionGap属性打印 JStack 的周期10 分钟。3.2 构造方法public AbortPolicyWithReport(String threadName, URL url) { this.threadName threadName; this.url url; }3.3 rejectedExecutionOverride public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { // 打印告警日志 String msg String.format(Thread pool is EXHAUSTED! Thread Name: %s, Pool Size: %d (active: %d, core: %d, max: %d, largest: %d), Task: %d (completed: %d), Executor status:(isShutdown:%s, isTerminated:%s, isTerminating:%s), in %s://%s:%d!, threadName, e.getPoolSize(), e.getActiveCount(), e.getCorePoolSize(), e.getMaximumPoolSize(), e.getLargestPoolSize(), e.getTaskCount(), e.getCompletedTaskCount(), e.isShutdown(), e.isTerminated(), e.isTerminating(), url.getProtocol(), url.getIp(), url.getPort()); logger.warn(msg); // 打印 JStack dumpJStack(); // 抛出 RejectedExecutionException 异常 throw new RejectedExecutionException(msg); }打印告警日志。调用#dumpJStack()方法打印JStack。抛出 RejectedExecutionException 异常。3.4 dumpJStackprivate void dumpJStack() { // 获得当前时间 long now System.currentTimeMillis(); // 每 10 分钟打印一次 // reset every one hour to avoid overflow if (now - lastPrintTime dumpExpectionGap) { return; } // 获得信号量 if (!guard.tryAcquire()) { return; } // 创建线程池后台执行打印 JStack ExecutorService pool Executors.newSingleThreadExecutor(); pool.execute(new Runnable() { Override public void run() { // 获得系统 String dumpPath url.getParameter(Constants.DUMP_DIRECTORY, System.getProperty(user.home)); SimpleDateFormat sdf; // 获得系统 String OS System.getProperty(os.name).toLowerCase(); // window system dont support : in file name if (OS.contains(win)) { sdf new SimpleDateFormat(yyyy-MM-dd_HH-mm-ss); } else { sdf new SimpleDateFormat(yyyy-MM-dd_HH:mm:ss); } // 写入文件 String dateStr sdf.format(new Date()); // 获得输出流 FileOutputStream jstackStream null; try { jstackStream new FileOutputStream(new File(dumpPath, Dubbo_JStack.log . dateStr)); // 打印 JStack JVMUtil.jstack(jstackStream); } catch (Throwable t) { logger.error(dump jstack error, t); } finally { // 释放信号量 guard.release(); // 关闭输出流 if (jstackStream ! null) { try { jstackStream.flush(); jstackStream.close(); } catch (IOException e) { } } } // 记录最后打印时间 lastPrintTime System.currentTimeMillis(); } }); // 关闭线程池 pool.shutdown(); }每 10 分钟打印一次 JStack 。通过信号量guard保证同一时间有且仅有一个线程执行打印。调用JVMUtil#jstack(OutputStream)方法打印 JStack 。代码如下public static void jstack(OutputStream stream) throws Exception { // 获得所有线程的栈 ThreadMXBean threadMxBean ManagementFactory.getThreadMXBean(); for (ThreadInfo threadInfo : threadMxBean.dumpAllThreads(true, true)) { // 写入到流 stream.write(getThreadDumpString(threadInfo).getBytes()); } } private static String getThreadDumpString(ThreadInfo threadInfo) { StringBuilder sb new StringBuilder(\ threadInfo.getThreadName() \ Id threadInfo.getThreadId() threadInfo.getThreadState()); if (threadInfo.getLockName() ! null) { sb.append( on threadInfo.getLockName()); } if (threadInfo.getLockOwnerName() ! null) { sb.append( owned by \ threadInfo.getLockOwnerName() \ Id threadInfo.getLockOwnerId()); } if (threadInfo.isSuspended()) { sb.append( (suspended)); } if (threadInfo.isInNative()) { sb.append( (in native)); } sb.append(\n); int i 0; StackTraceElement[] stackTrace threadInfo.getStackTrace(); for (; i stackTrace.length; i) { StackTraceElement ste stackTrace[i]; sb.append(\tat ste.toString()); sb.append(\n); if (i 0 threadInfo.getLockInfo() ! null) { Thread.State ts threadInfo.getThreadState(); switch (ts) { case BLOCKED: sb.append(\t- blocked on threadInfo.getLockInfo()); sb.append(\n); break; case WAITING: sb.append(\t- waiting on threadInfo.getLockInfo()); sb.append(\n); break; case TIMED_WAITING: sb.append(\t- waiting on threadInfo.getLockInfo()); sb.append(\n); break; default: } } for (MonitorInfo mi : threadInfo.getLockedMonitors()) { if (mi.getLockedStackDepth() i) { sb.append(\t- locked mi); sb.append(\n); } } } LockInfo[] locks threadInfo.getLockedSynchronizers(); if (locks.length 0) { sb.append(\n\tNumber of locked synchronizers locks.length); sb.append(\n); for (LockInfo li : locks) { sb.append(\t- li); sb.append(\n); } } sb.append(\n); return sb.toString(); }通过ThreadMXBean获得所有线程的栈写入到文件流中。3.5 registerExecutorpublic static void registerExecutor(Executor executor) { executors.put(String.valueOf(executor.hashCode()), executor); }在com.alibaba.dubbo.common.threadpool.ThreadPool的实现类中被调用。例如// FixedThreadPool.java public Executor getExecutor(URL url) { // 创建线程池 Executor executor new ThreadPoolExecutor(/* 参数省略 */); // 注册线程池 AbortPolicyWithReport.registerExecutor(executor); return executor; }这样我们就可以在executors中看到该线程池的状态。4. JVMUtilcom.alibaba.dubbo.common.utils.JVMUtilJVM 工具类。目前仅有 JStack 功能整体和 「3.4 dumpJStack」 的类似。代码如下public static void jstack() throws Exception { jstack(System.out); } public static void jstack(OutputStream stream) throws Exception { ThreadMXBean threadMxBean ManagementFactory.getThreadMXBean(); for (ThreadInfo threadInfo : threadMxBean.dumpAllThreads(true, true)) { stream.write(getThreadDumpString(threadInfo).getBytes()); } } private static String getThreadDumpString(ThreadInfo threadInfo) { // ... 省略代码和 AbortPolicyWithReport 的相同 }5. 日志在AbortPolicyWithReport打印的日志如下2018-04-14 10:00:02,948 WARN pool-4-thread-1 support.AbortPolicyWithReport: [DUBBO] Thread pool is EXHAUSTED! Thread Name: DubboServerHandler-172.16.132.166:20880, Pool Size: 200 (active: 200, core: 200, max: 200, largest: 200), Task: 295 (completed: 95), Executor status:(isShutdown:false, isTerminated:false, isTerminating:false), in dubbo://172.16.132.166:20880!, dubbo version: 2.0.0, current host: 172.16.132.166一般情况下线程池满的原因是业务执行比较慢阻塞在业务逻辑里。通过查看 JStack 文件可以很好的排查和定位问题。

更多文章