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)指定路径
Git
分支管理切一个新的本地分支123# 先切换到需要复制的分支,比如想从prod分支,切一个新的分支git checkout prod # 先到prod分支git checkout -b prod-fix # 从prod分支拷贝一个新的分支叫prod-fix 用命令pull request代码1234# 把本地的代码切换到mastergit checkout -b theme-next-master master# https://github.com/theme-next/hexo-theme-next.git 是要合并的代码git pull https://github.com/theme-next/hexo-theme-next.git master Git Key123cd ~/.sshssh-keygencat ~/.ssh/id_rsa.pub Git Bash启动慢123git config --global core.preloadindex truegit config --global core.fscache truegit config --global gc.auto 256 推送到远程12345git initgit add README.mdgit commit -m "first commit"git remote add origin [email protected]:YangAnLin/bbs.gitgit push -u origin master 创建新的分支推送到远程 先创建本地分支git checkout -b <branch-name> 本地分支推送到远程服务器时,远程分支自动创建,推送本地分支到远程 123456789101112git push --set-upstream <remote-name> <local-branch-name>:<remote-branch-name><remote-name>:远程git服务器名称,一般设为origin<local-branch-name>:本地分支名称<remote-branch-name>:远程分支名称最后的结果是: git push --set-upstream origin prerelease:prereleaseprerelease是分支的名字也可以这样操作:本地有个master_anthony,服务器中没有这个分支,git push -u origin master_anthony这个命令,就是把master_anthony分支的代码,提交到远程,还要在远程创建这个分支 删除远程仓库文件12345678# 加上 -n 这个参数,执行命令时,是不会删除任何文件,而是展示此命令要删除的文件列表预览。git rm -r -n --cached 文件/文件夹名称git rm -r --cached 文件/文件夹名称git commit -m "提交说明"git push 撤销修改1234567# 工作区修改过了,恢复工作区git checkout -- file# 已经commit了,但是还没有pushgit reset --soft HEAD^# 现在就已经把暂存区恢复到工作了 版本回退resetreset用于回退版本,可以遗弃不再使用的提交 1234567891011121314151617181920212223242526272829# 查看日志提交log$ git log --pretty=onelinec27561bbe42dc1ae1b08442ce0d8100d4e02a689 (HEAD -> master) stage1fd6bc87438d26b24c93441b82c8840a9ad47e32 test_366f444c9de0b3c2d1cddb8f17becca7ce774a7c7 test_22502c400cab4d7cb48ed3e4200577f428904da9b test_126d73f758411c3656b7fa243d77837943a6c782c 3213215fa733378d5eb187d1d46bf2d2395e6c67867a8d 测试e9fa63e3bafc275e99cc88b40cc68bde6933323c 1233d338b67e2b57dcd6887099cb3977f5d96b3cbb5 第一次提交# 远程地址回退版本# 先让本地回退到指定版本git reset --hard HEAD^#再推送到远程(这样也会把别人提交的给弄消息了)git push --force# 操作记录$ git reflogc27561b (HEAD -> master) HEAD@{0}: commit: stage1fd6bc8 HEAD@{1}: reset: moving to 1fd6bc866f444c HEAD@{2}: reset: moving to HEAD^1fd6bc8 HEAD@{3}: commit: test_366f444c HEAD@{4}: commit: test_22502c40 HEAD@{5}: commit: test_126d73f7 HEAD@{6}: commit: 3213215fa7333 HEAD@{7}: commit: 测试e9fa63e HEAD@{8}: commit: 1233d338b6 HEAD@{9}: commit (initial): 第一次提交 版本回退revert 12git reset <ID>git push 📌 操作同一个文件的时候,容易冲突 分支管理1234567891011121314# 查看提交历史git log --graph --pretty=oneline# 删除本地分支git branch -d localBranchName# 删除远程分支git push origin --delete remoteBranchName# 设置全局默认的分支名字git config --global init.defaultBranch <名称># 修改分支名字git branch -m <name> TAG管理查看本地所有的tag 1git tag 新建tag 12345678# 在本地创建一个taggit tag v2.0# 把本地所有tag推送到远程git push --tags# 把指定的tag推送到远程git push origin [tagname] Git子模块克隆项目指定分支1git clone -b prod https://git.oschina.net/oschina/android-app.git 克隆项目第一种方式: 123456789先clone父项目git clone sum.git再初始化子项目git submodule initgit submodule foreach git pull再更新子项目git submodule update 第二中方法 12# 克隆完整的项目git clone [email protected]:jjz/pod-project.git --recursive 添加项目1git submodule add module1.git Rebase12# 进入编辑模式git rebase -i N 这些 commit 自旧到新由上而下排列 在合并 commit 这个需求里,我们可以选择 pick(p) 最旧的 commit1,然后在后续的 commit_id 前添加 squash(s) 命令,将这些 commits 都合并到最旧的 commit1 上。 保存 rebase 结果后,再编辑 commit 信息,使这次 rebase 失效,git 会将之前的这些 commit 都删除,并将其更改合并为一个新的 commit5 提交的过程中,可能需要加 -f 强制推送 操作过程中,遇到出错,使用 1git rebase --abort/--continue/--edit-todo 要注意的 进入到子项目的目录中,默认的分支并不是master,需要手动切换到master 删除子模块的,没有这样的功能,需要手动修改.gitmodules文件 添加了新的子模块之后,.gitmodules就会被创建或者是修改,这个文件需要推送到远程仓库 储藏比如在当前分支正在开发,突然来了一个bug,但是当前写的代码又不能提交,需要先储藏起来 1git stash 等bug改好,再回来 1git stash pop 升级window 1git update-git-for-windows Github更新fork别人的项目1.打开自己的仓库,进入code下面 2.点击new pull request创建 终端配置github person token应用场景 Github Personal Access Token in Jenkins 12345678# 在命令行中的用户主目录创建如下文件vi .git-credentials# 写入如下内容,请注意将 {username}, {password} 替换成自己的 github 用户名和 tokenhttps://{username}:{password}@github.com# 退出保存,执行git config --global credential.helper store SourceTree各种状态1.添加文件或者修改文件uncommitted changes 这次是添加了f.txt文件 2.add的状态 3.提交的状态这里超前一个版本,是相对远程 4.推送完的状态 rebase分支1.这里的分割线的这次提交是用来分区之前的测试,表示新的测试,测试rebase分支,相当于原始的样子 2.如果现在想要开发一个新的功能先在远程的main分支上右键,检出一个自己的分支dev 3.在dev分支上新建/或者编辑文件,我这里创建的一个文件,叫dev分支创建的文件.txt,然后提交到本地,不要推送这里的超前1个版本,是相对于origin/main来说的 4.再切换到main分支 我这里创建了一个文件,叫master分支创建的文件.txt,提交并且推送到远程(这个操作相当于,你在dev分支 开发自己的功能,然后有别的同事提交了新的代码提交到main分支) pull main分支的代码 5.再切换的dev分支,选中main分支,右键,选择将当前变更变基到main 这里的: 超前1个版本: 相当于本地的dev分支来说 落后一个版本: 相对于远程分支来说 如果有冲突则合并冲突,点击左上角(中间位置)的提交,选择继续变基 1.第三步 的操作,已经提交到本地了 2.如果这里解决冲突,就相当于,有编辑了一次文件 3.再把编辑的提交一次到本地 6.此时我们的本地更新是基于最新的 main 分支点击推送,把dev分支,推送到远程 7.切换到main分支,点拉取,拉取dev分支到main分支 再推送main
PHP
安装Nginx与PHP 7.4123sudo apt updatesudo apt install php-fpmsystemctl status php7.4-fpm 1apt install nginx 123456789101112server { # . . . other code location ~ \.php$ { include snippets/fastcgi-php.conf; fastcgi_pass unix:/run/php/php7.4-fpm.sock; }}# 重启sudo systemctl restart nginx 测试PHP12cd /var/www/html/vim info.php 123456<?phpphpinfo();# 访问# http://your_server_ip/info.php WordPressDocker安装1234567# 安装docker-composeapt install docker-compose# 查看版本docker-compose versionvim docker-compose.yml 1234567891011121314151617181920212223242526version: '3.3'services: db: image: mysql:5.7 volumes: - db_data:/var/lib/mysql restart: always environment: MYSQL_ROOT_PASSWORD: somewordpress MYSQL_DATABASE: wordpress MYSQL_USER: wordpress MYSQL_PASSWORD: wordpress wordpress: depends_on: - db image: wordpress:latest ports: - "8000:80" restart: always environment: WORDPRESS_DB_HOST: db:3306 WORDPRESS_DB_USER: wordpress WORDPRESS_DB_PASSWORD: wordpress WORDPRESS_DB_NAME: wordpressvolumes: db_data: {} 123456# 后台运行docker-compose -f docker-compose.wordpress.yml up -d# 停止并删除服务docker-compose -f docker-compose.wordpress.yml down# IP:8000# IP:8000/wp-admin 12345678910111213141516171819202122232425# ningx反代WordPressserver { listen 80; listen [::]:80; # 域名 server_name .leozl.site; location / { ## service 的名称,docker-compose.yaml文件中的wordpress服务名称 proxy_pass http://wp; proxy_http_version 1.1; proxy_cache_bypass $http_upgrade; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Host $host; proxy_set_header X-Forwarded-Port $server_port; }} Vultr上安装上传备份 安装SSL 1234567891011# 安装证书certbot --nginx --redirect -d blog.airports.ink -m [email protected] --agree-tos --no-eff-email# 配置证书vim /etc/nginx/conf.d/wordpress_http.confvim /etc/nginx/conf.d/wordpress_https.confserver{ ssl_certificate /etc/nginx/ssl/server.crt; ssl_certificate_key /etc/nginx/ssl/server.key;} SEO优化
Python
安装虚拟环境12345678910111213141516171819202122232425262728293031323334# 判断有没有虚拟环境virtualenv -V# 安装虚拟环境# 需要sudopip install virtualenvpip install virtualenvwrapper# 查看有多少虚拟环境的文件夹workon# 创建虚拟环境文件夹mkvirtualenv 文件夹名字# 从虚拟文件夹退出deactiave# 进入虚拟环境中workon 虚拟环境名称# 删除虚拟环境rmvirutalenv# 查看虚拟环境有哪些框架,要在虚拟环境中执行pip freeze# 安装软件指定版本,要在虚拟环境中执行pip install flask==10.0.0.0# 导出虚拟环境中的所有扩展,要在虚拟环境中执行pip freeze > requirements.txt# 安装,要在虚拟环境中执行pip install -r requirements.txt centos7安装python3特别是在喜欢环境中已经安装的python2.x的版本中 1234567891011121314151617181920212223242526272829# 这个可能不一定要装sudo yum -y groupinstall "Development tools"# 需要的sudo yum -y install zlib-devel bzip2-devel openssl-devel ncurses-devel sqlite-devel readline-devel tk-devel gdbm-devel db4-devel libpcap-devel xz-devel libffi-devel# 下载安装包wget https://www.python.org/ftp/python/3.7.0/Python-3.7.0a1.tar.xz# 解压tar -xvxf Python-3.7.0a1.tar.xz# 复制文件夹mv Python-3.7.0 /usr/local# 进入到文件夹cd /usr/local/Python-3.7.0/# 编译,一定需要后面的参数./configure --prefix=/usr/local/bin/python3make & make install# 添加软连接ln -s /usr/local/bin/python3/bin/python3 /usr/bin/python3ln -s /usr/local/bin/python3/bin/pip3 /usr/bin/pip3# 验证python3pip3 基础1.注释123456789# 注释后面需要一个空格print("单行注释")print("单行注释") # 单行注释和代码之间至少要有两个空格"""多行注释"""print("这是多行注释") 2.算数运算符乘法的使用,用*可以拼接字符串 12345In [1]: "A" * 30Out[1]: 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'In [2]: 1 * 30Out[2]: 30 优先级 幂 >* > / > % > // >+ > - 3.变量3.1.变量的类型数字型 整数 int 浮点 float(计算要小心) 布尔 布尔值可以用 and、or 和 not 运算 复数(用于科学技术的) 非数字型 String(字符串) List(列表) Tuple(元组) Dictionary(字典) 3.2.type函数12345678# 整数print(type(1))# 浮点数print(type(1.5))# 字符串print(type("hello world"))# 空值print(type(None)) 3.3.不同类型的变量之间的计算1234567891011121314151617181920212223242526272829In [10]: age =13In [11]: sex =TrueIn [12]: height = 180.1In [13]: age + sexOut[13]: 14In [14]: age + heightOut[14]: 193.1In [15]: age + heightOut[15]: 193.1# 字符串的拼接In [17]: first_name ="东尼"In [18]: last_name="安"In [19]: last_name+first_nameOut[19]: '安东尼'# 字符串不能和数字相加In [20]: last_name + 10---------------------------------------------------------------------------TypeError Traceback (most recent call last)<ipython-input-20-45feb354f2d0> in <module>----> 1 last_name + 10TypeError: can only concatenate str (not "int") to str 3.4.变量的输入12345678910# 可以不需要参数In [24]: input()123# 加参数,做提示In [27]: pwd = input("输入数字")输入数字123In [28]: pwd + "456"Out[28]: '123456' 3.5.数据类型转换 方法名 含义 str() 转成字符串 float() 转成浮点 int() 转成int 123456789101112131415161718192021222324# 数字和字符串相互转In [3]: age = 23In [4]: type(age)Out[4]: intIn [5]: age_str = str(age)In [6]: type(age_str)Out[6]: strIn [7]: type(int(age_str))Out[7]: int# 浮点转数值,缺失精度In [9]: pi = "3.14"In [10]: piOut[10]: '3.14'In [13]: type(float(pi))Out[13]: floatIn [15]: int(float(pi))Out[15]: 3 3.6.变量的格式化输出 符号 描述 %c 格式化字符(输出数值对应的ASCII码) %s 格式化字符串 %d 格式化整数(%06d,不足的补0) %x 格式化十六进制数(小写) %X 格式化十六进制数(大写) %o 格式化八进制数 %f 格式化浮点数字,可以指定小数点精度(%.2f) %% 输出%号 12345678910"""'我的名字是:anthony,请多多关照我的学号是:000001,请多多关照单价是:1.00,购买了2.00斤,总价是:2.000数据的比例是:20%"""print("我的名字是:%s,请多多关照" % "anthony")print("我的学号是:%06d,请多多关照" % 1)print("单价是:%.2f,购买了%.2f斤,总价是:%.3f" % (1,2,2))print("数据的比例是:%02d%%" % 20) 3.7.变量的命名 字母,和下划线和数字组成 不能以数字开始 不能与关键字重名 其余的符号都不行 区分大小写 =左右都要添加空格 两个单词之间用_ 4.条件控制4.1.if12345678910age = 15if age >= 18: print("成年了") print("在一个缩进的,是一个代码块")elif age<=18: print("没有成年")else: print("输入错误")print("结束了") 4.2.逻辑运算符123print(True and False)print(True or False)print(not True) 4.3.随机数1234In [17]: import randomIn [18]: random.randint(12,20)Out[18]: 12 4.3.while1234i = 0while i < 5: print("....") i += 1 4.4.continue 和 break如果是嵌套循环,用这个两个关键字,也只是结束当前的循环,不会影响外层的循环 5.函数5.1.函数注释1234# 这个也是可以注释的def test(): """这个也是可以注释的""" print("打印乘法表") 5.2.函数调用index.py 12def chengfabiao(): print("打印乘法表") test.py 12import indexindex.chengfabiao() 5.3.局部方法修改全局变量12345678910num =10def mo(): # 声明num是全部变量 global num num=100 print(num)mo()print(num) 5.4.多个返回值12345678def change(x,y): return y,xx =1y=2x,y = change(1,2)print(x)print(y) 5.5.缺省函数1234567891011121314151617def measure(age,gender=1): print(age) print(gender)def measure2(age,gender=1,name="anthony"): print(age) print(gender) print(name)measure(1)measure(1,2)# 有多个缺省的时候,需要指定参数名称measure2(1,name="anthonyyang",gender=2)# 拆包measure(1,*(2,3,4),**{"name":"anthony"}) 5.6.多值参数习惯存元祖的使用用*args,存字典的时候用**args 5.7.函数的参数-默认参数1234567891011121314def my_print(name,age): print(name,age)def my_print2(name,age=13): print(name,age)# 有默认值的形参,不能放在前面,会报错# def my_print3(age=13,name):# print(name,age)my_print("anthony",12)my_print2("anthony2",123)my_print2("anthony2") 5.8.函数的参数-关键字参数12345678910111213141516def my_print(name,address): print(name,address)my_print("anthony","广东")my_print("广东","anthony")my_print(name="anthony",address="广东")my_print(address="广东",name="anthony",)# ----------------**kw是关键字参数,且 hobby 就是一个 dict (字典)-------------def my_print2(name,address,**kw): if 'age' in kw: print("age=",kw["age"])my_print2("anthony","广东",age=123)my_print2("anthony","广东",kw={"age":123}) 5.8.函数的参数-只接受关键字参数123456789def my_print(name,*,address): print(name,address)# 报错# my_print("anthony","广东")# my_print("广东","anthony")my_print("anthony",address="广东")my_print(address="广东",name="anthony") 5.8.函数的参数-不定长参数hobby是可变参数,且 hobby 其实就是一个 tuple (元祖) 12345678910def print_user_info( name , age , sex = '男' , * hobby): # 打印用户信息 print('昵称:{}'.format(name) , end = ' ') print('年龄:{}'.format(age) , end = ' ') print('性别:{}'.format(sex) ,end = ' ' ) print('爱好:{}'.format(hobby)) return;# 调用 print_user_info 函数print_user_info( '两点水' ,18 , '女', '打篮球','打羽毛球','跑步 6.容器6.1.列表虽然列表可以存储不同类型的数据,但是在开发中,存储的都是相同类型数据,因为要迭代 123456789101112131415161718192021222324mylist=["a","b","c"]print(mylist)# 通过索引,访问列表中的值print(mylist[1])# 通过方括号的形式来截取列表中的数据,访问列表中的值# 就是从第 0 个开始取,取到第 2 个,但是不包含第 2 个print(mylist[0:2])# 通过索引对列表的数据项进行修改或更新mylist[1] = "bb"print(mylist)# 使用 append() 方法来添加列表项mylist.append("d")print(mylist)# 使用 del 语句来删除列表的的元素del mylist[3]print(mylist)# 列表长度print(len(mylist)) 6.2.元祖元祖用的是括号 与列表比较,元祖元素不能修改 123456789101112131415161718192021222324252627# 创建元祖方法1tuple1=('两点水','twowter','liangdianshui',123,456)tuple2='两点水','twowter','liangdianshui',123,456# 创建元祖方法2tuple3 = ()# 创建元祖方法3tuple4 = (123,)print(tuple1)print(tuple2)print(tuple3)print(tuple4)# 访问元祖print(tuple1[1])# 修改元祖的值mylist=[1,2,3]tuple5=("ddd",mylist)print(tuple5)mylist[1]=43print(tuple5)# 删除元祖,tuple 元组中的元素值是不允许删除的,但我们可以使用 del 语句来删除整个元组del tuple1 元祖和列表相互转换 123456789101112131415# 声明元祖In [54]: num_list = (1,2,3,4)In [55]: type(num_list)Out[55]: tuple# 元祖转成列表In [56]: my_list = list(num_list)# 修改值In [57]: my_list[0]=5# 再转成元祖In [58]: print(tuple(my_list))(5, 2, 3, 4) 6.2.字典列表是有序的 字典是无序的 12345678910111213141516171819202122232425262728names={"name":"xiaoming","age":"23"}# 取值print(names["name"])# 新增和修改(key存在,就是新增,不存在就是修改)names["address"] ="feilvb"names["name"] ="anthony123"print(names)# 删除names.pop("name")print(names)# 统计键值对的数量print(len(names))# 合并键值对,如果合并的时候有相同的key,那个value就是更新值temp = {"a":"b"}names.update(temp)print(names)# 遍历字典for k in names: print("遍历",k,names[k])# 清空字典names.clear() 6.3.setset可以理解为只有key的字典 12345678# 创建setset1 = set([1,2,3])# 添加元素set1.add(200)# 删除元素set1.remove(1) 6.4.字符串123456789101112131415str ="hello hello"print("字符串长度",len(str))print("字符串出现次数",str.count("llo"))print("取索引",str.index("llo"))print("取值",str[1])# 换行符,都是空白字符print("判断空白字符",str.isspace())print("是否以指定字符串开始",str.startswith("hello"))print("是否以指定字符串结束",str.endswith("LLO"))print("查找指定字符串",str.find("llo"))print("替换字符串",str.replace("hello","HELLO"))print(str[0:9:2])# bytes转字符串print(b"abcde".decode("utf-8")) 字符串前加 b:b 前缀代表的就是bytes 字符串前加 r:r/R:非转义的原始字符串 7.公共方法 内置函数: len max 只能比较字典的key min 只能比较字典的key 2.字符串,列表,元祖都可以切片 3.查看地址值 1id(str) 面向对象类名需要大驼峰命名法 1.基本语法1.1.创建对象1234567891011class Cat: def eat(self): print("小猫爱吃鱼") def drink(self): print("小猫爱喝水")tom = Cat()tom.eat()tom.drink() 1.2.对象内置方法(魔术方法)123456789101112131415161718192021222324252627class Cat: # 构造方法 def __init__(self,name): print("初始化方法") self.name=name # 成员方法 def eat(self): print(self.name+"爱吃鱼") # 成员方法 def drink(self): print(self.name+"爱喝水") # 魔术方法 def __del__(self): print("销毁方法") # 魔术方法 def __str__(self): return "重写tostring"tom = Cat("Tom")tom.eat()tom.drink()print(tom) 1.3.私有属性和方法1234567891011121314151617181920212223242526272829303132333435class Cat: # 构造方法 def __init__(self,name): print("初始化方法") self.name=name self.__age =18 def eat(self): print(self.name+"爱吃鱼") def drink(self): print(self.name+"爱喝水") def say_age(self): print("年纪是:"+str(self.__age)) # 调用私有方法 self.__private_method() def __private_method(self): print("私有方法") def __del__(self): print("销毁方法") def __str__(self): return "重写tostring"tom = Cat("Tom")tom.eat()tom.drink()tom.say_age()print(tom)# 这种访问方式,也是可以访问到私有的属性和方法的print(tom._Cat__age) 1.4.继承和重写123456789101112131415161718192021222324252627282930class Animal: def __init__(self): self.name1 =100 self.__num2 = 200 def eat(self): print("动物吃") def run(self): print("动物跑") # 子类不允许调用私有方法 def __test(self): print("父类可以访问到私有属性和私有方法")class Dog(Animal): def run(self): print("子类打印,开始调用父类方法") super().run() print("调用完父类方法")# animal = Animal()# animal.eat()# animal.run()dog = Dog()dog.eat()dog.run() 1.5.多继承尽量避免使用多继承,如果继承了两个累,两个类有相同的方法和属性,容易混淆 123456789101112131415161718192021222324252627282930class Animal: def __init__(self): self.name1 = 100 self.__num2 = 200 def eat(self): print("动物吃") def run(self): print("动物跑") # 子类不允许调用私有方法 def __test(self): print("父类可以访问到私有属性和私有方法")class Zoo: def eat(self): print("动物园吃饭")class Dog(Animal, Zoo): def run(self): print("子类打印,开始调用父类方法") super().run() print("调用完父类方法")dog = Dog()dog.eat() 1.6.多态12345678910111213141516171819202122232425262728class Dog(object): def __init__(self,name): self.name = name def game(self): print("蹦蹦跳跳",self.name)class Xiaotianquan(Dog): def game(self): print("哮天犬",self.name)class Person(object): def __init__(self,name): self.name = name def game_with_dog(self,dog): print("人和狗玩耍",self.name,dog.name) dog.game()# dog = Dog("旺财")dog = Xiaotianquan("旺财")xiaoming = Person("xiaoming")xiaoming.game_with_dog(dog) 1.7.类属性和类方法和静态方法类属性 相当于静态变量 123456class Dog(object): # 类属性 age = 12 def __init__(self,name): self.name = name 类方法 123456789101112class Dog(object): # 类属性 age = 12 # 类方法 @classmethod def show_age(cls): print("静态方法",cls.age)dog = Dog()Dog.show_age() 静态方法,在不用方法类属性和静态属性的时候,可以定义成静态方法 1234567891011121314151617181920class Dog(object): # 类属性 age = 12 # 类方法 @classmethod def show_age(cls): print("类方法",cls.age) @staticmethod def static_method(): print("静态方法")dog = Dog()# 调用类方法Dog.show_age()# 调用静态方法Dog.static_method() 2.异常2.1.异常的完整语法1234567891011try: num = int(input("输入一个整数:")) 10 / numexcept ZeroDivisionError: print("请不要输入数字0")except Exception as result: print("未知错误 %s" % result)else: print("没有异常才会执行的代码")finally: print("无论是否有异常,都会异常的代码") 2.2.主动抛异常123456789101112def check(name): if(name == "anthony"): return "是安东尼" else: # 主动抛异常 raise Exception("不是安东尼")# 捕获异常try: print(check("anthony2"))except Exception as result: print(result) 3.模块导入的语法如下:[from 模块名】import [模块 1类1变量1函数1x[as别名]常用的组合形式如: import 模块名 from 模块名 import 类、变量、方法等 from 模块名 import * import 模块名 as 别名 from 模块名import 功能名 as 别名 3.1.导入模块不推荐使用, 12import pkg1import pkg2 3.2.简单的使用my_module.py 1234567title = "模块2"def say_hello(): print("i am module : %s " % title)class Cat: pass index.py 1234567import my_module# use module methodmy_module.say_hello()dog = my_module.Cat()print(dog) 3.3.导入的时候也可以起别名别名要使用大驼峰命名 1import my_module as MyModule 3.4.from…import导入一部分工具 使用的时候,就不需要写那个模块名了,直接使用 1234from my_module import say_hellofrom my_module import Catsay_hello()cat = Cat() 3.5.作为模块的正常写法123456def main(): pass# 有了这个之后,被别的模块调用的时候if __name__ = "__main__" main 3.6.包包 包含多个模块 创建一个新的文件夹,在文件夹里面创建__init__.py 123# . 是相对路径名from . import send_messagefrom . import receive_message 在文件夹里面创建两个模块 receive_message.py 12def receive(): print("接受信息") send_message.py 12def send(text): print("发送 %s" % text) 调用模块 1234import hm_messagehm_message.send_message.send('hello')hm_message.receive_message.receive() 3.7.发布模块1.创建setup.py 1234567from distutils.core import setupsetup(name="hm_message", version="1.0", description="push", py_modules=["hm_message.send_message", "hm_message.receive_message"]) 2.命令 12python setup.py buildpython setup.py sdist 3.8.安装模块123# 手动安装模块tar xxx.tar.gzpython setup.py install pymssql访问https://www.lfd.uci.edu/~gohlke/pythonlibs/#pymssql 下载[pymssql‑2.1.4‑cp38‑cp38‑win32.whl] 网络编程0.socket的历史套接字起源于 20 世纪 70 年代加利福尼亚大学伯克利分校版本的 Unix,即人们所说的 BSD Unix。 因此,有时人们也把套接字称为“伯克利套接字”或“BSD 套接字”。 一开始,套接字被设计用在同 一台主机上多个应用程序之间的通讯。这也被称进程间通讯或IPC。 套接字有两种(或者称为有两个种族),分别是基于文件型的和基于网络型的。 基于文件类型的套接字家族 - 套接字家族的名字:AF_UNIX unix一切皆文件,基于文件的套接字调用的就是底层的文件系统来取数据,两个套接字进程运行在同一机器,可以通过访问同一个文件系统间接完成通信 基于网络类型的套接字家族 - 套接字家族的名字:AF_INET (还有AF_INET6被用于ipv6, 还有一些其他的地址家族,不过,他们要么是只用于某个平台,要么就是已经被废弃,或者是很少被使用,或者是根本没有实现,所有地址家族中,AF_INET是使用最广泛的一个,python支持很多种地址家族,但是由于我们只关心网络编程,所以大部分时候我么只使用AF_INET) 套接字把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。 所以,我们无需深入理解tcp/udp协议,socket已经为我们封装好了,我们只需要遵循socket的规定去编程,写出的程序自然就是遵循tcp/udp标准的 1.udp发送端 123456789101112131415161718192021import socketdef main(): # 创建一个udp套接字 udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) while True: send_data = str = input("输入要发送的数据") if send_data == "exit": break # udp_socket.sendto(b"这是消息",("192.169.0.1",8000)) udp_socket.sendto(send_data.encode("utf-8"),("127.0.0.1",7788)) # 关闭套接字 udp_socket.close()if __name__ == '__main__': main() 接受者 1234567891011121314151617181920import socketdef main(): # 创建一个udp套接字 udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 绑定本地相关信息 local_addr = ("",7788) udp_socket.bind(local_addr) while True: # 等待接收对方发送的数据 recv_data = udp_socket.recvfrom(1024) print(recv_data[0].decode("gbk")) # 关闭套接字 udp_socket.close()if __name__ == '__main__': main() 2.tcp3.socket使用socket访问redis 12345678910111213141516171819import sockethost = '10.0.2.110'port = 6379buf_size = 1conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM)conn.connect((host, port))cmd = 'SELECT 2\n'.encode('utf-8')r = conn.sendall(cmd)cmd = 'PING\n'.encode('utf-8')conn.sendall(cmd)while True: res = conn.recv(buf_size) print(res) if not res: breakconn.close() 服务端套接字函数 123456789101112131415161718192021222324252627282930313233s.bind() #绑定(主机,端口号)到套接字s.listen() #开始TCP监听s.accept() #被动接受TCP客户的连接,(阻塞式)等待连接的到来**客户端套接字函数**s.connect() #主动初始化TCP服务器连接s.connect_ex() #connect()函数的扩展版本,出错时返回出错码,而不是抛出异常**公共用途的套接字函数(客户端和服务端都能使用)**s.recv() #接收TCP数据s.send() #发送TCP数据(send在待发送数据量大于己端缓存区剩余空间时,数据丢失,不会发完)s.sendall() #发送完整的TCP数据(本质就是循环调用send,sendall在待发送数据量大于己端缓存区剩余空间时,数据不丢失,循环调用send直到发完)s.recvfrom() #接收UDP数据s.sendto() #发送UDP数据s.getpeername() #连接到当前套接字的远端的地址s.getsockname() #当前套接字的地址s.getsockopt() #返回指定套接字的参数s.setsockopt() #设置指定套接字的参数s.close() #关闭套接字 Requestpost请求参数123456789101112131415161718# 表单提交import requestsdata = { "name":"anthony", "age":"12"}requests.post(url=url,data=data)# json提交import requestsjson_data = { "name":"anthony", "age":"12"}requests.post(url=url,json=json_data) cookie操作1234cookies={ 'auther':'11223'}request.post(url=url,data=data,cookies=cookies) headers1234headers ={ 'auth':'123'}request.post(url=url,data=data,cookies=cookies,headers=headers) 请求超时12# 如果超过2s就超时报错requests.post(url=url,timeout=2) 鉴权有些页面,需要,比如spring secuity的页面 1requests.post(url=url,timeout=2,auth=('anthony','123456')) 编码12# 先编码 再解码r.text.encode('utf-8').decode(unicode_escape) 下载流21234567url = "http://wx4.sinaimg.cn/large/d030806aly1fq1vn8j0ajj21ho28bduy.jpg"rsp = requests.get(url, stream=True)with open('1.jpg', 'wb') as f: # 边下载边存硬盘, chunk_size 可以自由调整为可以更好地适合您的用例的数字 for i in rsp.iter_content(chunk_size=1024): f.write(i) requests.get(url)默认是下载在内存中的,下载完成才存到硬盘上 Response.iter_content来边下载边存硬盘 Flask路由入门12345678910from flask import Flaskapp = Flask(__name__)@app.route('/login')def hello_world(): return 'Hello World!'if __name__ == '__main__': app.run() URL上动态参数1234567891011121314from flask import Flaskapp = Flask(__name__)@app.route("/")def hello_world(): return "hello world"@app.route("/<int:age>")def play_game(age): return "hello world"+str(age)if __name__ == '__main__': app.run(debug=True) 自定义转换器1234567891011121314151617181920212223242526from flask import Flaskfrom werkzeug.routing import BaseConverterapp = Flask(__name__)# 自定义,并且继承BaseConverterclass MyConverter(BaseConverter): def __init__(self, map, regex): # map 就是app.url_map 也就是请求路径 # regex,就是d{3}, 也就是自定义的规则 super(MyConverter, self).__init__(map) self.regex = regexapp.url_map.converters["haha"] = [email protected]('/<haha("\d{3}"):age>')def play_game2(age): return "自定义转换器,接收3位" + str(age)@app.route('/<haha("\d{4}"):age>')def play_game3(age): return "自定义转换器,接收4位" + str(age)if __name__ == '__main__': app.run(debug=True) 给路由添加请求方式12345678910from flask import Flaskapp = Flask(__name__)@app.route('/',methods=['POST'])def play_game2(age): return "自定义转换器,接收3位" + str(age)if __name__ == '__main__': app.run(debug=True) 返回响应体123456789101112131415161718192021from flask import Flaskapp = Flask(__name__)@app.route('/')def method1(): """直接返回字符串信息""" return "返回字符串信息"@app.route('/2')def method2(): """直接返回字符串信息""" return "返回字符串信息",[email protected]('/3')def method3(): """直接返回字符串信息""" return {"name":"anthony"},200,{"Content-Type":"application/json","token":"123456"}if __name__ == '__main__': app.run(debug=True) 重定向12345678910from flask import Flask,redirectapp = Flask(__name__)@app.route('/')def method1(): return redirect("http://baidu.com")if __name__ == '__main__': app.run(debug=True) 跳转页面123456789101112131415161718192021222324from flask import Flask, render_template, request,redirectapp = Flask(__name__)@app.route('/login', methods=['GET', 'POST'])def hello_world(): print("请求来了") # 获取post传过来的值 user = request.form.get("user") pwd = request.form.get("pwd") if user == "anthony" and pwd == "123456": # return render_template("login.html", **{"msg": "登录成功"}) return redirect("/index") else: return render_template("login.html", **{"msg": "用户名或者密码错误"})@app.route("/index")def index(): return "欢迎登录"if __name__ == '__main__': app.run() 异常捕获123456789101112131415161718from flask import Flask,abortapp = Flask(__name__)@app.route('/')def method1(): """abort 异常抛出""" abort(404) return "hello world""""捕获异常"""@app.errorhandler(404)def method1(e): print(e) return "页面找不到"if __name__ == '__main__': app.run(debug=True) 获取请求参数Form12345678910111213141516171819from flask import Flask, render_template, requestapp = Flask(__name__)@app.route('/login', methods=['GET', 'POST'])def hello_world(): print("请求来了") # 获取post传过来的值 user = request.form.get("user") pwd = request.form.get("pwd") if user == "anthony" and pwd == "123456": return render_template("login.html", **{"msg": "登录成功"}) else: return render_template("login.html", **{"msg": "用户名或者密码错误"})if __name__ == '__main__': app.run() 获取请求参数Body123456789101112from flask import Flask, requestapp = Flask(__name__)@app.route('/callback')def hello_world(): data = request.get_data() print(data) return 'Hello, World!'if __name__ == "__main__": app.run(debug=True, port=10086) 环境变量/配置文件12345678910111213141516171819202122from flask import Flask,redirect,requestapp = Flask(__name__)# 1.从配置类中加载class MyConfig(object): DEBUG =True# app.config.from_object(MyConfig)# 2.从配置文件中加载# app.config.from_pyfile("Config.ini")# 在项目的根目录创建个Config.ini文件# 3.从环境变量# app.config.from_envvar("")@app.route('/')def method1(): passif __name__ == '__main__': app.run() 钩子,类似拦截器12345678910111213141516171819202122232425262728293031from flask import Flask,redirect,requestapp = Flask(__name__)@app.before_first_requestdef before_first_request(): """只请求一次""" print("before_first_request")@app.before_requestdef before_request(): """每次都会请求""" print("before_request")@app.after_requestdef after_request(resp): """比如做json统一的返回格式""" print("after_request") return [email protected]_requestdef teardown_request(e): """最后会请求到这里,适合做异常信息统计""" print("teardown_request")@app.route('/')def method1(): return "hello world"if __name__ == '__main__': app.run(debug=True) 视图内容和模板123456789101112131415161718192021from flask import Flask, make_response, requestapp = Flask(__name__)# 设置[email protected]("/set_cookie")def set_cookie(): response = make_response("set cookie") response.set_cookie("computer","macbook pro") response.set_cookie("age","13 pro",1000) return [email protected]("/get_cookie")def get_cookie(): name = request.cookies.get("computer") age = request.cookies.get("age") return "name:%s,age:%s"%(name,age)if __name__ == '__main__': app.run(debug=True) Session1234567891011121314151617181920from flask import Flask, make_response, request, sessionapp = Flask(__name__)app.config["SECRET_KEY"]="123456"# 设置[email protected]("/set_session/<path:name>")def set_session(name): session["name"] =name return "set session"@app.route("/get_session")def get_session(): value = session.get("name") return "value:%s"%(value)if __name__ == '__main__': app.run(debug=True) orm入门12pip install flask_sqlalchemypip install pymysql 1234567891011121314151617181920212223242526272829303132333435363738from flask import Flask,render_templatefrom flask_sqlalchemy import SQLAlchemyapp = Flask(__name__)# 设置数据库的配置信息app.config["SQLALCHEMY_DATABASE_URI"] = "mysql+pymysql://Rong:[email protected]:3306/data36"app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False# 创建sqlAlchemy对象,关联appdb = SQLAlchemy(app)# 创建模型类class Role(db.Model): __tablename__ = "roles" id =db.Column(db.Integer,primary_key=True) name = db.Column(db.String(32))class Users(db.Model): __tablename__ = "users" id =db.Column(db.Integer,primary_key=True) name = db.Column(db.String(32)) # 建立外键 role_id =db.Column(db.Integer,db.ForeignKey(Role.id))@app.route("/")def demo(): return "response"if __name__ == '__main__': # 创建数据库的表 db.drop_all() db.create_all() app.run(debug=True) 蓝图基本使用demo01.py 12345678910from flask import Flask, Blueprintfrom demo02 import blueapp = Flask(__name__)# 讲蓝图注册到app中app.register_blueprint(blue)if __name__ == '__main__': app.run(debug=True) demo02.py 123456789101112from flask import Blueprint# 创建蓝图对象blue = Blueprint("my_blue", __name__)@blue.route("/")def demo(): return "response"@blue.route("/2")def demo2(): return "response2" 蓝图包的使用目录结构:|—demo.py|—-|user||—-|user|–__init__.py|—-|user|–views.py demo.py 123456789101112from flask import Flaskfrom user import user_blueapp = Flask(__name__)# 讲蓝图注册到app中app.register_blueprint(user_blue)if __name__ == '__main__': print(app.url_map) app.run(debug=True) init.py 123456from flask import Blueprint# 创建蓝图对象user_blue = Blueprint("user", __name__)from user import views views.py 123456789from user import user_blue@user_blue.route("/")def demo(): return "response"@user_blue.route("/2")def demo2(): return "response2" Django命令12345678# 安装djangopip install django# 生成数据库 迁移文件python manage.py makemigrations# 执行迁移生成表python manage.py migrate PyCharm和Django-admin创建的项目不一样12345678910111213# 项目创建django-admin startproject 项目名# 结构项目名|----|manage.py [项目管理,启动项目,创建app,数据管理]|----|项目名同名文件夹|----|----|__init__.py|----|----|asgi.py [项目配置]|----|----|settings.py [路由]|----|----|urls.py [接收网路请求,异步]|----|----|wsgi.py [接收网路请求,同步] 12345678910# 用PyCharm创建的目录结构项目名|----|manage.py |----|templates文件夹|----|项目名同名文件夹|----|----|__init__.py|----|----|asgi.py|----|----|settings.py|----|----|urls.py|----|----|wsgi.py PyCharm创建的根目录下有一个templates目录Django-admin 是没有的,要在app下的目录创建templates settings.py 1234567891011121314151617181920212223242526272829303132333435# PyCharm创建的TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [BASE_DIR / 'templates'] , 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], }, },]# 命令行创建的TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [] , 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], }, },] 创建应用123|项目||----|app,后台系统[独立的表结构,模板,不相互影响]|----|app,前台系统[独立的表结构,模板,不相互影响] 123456789101112131415161718192021# 创建apppython manage.py startapp app名字djangoProject├── app01 [刚创建的app]│ ├── __init__.py│ ├── admin.py [django默认admin后台,不用动]│ ├── apps.py [app启动类,不用动]│ ├── migrations [数据库的迁移的,数据库变更记录]│ │ └── __init__.py│ ├── models.py [重要,对数据库进行操作]│ ├── tests.py [单元测试]│ └── views.py [重要,函数]├── djangoProject│ ├── __init__.py│ ├── asgi.py│ ├── settings.py│ ├── urls.py│ └── wsgi.py├── manage.py└── templates 注册APP项目名→项目名同名文件夹→settings.py 12345678910INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', # 添加下面一行的配置,下面这个是怎么来的呢?看下面 'app01.apps.App01Config' ] 项目名→APP文件夹→apps.py 12345from django.apps import AppConfigclass App01Config(AppConfig): default_auto_field = 'django.db.models.BigAutoField' name = 'app01' 编写URL和视图函数对应关系编辑urls.py 在app文件夹下的views.py 1234567from django.http import HttpResponsefrom django.shortcuts import render# Create your views here.# request 必须要的def index(request): return HttpResponse("欢迎使用") 启动12# 命令行启动python ./manage.py runserver templates模板 静态资源12345678910111213141516171819202122232425djangoProject├── app01│ ├── __init__.py│ ├── admin.py│ ├── apps.py│ ├── migrations│ │ └── __init__.py│ ├── models.py│ ├── static [手动创建的]│ │ ├── css [手动创建的] │ │ ├── img [手动创建的]│ │ │ └── 1.png│ │ └── js│ ├── tests.py│ └── views.py├── db.sqlite3├── djangoProject│ ├── __init__.py│ ├── asgi.py│ ├── settings.py│ ├── urls.py│ └── wsgi.py├── manage.py└── templates └── user_list.html 12345678910111213141516171819{% load static %}<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>用户列表</title></head><body><h1>用户列表</h1>加载静态资源写法一:<img src="/static/img/1.png">写法二:<img src="{% static 'img/1.png' %}"></body></html> 数据库主意:app需要先注册 12345python ./manage.py makemigrationspython ./manage.py migrate# Pycharm->tools->Run manager.py Task 开启,可以替代python manager.py 这个前缀 1pip3 install pymysql 12345678910111213141516171819from pymysql import connectcon = connect(host="43.240.30.52", port=3306, user="yanganli_github", password="yanganli_github", database="yanganli_github", charset="utf8")cursor = con.cursor()# 查询出条数print(cursor.execute("select * from post"))for one in cursor.fetchall(): print(one)cursor.close()con.close() 自动化测试安装和命令1234567891011121314# 安装pytestpip install pytest# 安装python-html包,生成html报告pip install pytest-html# 生成allure报告pip install allure-pytest# -s 打印printpytest -s test_auth.py# 只测试这一个文件pytest -q -s test_auth.py Pycharm识别pytest 规范测试文件已test_开头 测试类已Test开头,并且不能带有init方法 测试函数以test_开头 断言基本使用assert 生成报告123456789101112# 生成html'--html=../report/report.html'# 生成xml格式的报告'--junitxml=./report/report.xml'# 生成allure报告'--alluredir','../report/reportallure'# 生成allure报告,在report的文件夹执行pytest.main(['../test_case/','--alluredir','../report/allure_raw/'])./allure serve /myitem/autotest/report/allure_raw Locust1234567pip install locust# centos需要添加环境变量# 如果locust在哪个目录,如果python安装在这个目录:/usr/local/bin/python3ln -s /usr/local/bin/python3/bin/locust /usr/bin/locustlocust --version 快速入门新建一个redis.py 12345678910111213141516class QuickstartUser(HttpUser): wait_time = between(1, 10) @task def index_page(self): self.client.get("/hello") self.client.get("/world") @task(3) def view_item(self): for item_id in range(10): self.client.get(f"/item?id={item_id}", name="/item") time.sleep(1) def on_start(self): self.client.post("/login", json={"username": "foo", "password": "bar"}) 在文件目录,执行 123locust -f redis.py# 启动成功后,访问 http://localhost:8089 写一个Locust文件Locust文件是一个普通的python文件,唯一的要求就是至少有一个类继承的User类 User一个User类标识一个用户,Locust会给每个用户模拟一个User实例,公共的属性通常在User类里被定义 wait_time 属性用户的wwait_time方法通常作用于执行的任务简直等待多长时间 这里有三个内建的等待时间的函数 constant 一段固定的时间 between 最小和最大数之间的随机时间 constant_pacing 确保任务运行一次最多不超过x秒的适应时间 举个例子,让每个用户在每个任务执行之间等待0.5s到10s 12345678from locust import User, task, betweenclass MyUser(User): @task def my_task(self): print("executing my_task") wait_time = between(0.5, 10) 也可以直接在你的类里声明wait_time方法,举个例子,下面的User类,会睡眠1s,2s… 12345678class MyUser(User): last_wait_time = 0 def wait_time(self): self.last_wait_time += 1 return self.last_wait_time ... weight 属性如果文件中存在多个用户类,并且在命令行中没有指定用户类,则Locust将生成每个用户类的相同数量。您还可以通过将用户类作为命令行参数传递来指定从同一个locustfile中使用哪些用户类 1locust -f locust_file.py WebUser MobileUser 如果希望模拟某种类型的更多用户,可以在这些类上设置权重属性,比方说,web用户是移动用户的三倍多 1234567class WebUser(User): weight = 3 ...class MobileUser(User): weight = 1 ... host 属性host属性是host的URL前缀,通常,当locust启动的时候,在locust的web界面或者在命令上中使用--hosts选项中使用 如果一个类声明了声明的host属性,它将在没有使用--host命令行或web请求仲使用 tasks属性用户类可以使用@task装饰器,将任务声明为方法,但是也可以使用下面详细描述的tasks属性来指定任务。 environment 属性对用户正在其中运行的环境的引用,使用它影响这环境,或者是运行着在它的容器中,比如去停止运行从任务方法中 1self.environment.runner.quit() 如果运行的是独立的locust实例,它将停止全部,如果是运行在工作节点,它将停止节点 on_start 和 on_stop 方法用户 或者是任务集合可以声明``on_start方法或者on_stop方法,用户将调用它自己的on_start方法在它将要开始运行的时候,on_stop方法,将在停止运行的时候调用,比如TaskSet,on_start方法被调用在模拟的用户开始执行任务,on_stop方法在停止模拟的用户执行任务的时候调用(或者被interrupt()`方法调用,或者是用被用杀掉) Tasks当启动负载测试,一个用户实例将被创建, 下载mp4 12345678910111213141516171819202122232425262728293031323334353637383940414243import osimport timeimport requestsfrom tqdm import tqdm # 进度条模块def down_from_url(url, dst): # 设置stream=True参数读取大文件 response = requests.get(url, stream=True) # 通过header的content-length属性可以获取文件的总容量 file_size = int(response.headers['content-length']) if os.path.exists(dst): # 获取本地已经下载的部分文件的容量,方便继续下载,如果不存在就从头开始下载。 first_byte = os.path.getsize(dst) else: first_byte = 0 # 如果大于或者等于则表示已经下载完成,否则继续 if first_byte >= file_size: return file_size header = {"Range": f"bytes={first_byte}-{file_size}"} pbar = tqdm(total=file_size, initial=first_byte, unit='B', unit_scale=True, desc=dst) req = requests.get(url, headers=header, stream=True) with open(dst, 'ab') as f: # 每次读取一个1024个字节 for chunk in req.iter_content(chunk_size=1024): if chunk: f.write(chunk) pbar.update(1024) pbar.close() return file_sizeif __name__ == '__main__': url = input("请输入.mp4格式的视频链接地址,按回车键确认") # 根据时间戳生成文件名 down_from_url(url, str(time.time()) + ".mp4") SSH下载服务器文件 12345678910111213141516171819202122232425262728293031323334353637383940414243444546import paramikoclass LinuxFile: def __init__(self, ip, port, username, password): try: self.ip = ip self.port = port self.username = username self.password = password self.transport = paramiko.Transport((str(self.ip), int(self.port))) self.transport.connect(username=self.username, password=self.password) self.sftp = paramiko.SFTPClient.from_transport(self.transport) except Exception as e: raise e def up_file(self, localhost_file, server_file): """ 将本地文件上传至服务器 :param localhost_file: 本地文件路径 :param server_file: 服务器保存路径 :return: """ self.sftp.put(localhost_file, server_file) def down_file(self, localhost_file, server_file): """ 将服务器文件下载至本地 :param localhost_file: 本地文件路径 :param server_file: 服务器保存路径 :return: """ self.sftp.get(localhost_file, server_file) def close(self): """ 关闭服务器 :return: """ self.transport.close()if __name__ == '__main__': test = LinuxFile('47.242.218.75', '22', 'root', 'Qwer1234') # test.up_file('../2020-10-11_20-21-28.py', '/root/2020-10-11_20-21-28.py') test.down_file('/var/log/nginx/access.log','a.log')
Excel
行和列隐藏或显示行和列在电子表格中隐藏或取消隐藏列,以仅显示需要查看或打印的数据 📌 不影响计算 隐藏列 选择一个或多个列,然后按 Ctrl 选择不相邻的其他列。 右键单击选定的列,然后选择隐藏。📌 两列之间的双线表示隐藏了一个列。 ✅ 参考:微软官方文档 取消隐藏列 选择隐藏列的相邻左列和相邻右列。 右键单击选定的列,然后选择“取消隐藏”。 或双击隐藏列所存在的两列之间的双线。 自定义列的格式比如这里要设置列的格式是 年月日星期几 默认的日期和时间是没有符合要求的 自定义设置 📌 这样设置之后,比如在单元格里值设置的是 2021-01-02实际上显现的会是 2021年1月2日 星期几 ✅ 参考:微软官方文档
Kubernetes
主节点上操作定义主机名字 12345sudo vim /etc/hosts192.168.11.128 k8s-master192.168.11.129 k8s-node01192.168.11.130 k8s-node01 修改主机名 1234# 打印hostnamehostname# 修改hostname,需要重新连接,才能看到变化sduo hostnamectl set-hostname xxx 下载k8s需要的镜像 123456789101112# 打印所需要的镜像kubeadm config images list# docker pull 上面命令的镜像# 比如sudo docker pull registry.k8s.io/kube-apiserver:v1.28.3sudo docker pull registry.k8s.io/kube-controller-manager:v1.28.3sudo docker pull registry.k8s.io/kube-scheduler:v1.28.3sudo docker pull registry.k8s.io/kube-proxy:v1.28.3sudo docker pull registry.k8s.io/pause:3.9sudo docker pull registry.k8s.io/etcd:3.5.9-0sudo docker pull registry.k8s.io/coredns/coredns:v1.10.1 创建master节点 123456sudo kubeadm init --apiserver-advertise-address=192.168.110.128 --pod-network-cidr=172.16.0.0/16# 创建完之后会提示,并且执行mkdir -p $HOME/.kubesudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/configsudo chown $(id -u):$(id -g) $HOME/.kube/config 查看k8s状态 123$ kubectl get nodeNAME STATUS ROLES AGE VERSIONk8s-node01 NotReady control-plane 4m9s v1.28.2 使用网络插件 123curl https://docs.projectcalico.org/manifests/calico.yaml -Okubectl apply -f calico.yaml