Mac
快捷键 描述 命令 一个应用多个窗口 切换 command+` 录屏幕 shift+command+5 截图 shift+command+4 重装系统 先一直按住不要松手:Common+R,再按开机键;非苹果键盘,按住微软键+R 上一级目录 command+↑ 网站链接 一招解决 Chrome / Edge 浏览器卡顿变慢视频掉帧问题 - 让浏览器重回丝般流畅 AltTab iphone强制重启 浏览器返回手势 放歌有噪音通过【活动监视器】程序终止【coreaudiod】进程: 聚焦搜索】中输入【activity monitor】 在窗口右上角的搜索栏里输入【coreaudiod】 选择【coreaudiod】进程后,点击窗口上方工具栏中的【停止按钮】(按钮图标为X) 只有mac不能访问github 打开https://github.com.ipaddress.com/ 打开https://fastly.net.ipaddress.com/github.global.ssl.fastly.net#ipinfo sudo vim /etc/hosts 1234# 这个ip从第一步获取到的140.82.112.4 github.com# 这个ip从第二步获取到的199.232.69.194 github.global.ssl.fastly.net 刷新:sudo killall -HUP mDNSResponder;say DNS cache has been flushed 启动台有问号的图标,点击没反应123# 执行了之后,自己新建的文件夹会重置defaults write com.apple.dock ResetLaunchPad -bool TRUEkillall Dock Mac OS X 访问 Windows共享文件夹 在Mac中,点击 Finder菜单的“前往” > “前往服务器”。在弹出的连接服务器对话框中输入「smb://Windows主机的IP地址」,点击“连接”。 一般需要输入Windows对应用户的名称和密码,然后弹出如下窗口,选择对应的文件夹,点击“好”即可进行远程访问 只使用搜狗输入法系统偏好设置 >> 键盘 >> 快捷键 >> 反选切换输入法的快捷键 网络踪迹1traceroute ap-southeast-1.signin.aws.amazon.com 安装mysql客户端1234567brew install mysql-client# 安装目录在/opt/homebrew/Cellar/mysql-client/8.0.33_1 # mysqldump的目录/opt/homebrew/opt/mysql-client/bin/mysqldump Iterm2Iterm2多窗口同时输入命令只需要输入快捷键 ⌘(command) + ⇧(shift) + i 调出复制过的文本历史快捷键:“shift+cmd+h” 按键回放回放一段时间内的你敲过的所有字符。快捷键:“cmd+alt+b”,如图会弹出一个进度条,按左右键就可以实现按键回放了。 CheatSheet1234brew install cheatsheet# 使用随便在一个软件中 长按`command` Navicat破解gitee脚本 mac安装 protobuf123brew install protobufprotoc --version/opt/homebrew/share/emacs/site-lisp/protobuf 视频格式转换12345brew install ffmpeg# 使用这个命令ffmpeg -i input.mov -c copy output.mp4 # 不要用这个命令,网上说是速度很慢ffmpeg -i input.mov output.mp4
虚拟机
VirtualBox增强功能Centos8123456789# 需要安装一定的依赖,例如:make gcc perlsudo yum install gcc perl make tar bzip2 elfutils-libelf-devel#同时安装的kernel-devel版本要和内核版本一致,安装对应版本sudo yum install -y "kernel-devel-uname-r == $(uname -r)"# 或者通过DNF安装所有:dnf install tar bzip2 kernel-devel-$(uname -r) kernel-headers perl gcc make elfutils-libelf-devel
Tron
波场官方trident-java文档 m1 mac Android protobuf 遇到的问题 tron中文文档 trc20离线签名的demo,有trxdent-java,就不需要这个了,使用http的方式可以看看 trc20离线签名的demo,有trxdent-java,就不需要这个了,使用http的方式可以看看,另外一个demo 要继续学习tron可以看看 打包依赖 下载源文件trident-java 这里选择main分支 进入trident-java文件夹,可以看到有build.gradle,如果是mac系统,修改一下依赖 1234567891011121314protoc { // 原来的依赖 artifact = 'com.google.protobuf:protoc:3.12.0' // 修改之后的依赖 artifact = 'com.google.protobuf:protoc:3.12.0:osx-x86_64' } plugins { grpc { // 原来的依赖 artifact = 'io.grpc:protoc-gen-grpc-java:1.31.0' // 修改之后的依赖 artifact = 'io.grpc:protoc-gen-grpc-java:1.31.0:osx-x86_64' } } IDEA打包 打包之后,在每个子模块的build/libs/xxx-jar 下面的操作就可以跟着开发文档了,创建项目 pom.xml 123456789101112131415161718192021222324252627<dependency> <groupId>org.tron.trident</groupId> <artifactId>abi</artifactId> <version>0.8.0</version> <scope>system</scope> <systemPath>${project.basedir}/src/main/resources/jar/abi-0.8.0.jar</systemPath></dependency><dependency> <groupId>org.tron.trident</groupId> <artifactId>utils</artifactId> <version>0.8.0</version> <scope>system</scope> <systemPath>${project.basedir}/src/main/resources/jar/utils-0.8.0.jar</systemPath></dependency><dependency> <groupId>org.tron.trident</groupId> <artifactId>core</artifactId> <version>0.8.0</version> <scope>system</scope> <systemPath>${project.basedir}/src/main/resources/jar/core-0.8.0.jar</systemPath></dependency><dependency> <groupId>io.grpc</groupId> <artifactId>grpc-all</artifactId> <version>1.48.1</version></dependency> 查询余额的代码 123456789101112131415public static String privateKey = "私钥"; public static String apiKey = "apiKey";//波场申请 @Test public void getCount() { //生成密钥对 KeyPair keyPair = new KeyPair(privateKey); //地址转换 System.out.println(keyPair.toHexAddress()); ApiWrapper wrapper = ApiWrapper.ofMainnet(privateKey,apiKey); //账号查询 Account account = wrapper.getAccount("账户地址"); //查询余额 System.out.println(account.getBalance()); System.out.println(account.getCreateTime()); } 转账示例 123456789101112131415161718192021public static String transferTrc(String fromAddress, String toAddress, String amount, String contractAddress, String privateKey) { ApiWrapper wrapper = ApiWrapper.ofNile(privateKey); String ret = ""; try { //根据合约地址获取封装好的合约 Contract contract = wrapper.getContract(contractAddress); //创建一个TRC20合约对象 Trc20Contract token = new Trc20Contract(contract, fromAddress, wrapper); //获取想要转移的数量 BigInteger sunAmountValue = Convert.toSun(amount, Convert.Unit.TRX).toBigInteger(); //设置最大手续费 long maxTrx = Convert.toSun("17", Convert.Unit.TRX).longValue(); ret = token.transfer(toAddress, sunAmountValue.longValue(), 0, "", maxTrx); System.out.println("哈希:" + ret); } catch (Exception e) { System.out.println("转移失败:" + e.getMessage().toString()); } finally { wrapper.close(); } return ret;}
Netty
Server 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162// 把 NettyServer 的创建交给 Spring 管理@Componentpublic class NettyServer { private Logger logger = LoggerFactory.getLogger(getClass()); @Value("${netty.port}") private Integer port; @Resource private NettyServerHandlerInitializer nettyServerHandlerInitializer; // boss 线程组,用于服务端接受客户端的连接 private EventLoopGroup bossGroup = new NioEventLoopGroup(); // worker 线程组,用于服务端接受客户端的数据读写 private EventLoopGroup workerGroup = new NioEventLoopGroup(); // Netty Server Channel private Channel channel; // 启动 Netty Server @PostConstruct public void start() throws InterruptedException { // 创建 ServerBootstrap 对象,用于 Netty Server 启动 ServerBootstrap bootstrap = new ServerBootstrap(); // 设置 ServerBootstrap 的各种属性 // 设置两个 EventLoopGroup 对象 bootstrap.group(bossGroup, workerGroup) // 指定 Channel 为服务端 NioServerSocketChannel .channel(NioServerSocketChannel.class) // 设置 Netty Server 的端口 .localAddress(new InetSocketAddress(port)) // 服务端 accept 队列的大小 .option(ChannelOption.SO_BACKLOG, 1024) // TCP Keepalive 机制,实现 TCP 层级的心跳保活功能 .childOption(ChannelOption.SO_KEEPALIVE, true) // 允许较小的数据包的发送,降低延迟 .childOption(ChannelOption.TCP_NODELAY, true) // 处理器 .childHandler(nettyServerHandlerInitializer); // 绑定端口,并同步等待成功,即启动服务端 ChannelFuture future = bootstrap.bind().sync(); if (future.isSuccess()) { channel = future.channel(); logger.info("[start][Netty Server 启动在 {} 端口]", port); } } // 关闭 Netty Server @PreDestroy public void shutdown() { // 关闭 Netty Server if (channel != null) { channel.close(); } // <3.2> 优雅关闭两个 EventLoopGroup 对象 bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); }} NettyServerHandlerInitializer 12345678910111213141516171819202122232425262728293031@Componentpublic class NettyServerHandlerInitializer extends ChannelInitializer<Channel> { /** * 心跳超时时间 */ private static final Integer READ_TIMEOUT_SECONDS = 3 * 60; @Resource private NettyServerHandler nettyServerHandler; @Override protected void initChannel(Channel ch) { // 获得 Channel 对应的 ChannelPipeline ChannelPipeline channelPipeline = ch.pipeline(); // 添加一堆 NettyServerHandler 到 ChannelPipeline 中 channelPipeline // 空闲检测 .addLast(new ReadTimeoutHandler(READ_TIMEOUT_SECONDS, TimeUnit.SECONDS)) // 编码器 .addLast(new InvocationEncoder()) // 解码器 .addLast(new InvocationDecoder()) // 消息分发器 .addLast(messageDispatcher) // 服务端处理器 .addLast(nettyServerHandler) ; }}
DataGrip
查看ddl和复制为insert语句 显示设置数据库颜色 显示树状结构 显示表的commnet 显示数据库DDL最后一次更新的时间 同步/比较两个表的数据结构用command 选中 两个想要比较的表,然后在其中一个表,选择 只显示不一样的字段 显示全部字段,绿色表示不一样 也可以比较两个库,还是刚才的方法,选中两个库,再比较 相当于Navicat的这个功能 复制功能标记1,选中表,Command + c 就是复制的表名 标记2,选中库,Command + c 就是复制的库名 右边的选项,让你复制出的SQL格式是什么样的,比如复制出insert的sql,或者update的sql,或者导出xml 或者,多条记录复制出一条sql语句,简单使用可以尝试 build-in: sql inserts sql updates scripted: sql-insert-multirow Sql-insert-statements 导出的功能使用mysqldump,导出整个库 Mac电脑需要安装,mysql-client 1234brew install mysql-client# 二进制目录(需要手动复制到上图的路径):/opt/homebrew/opt/mysql-client/bin/mysqldump 导入SQL要在库名上点右键 这个功能类似于
Java
基础知识点循环标签 如果不在嵌套循环中使用,用了跳转,也就相当于continue一样,没有区别 在嵌套循环中使用才有效果,用了innerLoop之后,可以看到 j=2 的时候是不打印 j=3,程序不会到j=3的时候,因为j=2的时候就已经跳出去了 12345678910111213141516171819202122232425262728293031323334/** * 0====0 * 0====1 * 1====0 * 1====1 * 2====0 * 2====1 * ------------------- * 0====0 * 0====1 * 0====2 * 0====3 * 1====0 * 1====1 * 1====2 * 1====3 * 2====0 * 2====1 * 2====2 * 2====3 */public static void main(String[] args) { innerLoop: for (int i = 0; i < 3; i++) { for (int j = 0; j < 4; j++) { if (j == 2) { continue innerLoop; } System.out.println(i+"===="+j); } }} Java常用的API方法集合/列表/数组/Map数组拼接1234567891011121314// 把数组风格,再循环添加逗号String[] split = result.split(" ");StringJoiner stringJoiner = new StringJoiner(",");Arrays.stream(split).forEach(stringJoiner::add);// 字符串数组数字,转成int类型,再转成数组Arrays.stream(split).mapToInt(Integer::parseInt).toArray()// BigDecimal想加,然后如果为空,就给个0BigDecimal winAmount = list.stream() .map(UserBetOrderRecord::getWinAmount) .reduce(BigDecimal::add) .map(bigDecimal -> bigDecimal.multiply(new BigDecimal("-1"))) .orElse(BigDecimal.ZERO); 数组转集合12345678910111213141516171819public static void test() { int[] arr = new int[]{4, 3, 2, 1, 12, 21, 11}; // 这样转是有问题的,不是想要的 // java.util.array List<int[]> list = Arrays.asList(arr); System.out.println(list); // 使用Hutool的方法,也不死想要的 // cn.hutool.core.collection List<int[]> ints = CollectionUtil.newArrayList(arr); System.out.println(ints); // 使用Spring的工具包,需要强转,强迫症受不了这种方法 // org.springframework.util.CollectionUtils List<Integer> objects = (List<Integer>) CollectionUtils.arrayToList(arr); System.out.println(objects);} 打印数组/集合/Map12345678910111213141516171819private static void test() { int[] arr = {1, 2, 3, 4, 5}; System.out.println(arr); // [I@17211155 System.out.println(Arrays.toString(arr)); // [1, 2, 3, 4, 5] List<Integer> integers = new ArrayList<>(); integers.add(1); integers.add(2); integers.add(3); System.out.println(integers); // [1, 2, 3] HashMap<Integer, String> integerStringHashMap = new HashMap<>(); integerStringHashMap.put(1, "a"); integerStringHashMap.put(2, "b"); integerStringHashMap.put(3, "c"); System.out.println(integerStringHashMap); // {1=a, 2=b, 3=c}} 数组截取1234567// java.util.Arrays// 从0开始,取出6个数private static void test() { int[] arr = {1, 2, 3, 4, 5, 6}; int[] ints = Arrays.copyOfRange(arr, 0, 6); System.out.println(Arrays.toString(ints));} 格式化数值格式化1234567private static void test() { String format = String.format("%02d", 2); System.out.println(format); // 02 String format2 = String.format("%02d", 20); System.out.println(format2); // 20} 数学余数/取模1234567891011121314private static void test() { int a = 5; int b = 3; // 使用 % 运算符取余数 int remainder = a % b; System.out.println(remainder); // 2 // 使用 Math.floorMod() 方法取余数 int floorMod = Math.floorMod(a, b); System.out.println(floorMod); // 2} BigDecimal比较1234567891011121314151617181920212223242526272829public static void main(String[] args) { BigDecimal a = new BigDecimal(10); BigDecimal b = new BigDecimal(5); int i = a.compareTo(b); switch (i) { case -1: // 小于 System.out.println("a < b"); break; case 0: // 等于 System.out.println("a = b"); break; case 1: // 大于 System.out.println("a > b"); break; } if (a.compareTo(b) != 0) { System.out.println("a != b"); } if (a.compareTo(b) != -1){ System.out.println("a >= b"); } if (a.compareTo(b) != 1) { System.out.println("a <= b"); } } 时间类型比较123456789101112131415161718192021222324252627public static void main(String[] args) throws ParseException { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); Date begin = sdf.parse("2020-01-01 00:01:00"); Date end = sdf.parse("2020-01-01 00:02:00"); // 使用compareTo方法 int i = begin.compareTo(end); switch (i) { case -1: System.out.println("开始时间大于结束时间"); break; case 0: System.out.println("开始时间等于结束时间"); break; case 1: System.out.println("开始时间小于结束时间"); break; } // 使用before和after方法 boolean b = begin.before(end); System.out.println(b); boolean c = begin.after(end); System.out.println(c); } 值传递和引用传递123456789101112131415161718192021222324252627public class Demo { public static void main(String[] args) { HashMap<Integer, User> ingUserHashMap = new HashMap<Integer, User>(); User user = new User(); user.setId(1); ingUserHashMap.put(1, user); // {1=User(id=1, bigDecimal=10)} System.out.println(ingUserHashMap); // 从map里取值,然后再修改 User user1 = ingUserHashMap.get(1); user1.setBigDecimal(BigDecimal.ONE); // 打印map是修改了的 // {1=User(id=1, bigDecimal=1)} System.out.println(ingUserHashMap); }}@Data@ToStringclass User{ int id; BigDecimal bigDecimal = BigDecimal.TEN;} Lambda给集合分组 1234567891011121314151617181920212223242526public class Demo { public static void main(String[] args) { ArrayList<User> list = new ArrayList<>(); list.add(new User(1, "anthony")); list.add(new User(2, "anthony")); list.add(new User(1, "didid")); // 按照用户Id分组 Map<Integer, List<User>> collect = list.stream().collect(Collectors.groupingBy(User::getId)); // {1=[User(id=1, name=anthony), User(id=1, name=didid)], 2=[User(id=2, name=anthony)]} System.out.println(collect); // 1==[User(id=1, name=anthony), User(id=1, name=didid)] // 2==[User(id=2, name=anthony)] collect.forEach((id,userList)->{ System.out.println(id+"=="+userList); }); }}@AllArgsConstructor@Dataclass User{ Integer id; String name;} ThreadLocal掘金 HashMap面试八股文…. HashMap和HashTable的区别 HashMap HashTable null值 允许key,value为null,但最多允许一条记录的key为null 不允许有null 安全性 不安全,Collections.synchronizedMap方法,使HashMap具有线程安全的能力 线程安全的 遍历 Iterator 进行遍历 使用 Enumeration 进行遍历 负载因子 负载因子和扩容机制有关,意思是如果当前容器的容量,达到我们设置的定最大值,就要开始扩容 比如说当前的容器容量是16,负载因子是0.75,16*0.75=12,也就是说,当容量达到了12的时候就会进行扩容操作。 负载因子是1的时候: 只有当数组的16个一直都填满了,才回扩容,因此在填满的过程中,Hash冲突是避免不了的,也就意味着会出现大量的Hash冲突,底层的红黑树就变得复杂,这种就牺牲了时间,保证空间的利用率 负载因为是0.5的时候: 当到达数组一半的就开始扩容,既然填充的元素少了,Hash冲突就变少,那么底层的链表长度和红黑数也就变少,查询效率就高, 原本存储1M的数据,现在需要2M,时间效率提升了,但是空间利用率降低了 不同JDK版本HashMap的区别 组成 数组+链表 数组+链表+红黑树 hash 值我们能够快速定位到数组的具体下标,但是之后的话, 需要顺着链表一个个比较下去才能找到我们需要的,时间复杂度取决于链表的长度 当满足一定条件时,会将链表转换为红黑树 **链表转红黑树要满足的条件 ** 链表中的元素个数>=8,为什么? 和hashcode碰撞次数的泊松分布有关,主要是为了寻找一种时间和空间的平衡 红黑数的TreeNode 是 链表中的Node所占空间的2倍,虽然红黑树的查询效率要高于链表,但是,当链表长度比较小的时候,即使全部遍历,时间复杂度也不会太高 为什么是8,JDK源码也没有说,只是说做了很多时间,经验的问题 红黑树转链表的阀值是6,因为:避免红黑树和链表频繁转换 当前数组的长度>=64,为什么? 链表转为红黑树的目的是为了解决链表过长,导致查询和插入的效率慢的问题 如果要解决这个问题,也可以通过数组扩容,把链接缩短也可以解决问题 所以在数组长度还不太长的情况,可以先通过数组扩容来解决链表过长的问题 满足这两个条件才会变成红黑树 HashMap的数组的大小是2的幂 TODO 还是没有搞明白 只有数组长度是2的幂次方倍才能够确保数组中的每一个位置发生hash冲突的概率是相同的,数组长度减一的二进制码必须全部是1,否则会出现部分位置永远不会发生hash冲突而造成资源浪费 HashMap put流程 对key进行hash算法,得到一个hashcode 如果数组长度为0或者为null,对数组进行扩容,得到数组长度n 通过 key的hashcode&数组长度n 计算出数组下标 如果数组下标位置中没有数据,将put进来的数据封装Node对象并存到给下标位置 如果数组下标位置有数据p,即发生hash碰撞,如果key存在,覆盖数据 如果数据p属于红黑树,会把新数据插入到红黑树中 如果以上都不是就遍历链表,遍历链表过程中统计链表长度,当链表长度超过8 进行treeifyBin 红黑树化链表 treeifyBin树化中如果数组长度小于64或数组为null则进行扩容,否则数组长度大于等于64 链表转红黑树 HashMap 是如何扩容的? HashMap的扩容指的就是数组的扩容, 因为数组占用的是连续内存空间,所以数组的扩容其实只能新开一个新的数组,然后把老数组上的元素转移到新数组上来,这样才是数组的扩容 先新建一个2被数组大小的数组 然后遍历老数组上的每一个位置,如果这个位置上是一个链表,就把这个链表上的元素转移到新数组上去 在jdk8中,因为涉及到红黑树,这个其实比较复杂,jdk8中其实还会用到一个双向链表来维护红黑树中的元素,所以jdk8中在转移某个位置上的元素时,会去判断如果这个位置是一个红黑树,那么会遍历该位置的双向链表,遍历双向链表统计哪些元素在扩容完之后还是原位置,哪些元素在扩容之后在新位置,这样遍历完双向链表后,就会得到两个子链表,一个放在原下标位置,一个放在新下标位置,如果原下标位置或新下标位置没有元素,则红黑树不用拆分,否则判断这两个子链表的长度,如果超过8,则转成红黑树放到对应的位置,否则把单向链表放到对应的位置。 元素转移完了之后,在把新数组对象赋值给HashMap的table属性,老数组会被回收到。 多线程线程池手写一个线程池1:编写任务类(MyTask),实现Runnable接口; 12345678910111213141516171819202122232425public class MyTask implements Runnable { private int id; public MyTask(int id) { this.id = id; } @Override public void run() { String name = Thread.currentThread().getName(); System.out.println("线程:"+name + "即将开始任务:" + id); try { Thread.sleep(200); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println("线程:"+name + "完成了任务:" + id); } @Override public String toString() { return "MyTask{" + "id=" + id + '}'; }} 2:编写线程类(MyWorker),用于执行任务,需要持有所有任务; 123456789101112131415161718public class MyWorker extends Thread{ private List<Runnable> tasks; public MyWorker(String name, List<Runnable> tasks) { super.setName(name); this.tasks = tasks; } @Override public void run() { // 判断集合中是否有任务,如果有就一直运行 while (!tasks.isEmpty()) { Runnable remove = tasks.remove(0); remove.run(); } }} 3:编写线程池类(MyThreadPool),包含提交任务,执行任务的能力; 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647public class MyThreadPool { // 任务队列 private List<Runnable> tasks = Collections.synchronizedList(new LinkedList<>()); // 当前线程数量 private int num; // 核心线程数量 private int corePoolSize; // 最大线程数量 private int maxSize; // 任务队列的长度 private int workSize; public MyThreadPool(int corePoolSize, int maxSize, int workSize) { this.corePoolSize = corePoolSize; this.maxSize = maxSize; this.workSize = workSize; } // 提交方法-将任务添加到队列中 public void submit(Runnable runnable){ // 判断当前队列的数量,是否超出了最大任务数量 if (tasks.size() >= workSize) { System.out.println("任务"+runnable+"被丢弃了"); }else { tasks.add(runnable); // 执行方法-判断当前线程的数量,决定创建核心线程数量还是非线程数量 execTask(runnable); } } private void execTask(Runnable runnable) { // 判断当前线程中的线程总数量,是否超出了核心线程数 if (num < corePoolSize) { // 创建核心线程 new MyWorker("核心线程:"+num, tasks).start(); num++; } else if (num < maxSize) { // 创建非核心线程 new MyWorker("非核心线程:"+num, tasks).start(); num++; }else { System.out.println("任务:"+runnable+"被缓存了...."); } }} 4:编写测试类(MyTest),创建线程池对象,提交多个任务测试; 123456789public class MyTest { public static void main(String[] args) { MyThreadPool myThreadPool = new MyThreadPool(2, 4, 20); for (int i = 0; i < 10; i++) { MyTask myTask = new MyTask(i); myThreadPool.submit(myTask); } }} 内置线程池ThreadPoolExecutor的使用 12345678public ThreadPoolExecutor(int corePoolSize, //核心线程数量 int maximumPoolSize,// 最大线程数 long keepAliveTime, // 最大空闲时间 TimeUnit unit, // 时间单位 BlockingQueue<Runnable> workQueue, // 任务队列 ThreadFactory threadFactory, // 线程工厂 RejectedExecutionHandler handler // 饱和处理机制) corePoolSize - 线程池核心线程的数量,即使线程是空闲的,核心线程也不会被销毁,除非设置了 allowCoreThreadTimeOut 为 true。 maximumPoolSize - 线程池中允许的最大线程数。 keepAliveTime - 非核心线程空闲时的超时时间,超过这个时间就会被销毁。 unit - keepAliveTime 参数的时间单位。 workQueue - 用于保存等待执行任务的阻塞队列。 SynchronousQueue: 一个不存储元素的阻塞队列。每个插入操作必须等待另一个线程的对应移除操作,反之亦然。适合于传递性的请求。 LinkedBlockingQueue: 一个无界队列,使用链表实现。如果没有指定容量,默认将是 Integer.MAX_VALUE。当任务数量超过核心线程数量时,多余的任务将被放置在队列中等待执行。 ArrayBlockingQueue: 一个有界的阻塞队列,使用数组实现。在创建时需要指定容量大小。适合于有界任务队列的场景,如数据库连接池和消息队列等。 threadFactory - 用于创建新线程的工厂。 handler - 当线程池中的线程数达到最大值且阻塞队列已满时,用来处理新提交的任务的饱和策略。 AbortPolicy: 默认的策略,将抛出RejectedExecutionException。 CallerRunsPolicy: 当任务被拒绝添加时,会使用当前线程执行被拒绝的任务。 DiscardPolicy: 直接丢弃被拒绝的任务,不提供任何反馈。 DiscardOldestPolicy: 丢弃等待时间最长的任务,然后尝试提交新的任务。 ExecutorService接口的常用方法: 接口名 作用 void shutdown() 启动一次顺序关闭,执行以前提交的任务,但不接受新任务。 List shutdownNow() 停止所有正在执行的任务,暂停处理正在等待的任务,并返回等待执行的任务列表。 Future submit(Callable task) 执行带返回值的任务,返回一个Future对象。 Future<?> submit(Runnable task) 执行 Runnable 任务,并返回一个表示该任务的 Future。 Future submit(Runnable task, T result) 执行 Runnable 任务,并返回一个表示该任务的 Future。 ExecutorService接口的的实现类 方法 static ExecutorService newCachedThreadPool() 创建一个默认的线程池对象,里面的线程可重用,且在第一次使用时才创建 static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) 线程池中的所有线程都使用ThreadFactory来创建,这样的线程无需手动启动,自动执行; static ExecutorService newFixedThreadPool(int nThreads) 创建一个可重用固定线程数的线程池 static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) 创建一个可重用固定线程数的线程池且线程池中的所有线程都使用ThreadFactory来创建。 static ExecutorService newSingleThreadExecutor() 创建一个使用单个 worker 线程的 Executor,以无界队列方式来运行该线程。 static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) 创建一个使用单个 worker 线程的 Executor,且线程池中的所有线程都使用ThreadFactory来创建。 ScheduledExecutorService任务调度ScheduledExecutorService是ExecutorService的子接口,具备了延迟运行或定期执行任务的能力 方法() static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)创建一个可重用固定线程数的线程池且允许延迟运行或定期执行任务; static ScheduledExecutorService newScheduledThreadPool(int corePoolSize, ThreadFactory threadFactory)创建一个可重用固定线程数的线程池且线程池中的所有线程都使用ThreadFactory来创建,且允许延迟运行或定期执行任务; static ScheduledExecutorService newSingleThreadScheduledExecutor() 创建一个单线程执行程序,它允许在给定延迟后运行命令或者定期地执行。 static ScheduledExecutorService newSingleThreadScheduledExecutor(ThreadFactory threadFactory) 创建一个单线程执行程序,它可安排在给定延迟后运行命令或者定期地执行。 ScheduledExecutorService常用方法如下: 方法 ScheduledFuture schedule(Callable callable, long delay, TimeUnit unit) 延迟时间单位是unit,数量是delay的时间后执行callable。 ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) 延迟时间单位是unit,数量是delay的时间后执行command。 ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) 延迟时间单位是unit,数量是initialDelay的时间后,每间隔period时间重复执行一次command。 ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) 创建并执行一个在给定初始延迟后首次启用的定期操作,随后,在每一次执行终止和下一次执行开始之间都存在给定的延迟。 Future异步计算结果 boolean cancel(boolean mayInterruptIfRunning) 试图取消对此任务的执行。 V get()如有必要,等待计算完成,然后获取其结果。 V get(long timeout, TimeUnit unit) 如有必要,最多等待为使计算完成所给定的时间之后,获取其结果(如果结果可用)。 boolean isCancelled() 如果在任务正常完成前将其取消,则返回 true。 boolean isDone() 如果任务已完成,则返回 true。 代码演示ExecutorService12345678910111213141516171819202122232425262728293031323334353637383940414243// 演示 newCachedThreadPoolpublic class Demo { public static void main(String[] args) {// test1();// test2(); } private static void test1() { ExecutorService executorService = Executors.newCachedThreadPool(); for (int i = 0; i < 10; i++) { executorService.submit(new MyRunable(i)); } } private static void test2() { ExecutorService executorService = Executors.newCachedThreadPool(new ThreadFactory() { int i=1; @Override public Thread newThread(Runnable r) { return new Thread(r, "自定义线程名字:" + i++); } }); for (int i = 0; i < 10; i++) { executorService.submit(new MyRunable(i)); } }}class MyRunable implements Runnable { int i = 0; public MyRunable(int i) { this.i = i; } @Override public void run() { String name = Thread.currentThread().getName(); System.out.println("线程:"+name + "执行了任务:"+i); }} 123456789101112131415161718192021222324252627282930313233343536373839404142434445// 演示newFixedThreadPoolpublic class Demo2 { public static void main(String[] args) {// test1(); test2(); } private static void test1() { ExecutorService executorService = Executors.newFixedThreadPool(3); for (int i = 0; i < 10; i++) { executorService.submit(new MyRunable2(i)); } } private static void test2() { ExecutorService executorService = Executors.newFixedThreadPool(3,new ThreadFactory() { int i=1; @Override public Thread newThread(Runnable r) { return new Thread(r, "自定义线程名字:" + i++); } }); for (int i = 0; i < 10; i++) { executorService.submit(new MyRunable2(i)); } }}class MyRunable2 implements Runnable { int i = 0; public MyRunable2(int i) { this.i = i; } @Override public void run() { String name = Thread.currentThread().getName(); System.out.println("线程:"+name + "执行了任务:"+i); }} 12345678910111213141516171819202122232425262728293031323334353637383940414243444546// 演示newSingleThreadExecutorpublic class Demo3 { public static void main(String[] args) {// test1(); test2(); } private static void test1() { ExecutorService executorService = Executors.newSingleThreadExecutor(); for (int i = 0; i < 10; i++) { executorService.submit(new MyRunable3(i)); } } private static void test2() { ExecutorService executorService = Executors.newSingleThreadExecutor(new ThreadFactory() { int i=1; @Override public Thread newThread(Runnable r) { return new Thread(r, "自定义线程名字:" + i++); } }); for (int i = 0; i < 10; i++) { executorService.submit(new MyRunable3(i)); } }}class MyRunable3 implements Runnable { int i = 0; public MyRunable3(int i) { this.i = i; } @Override public void run() { String name = Thread.currentThread().getName(); System.out.println("线程:"+name + "执行了任务:"+i); }} 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465// ExecutorService接口的shutdown(),shutdownNow(),submit()public class Demo4 { public static void main(String[] args) {// test1(); test2(); } private static void test2() { ExecutorService executorService = Executors.newSingleThreadExecutor(new ThreadFactory() { int i=1; @Override public Thread newThread(Runnable r) { return new Thread(r, "自定义线程名字:" + i++); } }); for (int i = 0; i < 10; i++) { executorService.submit(new MyRunable4(i)); } // 立刻关闭线程池,如果线程池中的还有缓存任务,没有执行,则取消执行,并返回这些任务 List<Runnable> runnables = executorService.shutdownNow(); System.out.println(runnables); // 不能再接收新的方法了,会报错 executorService.submit(new MyRunable4(888)); } private static void test1() { ExecutorService executorService = Executors.newSingleThreadExecutor(); for (int i = 0; i < 10; i++) { executorService.submit(new MyRunable4(i)); } // 关闭线程池,仅仅是不再接收新的任务,以前的任务还会记录执行 executorService.shutdown(); // 不能再接收新的方法了,会报错 executorService.submit(new MyRunable4(888)); }}class MyRunable4 implements Runnable { int i = 0; public MyRunable4(int i) { this.i = i; } @Override public void run() { String name = Thread.currentThread().getName(); System.out.println("线程:"+name + "执行了任务:"+i); } @Override public String toString() { return "MyRunable4{" + "i=" + i + '}'; }} 代码演示ScheduledExecutorService12345678910111213141516171819202122232425262728293031323334/** * 演示newScheduledThreadPool,测试延迟执行和重复执行的功能 */public class Demo { public static void main(String[] args) { // 获取具备延迟执行任务的线程池对象 ScheduledExecutorService es = Executors.newScheduledThreadPool(3); for (int i = 0; i < 10; i++) { // 创建多个任务,并且提交任务,每个任务延迟2s执行 es.schedule(new MyRunable(i), 2, TimeUnit.SECONDS); } System.out.println("结束"); }}class MyRunable implements Runnable { int i = 0; public MyRunable(int i) { this.i = i; } @Override public void run() { String name = Thread.currentThread().getName(); System.out.println("线程:"+name + "执行了任务:"+i); }} 12345678910111213141516171819202122232425262728293031323334353637383940414243// 演示newScheduledThreadPoolpublic class Demo2 { public static void main(String[] args) { // 获取具备延迟执行任务的线程池对象 ScheduledExecutorService es = Executors.newScheduledThreadPool(3, new ThreadFactory() { int n = 1; @Override public Thread newThread(Runnable r) { return new Thread("自定义线程:" + n++); } }); // 创建多个任务,并且提交任务,每个任务延迟2s执行 es.scheduleAtFixedRate(new MyRunable2(1), 1,2, TimeUnit.SECONDS); System.out.println("结束"); }}class MyRunable2 implements Runnable { int i = 0; public MyRunable2(int i) { this.i = i; } @Override public void run() { String name = Thread.currentThread().getName(); try { Thread.sleep(1500); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println("线程:"+name + "执行了任务:"+i); }} 12345678910111213141516171819202122232425262728293031323334353637383940414243// 演示newSingleThreadScheduledExecutorpublic class Demo3 { public static void main(String[] args) { // 获取具备延迟执行任务的线程池对象 ScheduledExecutorService es = Executors.newSingleThreadScheduledExecutor( new ThreadFactory() { int n = 1; @Override public Thread newThread(Runnable r) { return new Thread("自定义线程:" + n++); } }); // 创建多个任务,并且提交任务,每个任务延迟2s执行 es.scheduleWithFixedDelay(new MyRunable2(1), 1,2, TimeUnit.SECONDS); System.out.println("结束"); }}class MyRunable3 implements Runnable { int i = 0; public MyRunable3(int i) { this.i = i; } @Override public void run() { String name = Thread.currentThread().getName(); try { Thread.sleep(2000); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println("线程:"+name + "执行了任务:"+i); }} 代码演示Future1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556// public class Demo5 { public static void main(String[] args) throws ExecutionException, InterruptedException, TimeoutException { // 创建线程池对象 ExecutorService executorService = Executors.newCachedThreadPool(); Future<Integer> submit = executorService.submit(new MyCall(1, 2));// test1(submit);// test2(submit); } private static void test2(Future<Integer> submit) throws InterruptedException, ExecutionException, TimeoutException { Thread.sleep(1000); System.out.println("取消任务执行的结果" + submit.cancel(true)); // 主线程等待超时 Integer i = submit.get(1, TimeUnit.SECONDS); System.out.println("任务执行的结果:" + i); } private static void test1(Future<Integer> submit) throws InterruptedException, ExecutionException { // 判断任务是否完成 boolean done = submit.isDone(); System.out.println("第一次判断任务是否完成:" + done); boolean cancelled = submit.isCancelled(); System.out.println("第一次判断任务是否取消:" + cancelled); // 无限期等待 Integer i = submit.get(); System.out.println("任务执行的结果:" + i); System.out.println("第二 次判断任务是否完成:" + submit.isDone()); System.out.println("第一次判断任务是否取消:" + submit.isCancelled()); }}class MyCall implements Callable<Integer> { int a; int b; public MyCall(int a, int b) { this.a = a; this.b = b; } @Override public Integer call() throws Exception { String name = Thread.currentThread().getName(); System.out.println("线程:"+name + "准备开始计算:"); Thread.sleep(2000); System.out.println("线程:"+name + "计算完成:"); return a+b; }} JVMJVM内存结构 程序计数器虚拟机指令的执行流程:CPU不是不是直接读二进制字节码 , 解释器读二进制字节码 转换成 机器码,交给CPU执行 程序计数器的在作用:记住下一跳JVM指令的执行地址 特点: 线程私有的, 不会存在内存溢出 虚拟机栈每个栈由多个栈桢组成,对应这每次方法调用的时所占用的内存 每个线程只能有一个活动栈桢,对应着当前正在执行的那个方法 数据结构,像子弹夹,先进后出,所以不需要GC -Xss 设置每个线程栈内存大小,默认1M,内存越大,能使用的线程越少 演示栈桢 演示线程安全方法内的局部变量线程是安全的 12345678910public class Demo3 { public void test() { // 每个线程,有各自的x int x = 0; for (int i = 0; i < 1000; i++) { x++; } System.out.println(x); }} 演示线程不安全如果是静态变量,就不是安全的了 123456789101112public class Demo3 { static int x = 0; // 多线程运行 public void demo() { for (int i = 0; i < 1000; i++) { x++; } System.out.println(x); }} 演示栈内存溢出栈桢过大或者过多会溢出 12345678910111213141516171819202122232425public class Demo2 { private static int count = 0; public static void main(String[] args) { try { method(); } catch (Throwable e) { e.printStackTrace(); // 19994 System.out.println(count); } } private static void method(){ count++; method(); }}// java.lang.StackOverflowError// at 栈.Demo2.method(Demo2.java:22)// at 栈.Demo2.method(Demo2.java:22) 线程诊断1.线程CPU占用高 12345678# 使用top命令看哪个进程占用CPU过高,只能看到进程,不能看到线程# 查看哪个线程占用过高ps H -eo pid,tid,%cpu | grep 进程id# jstack 线程id在具体查找的时候,十进制的线程ID转成16进制 2.运行很长时间,得不到结果 有可能发生死锁了 本地方法栈native 方法,比如object里的clone方法 堆使用-Xmx 设置堆内存空间大小 特点: 线程共享的,堆中对象需要考虑线程安全的问题 有垃圾回收机制 演示堆内存溢出1234567891011121314151617public class Demo { public static void main(String[] args) { int i = 0; List<String> List = new ArrayList<>(); String a = "hello"; try { while (true) { List.add(a); a = a + a; i++; } } catch (Exception e) { e.printStackTrace(); System.out.println(i); } }} 演示jmap12345678# 查看堆内存使用空间# 16279是pidjmap -heap 16279Error: -heap option usedCannot connect to core dump or remote debug server. Use jhsdb jmap instead# 对于jdk8之后的版本,不能再使用jmap -heap pid的命令了jhsdb jmap --heap --pid 16279 演示jconsole12345678910111213public class Demo2 { public static void main(String[] args) throws InterruptedException { System.out.println("1....."); Thread.sleep(30000); byte[] bytes = new byte[1028 * 1024 * 10]; System.out.println("2....."); Thread.sleep(30000); bytes = null; System.gc(); System.out.println("3....."); Thread.sleep(1000000); }} 打印1的时候,是刚开始运行,默认的内存占用 打印2的时候,代码创建了一个10MB数组 打印3的时候,数据被垃圾回收了,还回收了一些默认的创建的空间,所以内存占用就降下来了 演示jvisualvm点击gc之后,内存占用不下降的情况 1234567891011121314151617181920/** * 演示jvisualvm */public class Demo3 { public static void main(String[] args) throws InterruptedException { ArrayList<Persion> persions = new ArrayList<>(); for (int i = 0; i < 200; i++) { persions.add(new Persion()); } Thread.sleep(99999999999999999L); }}class Persion{ private byte[] big = new byte[1024 * 1024];} 点击Heap Dump 仔细找找,就能看到那些数据占用的内存比较多 方法区 12345678演示永久代内存溢出:# 1.8以前会导致永久代内存溢出java.lang.OutOfMemoreryError: PermGen space-XX:MaxPermSize=8m# 1.8以前会导致元空间内存溢出java.lang.OutOfMemoreryError: Metaspace-XX:MaxMetaspaceSize=8m 123456789101112131415161718192021222324252627282930313233343536// 没有演示出来import jdk.internal.org.objectweb.asm.ClassWriter;import jdk.internal.org.objectweb.asm.Opcodes;/** * 演示元空间内存溢出 * 方法区内存溢出,1.8才有这个版本 * -XX:MaxMetaspaceSize=8m * * ClassLoader:类加载器,可以用来加载类的二进制字节码 */public class Demo extends ClassLoader{ public static void main(String[] args) { int j = 0; try { Demo test = new Demo(); for (int i = 0; i < 10000; i++, j++) { // 生成类的二进制字节码 ClassWriter cw = new ClassWriter(0); // 版本号 // 访问修饰符 // 类名 // 包名 // 父类 // 接口名称,这里没有实现接口 cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, "Class" + i, null, "java/lang/Object", null); // 返回byte数组 byte[] code = cw.toByteArray(); // 加载类,生成Class对象 test.defineClass("Class" + i, code, 0, code.length); } }finally { System.out.println(j); } }} 运用的场景: Spring和Mybatis都使用到了CGLIB技术,CGLIB也会像上面演示的使用ClassWriter创建对象,创建的对象多了,就会导致方法区异常,所以就移动到直接内存,虽然没啥本质上的作用,只是会影响OOM的时间 常量池123456789101112/** * 常量池 * 二进制字节码(类基本信息,常量池,类方法定义,包含了虚拟机指令) */public class Demo3 { public static void main(String[] args) { System.out.println("Hello world"); }}// 使用命令编译javap -v Demo3.class 编译的结果 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283// 以下是类的基本信息Classfile /Users/anthony/Documents/GitHub/study-java/java-base/target/classes/方法区/Demo3.class Last modified 2023年8月23日; size 538 bytes MD5 checksum 1aca08c8871c6946b2737975bbf15625 Compiled from "Demo3.java"public class 方法区.Demo3 minor version: 0 major version: 55 flags: (0x0021) ACC_PUBLIC, ACC_SUPER this_class: #5 // 方法区/Demo3 super_class: #6 // java/lang/Object interfaces: 0, fields: 0, methods: 2, attributes: 1 // 常量池Constant pool: #1 = Methodref #6.#20 // java/lang/Object."<init>":()V #2 = Fieldref #21.#22 // java/lang/System.out:Ljava/io/PrintStream; #3 = String #23 // Hello world #4 = Methodref #24.#25 // java/io/PrintStream.println:(Ljava/lang/String;)V #5 = Class #26 // 方法区/Demo3 #6 = Class #27 // java/lang/Object #7 = Utf8 <init> #8 = Utf8 ()V #9 = Utf8 Code #10 = Utf8 LineNumberTable #11 = Utf8 LocalVariableTable #12 = Utf8 this #13 = Utf8 L方法区/Demo3; #14 = Utf8 main #15 = Utf8 ([Ljava/lang/String;)V #16 = Utf8 args #17 = Utf8 [Ljava/lang/String; #18 = Utf8 SourceFile #19 = Utf8 Demo3.java #20 = NameAndType #7:#8 // "<init>":()V #21 = Class #28 // java/lang/System #22 = NameAndType #29:#30 // out:Ljava/io/PrintStream; #23 = Utf8 Hello world #24 = Class #31 // java/io/PrintStream #25 = NameAndType #32:#33 // println:(Ljava/lang/String;)V #26 = Utf8 方法区/Demo3 #27 = Utf8 java/lang/Object #28 = Utf8 java/lang/System #29 = Utf8 out #30 = Utf8 Ljava/io/PrintStream; #31 = Utf8 java/io/PrintStream #32 = Utf8 println #33 = Utf8 (Ljava/lang/String;)V // 类的方法定义{ public 方法区.Demo3(); descriptor: ()V flags: (0x0001) ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return LineNumberTable: line 6: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this L方法区/Demo3; public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: (0x0009) ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=1, args_size=1 // 虚拟机指令 0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 3: ldc #3 // String Hello world 5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 8: return LineNumberTable: line 8: 0 line 9: 8 LocalVariableTable: Start Length Slot Name Signature 0 9 0 args [Ljava/lang/String;}SourceFile: "Demo3.java" 串池(StringTable)的特性常量池中的字符串仅是符号,第一次用到的时候才回变成对象 利用串池的机制,来避免重复创建字符串对象 字符串变量拼接的原理是StringBuilder(1.8) 字符串常量拼接的原理是编译期优化 可以使用intern方法,主动将串池中的还没有的字符串对象放入串池 12345678910111213public class Demo2 { public static void main(String[] args) { String s = new String("a") + new String("b"); // 这是1.8的 讲字符串对象尝试放入到串池中,如果有不会放入,如果没有则会放入串池,会把串池中的对象返回 // 1.8和1.6 不一样 String intern = s.intern(); // true System.out.println(intern=="ab"); // true System.out.println(s=="ab"); }} 常量池和串池的区别1234567891011121314151617181920212223// StringTable["a","b","ab"]public class Demo { // 反编译之后,常量池中的信息,都会被加载到运行时常量池中,这时都是常量池中的符号,还没有变成Java字符串对象 // 运行到s1这行代码的时候,就会把a符号变成a字符串对象,存入到stringtable[]中 // 用到才会创建字符串对象放到串池中,也就是懒加载 public static void main(String[] args) { String s1 = "a"; // 懒加载 String s2 = "b"; String s3 = "ab"; // 字符串相加的原理:new StringBuilder().append(s1).append(s2).toString() // 上面s3的位置是在串池中,s4 是在堆内存中 String s4 = s1 + s2; // false System.out.println(s3 == s4); // javac在编译期间就确定为ab了,运行的时候直接去串池中查找 String s5 = "a" + "b"; // true System.out.println(s3 == s5); }} 证明字符串加载的延迟特性 StringTable的位置位置的移动,在1.6的时候要使用full gc才能被回收,1.8移动到堆内存了,minco gc 就可以回收 StringTable的调优1.调整-XX:StringTableSize=桶的个数 2.考虑将字符串对象是否入池 比如有很多人的地址,都包含北京市这样的字符串,就应该考虑入池 直接内存(Direct Memory)常见于NIO(ByteBuffer)操作时,用户数据缓冲区 分配回收成本较高,读写性能高 不受JVM内存回收管理 演示直接内存溢出12345678910111213141516171819202122232425262728public class Demo { static int _100MB = 1024 * 1024 * 100; public static void main(String[] args) { List<ByteBuffer> objects = new ArrayList<>(); int i = 0; try { while (true) { ByteBuffer allocate = ByteBuffer.allocateDirect(_100MB); objects.add(allocate); i++; } } finally { System.out.println(i); } }}Exception in thread "main" java.lang.OutOfMemoryError: Direct buffer memory at java.base/java.nio.Bits.reserveMemory(Bits.java:175) at java.base/java.nio.DirectByteBuffer.<init>(DirectByteBuffer.java:118) at java.base/java.nio.ByteBuffer.allocateDirect(ByteBuffer.java:317) at 直接内存.Demo.main(Demo.java:18) 释放原理使用UnSafe对象完成了直接内存的分配回收,而且回收需要主动调用freeMemory方法 ByteBuffer的实现类内部,使用了Cleaner(虚引用)来检测ByteBuffer对象,一旦ByteBuffer对象被垃圾回收,就会由ReferenceHandler线程通过Cleaner方法调用freeMemory来释放直接内存 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123public class Demo2 { static int _1GB = 1024 * 1024 * 1024; public static void main(String[] args) throws IOException { ByteBuffer byteBuffer = ByteBuffer.allocateDirect(_1GB); System.out.println("分配完毕"); // 看任务管理器,可以看到这个程序占用1G+ System.in.read(); System.out.println("开始释放"); byteBuffer = null; System.gc(); // 可以看到被会回收了,为什么会被回收呢 System.in.read(); }}// 释放原理public class Demo3 { static int _1GB = 1024 * 1024 * 1024; public static void main(String[] args) throws IOException, NoSuchFieldException, IllegalAccessException { Unsafe unsafe = getUnsafe(); // 分配内存 // base 表示内存地址 long base = unsafe.allocateMemory(_1GB); unsafe.setMemory(base,_1GB,(byte)0); System.in.read(); // 释放内存 unsafe.freeMemory(base); System.in.read(); } public static Unsafe getUnsafe() throws NoSuchFieldException, IllegalAccessException { Field declaredField = Unsafe.class.getDeclaredField("theUnsafe"); declaredField.setAccessible(true); return (Unsafe) declaredField.get(null); }}// 源码ByteBuffer.allocateDirect(_1GB);public static ByteBuffer allocateDirect(int capacity) { return new DirectByteBuffer(capacity);}DirectByteBuffer(int cap) { // package-private super(-1, 0, cap, cap); boolean pa = VM.isDirectMemoryPageAligned(); int ps = Bits.pageSize(); long size = Math.max(1L, (long)cap + (pa ? ps : 0)); Bits.reserveMemory(size, cap); long base = 0; try { // 分配内存 base = UNSAFE.allocateMemory(size); } catch (OutOfMemoryError x) { Bits.unreserveMemory(size, cap); throw x; } UNSAFE.setMemory(base, size, (byte) 0); if (pa && (base % ps != 0)) { // Round up to page boundary address = base + ps - (base & (ps - 1)); } else { address = base; } // 当this被回收时,就会调用回调函数Deallocator // 就是Java对象被回收,触发直接内存回收 cleaner = Cleaner.create(this, new Deallocator(base, size, cap)); att = null;}// 继承了虚应用对象public class Cleaner extends PhantomReference<Object>{ public void clean() { if (!remove(this)) return; try { thunk.run(); } catch (final Throwable x) { AccessController.doPrivileged(new PrivilegedAction<>() { public Void run() { if (System.err != null) new Error("Cleaner terminated abnormally", x) .printStackTrace(); System.exit(1); return null; }}); } }}// 回调函数private static class Deallocator implements Runnable{ private long address; private long size; private int capacity; private Deallocator(long address, long size, int capacity) { assert (address != 0); this.address = address; this.size = size; this.capacity = capacity; } public void run() { if (address == 0) { // Paranoia return; } // 释放直接内存 UNSAFE.freeMemory(address); address = 0; Bits.unreserveMemory(size, capacity); }} 垃圾回收机制可达性分析法如何判断对象是否可以回收 引用计数法 可达性分析法,使用Eclipse Memory Analyzer ,沿着GC Root查找,找到就不能回收 Eclipse Memory Analyzer的使用方法 123456789101112131415161718192021/** * 使用mat 演示GC root */public class Demo { public static void main(String[] args) throws IOException { ArrayList<Object> list1 = new ArrayList<>(); list1.add("a"); list1.add("b"); System.out.println(1); // jsp 获取到进程id // jmap -dump:format=b,live,file=1.bin 进程id System.in.read(); list1 = null; System.out.println(2); System.in.read(); System.out.println("end...."); }} live 代表的意思是,执行dump文件的时候,先执行一次GC 大概有四类: System Class Native Stack,调用native方法要使用到的 Thread,活动线程,使用到的对象 Busy Monitor,各种锁 四种引用 强引用 强引用 软引用 有软引用引用该对象,只有在内存不足时才会被垃圾回收 可以配合引用队列来释放引用自身 弱引用 在GC时,不管内存是否充足都会被回收 可以配合引用队列来释放引用自身 需引用 主要分配ByteBuffer使用,被引用对象回收时,被引用对象回收时,会将虚引用入队,由Reference Handler线程调用虚引用相关方法释放直接内存 必须配合引用队列 终结器引用 无需手动编码,GC时,终结器引用入队,再由Finalizer线程通过终结器引用并调用finalize方法,第二次GC时,才能被回收 垃圾回收算法 标记清除 标记整理 复制 标记清除原本的内存使用情况 标记 把要清楚的内存的开始地址放在列表你,等下次要分配内存的时候,直接在列表里找 优点:就是速度快 缺点:产生内存碎片 标记整理标记 整理 优点:不会产生碎片 缺点:速度慢 复制算法原来的内存使用 标记 复制 清理 交换from 和 to的位置 分代回收12345新生代: Minor GC 伊甸园 幸存区 from 幸存区 to老年代: Full GC 也会清理新生代 对象首先分配在伊甸园区域 新生代空间不足时,触发 minor ge,伊甸园和trom 存活的对象使用copy 复制到10中,存活的对象年龄加1并且交换 from to minor se 会引1发 stop the wvorid,暂停其它用户的线程,等垃圾回收结束,用户线程才恢复运行 当对象寿命超过國值时,会晋升至老年代,最大寿命是15 (4bit) 当老年代空间不足,会先尝试触发 mninor ge,如果之后空间仍不足,那么触发 foll ge, sTw的时间更长 含义 参数 堆初始大小 Xms 堆最大大小 xmx 或 -Xx:MaxFeapSize=size 新生代大小 Xmn 或 (-XX:NewSize=size + -XX:MaxNewSize=size) 幸存区比例(动态) Xx:InitialSurvivorRatio=ratio 和-XX:+UseAdaptiveSizePolicy 幸存区比例 XX:SurvivorRatio=ratio 晋升阈值 XX:MaxTenuringThreshold=threshold (threshold和具体的回收器有关) 晋升详情 XX: +PrintTenuringDistribution GC详情 XX:+PrintGCDetails - verbose:go FullGC 前 MinorGC XX:+ScavengeBeforeFullGC Servlet搭建环境(XML版本)下载,解压tomcat,运行 1234567891011121314151617181920212223# 进入目录cd apache-tomcat-10.0.27/bin# 运行sh startup.sh# 报错信息The file is absent or does not have execute permissionThis file is needed to run this program# 授权chmod 777 apache-tomcat-10.0.27# 再次运行sh startup.sh# 打印结果Using CATALINA_BASE: /Users/anthony/Downloads/apache-tomcat-10.0.27Using CATALINA_HOME: /Users/anthony/Downloads/apache-tomcat-10.0.27Using CATALINA_TMPDIR: /Users/anthony/Downloads/apache-tomcat-10.0.27/tempUsing JRE_HOME: /Library/Java/JavaVirtualMachines/jdk-18.0.1.jdk/Contents/HomeUsing CLASSPATH: /Users/anthony/Downloads/apache-tomcat-10.0.27/bin/bootstrap.jar:/Users/anthony/Downloads/apache-tomcat-10.0.27/bin/tomcat-juli.jarUsing CATALINA_OPTS:Tomcat started.# 访问http://localhost:8080 文件目录结构 导包 1234567891011121314151617181920212223<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.example</groupId> <artifactId>servlet-xml</artifactId> <!-- 打war包 --> <packaging>war</packaging> <version>1.0-SNAPSHOT</version> <name>servlet-xml Maven Webapp</name> <dependencies> <dependency> <groupId>org.apache.tomcat</groupId> <artifactId>tomcat-servlet-api</artifactId> <version>10.0.4</version> </dependency> </dependencies> <build> <finalName>servlet-xml</finalName> </build></project> 因为是使用的tomcat10,所有导入的包不一样,如果是tomcat10之前的版本,导入的包好像是java-serverlt-api 参考:https://taurusxin.com/tomcat10-issue/ 代码 HelloWorld.java 1234567891011121314151617181920212223242526272829303132333435363738package com.mmzcg.servlet;// 导入必需的 java 库import jakarta.servlet.ServletException;import jakarta.servlet.http.HttpServlet;import jakarta.servlet.http.HttpServletRequest;import jakarta.servlet.http.HttpServletResponse;import java.io.IOException;import java.io.PrintWriter;// 扩展 HttpServlet 类public class HelloWorld extends HttpServlet { private String message; public void init() throws ServletException { // 执行必需的初始化 message = "执行必需的初始化"; } public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 设置响应内容类型 response.setContentType("text/html"); // 实际的逻辑是在这里 PrintWriter out = response.getWriter(); out.println("<h1>" + message + "</h1>"); } public void destroy() { // 什么也不做 System.out.printf("我就没见过销毁过"); }} Version.java 1234567891011121314151617181920212223242526272829303132333435363738package com.mmzcg.servlet;// 导入必需的 java 库import jakarta.servlet.ServletException;import jakarta.servlet.http.HttpServlet;import jakarta.servlet.http.HttpServletRequest;import jakarta.servlet.http.HttpServletResponse;import java.io.IOException;import java.io.PrintWriter;// 扩展 HttpServlet 类public class Version extends HttpServlet { private String message; public void init() throws ServletException { // 执行必需的初始化 message = "执行必需的初始化"; } public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 设置响应内容类型 response.setContentType("text/html"); // 实际的逻辑是在这里 PrintWriter out = response.getWriter(); out.println("<h1>" + "返回的是版本号" + "</h1>"); } public void destroy() { // 什么也不做 System.out.printf("我就没见过销毁过"); }} index.jsp 12345678910111213// 解决中文乱码<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%><html><body><h2>这是首页</h2>有两个url<a href="/hello">hello</a><a href="/version">version</a></body></html> web.xml 1234567891011121314151617181920212223242526<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd" ><web-app> <display-name>Archetype Created Web Application</display-name> <servlet> <servlet-name>HelloWorld</servlet-name> <servlet-class>com.mmzcg.servlet.HelloWorld</servlet-class> </servlet> <servlet> <servlet-name>Version</servlet-name> <servlet-class>com.mmzcg.servlet.Version</servlet-class> </servlet> <servlet-mapping> <servlet-name>HelloWorld</servlet-name> <url-pattern>/hello</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>Version</servlet-name> <url-pattern>/version</url-pattern> </servlet-mapping></web-app> 12345678910111213141516171819202122<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.example</groupId> <artifactId>servlet-xml</artifactId> <packaging>war</packaging> <version>1.0-SNAPSHOT</version> <name>servlet-xml Maven Webapp</name> <dependencies> <dependency> <groupId>org.apache.tomcat</groupId> <artifactId>tomcat-servlet-api</artifactId> <version>10.0.4</version> </dependency> </dependencies> <build> <finalName>servlet-xml</finalName> </build></project> 运行环境一:在IDEA上运行tomcat 添加tomcat模板 配置tomcat_home 配置包和路径 运行环境一:在命令行运行tomcat 先停掉tomcat,复制war包到webapps目录里 再启动tomcat,默认的访问地址就是:http://localhost:8080/servlet-xml/ 搭建环境(注解版本)前面的都不变,只是改下代码 web.xml 1234567<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd" ><web-app> <display-name>Archetype Created Web Application</display-name></web-app> index.jsp 12345<html><body><h2>Hello World!</h2></body></html> 运行环境一:在命令行运行tomcat HelloWorld.java 12345678910111213141516171819202122232425262728293031323334353637383940package com.mmzcg.annotation;// 导入必需的 java 库import jakarta.servlet.ServletException;import jakarta.servlet.annotation.WebServlet;import jakarta.servlet.http.HttpServlet;import jakarta.servlet.http.HttpServletRequest;import jakarta.servlet.http.HttpServletResponse;import java.io.IOException;import java.io.PrintWriter;// 扩展 HttpServlet 类@WebServlet("/hello")public class HelloWorld extends HttpServlet { private String message; public void init() throws ServletException { // 执行必需的初始化 message = "注解执行必需的初始化"; } public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 设置响应内容类型 response.setContentType("text/html"); // 实际的逻辑是在这里 PrintWriter out = response.getWriter(); out.println("<h1>" + message + "</h1>"); } public void destroy() { // 什么也不做 System.out.printf("注解我就没见过销毁过"); }} Version.java 12345678910111213141516171819202122232425262728293031323334353637383940package com.mmzcg.annotation;// 导入必需的 java 库import jakarta.servlet.ServletException;import jakarta.servlet.annotation.WebServlet;import jakarta.servlet.http.HttpServlet;import jakarta.servlet.http.HttpServletRequest;import jakarta.servlet.http.HttpServletResponse;import java.io.IOException;import java.io.PrintWriter;// 扩展 HttpServlet 类@WebServlet("/version")public class Version extends HttpServlet { private String message; public void init() throws ServletException { // 执行必需的初始化 message = "执行必需的初始化"; } public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 设置响应内容类型 response.setContentType("text/html"); // 实际的逻辑是在这里 PrintWriter out = response.getWriter(); out.println("<h1>" + "注解返回的是版本号" + "</h1>"); } public void destroy() { // 什么也不做 System.out.printf("我就没见过销毁过"); }} 中文乱码Tomcat配置文件apache-tomcat-8.5.69\conf\server.xml 添加URIEncoding=”UTF-8” 属性 1234<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" URIEncoding="UTF-8"/> Idea指定编码 Jsp指定编码在jsp的html最先添加 1<%@ **page** language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> httpResponse指定编码12345678// 备注:该方法只对POST方式提交的数据有效,对GET方式提交的数据无效!request.setCharacterEncoding("UTF-8");//在响应中主动告诉浏览器使用UTF-8编码格式来接收数据response.setHeader("Content-Type", "text/html;charset=UTF-8");//可以使用封装类简写Content-Type,使用该方法则无需使用setCharacterEncodingresponse.setContentType("text/html;charset=UTF-8");
代码片段
谷歌验证码12345<dependency> <groupId>com.warrenstrange</groupId> <artifactId>googleauth</artifactId> <version>1.5.0</version></dependency> 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253package utils;import com.warrenstrange.googleauth.GoogleAuthenticator;import com.warrenstrange.googleauth.GoogleAuthenticatorKey;import java.io.UnsupportedEncodingException;import java.net.URLEncoder;public class GoogleAuthUtil { /** * 生成key */ public static String generaKey(){ GoogleAuthenticator gAuth = new GoogleAuthenticator(); GoogleAuthenticatorKey key = gAuth.createCredentials(); return key.getKey(); } /** * 生成二维码 * @param key 生成的key * @param issuer app上title名字 服务名称 如: Google Github 印象笔记 * @param account app上title名字(括号里的) 用户账户 如: [email protected] 138XXXXXXXX */ public static String createGoogleAuthQRCodeData(String key, String account,String issuer) throws UnsupportedEncodingException { return String.format("otpauth://totp/%s?secret=%s&issuer=%s", URLEncoder.encode(issuer + ":" + account, "UTF-8").replace("+", "%20"), URLEncoder.encode(key, "UTF-8").replace("+", "%20"), URLEncoder.encode(issuer, "UTF-8").replace("+", "%20")); } public static void main(String[] args) throws UnsupportedEncodingException { String s = generaKey(); System.out.println(s); String googleAuthQRCodeData = createGoogleAuthQRCodeData(s, "anthony", "V16"); System.out.println(googleAuthQRCodeData); } /** * 检查 * @param key 生成的key * @param code 用户输入的面膜 */ public static boolean check(String key,Integer code){ GoogleAuthenticator gAuth = new GoogleAuthenticator(); // 根据用户密钥和用户输入的密码,验证是否一致。 boolean isCodeValid = gAuth.authorize(key, code); return isCodeValid; }} 图片验证码12345<dependency> <groupId>com.github.whvcse</groupId> <artifactId>easy-captcha</artifactId> <version>1.6.2</version> </dependency> 1234567891011121314151617181920@ApiOperation("获取验证码")@PostMapping("/getVerify")public Response<Map<String, String>> getVerify() { try { SpecCaptcha specCaptcha = new SpecCaptcha(130, 48, 6); specCaptcha.setFont(new Font("Default", Font.PLAIN, 32)); String verCode = specCaptcha.text().toLowerCase(); String key = UUID.randomUUID().toString(); Map<String, String> stringStringHash = new HashMap<>(); stringStringHash.put("uuid", key); stringStringHash.put("img", specCaptcha.toBase64()); key = "login_code:" + key; redisDao.setString(key, verCode); redisDao.expire(key, 2, TimeUnit.MINUTES); return Response.successData(stringStringHash); } catch (Exception e) { throw new RuntimeException("验证码生成失败", e); }} 华为云上传图片12345<dependency> <groupId>com.huaweicloud</groupId> <artifactId>esdk-obs-java-bundle</artifactId> <version>3.23.9.1</version></dependency> 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768import com.obs.services.ObsClient;import com.obs.services.ObsConfiguration;import com.obs.services.exception.ObsException;import com.obs.services.model.*;import java.io.IOException;import java.io.InputStream;public class SimpleMultipartUploadSample { // 通常一个链接是 https://桶的名字.obs.ap-southeast-1.myhuaweicloud.com/资源路径 private static final String endPoint = "https://obs.ap-southeast-1.myhuaweicloud.com"; private static final String ak = "xxxx"; private static final String sk = "xxxx"; private static ObsClient obsClient; private static String bucketName = "桶的名字"; public static void upload(String objectKey, InputStream file) throws IOException { ObsConfiguration config = new ObsConfiguration(); config.setSocketTimeout(30000); config.setConnectionTimeout(10000); config.setEndPoint(endPoint); try { obsClient = new ObsClient(ak, sk, config); System.out.println("Step 1: initiate multipart upload \n"); InitiateMultipartUploadRequest request = new InitiateMultipartUploadRequest(); request.setBucketName(bucketName); request.setObjectKey(objectKey); InitiateMultipartUploadResult result = obsClient.initiateMultipartUpload(request); System.out.println("Step 2: upload part \n"); UploadPartResult uploadPartResult = obsClient.uploadPart(bucketName, objectKey, result.getUploadId(), 1, file); System.out.println("Step 3: complete multipart upload \n"); CompleteMultipartUploadRequest completeMultipartUploadRequest = new CompleteMultipartUploadRequest(); completeMultipartUploadRequest.setBucketName(bucketName); completeMultipartUploadRequest.setObjectKey(objectKey); completeMultipartUploadRequest.setUploadId(result.getUploadId()); PartEtag partEtag = new PartEtag(); partEtag.setPartNumber(uploadPartResult.getPartNumber()); partEtag.setEtag(uploadPartResult.getEtag()); completeMultipartUploadRequest.getPartEtag().add(partEtag); obsClient.completeMultipartUpload(completeMultipartUploadRequest); } catch (ObsException e) { System.out.println("Response Code: " + e.getResponseCode()); System.out.println("Error Message: " + e.getErrorMessage()); System.out.println("Error Code: " + e.getErrorCode()); System.out.println("Request ID: " + e.getErrorRequestId()); System.out.println("Host ID: " + e.getErrorHostId()); } finally { if (obsClient != null) { try { obsClient.close(); } catch (IOException e) { } } } }}
Go
入门参考:Go英文文档 Hello World123456789// 一定要在main的包下才能运行// 一个main包下,只能有一个main入口package mainimport "fmt"func main() { fmt.Print("hello world")} 123456789# 第一种,编译后运行.exe文件# 编译C:\Users\anthony\go\src\awesomeProject\demo\main>go build demo.go# 运行编译后exe文件C:\Users\anthony\go\src\awesomeProject\demo\main>demo.exehello world# 第二种,直接运行源代码go run *.go 标准输入1234567891011121314151617package mainimport "fmt"func main() { var name string var age int /* 使用"&"获取score变量的内存地址(即取变量内存地址的运算符),通过键盘输入为score变量指向的内存地址赋初值。 fmt.Scan是一个阻塞的函数,如果它获取不到数据就会一直阻塞哟。 fmt.Scan可以接收多个参数,用户输入参数默认使用空格或者回车换行符分割输入设备传入的参数,直到接收所有的参数为止 */ fmt.Scan(&name, &age) fmt.Println(name, age)} 1234567891011121314151617package mainimport "fmt"func main() { var name string var age int /* 和fmt.Scan功能类似,fmt.Scanln也是一个阻塞的函数,如果它获取不到数据就会一直阻塞哟。 fmt.Scanln也可以接收多个参数,用户输入参数默认使用空格分割输入设备传入的参数,遇到回车换行符就结束接收参数 */ fmt.Scanln(&name, &age) fmt.Println(name, age)} 1234567891011121314151617181920212223package mainimport "fmt"func main() { var name string var age int /* 和fmt.Scanln功能类似,fmt.Scanf也是一个阻塞的函数,如果它获取不到数据就会一直阻塞哟。 其实fmt.Scanln和fmt.Scanf可都以接收多个参数,用户输入参数默认使用空格分割输入设备传入的参数,遇到回车换行符就结束接收参数 唯一区别就是可以格式化用户输入的数据类型,如下所示: %s: 表示接收的参数会被转换成一个字符串类型,赋值给变量 %d: 表示接收的参数会被转换成一个整形类型,赋值给变量 生产环境中使用fmt.Scanln和fmt.Scanf的情况相对较少,一般使用fmt.Scan的情况较多~ */ fmt.Scanf("%s%d", &name, &age) fmt.Println(name, age)} 12345func method_if() { reader := bufio.NewReader(os.Stdin) str, _ := reader.ReadString('\n') fmt.Printf("输入的值是:%s\n", str)} 声明变量基本类型,声明变量,不赋值,就有默认值 1234567891011// 基本类型有默认值func main() { var age int = 0 fmt.Println(age) var code int fmt.Println(code) var sex bool fmt.Println(sex)} 一次性声明不同类型的变量 12345678910111213141516171819func main() { var age, name = 10, "anthony" println("可以声明不同类型,只能自动推导,不能定义类型:", age, name) // 报错,多变量赋值值,不能声明类型 // var age2 int,name2 string = 10, "anthony" // println(age2,name2)}func method6() { var ( age int = 23 name string = "anthony" sex bool ) println("使用集合的方式声明变量,可以声明不同类型,并且可以赋值", age, name, sex)} 先声明,后赋值 12345678func main() { // 定义变量 var i int // 给i赋值 i=10 // 使用变量 fmt.Print("i=", i)} 类型推导 1234func method2() { var name = "anthony" print("类型推导:", name)} 常量常量是一个简单值的标识符,在程序运行时,不会被修改的量 出于习惯,常量用大写,不过小写也没有问题 12345678910// 常量的格式定义const identifier [type] = value// 显式类型定义const A string = "abc"// 隐式类型定义const B = "abc"// 多个相同类型的声明可以简写为const C,D = 1,2// 多个不同类型的声明可以简写为const E, F, G = 1, false, "str" 常量用作枚举常量可以作为枚举,常量组 123456789101112131415161718func main() { const ( RED = 1 BLACK = 2 WHITE = 3 ) const ( x int = 16 y s = "abc" z ) fmt.Println(RED, BLACK, WHITE) fmt.Println("常量组中如不指定类型和初始化值,则与上一行非空常量右值相同:", x, y, s, z)} 特殊常量:iotaiota,特殊常量,可以认为是一个可以被编译器修改的常量 如果中断iota自增,则必须显式恢复。且后续自增值按行序递增 自增默认是int类型,可以自行进行显示指定类型 数字常量不会分配存储空间,无须像变量那样通过内存寻址来取值,因此无法获取地址 1234567891011121314151617func demo() { // 报错 //a = iota const ( a = iota //0 b //1 c //2 d = "ha" //独立值,iota += 1 e //"ha" iota += 1 f = 100 //iota +=1 g //100 iota +=1 h = iota //7,恢复计数 i //8 ) fmt.Println(a, b, c, d, e, f, g, h, i)} 字符串12345678func main() { name := "Hello World" for index := range name { fmt.Printf("遍历字符串:%v,%v\n",index,name[index]) // 字符串长度 fmt.Println(len(name[index])) }} 字符串切片12345var response = "var r = [[\"000001\",\"HXCZHH\",\"华夏成长混合\",\"混合型-偏股\",\"HUAXIACHENGZHANGHUNHE\"]];"fmt.Println(response[8:len(response)-1])// 打印[["000001","HXCZHH","华夏成长混合","混合型-偏股","HUAXIACHENGZHANGHUNHE"]] 字符串数组转一维数组1234567891011121314func main() { response := "[1,2,3]" var arr []int if err := json.Unmarshal([]byte(response), &arr); err != nil { fmt.Println("Error:", err) } // 第1行;数据是:1 // 第2行;数据是:2 // 第3行;数据是:3 for i:=0 ;i< len(arr);i++ { fmt.Printf("第%v行;数据是:%v\n",i+1,arr[i]) }} 字符串数组转二维数组12345678910111213func main() { response := "[[1,2,3],[2,3,4]]" var arr [][]int if err := json.Unmarshal([]byte(response), &arr); err != nil { fmt.Println("Error:", err) } // 第1行;数据是:[1 2 3] // 第2行;数据是:[2 3 4] for i:=0 ;i< len(arr);i++ { fmt.Printf("第%v行;数据是:%v\n",i+1,arr[i]) }} 字符串和数字互转1234567string转成int:int, err := strconv.Atoi(string)string转成int64:int64, err := strconv.ParseInt(string, 10, 64)int转成string:string := strconv.Itoa(int)int64转成string:string := strconv.FormatInt(int64,10) type关键字12345678910func main() { // 类型转换 var one int = 17 mean := float32(one) fmt.Println(mean) // 字符串不能强转整型} 流程控制123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120// iffunc method_if() { reader := bufio.NewReader(os.Stdin) str, _ := reader.ReadString('\n') if str == "" { fmt.Println("空的") } fmt.Printf("输入的值是:%s\n", str)}// if-ikfunc main() { var data = make(map[string]int) data["a"] = 0 data["b"] = 1 data["c"] = 2 // 遍历 for k, v := range data { fmt.Println(k, v) } if _, ok := data["a"]; ok { //存在 fmt.Println("存在") }}// if-elsefunc method_if2() { if num := 10; num%2 == 0 { //checks if number is even fmt.Println(num, "is even") } else { fmt.Println(num, "is odd") }}// switchfunc method_switch() { num := 1 result := -1 switch num { case 1: result = 1 case 2, 3, 4: result = 2 default: result = -2 } fmt.Println(result)}// for 开始012345678910结束func demo() { fmt.Print("开始") for i := 0; i <= 10; i++ { fmt.Print(i) } fmt.Print("结束")}// break 开始01234结束func demo_break() { fmt.Print("开始") for i := 0; i <= 10; i++ { if i == 5 { break } fmt.Print(i) } fmt.Print("结束")}// coutinue 1 3 5 7 9func demo_continue() { for i := 1; i <= 10; i++ { if i%2 == 0 { continue } fmt.Printf("%d ", i) }}// go-to// go// 会打印两次 ===func demo_goto() { /* 定义局部变量 */ var a = 0 /* 循环 */LOOP: fmt.Println("===") for a < 10 { if a == 5 { /* 跳过迭代 */ a = a + 1 goto LOOP } fmt.Printf("a的值为 : %d\n", a) a++ }} 1 值传递和引用传递程序中使用的是值传递, 所以两个值并没有实现交互 123456789101112131415161718192021222324252627282930func main() { /* 定义局部变量 */ var a int = 100 var b int = 200 fmt.Printf("交换前 a 的值为 : %d\n", a ) fmt.Printf("交换前 b 的值为 : %d\n", b ) /* 通过调用函数来交换值 */ swap(a, b) fmt.Printf("交换后 a 的值 : %d\n", a ) fmt.Printf("交换后 b 的值 : %d\n", b )}/* 定义相互交换值的函数 */func swap(x, y int) int { var temp int temp = x /* 保存 x 的值 */ x = y /* 将 y 值赋给 x */ y = temp /* 将 temp 值赋给 y*/ return temp;}//交换前 a 的值为 : 100//交换前 b 的值为 : 200//交换后 a 的值 : 100//交换后 b 的值 : 200 引用传递 1234567891011121314151617181920212223242526272829func main() { /* 定义局部变量 */ var a int = 100 var b int= 200 fmt.Printf("交换前,a 的值 : %d\n", a ) fmt.Printf("交换前,b 的值 : %d\n", b ) /* 调用 swap() 函数 * &a 指向 a 指针,a 变量的地址 * &b 指向 b 指针,b 变量的地址 */ swap(&a, &b) fmt.Printf("交换后,a 的值 : %d\n", a ) fmt.Printf("交换后,b 的值 : %d\n", b )}func swap(x *int, y *int) { var temp int temp = *x /* 保存 x 地址上的值 */ *x = *y /* 将 y 值赋给 x */ *y = temp /* 将 temp 值赋给 y */}//交换前,a 的值 : 100//交换前,b 的值 : 200//交换后,a 的值 : 200//交换后,b 的值 : 100 变量作用域12345678910111213141516// 全局变量var global int = 32func main() { // 局部变量 var a,b int =1, 2 fmt.Printf("打印全局变变量:%v\n", global) global = a+ b fmt.Printf("打印全局变变量:%v\n", global) var global int = 3 fmt.Printf("打印全局变变量:%v\n", global)} 匿名代码块 匿名代码块被用来定义一个局部变量 message,该变量仅在代码块内部可见。一旦离开代码块,变量 message 就不再可见,任何尝试在代码块外部访问它都会导致编译错误。 1234567891011121314151617181920212223package mainimport ( "fmt")func main() { fmt.Println("在main函数外部") // 进入匿名代码块 { fmt.Println("在匿名代码块内部") // 定义一个局部变量,仅在代码块内可见 message := "这是一个局部变量" fmt.Println(message) } // message 变量在代码块外不可见 // fmt.Println(message) // 这会导致编译错误 fmt.Println("在main函数外部")} 数组和切片数组 12345678910111213141516171819202122232425262728293031323334353637func get() { list := [5]int{1,2,3,4,5} for i:=0;i< len(list);i++ { fmt.Println(i, "==", list[i]) } for k, v := range my { fmt.Println(k, v) }}func create(){ var numbers []int fmt.Println("新建个空数组:",numbers) var defaultcount [4]int fmt.Println("新建个指定长度的数组:",defaultcount) var balance = []int{1,2,3,4,5} fmt.Println("新建个不指定长度的数组:",balance) // 根据元素的个数,设置数组的大小 d := [...] int{1,2,3,4,5} fmt.Println("新建个指定位置的数组:",d) // 指定位置 e := [5] int{4: 100} // [0 0 0 0 100] fmt.Println("新建个指定位置的数组:",e) // 指定位置 f := [...] int{0: 1, 4: 1, 9: 1} // [1 0 0 0 1 0 0 0 0 1] fmt.Println("新建个指定位置的数组:",f)} 切片 12345678910111213141516171819202122232425262728293031323334func define() { var identifier []int fmt.Println("空数组", identifier) var slice1 []int = make([]int, 10) fmt.Println("切片", slice1) i0 := slice1[0] i1 := slice1[1] i2 := slice1[2] i3 := slice1[3] fmt.Println("通过索引,获取值", i0, i1, i2, i3) // 修改切片 for i := 0; i < len(slice1); i++ { slice1[i] = i } fmt.Println("修改完切片::", slice1) fmt.Println("获取切片区间1:", slice1[0:2]) fmt.Println("获取切片区间2:", slice1[5:]) fmt.Println("获取切片区间3:", slice1[:5]) slice1 = append(slice1, 10, 11, 12) fmt.Println("追加完切片::", slice1) slice2 := make([]int, len(slice1), cap(slice1)*2) fmt.Println("创建个容量是原来容量两位的数组:", slice2) number := copy(slice2, slice1) fmt.Printf("slice:%v,slice2:%v,number:%v:", slice1, slice2, number)} Map12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849func main() { keyvale := make(map[string]string) keyvale["k1"] = "v1" keyvale["k2"] = "v2" keyvale["k3"] = "v3" keyvale["k4"] = "v4" // 循环遍历 for key := range keyvale { fmt.Println("循环遍历:", key, keyvale[key]) } // 删除元素 delete(keyvale, "k1") for key := range keyvale { fmt.Println("删除值之后,循环遍历:", key, keyvale[key]) } // 查看元素是否存在 _, ok := keyvale["United States"] if ok { fmt.Printf("存在\n") } else { fmt.Printf("不存在\n") } // 但是当key如果不存在的时候,我们会得到该value值类型的默认值,比如string类型得到空字符串,int类型得到0。但是程序不会报错 m := make(map[string]int) m["a"] = 1 x, ok := m["b"] fmt.Println("key不存在时,会有默认值", x, ok) // map长度 fmt.Println("map长度", len(m)) // map是引用类型 mymap := map[string]int{ "steven": 12000, "anthony": 15000, } mymap["mike"] = 9000 fmt.Printf("原来的数据:%v\n", mymap) newmymap := mymap newmymap["anthony"] = 50000 fmt.Printf("改变后,原来的数据:%v\n", mymap) fmt.Printf("改变后,新的的数据:%v\n", newmymap)} 函数12345678910111213141516171819202122232425// 函数返回两个数的最大值func max(num1, num2 int) int { /* 声明局部变量 */ var result int if (num1 > num2) { result = num1 } else { result = num2 } return result}// 函数调用func main() { /* 定义局部变量 */ var a int = 100 var b int = 200 var ret int /* 调用函数并返回最大值 */ ret = max(a, b) fmt.Printf( "最大值是 : %d\n", ret )} 函数返回多个值12345678910// 入参多个值,x和y 都是字符串// 返回值是多个,都是字符串func swap(x, y string) (string, string) { return y, x}func main() { a, b := swap("Google", "Runoob") fmt.Println(a, b)} 函数返回值的命名123456789101112131415161718type studen struct { name string age int}func test4() { stu := test4_return() fmt.Println(stu.name)}func test4_return() (stu *studen) { stu = &studen{ "anthony", 13, } return} 函数作为实参12345678910111213141516171819type fb func(x int) intfunc main() { myfunc := func(x int) int{ return x } // 知识点1 fmt.Println(myfunc(3)) // 知识点2 demo(2, myfunc)}// 函数作为参数传递,实现回调func demo(x int,myfb fb) { myfb(x)} 函数作为实参-回调1234567891011121314151617181920func main() { test2()}func test2() { group("/system1", myfunc) group("/system2", func(path2 string) { fmt.Printf("回调Path:%v \n", path2) })}func group(path string, group func(path2 string)) { fmt.Printf("手动实现 \n" + path) group(path)}func myfunc(path2 string) { fmt.Printf("回调Path:%v \n", path2)} 函数作为参数-interface{}12345678func test3() { test3_object(myfunc)}func test3_object(object interface{}) { a := object fmt.Printf("什么鬼:%v", a)} 指针 12345678910111213141516171819202122232425262728293031func main() { var a int= 20 /* 声明实际变量 */ var ip *int /* 声明指针变量 */ ip = &a /* 指针变量的存储地址 */ fmt.Printf("a 变量的地址是: %x\n", &a ) /* 指针变量的存储地址 */ fmt.Printf("ip 变量储存的指针地址: %x\n", ip ) /* 使用指针访问值 */ fmt.Printf("*ip 变量的值: %d\n", *ip )}// 函数使用指针func main() { /* 定义局部变量 */ var a int = 100 var b int= 200 swap(&a, &b); fmt.Printf("交换后 a 的值 : %d\n", a ) fmt.Printf("交换后 b 的值 : %d\n", b )}/* 交换函数这样写更加简洁,也是 go 语言的特性,可以用下,c++ 和 c# 是不能这么干的 */func swap(x *int, y *int){ *x, *y = *y, *x} 结构体123456789101112131415func main() { // 返回的是该实例的结构类型 var s people s.age = 1 fmt.Println(s) // 第二第三种,返回的是一个指向这个结构类型的一个指针,是地址 ming := new(people) ming.name = "xiao ming" ming.age = 18 fmt.Println(ming) ming2 := &people{"xiao ming", 18} fmt.Println(ming2)} 第二第三种返回指针的声明形式,在我们需要修改他的值的时候,其实应该使用的方式是: 1(*ming).name = "xiao wang" 也就是说,对于指针类型的数值,应该要先用*取值,然后再修改。 但是,在Golang中,可以省略这一步骤,直接使用ming.name = "xiao wang"。 方法1234567891011121314151617181920212223242526type Vertex struct { X, Y float64}func (v Vertex) test1() { v.X++ v.Y++}func (v *Vertex) test2() { v.X++ v.Y++}func main() { v1 := Vertex{1, 1} v2 := &Vertex{1, 1} v1.test1() v2.test2() // {1 1} fmt.Println(v1) // &{2 2} fmt.Println(v2)} test1和test2,他们唯一的区别就是方法名前面的接收者不同,一个是指针类型的,一个是值类型的。 测试1234567891011121314151617type Vertex struct { X, Y float64}func (v Vertex) test1(){ // 自己定义的v1内存地址为:0xc00000a0e0 fmt.Printf("在方法中的v的地址为:%p\n", &v) v.X++; v.Y++;}func main() { v1 := Vertex{1, 1} // 在方法中的v的地址为:0xc00000a100 fmt.Printf("自己定义的v1内存地址为:%p\n", &v1) v1.test1()} 1234567891011121314151617type Vertex struct { X, Y float64}func (v *Vertex) test2(){ // 自己定义的v1内存地址为:0xc00000a0e0 fmt.Printf("在方法中的v的地址为:%p\n", v) v.X++; v.Y++;}func main() { v1 := &Vertex{1, 1} // fmt.Printf("自己定义的v1内存地址为:%p\n", v1) v1.test2()} 123456789101112131415161718192021222324252627282930313233type Books struct { title string author string subject string book_id int}type Library struct { // 匿名字段,那么默认Student就包含了Human的所有字段 Books address string}func main() { var book Books book.title = "Go 语言" book.author = "www.runoob.com" book.subject = "Go 语言教程" book.book_id = 6495407 // 初始化一个图书馆 mark := Library{Books{"Go 语言","www.runoob.com","Go 语言教程",6495407},"广东"} // 我们访问相应的字段 fmt.Println("His name is ", mark.title) fmt.Println("His age is ", mark.author) fmt.Println("His weight is ", mark.subject) fmt.Println("His speciality is ", mark.address) // 修改对应的备注信息 mark.title = "AI" fmt.Println("Mark changed his speciality") fmt.Println("His speciality is ", mark.title)} 面向对象123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778// 定义接口type PersonMethod interface { show()}// 定义结构体type Person struct { name string age int}// 对象成员方法func (person *Person) setAge(age int) { person.age = age}// 对象成员方法实现接口func (person Person) show() { fmt.Printf("Person类打印SHOW:%v\n", person)}// 多态func show2(person PersonMethod) { person.show()}// 学生类,继承Person类type Student struct { // 匿名,相当于继承 Person level string}func (student *Student) show() { fmt.Printf("Student类打印SHOW:%v\n", student)}// 老师类,继承Person类type Teacher struct { Person price int}func (teacher Teacher) show() { fmt.Printf("Teacher类打印SHOW:%v\n", teacher)}func main() { anthony := Person{"anthony", 25} fmt.Printf("anthony信息:%v\n", anthony) // 调用成员方法 anthony.setAge(12) fmt.Printf("anthony信息:%v\n", anthony) anthony2 := Person{} fmt.Printf("anthony2信息:%v\n", anthony2) anthony2.age = 26 anthony2.name = "anthony2" fmt.Printf("anthony2信息:%v\n", anthony2) // 学生,继承 student := Student{} student.level = "小学生" student.name = "anthony" student.age = 23 fmt.Printf("学生继承类:%v\n", student) // 老师,继承 teacher := Teacher{price: 12, Person: Person{name: "li teacher", age: 56}} fmt.Printf("老师继承类:%v\n", teacher) show2(&student) show2(teacher) show2(anthony)} 创建对象的方式 12345678910111213141516171819// 方式一:使用T{…}方式,结果为值类型c := Car{}// 方式二:使用new的方式,结果为指针类型c1 := new(Car)// 方式三:使用&方式,结果为指针类型c2 := &Car{}// 以下为创建并初始化c3 := &Car{"红色", "1.2L"}c4 := &Car{color: "红色"}c5 := Car{color: "红色"}// 没有构造函数的概念,对象的创建通常交由一个全局的创建函数来完成,以NewXXX 来命名,表示“构造函数” :func NewCar(color,size string)*Car { return &Car{color,size}} 接口12345678910111213141516171819202122232425262728type Phone interface { call()}type NokiaPhone struct {}func (nokiaPhone NokiaPhone) call() { fmt.Println("I am Nokia, I can call you!")}type IPhone struct {}func (iPhone IPhone) call() { fmt.Println("I am iPhone, I can call you!")}func main() { var phone Phone phone = new(NokiaPhone) phone.call() phone = new(IPhone) phone.call()} 错误处理123456789101112131415161718192021222324252627282930313233343536373839404142434445// 自定义错误信息结构type DivErr struct { etype int // 错误类型 v1 int // 记录下出错时的除数、被除数 v2 int}// 实现接口方法 error.Error()func (divErr DivErr) Error() string { if 0== divErr.etype { return "除零错误" }else{ return "其他未知错误" }}// 除法func div(a int, b int) (int,*DivErr) { if b == 0 { // 返回错误信息 return 0,&DivErr{0,a,b} } else { // 返回正确的商 return a / b, nil }}func main() { // 正确调用 v,r :=div(100,2) if nil!=r{ fmt.Println("(1)fail:",r) }else{ fmt.Println("(1)succeed:",v) } // 错误调用 v,r =div(100,0) if nil!=r{ fmt.Println("(2)fail:",r) }else{ fmt.Println("(2)succeed:",v) }} GoRoutine1234567891011func main() { go say("world") say("hello")}func say(s string) { for i := 0; i < 5; i++ { time.Sleep(100 * time.Millisecond) fmt.Println(s) }} Channel12345678910111213package mainimport "fmt"func main() { messages := make(chan string) go func() { messages <- "ping" }() msg := <-messages fmt.Println(msg)} 包管理器modulesgo mod常用命令 命令 说明 download download modules to local cache(下载依赖包) edit edit go.mod from tools or scripts(编辑go.mod graph print module requirement graph (打印模块依赖图) init initialize new module in current directory(在当前目录初始化mod) tidy add missing and remove unused modules(拉取缺少的模块,移除不用的模块) vendor make vendored copy of dependencies(将依赖复制到vendor下) verify verify dependencies have expected content (验证依赖是否正确) why explain why packages or modules are needed(解释为什么需要依赖) 示例一:创建一个新项目 在GOPATH 目录之外新建一个目录,并使用go mod init 初始化生成go.mod 文件 go.mod文件一旦创建后,它的内容将会被go toolchain全面掌控。go toolchain会在各类命令执行时,比如go get、go build、go mod等修改和维护go.mod文件。 go.mod 提供了module, require、replace和exclude 四个命令 module 语句指定包的名字(路径) require 语句指定的依赖项模块 replace 语句可以替换依赖项模块 exclude 语句可以忽略依赖项模块 添加依赖 新建一个 server.go 文件,写入以下代码: 123456789101112131415package mainimport ( "net/http" "github.com/labstack/echo")func main() { e := echo.New() e.GET("/", func(c echo.Context) error { return c.String(http.StatusOK, "Hello, World!") }) e.Logger.Fatal(e.Start(":1323"))} 执行 go run server.go 运行代码会发现 go mod 会自动查找依赖自动下载: 1234567891011121314151617181920$ go run server.gogo: finding github.com/labstack/echo v3.3.10+incompatiblego: downloading github.com/labstack/echo v3.3.10+incompatiblego: extracting github.com/labstack/echo v3.3.10+incompatiblego: finding github.com/labstack/gommon/color latestgo: finding github.com/labstack/gommon/log latestgo: finding github.com/labstack/gommon v0.2.8# 此处省略很多行... ____ __ / __/___/ / ___ / _// __/ _ \/ _ \/___/\__/_//_/\___/ v3.3.10-devHigh performance, minimalist Go web frameworkhttps://echo.labstack.com____________________________________O/_______ O\⇨ http server started on [::]:1323复制代码 现在查看go.mod 内容: 123456789101112131415$ cat go.modmodule hellogo 1.12require ( github.com/labstack/echo v3.3.10+incompatible // indirect github.com/labstack/gommon v0.2.8 // indirect github.com/mattn/go-colorable v0.1.1 // indirect github.com/mattn/go-isatty v0.0.7 // indirect github.com/valyala/fasttemplate v1.0.0 // indirect golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a // indirect)复制代码 go module 安装 package 的原則是先拉最新的 release tag,若无tag则拉最新的commit,详见 Modules官方介绍。 go 会自动生成一个 go.sum 文件来记录 dependency tree: 123456789$ cat go.sumgithub.com/labstack/echo v3.3.10+incompatible h1:pGRcYk231ExFAyoAjAfD85kQzRJCRI8bbnE7CX5OEgg=github.com/labstack/echo v3.3.10+incompatible/go.mod h1:0INS7j/VjnFxD4E2wkz67b8cVwCLbBmJyDaka6Cmk1s=github.com/labstack/gommon v0.2.8 h1:JvRqmeZcfrHC5u6uVleB4NxxNbzx6gpbJiQknDbKQu0=github.com/labstack/gommon v0.2.8/go.mod h1:/tj9csK2iPSBvn+3NLM9e52usepMtrd5ilFYA+wQNJ4=github.com/mattn/go-colorable v0.1.1 h1:G1f5SKeVxmagw/IyvzvtZE4Gybcc4Tr1tf7I8z0XgOg=github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=... 省略很多行复制代码 再次执行脚本 go run server.go 发现跳过了检查并安装依赖的步骤。 可以使用命令 go list -m -u all 来检查可以升级的package,使用go get -u need-upgrade-package 升级后会将新的依赖版本更新到go.mod * 也可以使用 go get -u 升级所有依赖 go get 升级 运行 go get -u 将会升级到最新的次要版本或者修订版本(x.y.z, z是修订版本号, y是次要版本号) 运行 go get -u=patch 将会升级到最新的修订版本 运行 go get package@version 将会升级到指定的版本号version 运行go get如果有版本的更改,那么go.mod文件也会更改 示例二:改造现有项目(helloword)项目目录为: 1234567$ tree.├── api│ └── apis.go└── server.go1 directory, 2 files server.go 源码为: 12345678910111213package mainimport ( api "./api" // 这里使用的是相对路径 "github.com/labstack/echo")func main() { e := echo.New() e.GET("/", api.HelloWorld) e.Logger.Fatal(e.Start(":1323"))}复制代码 api/apis.go 源码为: 123456789101112package apiimport ( "net/http" "github.com/labstack/echo")func HelloWorld(c echo.Context) error { return c.JSON(http.StatusOK, "hello world")}复制代码 使用 go mod init *** 初始化go.mod 123$ go mod init helloworldgo: creating new go.mod: module helloworld复制代码 运行 go run server.go 123456go: finding github.com/labstack/gommon/color latestgo: finding github.com/labstack/gommon/log latestgo: finding golang.org/x/crypto/acme/autocert latestgo: finding golang.org/x/crypto/acme latestgo: finding golang.org/x/crypto latestbuild command-line-arguments: cannot find module for path _/home/gs/helloworld/api 首先还是会查找并下载安装依赖,然后运行脚本 server.go,这里会抛出一个错误: 1build command-line-arguments: cannot find module for path _/home/gs/helloworld/api 但是go.mod 已经更新: 12345678910111213$ cat go.modmodule helloworldgo 1.12require ( github.com/labstack/echo v3.3.10+incompatible // indirect github.com/labstack/gommon v0.2.8 // indirect github.com/mattn/go-colorable v0.1.1 // indirect github.com/mattn/go-isatty v0.0.7 // indirect github.com/valyala/fasttemplate v1.0.0 // indirect golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a // indirect) 那为什么会抛出这个错误呢?这是因为 server.go 中使用 internal package 的方法跟以前已经不同了,由于 go.mod会扫描同工作目录下所有 package 并且变更引入方法,必须将 helloworld当成路径的前缀,也就是需要写成 import helloworld/api,以往 GOPATH/dep 模式允许的 import ./api 已经失效,详情可以查看这个 issue。 更新旧的package import 方式 所以server.go 需要改写成: 12345678910111213package mainimport ( api "helloworld/api" // 这是更新后的引入方法 "github.com/labstack/echo")func main() { e := echo.New() e.GET("/", api.HelloWorld) e.Logger.Fatal(e.Start(":1323"))}复制代码 一个小坑:开始在golang1.11 下使用go mod 遇到过 go build github.com/valyala/fasttemplate: module requires go 1.12 这种错误,遇到类似这种需要升级到1.12 的问题,直接升级golang1.12 就好了。幸亏是在1.12 发布后才尝试的go mod 🤷♂️ 网络编程最简单的网络编程123456789101112131415161718package mainimport ( "io" "log" "net/http")func HelloServer(w http.ResponseWriter, req *http.Request) { io.WriteString(w, "hello, world!\n")}func main() { http.HandleFunc("/hello", HelloServer) err := http.ListenAndServeTLS(":8080", "cert.pem", "key.pem", nil) if err != nil { log.Fatal("ListenAndServe: ", err) }} TLS服务端和客户端serve.go 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748package mainimport ( "bufio" "crypto/tls" "log" "net")func main() { log.SetFlags(log.Lshortfile) key, err := tls.LoadX509KeyPair("cert.pem", "key.pem") if err != nil { log.Println(err) return } config := &tls.Config{Certificates: []tls.Certificate{key}} ln, err := tls.Listen("tcp", ":8000", config) if err != nil { log.Println(err) return } defer ln.Close() for { conn, err := ln.Accept() if err != nil { log.Println(err) continue } go handleConnection(conn) }}func handleConnection(conn net.Conn) { defer conn.Close() r := bufio.NewReader(conn) for { msg, err := r.ReadString('\n') if err != nil { log.Println(err) return } println(msg) n, err := conn.Write([]byte("world\n")) if err != nil { log.Println(n, err) return } }} clinet.go 123456789101112131415161718192021222324252627282930package mainimport ( "crypto/tls" "log")func main() { log.SetFlags(log.Lshortfile) conf := &tls.Config{ InsecureSkipVerify: true, } conn, err := tls.Dial("tcp", "127.0.0.1:8000", conf) if err != nil { log.Println(err) return } defer conn.Close() n, err := conn.Write([]byte("hello\n")) if err != nil { log.Println(n, err) return } buf := make([]byte, 100) n, err = conn.Read(buf) if err != nil { log.Println(n, err) return } println(string(buf[:n]))} 标准库flag1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768import ( "errors" "flag" "fmt" "strings" "time")/**// 编译go build demo.go// 帮助信息demo.exe -h// 正常使用demo.exe -name yang*/func main() { //demo1() demo2()}// 第一种用法func demo1() { var nFlag = flag.String("name", "anthony", "help message for flag n") flag.Parse() fmt.Println(*nFlag)}// 第二种用法func demo2() { var name string flag.StringVar(&name, "name", "anthonyyang", "help message for flag n") flag.Parse() fmt.Println(name)}// 第三种用法func demo3() { var intervalFlag mystring flag.Var(&intervalFlag, "anthonyyang", "name") fmt.Println(intervalFlag)}type mystring []time.Durationfunc (i *mystring) String() string { return fmt.Sprint()}func (i *mystring) Set(value string) error { if len(*i) > 0 { return errors.New("interval flag already set") } for _, dt := range strings.Split(value, ",") { duration, err := time.ParseDuration(dt) if err != nil { return err } *i = append(*i, duration) } return nil} 参考 logioio.Reader对于要用作读取器的类型,它必须实现 io.Reader 接口的唯一一个方法 Read(p []byte)。换句话说,只要实现了 Read(p []byte) ,那它就是一个读取器。 123type Reader interface { Read(p []byte) (n int, err error)} Read() 方法有两个返回值,一个是读取到的字节数,一个是发生错误时的错误。同时,如果资源内容已全部读取完毕,应该返回 io.EOF 错误。 io.Copysyncsync.WaitGroup1234567891011121314import ( "fmt")// 执行代码很可能看不到输出,因为有可能这两个协程还没得到执行主协程已经结束了,而主协程结束时会结束所有其他协程func main() { go func() { fmt.Println("Goroutine 1") }() go func() { fmt.Println("Goroutine 2") }()} 解决办法: 1234567891011121314151617import ( "fmt" "time")// 在main函数结尾加上等待func main() { go func() { fmt.Println("Goroutine 1") }() go func() { fmt.Println("Goroutine 2") }() time.Sleep(time.Second * 1) // 睡眠1秒,等待上面两个协程结束} 问题来了,不确定协程到底要用运行多长时间,比如协程需要用到2s,这里main等待1s,就不够用了 解决办法:用管道 12345678910111213141516171819202122232425262728import ( "fmt")func main() { ch := make(chan struct{}) count := 2 // count 表示活动的协程个数 go func() { fmt.Println("Goroutine 1") ch <- struct{}{} // 协程结束,发出信号 }() go func() { fmt.Println("Goroutine 2") ch <- struct{}{} // 协程结束,发出信号 }() for range ch { // 每次从ch中接收数据,表明一个活动的协程结束 count-- // 当所有活动的协程都结束时,关闭管道 if count == 0 { close(ch) } }} 虽然比较完美的方案,但是Go提供了更简单的方法——使用sync.WaitGroup WaitGroup顾名思义,就是用来等待一组操作完成的。 WaitGroup内部实现了一个计数器,用来记录未完成的操作个数,它提供了三个方法,Add()用来添加计数。 Done()用来在操作结束时调用,使计数减一。Wait()用来等待所有的操作结束,即计数变为0,该函数会在计数不为0时等待,在计数为0时立即返回 12345678910111213141516171819202122import ( "fmt" "sync")func main() { var wg sync.WaitGroup wg.Add(2) // 因为有两个动作,所以增加2个计数 go func() { fmt.Println("Goroutine 1") wg.Done() // 操作完成,减少一个计数 }() go func() { fmt.Println("Goroutine 2") wg.Done() // 操作完成,减少一个计数 }() wg.Wait() // 等待,直到计数为0} nethttpGet不带参数 123456789101112131415161718func main() { resp, err := http.Get("http://fund.eastmoney.com/js/fundcode_search.js") if err != nil { fmt.Println(err) return } defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) // 打印body fmt.Println(string(body)) // 打印状态码 fmt.Println(resp.StatusCode) if resp.StatusCode == 200 { fmt.Println("ok") parse(string(body)) }} net.ListenListen通知本地网络地址。 网络必须是“tcp”,“tcp4”,“tcp6”,“unix”或“unixpacket”。 对于TCP网络,如果地址参数中的主机为空或文字未指定的IP地址,则Listen会监听本地系统的所有可用单播和任播IP地址。 要仅使用IPv4,请使用网络“tcp4”。 该地址可以使用主机名称,但不建议这样做,因为它将为主机的至多一个IP地址创建一个监听器。 如果地址参数中的端口为空或“0”,如“127.0.0.1:”或“:: 1:0”中所示,则会自动选择一个端口号。Listener的Addr方法可用于发现所选端口。 12345678910for retry := 0; retry < 16; retry++ { l, err := net.Listen("tcp", host+":0") if err != nil { continue } defer l.Close() _, port, err := net.SplitHostPort(l.Addr().String()) p, err := strconv.ParseInt(port, 10, 32) return int(p)} net.Conn.SetReadDeadline设置超时时间 net.Conn为Deadline提供了多个方法Set[Read|Write]Deadline(time.Time)。 Deadline是一个绝对时间值,当到达这个时间的时候,所有的 I/O 操作都会失败,返回超时(timeout)错误。 OSStat12345678910111213141516171819202122232425262728package mainimport ( "fmt" "os")func main() { fileinfo, err := os.Stat(`C:\Users\Administrator\Desktop\UninstallTool.zip`) if err != nil { panic(err) } fmt.Println(fileinfo.Name()) //获取文件名 fmt.Println(fileinfo.IsDir()) //判断是否是目录,返回bool类型 fmt.Println(fileinfo.ModTime()) //获取文件修改时间 fmt.Println(fileinfo.Mode()) fmt.Println(fileinfo.Size()) //获取文件大小 fmt.Println(fileinfo.Sys())}type FileInfo interface { Name() string // 文件的名字(不含扩展名) Size() int64 // 普通文件返回值表示其大小;其他文件的返回值含义各系统不同 Mode() FileMode // 文件的模式位 ModTime() time.Time // 文件的修改时间 IsDir() bool // 等价于Mode().IsDir() Sys() interface{} // 底层数据来源(可以返回nil)} IsExist返回一个布尔值说明该错误是否表示一个文件或目录已经存在。ErrExist和一些系统调用错误会使它返回真。 12345678910func check() bool { stat, err := os.Stat("/Users/anthony/Desktop/go-testt/go.mod") if err != nil { if os.IsExist(err) { return true } return false } return true} Text/templatehtml模板 https://pkg.go.dev/text/template GinDemo1234567891011package mainimport "github.com/gin-gonic/gin"func main() { r := gin.Default() r.GET("/", func(c *gin.Context) { c.String(200, "Hello, Geektutu") }) r.Run() // listen and serve on 0.0.0.0:8080} 路由基本使用 1234567891011121314151617181920// 设置路由router := gin.Default()// 第一个参数是:路径; 第二个参数是:具体操作 func(c *gin.Context)router.GET("/Get", getting)router.POST("/Post", posting)router.PUT("/Put", putting)router.DELETE("/Delete", deleting)// 默认启动的是 8080端口router.Run()// 第一种方法:r.GET("/", func(c *gin.Context) { c.String(http.StatusOK, "Who are you?")})// 第二种方法r.POST("createApi", CreateApi)func CreateApi(c *gin.Context) { // 代码} 路由分组 12345678910111213// 两个路由组,都可以访问,大括号是为了保证规范v1 := r.Group("/v1"){ // 通过 localhost:8080/v1/hello访问,以此类推 v1.GET("/hello", sayHello) v1.GET("/world", sayWorld)}v2 := r.Group("/v2"){ v2.GET("/hello", sayHello) v2.GET("/world", sayWorld)}r.Run(":8080") 大量路由实现 建立routers包,将不同模块拆分到多个go文件 每个文件提供一个方法,该方法注册实现所有的路由 之后main方法在调用文件的方法实现注册 第一种实现方式 1234567891011121314151617// 这里是routers包下某一个router对外开放的方法func LoadRouter(e *gin.Engine) { e.Group("v1") { v1.GET("/post", postHandler) v1.GET("/get", getHandler) } ...}func main() { r := gin.Default() // 调用该方法实现注册 routers.LoadRouter(r) routers.LoadRouterXXX(r) // 代表还有多个 r.Run()} 第二种方式 1像vue一样注册路由,没看懂项目 为啥要注册很多次 获取参数获取body的数据,并绑定实体类 12345678910func (b *BaseApi) Login(c *gin.Context) { // 声明实体类 var l systemReq.Login // 赋值 err := c.ShouldBindJSON(&l) // 获取ip key := c.ClientIP()} 响应处理1234567891011121314151617181920212223// 1.JSONr.GET("/someJSON", func(c *gin.Context) { c.JSON(200, gin.H{ "message": "Json", "status": 200, })})// 2.XMLr.GET("/someXML", func(c *gin.Context) { c.XML(200, gin.H{"message": "abc"})})// 3.YAMLr.GET("/someYAML", func(c *gin.Context) { c.YAML(200, gin.H{"name": "zhangsan"})})// 4.protobufr.GET("/someProtoBuf", func(c *gin.Context) { reps := []int64{1, 2} data := &protoexample.Test{ Reps: reps, } c.ProtoBuf(200, data)}) 中间件中间件分为:全局中间件 和 路由中间件,区别在于前者会作用于所有路由。 1234567891011121314// 默认的中间件有func BasicAuth(accounts Accounts) HandlerFunc // 身份认证func BasicAuthForRealm(accounts Accounts, realm string) HandlerFuncfunc Bind(val interface{}) HandlerFunc //拦截请求参数并进行绑定func ErrorLogger() HandlerFunc //错误日志处理func ErrorLoggerT(typ ErrorType) HandlerFunc //自定义类型的错误日志处理func Logger() HandlerFunc //日志记录func LoggerWithConfig(conf LoggerConfig) HandlerFuncfunc LoggerWithFormatter(f LogFormatter) HandlerFuncfunc LoggerWithWriter(out io.Writer, notlogged ...string) HandlerFuncfunc Recovery() HandlerFuncfunc RecoveryWithWriter(out io.Writer) HandlerFuncfunc WrapF(f http.HandlerFunc) HandlerFunc //将http.HandlerFunc包装成中间件func WrapH(h http.Handler) HandlerFunc //将http.Handler包装成中间件 作用范围 1234567891011121314// 作用于全局r.Use(gin.Logger())r.Use(gin.Recovery())// 作用于单个路由r.GET("/benchmark", MyBenchLogger(), benchEndpoint)// 作用于某个组authorized := r.Group("/")authorized.Use(AuthRequired()){ authorized.POST("/login", loginEndpoint) authorized.POST("/submit", submitEndpoint)} 自定义中间件 123456789101112func Logger() gin.HandlerFunc { return func(c *gin.Context) { t := time.Now() // 给Context实例设置一个值 c.Set("geektutu", "1111") // 请求前 c.Next() // 请求后 latency := time.Since(t) log.Print(latency) }} 控制中间件 中间件控制的方法gin提供了两个函数Abort()和Next(),二者区别在于: next()函数会跳过当前中间件中next()后的逻辑,当下一个中间件执行完成后再执行剩余的逻辑 abort()函数执行终止当前中间件以后的中间件执行,但是会执行当前中间件的后续逻辑 举例子更好理解: 我们注册中间件顺序为m1、m2、m3,如果采用next(): 执行顺序就是 m1的next()前面、m2的next()前面、m3的next()前面、 业务逻辑 m3的next()后续、m2的next()后续、m1的next()后续。 那如果m2中间调用了Abort(),则m3和业务逻辑不会执行,只会执行m2的next()后续、m1的next()后续。 GormCRUD 接口创建创建记录12345678// 没有数据的字段,就是基本数据user := User{Name: "Jinzhu", Age: 18, Birthday: time.Now()}result := db.Create(&user) // 通过数据的指针来创建user.ID // 返回插入数据的主键result.Error // 返回 errorresult.RowsAffected // 返回插入记录的条数 用指定的字段创建记录创建记录并更新给出的字段。 123// 没有数据的字段,插入的是nulldb.Select("Name", "Age", "CreatedAt").Create(&user)// INSERT INTO `users` (`name`,`age`,`created_at`) VALUES ("jinzhu", 18, "2020-07-04 11:05:21.775") 创建一个记录且一同忽略传递给略去的字段值 12db.Omit("Name", "Age", "CreatedAt").Create(&user)// INSERT INTO `users` (`birthday`,`updated_at`) VALUES ("2020-01-01 00:00:00.000", "2020-07-04 11:05:21.775") ❓ 用指定的字段创建记录 不返回ID 批量插入1234567891011var users = []User{{Name: "jinzhu1"}, {Name: "jinzhu2"}, {Name: "jinzhu3"}}db.Create(&users)for _, user := range users { user.ID // 1,2,3}// 使用 CreateInBatches 分批创建时,你可以指定每批的数量var users = []User{{name: "jinzhu_1"}, ...., {Name: "jinzhu_10000"}}// 数量为 100db.CreateInBatches(users, 100) 创建钩子123456789101112131415161718192021222324252627282930313233343536373839404142434445464748type SysMenu struct { gorm.Model // 菜单名称 Title string // 直属上级菜单标识 ParentId int // 菜单URL Path string // 0:Flase,1:True AlwaysShow int // 组件 Component string // 0:Flase,1:True Hidden int // 图标 Icon string // 0:Flase,1:True NoCache string // 前端内部名字 Name string // 前端内部路由 Redirect string // 0:否,1:是 IsButton int // 请求后端接口 Ajax string}// 钩子func (s *SysMenu) BeforeSave(tx *gorm.DB) (err error) { fmt.Println("BeforeSave") return}func (s *SysMenu) BeforeCreate(tx *gorm.DB) (err error) { fmt.Println("BeforeCreate") return}func (s *SysMenu) AfterCreate(tx *gorm.DB) (err error) { fmt.Println("AfterCreate") return}func (s *SysMenu) AfterSave(tx *gorm.DB) (err error) { fmt.Println("AfterSave") return} 根据 Map 创建123456789db.Model(&User{}).Create(map[string]interface{}{ "Name": "jinzhu", "Age": 18,})// batch insert from `[]map[string]interface{}{}`db.Model(&User{}).Create([]map[string]interface{}{ {"Name": "jinzhu_1", "Age": 18}, {"Name": "jinzhu_2", "Age": 20},}) 默认值12345type User struct { ID int64 Name string `gorm:"default:galeone"` Age int64 `gorm:"default:18"`} Cobra简介cobra是一个命令行程序库,可以用来编写命令行程序。 快速入门安装包 1$ go get github.com/spf13/cobra/cobra 文件目录 12345- my-go-test - cmd - version - root main.go main.go 12345import "go-test/cmd"func main() { cmd.Execute()} version.go 1234567891011121314151617181920package cmdimport ( "fmt" "github.com/spf13/cobra")var versionCmd = &cobra.Command{ Use: "version", Short: "显示计算器版本", Long: "显示计算器版本", Run: func(cmd *cobra.Command, args []string) { fmt.Println("anthony's jsq version 0.0.1") },}func init() { rootCmd.AddCommand(versionCmd)} root.go 1234567891011121314151617import ( "fmt" "github.com/spf13/cobra")var rootCmd = &cobra.Command{ Use: "jsq", Short: "计算器", Long: `anthony写的计算器`, Run: func(cmd *cobra.Command, args []string) { fmt.Println("anthony写的计算器,请使用 ./jsq -h") },}func Execute() { rootCmd.Execute()} 打包运行123456789101112131415161718192021222324252627282930313233# 打包➜ go-testt go build -o jsq# 运行不带参数的命令➜ go-testt ./jsq anthony写的计算器,请使用 ./jsq -h# 运行主命令的help➜ go-testt ./jsq -helpError: unknown shorthand flag: 'e' in -elpUsage: jsq [flags] jsq [command]Available Commands: completion Generate the autocompletion script for the specified shell help Help about any command version 显示计算器版本Flags: -h, --help help for jsqUse "jsq [command] --help" for more information about a command.# 运行子命令➜ go-testt ./jsq version -h显示计算器版本Usage: jsq version [flags]Flags: -h, --help help for version 包成window包 1go build -o jsq.exe 库Yaml解析库1go get gopkg.in/yaml.v3 测试文件yaml 1234567mysql: url: 127.0.0.1 port: 3306redis: host: 127.0.0.1 port: 6379 将 yaml 文件的数据转成自定义的结构体或 Map 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647import ( "fmt" "gopkg.in/yaml.v3" "os")type Config struct { Mysql Mysql `json:"mysql"` Redis Redis `json:"redis"`}type Mysql struct { Url string Port int}type Redis struct { Host string Port int}func main() { dataBytes, err := os.ReadFile("test.yaml") if err != nil { fmt.Println("读取文件失败:", err) return } fmt.Println("yaml 文件的内容: \n", string(dataBytes)) config := Config{} err = yaml.Unmarshal(dataBytes, &config) if err != nil { fmt.Println("解析 yaml 文件失败:", err) return } fmt.Printf("config → %+v\n", config) // config → {Mysql:{Url:127.0.0.1 Port:3306} Redis:{Host:127.0.0.1 Port:6379}} mp := make(map[string]any, 2) err = yaml.Unmarshal(dataBytes, mp) if err != nil { fmt.Println("解析 yaml 文件失败:", err) return } fmt.Printf("map → %+v", config) // config → {Mysql:{Url:127.0.0.1 Port:3306} Redis:{Host:127.0.0.1 Port:6379}}} yaml解析库1go get github.com/spf13/viper 测试 123456789101112131415161718192021import ( "fmt" "github.com/spf13/viper")func main() { // 设置配置文件的名字 viper.SetConfigName("test") // 设置配置文件的类型 viper.SetConfigType("yaml") // 添加配置文件的路径,指定 config 目录下寻找 viper.AddConfigPath("./config") // 寻找配置文件并读取 err := viper.ReadInConfig() if err != nil { panic(fmt.Errorf("fatal error config file: %w", err)) } fmt.Println(viper.Get("mysql")) // map[port:3306 url:127.0.0.1] fmt.Println(viper.Get("mysql.url")) // 127.0.0.1} 笔记字符串互转byte1234// string to []bytes1 := "hello" b := []byte(s1)// []byte to strings2 := string(b) 创建对象的方式 12345678910111213141516171819202122232425type Car struct { color string size string}// 方式一:使用T{…}方式,结果为值类型c := Car{}// 方式二:使用new的方式,结果为指针类型c1 := new(Car)// 方式三:使用&方式,结果为指针类型c2 := &Car{}// 以下为创建并初始化c3 := &Car{"红色", "1.2L"}c4 := &Car{color: "红色"}c5 := Car{color: "红色"}// 构造函数:// 在Go语言中没有构造函数的概念,对象的创建通常交由一个全局的创建函数来完成,以// NewXXX 来命名,表示“构造函数” :func NewCar(color,size string)*Car { return &Car{color,size}} 报错1.debug call has arguments but no formatting directives 打印/日志 格式解析错误,错误的原因就是没加%v 2.取消代理 123456go envGOPROXY='https://goproxy.cn,direct' # 这个是国内的代理,在国外不容易下载依赖➜ server git:(main) ✗ go env -u GOPROXY # 取消代理,使用默认的配置➜ server git:(main) ✗ go envGOPROXY='https://proxy.golang.org,direct'
Windows
Windows Server开放指定端口 服务器禁用IPV6 ⛔ 一定要重启 官方文档 IE浏览器允许下载通常服务器不允许乱下载软件,所以一般默认被禁用,下面两种方法都行 安装win10系统,创建本地账号在提示需要账号登录的时候,按Shift + F10打开命令行,输入oobe\bypassnro.cmd,就会重启,再安装,就不会要求输入微软账号了;也有例外的情况,用过win11商业版的镜像安装的时候,用这个命令不起作用 参考:系统安装跳过登录 Powershell改变默认编码中文版Powershell默认编码为GB2312,而编程,运维,开发中常用编码格式为UTF-8。 临时修改1chcp 65001 永久修改进入注册表win+R->输入regedit在HKEY_CURRENT_USER\Console,双击CodePage修改键值为65001检查是否生效: 新开一个PowerShell,或者重启 Win10杀线程1234# 查找5091的端口netstat -aon | find "5091"# 杀5091的PIDtaskkill /f /pid 6880 Win10安装Scoop包管理器Scoop是windows的包管理器 12345# 使用了PowerShell在你当前Windows的账户下set-executionpolicy remotesigned -s cu# 在PowerShell下输入iex (new-object net.webclient).downloadstring('https://get.scoop.sh') Windows Terminal策略以管理员身份打开PowerShell 输入 1set-executionpolicy remotesigned Win10去掉小箭头123456789101112131415# 去掉小箭头reg add "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Shell Icons" /v 29 /d "%systemroot%\system32\imageres.dll,197" /t reg_sz /ftaskkill /f /im explorer.exeattrib -s -r -h "%userprofile%\AppData\Local\iconcache.db"del "%userprofile%\AppData\Local\iconcache.db" /f /qstart explorerpause# 回复小箭头reg delete "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Shell Icons" /v 29 /ftaskkill /f /im explorer.exeattrib -s -r -h "%userprofile%\AppData\Local\iconcache.db"del "%userprofile%\AppData\Local\iconcache.db" /f /qstart explorerpause 复制上面的代码。新建一个文本文件。粘贴后另存为.bat文件,然后以管理员身份打开。 Win10安装concfg,用来清空掉命令行和powershell的多配置文件github仓库:https://github.com/lukesampson/concfg 12345678# 安装concfg软件scoop install concfg# 删除掉多余的配置文件concfg clean# 导入内置的主题配置,还有别的配置concfg import solarized-dark Cmd和Powershell的默认值和属性的区别默认值和属性的不同设置,从哪里启动有关系,比如用开始菜单的 和 从shift + 右键,加载的命令行不一样,还是安装好concfg软件,把多余的都删掉,只留一个 如果你打开 PowerShell 快捷方式时不小心右键修改它的“属性”中的设置项的话,那么它的表现又会不同了。所以如果想一直保持一致,最好不要动任何一个控制台实例的“属性”。https://www.zhihu.com/question/63867578/answer/220101109 Powershell美化教程用的是这三个地址,等下回安装的时候再写的详细点 123https://zhuanlan.zhihu.com/p/51901035https://blog.walterlv.com/post/beautify-powershell-like-zsh.htmlhttps://coolcode.org/2018/03/16/how-to-make-your-powershell-beautiful/ 把软件加入到自启动在运行里输入shell:startup,把快捷键拖到打开的文件夹里 WSL2安装1.开启2.下载安装镜像 3.设置wsl2 123PS C:\Users\anthony> wsl -l -v NAME STATE VERSION* Ubuntu-20.04 Stopped 2 Docker和VMware的虚拟机冲突把Windows自带虚拟机删除掉 再用管理员的powershell执行bcdedit /set hypervisorlaunchtype off EverythingToolbarhttps://github.com/stnkl/EverythingToolbar PicGO+Github生成token:在github->setting->developer settings 选择generate new token 记得保存好密码,只会创建成功后显示,以后就看不到了 PicGo设置 1234仓库名:YangAnLin/images分支名:master设定token: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设定自定义域名:https://cdn.jsdelivr.net/gh/YangAnLin/images 通过修改注册表更改IE浏览器的代理设置打开注册表 1regedit或regedit.exe、regedt32或regedt32.exe Xshell输入删除回车变成^HFile–>Properties–>Terminl里选择Keyboard,然后在Backspace key sequence里选择ASCII 127 WhatsApp申请测试版 Vscode不同的窗口配置不同的主题在settings.json中添加 1"workbench.colorTheme": "GitHub Light" 快捷键 Win命令 Mac命令 描述 Ctrl + J Command + J 隐藏/显示终端 Ctrl + P Command + P 快速打开文件 Alt + Shift+鼠标移动 列编辑 Alt+鼠标点选 列编辑+点选 设置Python自动补全函数1settings—>addBrackets Go自动补全函数123# 这个选项打勾useCodeSnippetsOnMethodSuggest":true# 或者搜索suggest相关的设置 隐藏目录 文本乱码和重新编码比如打开的一个文本是GBK编码的文件,用vscode打开的是乱码如果想要正常显示,先选择文件本来的编码 如果想要把GBK编码的文件,转成UTF-8的编码格式,再保存 使用制表符而不是空格在设置你搜索indent,找到 Editor:Detect Indexntation 和 Editor:Insert Spaces 两个勾选框,取消勾选 显示空格和制表符打开setting 在搜索框中输入renderControlCharacters,选中勾选框,即可显示tab. 在搜索框中输入renderWhitespace,选择all,即可显示空格. 设置制表符为4个空格打开setting,搜索tab size为4,如果不起作用,接下来要设置 搜索Detect Indentation 取消勾选,或者勾选试试 插件Write Timestamp作用:插入当前时间Ctrl + Shift + T 也可以自己修改时间格式: PicGO1.在插件那搜索picgo,安装好插件在插件那里点设置2.配置阿里云oss,参考:https://picgo.github.io/PicGo-Doc/ 12345678{ "accessKeyId": "", "accessKeySecret": "", "bucket": "", // 存储空间名 "area": "", // 存储区域代号 "path": "", // 自定义存储路径 "customUrl": "" // 自定义域名,注意要加http://或者https://} 3.通过三个组合键,可以分别从:1.(Ctrl+alt+U)剪切板 2.(Ctrl+alt+E)文件夹 3.(Ctrl+alt+O)指定路径