• [技术干货] Java SE 学习哈希表后对String的讨论
    1.发现问题我们在学习String后,知道它常用的构造方法分别为以下三种:1.使用常量字符串直接构造,比如:String s1 = "hello";2.通过new String对象构造,比如:String s2 = new String("hello");3.使用字符数组构造,比如:char[] ch = {'h','e','l','l','o'};String s3 = new String(ch);不仅如此,我们还知道String的字符串是储存在String类中的一个数组中,并且这个数组是私有的,无法直接对它进行操作,这也就是String的不可变性的由来。知道了String是怎么保存字符串之后,难免会产生一个疑问:String作为Java中经常使用的引用类型,创建String对象时,它是直接通过构造方法创建的,还是有别的方法。请看下面的代码:public class Test {    public static void main(String[] args) {        String s1 = "hello";        String s2 = "hello";         String s3 = new String("hello");        String s4 = new String("hello");         System.out.println(s1 == s2);        System.out.println(s3 == s4);        System.out.println(s1 == s3);    }} //运行结果:truefalsefalse一键获取完整项目代码java发现:s1和s2引用的是同一个对象,而s3和s4引用的不是同一个对象。为什么会造成这种原因呢?这就不得不引入一个新的概念了——“池”。2.“池”是什么东西?“池”(Pool)的核心逻辑就是提前准备好一批常用资源,谁要用就直接拿,用完了还回来,避免反复创建和销毁,就像生活里的 “共享工具站”,它是编程中的一种常见的, 重要的提升效率的方式, 有各种各样的池,比如说: "内存池", "线程池", "数据库连接池" ……在Java程序中,类似于:1,2,3.14,"hello"等字面类型(指的是在代码中直接写出的、代表固定值的 “字面量”,它是数据的直接表示形式,无需计算或引用其他变量就能确定值)的常量经常频繁使用,为了使程序的运行速度更快、更节省内存,Java为8种基本数据类型和String类都提供了常量池。为了节省存储空间以及程序的运行效率,Java中引入了:Class文件常量池:每个.Java源文件编译后生成.Class文件中会保存当前类中的字面常量以及符号信息。运行时常量池:在.Class文件被加载时,.Class文件中的常量池被加载到内存中称为运行时常量池,每个类都有一份运行时常量池。字符串常量池:String 常量池是 JVM 内存中一块特殊的区域(在 JDK 7 及之后位于堆内存,之前位于方法区),专门用于存储字符串常量(即编译期确定的字符串,如直接写在代码中的"abc")。3.关于字符串常量池(StringTable)在Java的JVM中字符串常量池是StringTable类,这个类实际是一个固定大小的哈希表(HashTable),在不同JDK版本下字符串常量池的位置以及默认大小是不同的,比如说:JDK版本    字符串常量池位置    大小设置Java6    (方法区)永久代    固定大小:1009Java7    堆中    可设置,没有大小限制,默认大小:60013Java8    堆中    可设置,有范围限制,最小是10094.再次探讨创建String对象开头,我们说Java中String对象的创建使用的构造方法通常有三种,但是它们真的都是构造方法么,其实第一种方式,即使用常量字符串直接构造,这种方式严格意义上不能算做一种构造方法,因为构造方法的调用必须通过 new 关键字触发。现在我们结合字符串常量池来分析一下三种构造方法的底层实现。在开始前。再次明确“池”的核心逻辑:提前准备好一批常用资源,谁要用就直接拿,用完了还回来,避免反复创建和销毁。1.使用常量字符串直接构造当我们通过这种方式创建String对象时,比如说 String s1 = "abc",此时JVM会先去字符串常量池中查找是否有"abc"这个String对象,如果有,那么令s1直接指向这个已有的对象(不会创建新的String对象);如果没有,在常量池中创建String对象("abc"),再让s1引用它(仅创建一次),同时字符串常量池保存这个对象,方便之后复用。注:在字符串常量池中创建String对象并不依赖于String类的构造方法,它依赖的是JVM的底层操作——直接在常量池区域分配内存并初始化(底层是数组)。同时得益于String的不可变性,使得字符串常量池可以实现“多个引用共用一个对象”!一旦字符串对象存入常量池,其内容就永远不会被修改,因此复用是安全的。画图举例如下: 2.通过new创建String对象通过new创建String对象,这里主要讨论开头所说的常用方法的两种。2.1 通过new String对象构造当我们通过像:String s3 = new String("hello");这样的方式创建String对象时,创建的过程是这样的:JVM会先去字符串常量池查找是否存在“hello”这个String对象,如果没有就在字符串常量池中创建String对象("hello"),接着获取储存"hello"数组的引用;如果有,就直接获取"hello"数组的引用。接着按照构造方法String(String original)在堆中创建一个String对象,并且令这个String对象的value指向获取的"hello"的数组,这就实现了String对象的创建。需要注意的是:局部变量s3引用的是堆中新建的String对象,而这个String对象的value指向的是字符串常量池中"hello"String对象的value所指向数组。具体过程如图所示: 2.2 使用字符数组构造对于使用字符数组构造的方式来创建String对象的方式,它与上述两种方式有明显的区别,它的具体过程是这样的:调用String(char[] value)构造方法,直接在堆中创建新对象,这个新对象的value指向的是传入数组value的副本,这种方式不主动关联常量池。图示如下: 假设说我们现在通过字符数组创建String对象了,那么也没有办法把这个String对象添加到字符串常量池方便我们以后使用呢?其实是有的!就是intern方法,intern 是一个native方法(Native方法指:底层使用C++实现的,因此看不到其实现的源代码),该方法的作用是手动将创建的String对象添加到常量池中。像这样使用:public class Test {    public static void main(String[] args) {        char[] ch = {'a','b','c'};        String s1 = new String(ch);        s1.intern();         //将s1这个对象放入字符串常量池中        String s2 = "abc";   //创建s2时,发现字符串常量池中有"abc"这个对象,                             //那么s2直接指向这个对像                                                                                                                  System.out.println(s1 == s2);    }} //运行结果:true一键获取完整项目代码java根据运行结果,发现s1与s2引用的是同一个对象,说明s1放入字符串之后,创建s2时,在字符串常量池中查找到"abc"这个对象,那么直接令s2指向这个对象。5.总结字符串常量池遵循着“池”的核心逻辑:提前准备好一批常用资源,谁要用就直接拿,用完了还回来,避免反复创建和销毁。当我们创建一个String对象时,JVM会先去字符串常量池查找是否有创建String对象的字符串,如果有,那么直接令String引用指向字符串常量池中的对象或者令String引用的String对象的value字段指向字符串常量池中的对象的value所指向的数组即可;没有再考虑创建新的String对象。简单来说,字符串常量池主要实现了以下关键作用:字符串对象的复用提高代码的执行效率保障字符串不可变性的安全复用。跨类全局共享。字符串常量池是 JVM 级别的全局资源(全进程共享),不同类、不同方法中声明的相同字符串,都能复用同一个常量池对象,实现了字符串资源的全局优化。————————————————原文链接:https://blog.csdn.net/2301_80037974/article/details/154085031
  • [技术干货] 【Java】String类(超级详细!!!)
    1.String类在JAVA中被双括号括起来的就是String类,在JAVA中没有’\0’的说法,String类是一个引用类型,本文主要是在Sting类的内部方法的学习以及使用。2.比较两个字符串的大小比较2.1 ComparableComparable 是一个接口,用于定义对象的自然排序规则。实现了 Comparable 接口的类,可以通过其compareTo 方法来比较两个对象的大小,从而支持排序操作(如:Arrays.sort())2.1.1 compareTocompareTo 方法通过返回值定义两个对象(this 和参数 o)的大小关系:返回负数:this < o(当前对象小于参数对象)返回 0:this == o(当前对象等于参数对象)返回正数:this > o(当前对象大于参数对象)Comparable 接口的典型用途是:让类的对象支持自然排序(如数字从小到大、字符串按字典序)。使对象可以直接作为 TreeSet(有序集合)的元素或 TreeMap 的键(自动按排序规则存储)。支持通过 Arrays.sort() 对对象集合 / 数组进行排序。示例:String s1 = "apple";String s2 = "banana";String s3 = "apple";System.out.println(s1.compareTo(s2)); // -1(a在b前)System.out.println(s2.compareTo(s1)); // 1(b在a后)System.out.println(s1.compareTo(s3)); // 0(内容相同)一键获取完整项目代码java2.2 Comparator在 Java 中,Comparator 是一个用于定义对象排序规则的函数式接口(位于 java.util 包),它与 Comparable 不同:Comparable 是类自身实现的 “内部比较器”(定义自然排序),而 Comparator 是 “外部比较器”,允许在不修改原类的情况下,为类定义额外或临时的排序规则。核心方法为compare。2.2.1 compare与 Comparable 的 compareTo 类似,compare 方法的返回值定义排序逻辑:返回负数:o1 < o2(o1 应排在o2 前面)返回 0:o1 == o2(o1 和 o2 排序位置相同)返回正数:o1 > o2(o1 应排在 o2 后面)Comparator 适用于以下场景:为未实现 Comparable 接口的类定义排序规则。为已实现 Comparable的类提供额外的排序方式(不改变其默认的自然排序)。作为参数传递给排序方法(如Collections.sort()、Arrays.sort())或有序集合(如 TreeSet、TreeMap),指定排序逻辑。示例:Comparator<String> comp = String::compareToIgnoreCase;System.out.println(comp.compare("Apple", "apple")); // 0(忽略大小写相等)System.out.println(comp.compare("Banana", "apple")); // 1(B的ASCII大于A)System.out.println(comp.compare("cat", "Dog")); // -1(c的ASCII小于D)一键获取完整项目代码java2.3 equals在 Java 中,String的equals()方法用于比较两个字符串的内容是否相等,而==比较的是对象的内存地址。equals()是Object类的方法,String重写了它,实现逻辑为:先判断是否为同一对象,再比较长度和每个字符是否一致示例:String s1 = "hello";String s2 = new String("hello");String s3 = "hello";// 比较内容是否相同System.out.println(s1.equals(s2)); // true(内容一致)System.out.println(s1.equals(s3)); // true(常量池同一对象)System.out.println(s1.equals("hello")); // true(字面量比较)一键获取完整项目代码java2.4 Comparable、Comparator 、 equals和==的关系类型    作用    核心方法 / 逻辑    所属范畴Comparable    定义类的自然排序规则    int compareTo(T o)    类自身实现(内部比较)Comparator    定义类的外部 / 临时排序规则    int compare(T o1, T o2)    外部实现(外部比较)equals    判断两个对象是否逻辑相等    boolean equals(Object obj)    对象的逻辑相等性判断==    判断两个对象的内存地址是否一致    ==    /3.字符串的查找3.1 字符串转字符String str = "Hello";char ch = str.charAt(0); // 输出H一键获取完整项目代码java上述代码是从字符串str中获取第0个下标的字符赋值给ch。3.2 在字符串中找字符或字符串并返回下标int i1 = str.indexOf('l',4); // 返回-1 找不到就返回-1int i2 = str.indexOf('l',3); // 返回3int i3 = str.indexOf("ll"); // 返回2int i4 = str.indexOf("ll",3); // 返回-1一键获取完整项目代码javai1是在str中找l字符,从第四个下标开始找,找不到就返回-1.i2找到了就返回找到的下标i3是可以在字符串中找字符串,并返回其下标i4是从字符串str的3下标开始找子字符串,找不到就返回-1int i5 = str.lastIndexOf('l'); // 3int i6 = str.lastIndexOf('l',2); // 2int i7 = str.lastIndexOf("ll"); // 2int i8 = str.lastIndexOf("ll",3); // 2一键获取完整项目代码javai5是在str中从后向前找l字符,从最后一个下标开始找,找不到就返回-1.i6是在str中从3下标开始向前找l字符,i7是可以在字符串中从后向前找字符串,并返回其下标i8是从字符串str的3下标开始从后向前找子字符串,找不到就返回-1.但是如果是从2开始的话,也就是从2下标开始向前走的,但是这个子串ll的起始位置是2,是可以找到这个ll子串的所以返回的是2.4.字符串的转换顾名思义就是将对象转换成字符串String str = String.valueOf("abc");// abcString str1 = String.valueOf(1); // 1String str2 = String.valueOf(19); // 19一键获取完整项目代码java将对象转换成字符串,str1这里发生了装箱,将int->Integer.在输出的时候使用"…" + 变量就是调用了valueOf方法转换 char[] 有特殊重载:String.valueOf(char[] arr) 会将字符数组拼接为字符串(如 valueOf(new char[]{‘a’,‘b’}) → “ab”)4.1 包装类.valueOf () —— 基本类型 / 字符串转包装类对象Integer.valueOf(int i) 会缓存 -128~127 之间的整数对象,重复调用时返回同一个对象(而非新建),这是面试高频考点!// 1. 基本类型 → 包装类(带缓存)Integer i1 = Integer.valueOf(100);Integer i2 = Integer.valueOf(100);System.out.println(i1 == i2);  // true(缓存命中,同一对象)Integer i3 = Integer.valueOf(200);Integer i4 = Integer.valueOf(200);System.out.println(i3 == i4);  // false(超出缓存范围,新建对象)// 2. 字符串 → 包装类(需字符串格式匹配,否则抛异常)Integer i5 = Integer.valueOf("123");  // 字符串"123"→Integer对象123Long l1 = Long.valueOf("456");        // 字符串"456"→Long对象456Double d1 = Double.valueOf("3.14");   // 字符串"3.14"→Double对象3.14一键获取完整项目代码javaDouble.valueOf():无缓存(因为浮点数范围太大,无法缓存),每次调用都会新建对象;与 new Integer(100)的区别:new 关键字强制新建对象,不使用缓存;valueOf() 优先使用缓存,效率更高。4.2 BigDecimal.valueOf () —— 浮点数转 BigDecimal(推荐)BigDecimal 有两个常用构造:new BigDecimal(double d) 可能因浮点数精度问题出错,而BigDecimal.valueOf(double d) 会先将浮点数转为字符串,避免精度丢失,是推荐用法。// 错误示例:new BigDecimal(double) 精度丢失BigDecimal bd1 = new BigDecimal(0.1);System.out.println(bd1);  // 输出:0.1000000000000000055511151231257827021181583404541015625// 正确示例:valueOf(double) 避免精度丢失BigDecimal bd2 = BigDecimal.valueOf(0.1);System.out.println(bd2);  // 输出:0.1(本质是调用 new BigDecimal("0.1"))一键获取完整项目代码java4.3 高频面试考点String.valueOf(null) 和 null.toString() 的区别:前者返回 “null”,后者抛空指针;Integer.valueOf(100) == Integer.valueOf(100) 的结果:true(缓存命中);为什么推荐BigDecimal.valueOf(0.1) 而非 new BigDecimal(0.1):避免浮点数精度丢失。5.字符串的大小写转换String str = "hello"; String str1 = "HELLO"; str = str.toUpperCase();// HELLO  小写转大写str1 = str1.toLowerCase();// hello 大写转小写一键获取完整项目代码java6.字符串和字符数组的转换String str = "hello";char[] ch = str.toCharArray();for(char ch1 : ch){    System.out.print(ch1 + " "); // h e l l o}System.out.println();String str1 = new String(ch);System.out.println(str1.toString()); //hello一键获取完整项目代码java7.字符串替换String str = "hello";str = str.replace('h','b');System.out.println(str.toString()); //bellostr = str.replace("ll","s");System.out.println(str.toString()); //heso一键获取完整项目代码javareplace是String类的内置方法.replaceFirst()方法是只替换第一个replaceAll()方法是全部都替换8.字符串拆分String str = " I_am_a_teacher";String[] str1 = str.split("_");System.out.println(str.toString()); // I_am_a_teacherfor(String s : str1){    System.out.print(s.toString() + " "); // I am a teacher }一键获取完整项目代码java按照_来拆分字符串,返回的是一个String[]数组。9.字符串的截取String str = "helloworld";str = str.substring(2);System.out.println(str.toString());// lloworldstr = str.substring(2,4);System.out.println(str.toString());// ow一键获取完整项目代码java将字符串的前两个进行截取掉。也可以是两个参数,一般是左闭右开的区间10.删除字符串首尾的空格String str = "    hello    world    ";str = str.trim();System.out.println(str.toString()); //hello    world一键获取完整项目代码java11.字符串的不可变性String类在设计的时候就是不可以被修改的,所有涉及到可能修改String内容的都是重新创建新对象,对新对象进行修改。被final修饰的类声明该类不可以被继承,final修饰引用类型表明了该引用变量不能再引用其他对象,但是其引用对象的内容可以修改。12.StringBuilder和StringBuffer逆制字符串:StringBuilder.reverse();String不可以被修改,但是StringBuilder和StringBuffer可以被修改。但是StringBuilder一般是在单线程下,StringBuffer一般是在多线程下,可以保证线程安全。StringBuilder和StringBuffer的方法大多相同.————————————————原文链接:https://blog.csdn.net/2301_76766304/article/details/153703201
  • [技术干货] 【Java 开发日记】设计模式了解吗,知道什么是饿汉式和懒汉式吗?
    当然了解,设计模式是软件设计中针对常见问题的通用、可复用的解决方案。它能让代码更易于维护、扩展和复用。饿汉式和懒汉式是单例模式的两种经典实现方式。核心概念:单例模式目的:确保一个类只有一个实例,并提供一个全局访问点来获取这个实例。应用场景:比如数据库连接池、线程池、日志对象、应用的配置类等。这些对象在程序中只需要一个实例即可,创建多个实例会浪费资源或导致行为异常。饿汉式核心思想:“饿”,顾名思义,很饥饿,所以在类加载的时候就已经创建好实例,不管后面用不用,先创建了再说。特点:线程安全:因为实例的创建是在类加载阶段完成的,这个阶段由JVM保证线程安全。加载慢,获取快:类一加载就初始化实例,可能会稍微拖慢启动速度,但获取实例对象的速度非常快。代码示例:public class SingletonEager {    // 1. 私有静态实例,在类加载时就直接创建    private static final SingletonEager INSTANCE = new SingletonEager();     // 2. 私有构造函数,防止外部通过 new 关键字创建实例    private SingletonEager() {}     // 3. 公共的静态方法,用于获取唯一实例    public static SingletonEager getInstance() {        return INSTANCE;    }}一键获取完整项目代码优点:实现简单,绝对线程安全。缺点:如果这个实例非常耗费资源,但在整个程序运行过程中又可能用不到,就会造成资源的浪费。懒汉式核心思想:“懒”,很懒惰,所以只有在第一次被用到的时候才创建实例。特点:资源利用率高:只有在需要的时候才创建,避免了不必要的资源消耗。加载快,获取慢(第一次):类加载时不初始化,第一次调用 getInstance() 时才创建,所以第一次获取会稍慢。基础版(非线程安全)这个版本在多线程环境下会出问题。public class SingletonLazy {    private static SingletonLazy instance; // 不直接初始化     private SingletonLazy() {}     public static SingletonLazy getInstance() {        // 判断如果实例为空,则创建        if (instance == null) {            instance = new SingletonLazy(); // 问题所在:多个线程可能同时进入这里        }        return instance;    }}一键获取完整项目代码改进版(线程安全,使用 synchronized)通过给方法加锁来解决线程安全问题,但效率较低。public class SingletonLazySync {    private static SingletonLazySync instance;     private SingletonLazySync() {}     // 使用 synchronized 关键字,保证同时只有一个线程能进入该方法    public static synchronized SingletonLazySync getInstance() {        if (instance == null) {            instance = new SingletonLazySync();        }        return instance;    }}一键获取完整项目代码最优版(双重检查锁 DCL)为了兼顾线程安全和效率,我们只在实例还没创建的时候进行同步。public class SingletonLazyDCL {    // 使用 volatile 关键字,防止指令重排,保证可见性    private static volatile SingletonLazyDCL instance;     private SingletonLazyDCL() {}     public static SingletonLazyDCL getInstance() {        // 第一次检查,如果实例已存在,则直接返回,避免进入同步块,提高效率。        if (instance == null) {            // 加锁,确保只有一个线程能进入同步块            synchronized (SingletonLazyDCL.class) {                // 第二次检查,防止在等待锁的过程中,已有其他线程创建了实例。                if (instance == null) {                    instance = new SingletonLazyDCL();                }            }        }        return instance;    }}一键获取完整项目代码为什么用 volatile?instance = new SingletonLazyDCL(); 这行代码不是一个原子操作,它分为三步:分配内存空间初始化对象将 instance 指向分配的内存地址如果没有 volatile,JVM 可能会进行指令重排序,导致步骤 3 在步骤 2 之前执行。这样另一个线程可能在第一次检查时看到 instance 不为 null(已经指向了内存地址),但对象还没有初始化完成,从而拿到一个不完整的对象。volatile 可以禁止这种重排序。总结对比特性饿汉式懒汉式(基础版)懒汉式(双重检查锁)创建时机类加载时第一次调用 getInstance()时第一次调用 getInstance()时线程安全是否是资源利用差,可能浪费好好性能获取实例快(线程不安全,无意义)第一次稍慢,之后快实现难度简单简单复杂在现代 Java 开发中,还有更简洁的实现单例的方式,比如使用 枚举(Enum),它天生就是单例的,并且能防止反射和反序列化攻击,是《Effective Java》作者强烈推荐的方式。public enum SingletonEnum {    INSTANCE; // 这就是单例的实例     public void doSomething() {        // ... 业务方法    }}————————————————原文链接:https://blog.csdn.net/2402_87298751/article/details/154399848
  • [技术干货] 【Java Web学习 | 第三篇】CSS(2) - 元素显示模式
    CSS元素显示模式2.1 什么是元素显示模式?元素显示模式指的是HTML元素在页面中默认的表现形态,它决定了元素的排列方式、尺寸计算规则和嵌套关系。简单来说,就是元素"如何展示自己"以及"如何与其他元素相处"。浏览器会为每个HTML标签预设默认的显示模式,例如:<div>默认占据一整行(块级模式)<span>默认与其他元素并排显示(行内模式)<input>既可以并排显示又能设置宽高(行内块模式)我们可以通过CSS的display属性修改元素的显示模式,这是实现灵活布局的关键技巧。2.2 三大核心显示模式详解1. 块级元素(Block Level Elements)典型标签:<div>、<h1>-<h6>、<p>、<ul>、<ol>、<li>、<header>、<footer>等核心特性:独占一行:无论内容多少,都会单独占据一整行空间尺寸可控:可以通过width和height属性设置宽高默认宽度:未设置时默认占满父容器的100%宽度边距生效:margin和padding的四个方向(上下左右)都能正常生效容器特性:可以嵌套其他块级元素或行内元素代码示例:.change {  height: 200px;  background-color: aqua;}一键获取完整项目代码css注意事项:文字类块元素(如<p>、<h1>-<h6>)不能嵌套其他块级元素,例如<p>中不能放<div>块元素虽然可以嵌套行内元素,但语义上应保持合理(如导航容器嵌套链接)2. 行内元素(Inline Elements)典型标签:<span>、<a>、<strong>、<em>、<b>、<i>、<del>、<ins>等核心特性:并肩排列:多个行内元素可以在同一行显示尺寸受限:width和height设置无效,尺寸由内容决定边距限制:margin和padding仅左右方向生效,上下方向不影响布局嵌套限制:只能容纳文本或其他行内元素,不能嵌套块级元素代码示例:.span1 {  /* 宽高设置无效 */  width: 100px;  height: 100px;  /* 背景色等样式有效 */  background-color: hotpink;}一键获取完整项目代码css注意事项:链接<a>不能嵌套其他链接行内元素虽然不能设置宽高,但可以通过转换显示模式实现(见后文)3. 行内块元素(Inline-Block Elements)典型标签:<img>、<input>、<td>等核心特性:兼具两者优点:可以和其他行内元素并排显示(行内元素特性)可以设置width、height(块级元素特性)margin和padding四个方向均有效(块级元素特性)默认宽度:由内容决定(类似行内元素)间隙特性:相邻行内块元素之间会有默认空白间隙代码示例:input {  width: 249px;  height: 50px;}一键获取完整项目代码css2.3元素显示模式的转换语法实际开发中,我们经常需要改变元素的默认显示模式以实现特定布局。CSS提供了三种主要转换方式:1. 转为块级元素:display: block适用场景:需要让行内元素(如<a>)独占一行需要为行内元素设置宽高需要让链接等可点击区域扩大.a1 {  width: 150px;  height: 50px;  /* 转换为块级元素 */  display: block;  background-color: pink;}一键获取完整项目代码css2. 转为行内元素:display: inline适用场景:需要让块级元素(如<div>)并排显示需要取消块级元素的默认独占一行特性.change2 {  /* 转换为行内元素 */  display: inline;}一键获取完整项目代码css3. 转为行内块元素:display: inline-block适用场景:需要元素并排显示且能设置宽高(如导航按钮、表单控件)需要保持行内特性的同时拥有块级元素的尺寸控制能力.change3 {  width: 300px;  height: 30px;  background-color: skyblue;  /* 转换为行内块元素 */  display: inline-block;}一键获取完整项目代码css2.4 实战案例:小米侧边栏实现以小米官网侧边栏为例,展示元素显示模式转换的实际应用:实现思路:将行内元素<a>转换为块级元素,使其独占一行并可设置宽高设置背景色和文字样式使用line-height实现文字垂直居中添加hover效果实现交互反馈.a-change {  color: white;  width: 230px;  height: 40px;  background-color: #535758;  /* 转换为块级元素 */  display: block;  text-decoration: none;  font-size: 14px;  text-indent: 2em;  /* 文字垂直居中(行高=盒子高度) */  line-height: 40px;}/* 鼠标悬停效果 */a:hover {  background-color: #ff6700;}一键获取完整项目代码cssHTML结构:<a href="#" class="a-change">手机 电话卡</a><a href="#" class="a-change">电视 盒子</a><a href="#" class="a-change">笔记本 平板</a><!-- 更多链接 -->一键获取完整项目代码html实现效果:2.5综合代码演示<!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <meta name="viewport" content="width=device-width, initial-scale=1.0">    <title>元素显示模式</title>    <!-- ------  元素显示模式   ------ -->    <!--    1.作用:网页标签非常多,在不同地方会用到不同的标签,了解他们的特点可以更好的布局我们的网页            2.元素显示模式就是 元素(标签) 以什么方式进行显示,比如<div>自己独占一行,而<span>一行可以放多个            3.HTML元素一般分为 块元素 和 行内元素 两种类型    -->    <style>        /* 111.块元素 常见的块元素有<h1>~<h6>、<p>、<div>、<ul>、<ol>、<li>,其中<div>标签是最典型的块元素            11.块元素特点:                1.独占一行                2.高度、宽度、外边距和内边距都可以控制                3.宽度默认是容器(父级宽度)的100%                4.是一个容器和盒子,里面可以放行内或块级元素            22.注意:                1.文字类元素不能使用块元素                2.<p>标签主要用于存放文字,因此<p>里面不能放块级元素,特别是不能放<div>                3.同理,<h1>~<h6>等都是文字类块级标签,里面也不能放其他块级元素                    例如:  <p>                                <div>这里有问题</div>   (使用浏览器的审查器即可发现问题)                        </p>        */        .change {            /* width: 200px; */            height: 200px;            background-color: aqua;        }        /* 222.行内元素 常见的行内元素有<a>、<strong>、<b>、<em>、<i>、<del>、<s>、<ins>、<u>、<span>                       其中<span>是最典型的行内元素(内联元素)            11.行内元素的特点:                1.相邻行内元素在一行上,即一行可以显示多个                2.高、宽直接设置是无效的                3.默认宽度就是它本身内容的宽度                4.行内元素只能容纳文本后者其他行内元素            22.注意:                1.链接里面不能再放链接                2.特殊情况链接<a>里面可以放块级元素,但给<a>转换一下块级模式最安全        */        .span1 {            /* 这里设置宽度和高度是无效的,但是设置其他的是可以的,比如下面设置背景颜色 */            width: 100px;            height: 100px;            background-color: hotpink;        }        /* 333.行内块元素 在行内元素中有几个特殊的标签 <img>、<input>、<td>,他们同时具有块元素和行内元素的特点(行内块元素)*/        /*  11.行内块元素的特点                1.和相邻行内元素(行内块)在一行上,但是他们之间会有空白间隙,一行可以显示多个(行内元素的特定)                2.默认宽度就是它本身内容的宽度(行内元素的特点)                3.高度、行高、外边距和内边距都可以控制(块级元素的特点)        */        input {            width: 249px;            height: 50px;        }        /* --- 元素显示模式的转换 --- (特殊情况下,我们需要元素模式的转换 简单理解:一个模式的元素需要另一种模式的特性)*/        /*              1. 转换为块元素:display:block            2. 转换为行内元素:display:inline;            3. 转换为行内块元素:display:inline-block;        */        /* 这里想要增加链接<a>的触发范围  */        .a1 {            width: 150px;            height: 50px;            /* 1.把行内元素<a>转换为块级元素 这样width和height的修改才能生效 */            display: block;            background-color: pink;        }        .change2 {            /* 2.把块级元素转换为行内元素,实现一行放多个块级元素 */            display: inline;        }        .change3 {            /* 把行内元素转换为行内块元素 */            width: 300px;            height: 30px;            background-color: skyblue;            /* 转换前,对于行内元素来说,宽度和高度的设置是无效的 */            display: inline-block;        }        /* 简洁版小米侧边栏案例 */        .a-change {            /* 设置字体颜色 */            color: white;            width: 230px;            height: 40px;            background-color: #535758;            /* 把行内元素转换为块元素 */            display: block;            /* 取消链接下划线 */            text-decoration: none;            font-size: 14px;            /* 空两个格子 */            text-indent: 2em;            /* ! 实现单行文字垂直居中 ! --> 解决方案:让文字的行高等于盒子的高度*/            /*  原理:行高由上空隙、文本本省的高度、下空隙组成                 简单理解:行高的上空隙和下空隙把文字挤到中间了,                所以如果行高小于盒子高度,文字就会偏上;吐过行高大于盒子高度,则文字会偏下            */            line-height: 40px;        }        a:hover {            background-color: #ff6700;        }        del{            display:block;            height:200px;            background-color: red;        }    </style></head><body>    <!-- 块元素测验 -->    <div class="change">比较霸道,自己独占一行</div>    <!-- 不能这么用!!用浏览器检查会发现有问题 -->    <p>    <div class="change">这里有问题</div>    </p>    <!-- 行内元素测验 -->    <span class="span1">pink老师你怎么穿着品如的衣服呢</span> <strong>品如的衣服</strong>    <span class="span1">pink老师</span> <strong>品如的衣服</strong>    <!-- 行内块元素测验 -->    <input type="text" />    <input type="text" />    <!-- 元素模式的转换测验 -->    <!-- 这里想要增加链接<a>的触发范围 -->    <a href="picture/img1.jpg" class="a1">夏至未至</a>    <a href="picture/img1.jpg" class="a1">夏至未至</a>    <!-- 使块级元素在一行显示多个 -->    <div class="change2">我是块级元素,将要通过元素转换模式(改为行内元素)</div>    <div class="change2">我是块级元素 </div>    <!-- 想行内元素的宽度和高度 -->    <span class="change3">把行内元素转换为行内块元素</span>    <span class="change3">把行内元素转换为行内块元素</span>    <br />    <!-- 简洁版小米侧边栏案例  主要思路        1.链接<a>要实现竖着显示(行内元素转换为块级元素,这样链接就可以独占一行并且有高度和宽度)        2.链接有背景颜色,鼠标经过<a>时链接变色    -->    <a href="https://re.jd.com/search?keyword=%E7%94%B5%E8%AF%9D%E5%8D%A1&enc=utf-8" target="_blank" class="a-change">手机        电话卡</a>    <a href="picture/img1.jpg" class="a-change">电视 盒子</a>    <a href="#" class="a-change" target="_blank">笔记本呢 平板</a>    <a href="#" class="a-change">出行 穿戴</a>    <a href="#" class="a-change">智能 路由器</a>    <a href="#" class="a-change">健康儿童</a>    <a href="#" class="a-change">耳机 音响</a>    <del>你好啊</del>林七夜</body></html>一键获取完整项目代码html————————————————原文链接:https://blog.csdn.net/2401_86760859/article/details/154190239
  • [技术干货] 【Java 开发日记】finally 释放的是什么资源?
    finally 块本身并不直接释放资源,但它提供了一个保证执行的代码块,我们在这个代码块中手动编写释放资源的代码。简单来说:finally 块是释放资源的“黄金位置”,我们在这里手动关闭文件、数据库连接、网络连接等。详细解释1. 为什么需要 finally?程序在运行时可能会发生异常(Exception)。当异常被抛出时,程序会中断当前的执行流程,跳转到能够处理该异常的 catch 块。这会导致一个严重问题:在异常发生点之后的代码可能没有机会执行。考虑一个没有 finally 的场景:public void readFile() {    FileInputStream file = null;    try {        file = new FileInputStream("myfile.txt");        // 1. 打开文件资源        // ... 读取文件,假设这里发生了 IOException        // 2. 使用资源        file.close(); // 3. 关闭资源 - 如果上面发生异常,这行代码永远执行不到!    } catch (IOException e) {        e.printStackTrace();    }    // 文件描述符未被关闭,资源泄漏!}一键获取完整项目代码在上面的代码中,如果在“读取文件”时发生异常,程序会立刻跳转到 catch 块,file.close() 这行代码就被跳过了。这个文件句柄/描述符就一直没有被释放,导致资源泄漏。如果这种情况发生多次,可能会耗尽系统资源(如可用的文件句柄数量),导致程序甚至系统崩溃。2. finally 如何解决问题?finally 块的关键特性是:无论 try 块中是否发生异常,也无论是否被 catch 捕获,甚至 try 块中有 return 语句,finally 块中的代码都保证会执行。因此,我们把释放资源的代码放在 finally 块中,确保万无一失。public void readFile() {    FileInputStream file = null;    try {        file = new FileInputStream("myfile.txt");        // ... 读取文件,可能发生异常    } catch (IOException e) {        e.printStackTrace();    } finally {        // 无论 try 成功还是失败,都会执行这里的代码        if (file != null) {            try {                file.close(); // 确保文件被关闭            } catch (IOException e) {                e.printStackTrace(); // 关闭操作本身也可能出错            }        }    }}一键获取完整项目代码现在,无论 try 块中发生什么,我们都能在 finally 块中安全地关闭文件,释放它占用的系统资源。常见的需要释放的资源包括:文件 I/O 流:FileInputStream, FileOutputStream, Reader, Writer 等。数据库连接:java.sql.Connection 对象。数据库连接池非常宝贵,必须在使用后归还/关闭。网络连接:Socket 等。图形资源:在某些环境中,需要手动释放图形上下文等。现代写法:try-with-resources从 Java 7 开始,引入了 try-with-resources 语法,这是一种更优雅、更简洁的自动资源管理方式。它能自动在 try 块结束时调用资源的 close() 方法,本质上还是在背后使用了 finally 逻辑。使用条件:资源类必须实现 AutoCloseable 接口(几乎所有标准库中的资源类都实现了)。public void readFile() {    // 在try后的括号中声明和初始化资源    try (FileInputStream file = new FileInputStream("myfile.txt")) {        // ... 使用文件    } catch (IOException e) {        e.printStackTrace();    }    // 无需显式调用 file.close(),编译器会自动生成代码在背后调用它}一键获取完整项目代码在这个例子中,当 try 块正常结束或发生异常时,file.close() 方法会被自动调用。这大大减少了模板代码,避免了人为错误,是现在首选的资源管理方式。总结概念解释finally 的作用提供一个保证执行的代码块。释放的资源系统资源,如文件句柄、网络端口、数据库连接等。由开发者手动编写代码在 finally块中释(例如调用 close()方法)。现代替代方案try-with-resources语句(Java 7+),自动管理资源释放,本质是语法糖,底层依然依赖 finally机制。所以,finally 释放的是那些稀缺的、需要显式关闭的、由操作系统或外部系统管理的资源。————————————————原文链接:https://blog.csdn.net/2402_87298751/article/details/154342798
  • [技术干货] 【JAVA 进阶】深入探索Spring AOP:从原理到实战
    一、揭开 Spring AOP 的神秘面纱1.1 什么是 AOP在软件编程的世界里,随着系统规模的不断扩大和复杂性的日益增加,我们常常会遇到一些问题,这些问题涉及到多个模块或类,却又不属于核心业务逻辑。比如日志记录、事务管理、权限控制等功能,它们分散在各个业务代码中,导致代码的重复和臃肿,维护起来也变得异常困难。为了解决这些问题,AOP 应运而生。AOP,即 Aspect-Oriented Programming,面向切面编程,是一种编程范式,它的核心思想是将横切关注点(cross-cutting concerns)从核心业务逻辑中分离出来,形成一个个独立的切面(Aspect)。横切关注点就像是一条贯穿多个模块的 “横线”,这些功能在多个地方都需要用到,但又和具体的业务逻辑没有直接关系。通过 AOP,我们可以将这些横切关注点模块化,以一种声明式的方式将它们应用到需要的地方,从而实现代码的解耦和复用,提高代码的可维护性和可扩展性。AOP 并不是要取代面向对象编程(OOP),而是对 OOP 的一种补充和完善。OOP 主要关注的是对象的封装、继承和多态,通过类和对象来组织代码,解决的是业务逻辑的纵向划分问题;而 AOP 则关注的是那些横跨多个类的行为,通过切面来模块化这些横切关注点,解决的是业务逻辑的横向抽取问题。两者相互配合,可以让我们的代码更加清晰、灵活和易于维护。1.2 Spring AOP 是什么Spring AOP 是 Spring 框架的一个重要组成部分,它基于 AOP 思想,提供了一种强大的功能,使得我们可以在 Spring 应用中轻松地实现横切关注点的分离和模块化。Spring AOP 主要通过动态代理机制来实现,它允许我们在运行时为目标对象创建代理对象,在不修改目标对象源代码的情况下,对其方法进行增强。在 Spring AOP 中,我们可以通过配置或注解的方式定义切面、切点和通知。切面是横切关注点的模块化封装,它包含了切点和通知的定义;切点用于指定在哪些连接点上应用切面的通知,连接点是程序执行过程中的一个特定点,比如方法调用、异常抛出等;通知则是切面在特定连接点上执行的代码,它可以在方法执行前、执行后、返回结果后或抛出异常时执行,从而实现对目标方法的各种增强操作。1.3 AOP 的核心术语通知(Advice):通知是切面在特定连接点执行的操作,它定义了 “何时” 和 “做什么”。根据执行时机的不同,通知可以分为以下几种类型:前置通知(Before Advice):在目标方法执行之前执行,比如在方法执行前进行日志记录、权限检查等操作。后置通知(After Advice):在目标方法执行之后执行,无论方法是否正常返回或抛出异常,都会执行后置通知,常用于资源清理等操作。返回通知(After Returning Advice):在目标方法正常返回后执行,它可以获取到目标方法的返回值,比如在方法返回后进行结果处理、缓存设置等操作。异常通知(After Throwing Advice):在目标方法抛出异常时执行,它可以获取到异常信息,常用于异常处理、日志记录等操作。环绕通知(Around Advice):环绕通知是功能最强大的通知类型,它可以在目标方法执行前后都执行操作,甚至可以决定是否执行目标方法。环绕通知需要手动调用 ProceedingJoinPoint 的 proceed () 方法来执行目标方法,通过这种方式,我们可以实现对目标方法的完全控制,比如在方法执行前进行参数校验、在方法执行后进行性能统计等操作。连接点(Join Point):连接点是程序执行过程中的一个特定点,比如方法调用、字段访问、构造函数调用等。在 Spring AOP 中,连接点主要指方法的执行,它是通知可以插入的位置。Spring AOP 通过动态代理机制,在方法调用时拦截方法执行,从而在连接点处织入通知。切点(Pointcut):切点是一组连接点的集合,它定义了哪些连接点会被切面所影响,也就是决定在哪些方法或函数上应用通知。切点通过切点表达式来定义,切点表达式可以根据方法的签名、参数、返回值、类名、包名等条件来匹配连接点。比如,我们可以定义一个切点表达式,匹配所有以 “save” 开头的方法,或者匹配所有标注了特定注解的方法。通过切点的定义,我们可以精确地控制切面的作用范围,只对需要的连接点应用通知,从而避免对不需要的方法进行不必要的增强。切面(Aspect):切面是通知和切点的结合体,它将横切关注点封装为一个可重用的模块。一个切面可以包含多个切点和通知,通过切面的定义,我们可以将相关的横切逻辑集中管理,提高代码的模块化程度和可维护性。例如,我们可以定义一个日志切面,它包含了在方法执行前后记录日志的通知,以及匹配所有业务方法的切点,这样就可以对所有业务方法进行统一的日志记录。引入(Introduction):引入允许我们在运行时为目标对象动态添加新的接口和实现。通过引入,我们可以为现有的类添加额外的功能,而不需要修改类的源代码。比如,我们可以为一个类引入一个新的接口,使其具备某种新的行为,这种方式在扩展现有类的功能时非常有用。织入(Weaving):织入是将切面应用到目标对象并创建代理对象的过程。织入可以在编译时、类加载时或运行时进行。在 Spring AOP 中,默认采用运行时织入的方式,通过动态代理在运行时为目标对象创建代理对象,并将切面的通知织入到代理对象的方法调用中。在编译时织入需要特殊的编译器支持,类加载时织入则需要特殊的类加载器支持。目标对象(Target Object):目标对象是被切面增强的对象,也就是实际执行业务逻辑的对象。在 Spring AOP 中,目标对象通常是一个普通的 POJO(Plain Old Java Object),它不知道自己被代理和增强。代理对象(Proxy Object):代理对象是 Spring AOP 为目标对象创建的代理实例,它负责在目标对象的方法调用前后执行切面的通知。代理对象实现了与目标对象相同的接口(如果目标对象实现了接口),或者继承了目标对象(如果目标对象没有实现接口)。当我们调用代理对象的方法时,实际上是调用代理对象的方法,代理对象会在方法调用前后执行切面的通知,然后再调用目标对象的方法。根据代理方式的不同,Spring AOP 提供了两种代理对象:JDK 动态代理和 CGLIB 代理。JDK 动态代理基于 Java 反射机制,只能为实现了接口的目标对象创建代理;CGLIB 代理基于字节码生成技术,可以为没有实现接口的目标对象创建代理。二、Spring AOP 的底层基石:动态代理2.1 JDK 动态代理JDK 动态代理是 Java 原生提供的一种动态代理机制,它基于反射机制实现,要求目标对象必须实现至少一个接口。在 JDK 动态代理中,核心的类和接口有两个:java.lang.reflect.Proxy和java.lang.reflect.InvocationHandler。Proxy类主要负责生成代理对象,它提供了一个静态方法newProxyInstance,通过这个方法可以创建一个代理对象。该方法接受三个参数:类加载器(ClassLoader)、目标对象实现的接口数组(Class<?>[] interfaces)以及一个实现了InvocationHandler接口的处理器对象(InvocationHandler h)。InvocationHandler接口则定义了代理对象的行为逻辑,它只有一个方法invoke,当我们调用代理对象的方法时,实际上会调用到这个invoke方法。在invoke方法中,我们可以在目标方法执行前后添加自定义的逻辑,比如日志记录、事务管理等,然后通过反射调用目标对象的实际方法。下面通过一个简单的代码示例来演示 JDK 动态代理的使用:// 定义一个接口interface UserService {    void addUser(String username);}// 实现接口的目标类class UserServiceImpl implements UserService {    @Override    public void addUser(String username) {        System.out.println("Adding user: " + username);    }}// 实现InvocationHandler接口class MyInvocationHandler implements InvocationHandler {    private Object target;    public MyInvocationHandler(Object target) {        this.target = target;    }    @Override    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {        System.out.println("Before method execution");        Object result = method.invoke(target, args);        System.out.println("After method execution");        return result;    }}public class Main {    public static void main(String[] args) {        UserService target = new UserServiceImpl();        InvocationHandler handler = new MyInvocationHandler(target);        UserService proxy = (UserService) Proxy.newProxyInstance(                target.getClass().getClassLoader(),                target.getClass().getInterfaces(),                handler        );        proxy.addUser("Alice");    }}一键获取完整项目代码java在上述代码中,首先定义了一个UserService接口和实现该接口的UserServiceImpl类。然后创建了一个MyInvocationHandler类,实现了InvocationHandler接口,在invoke方法中添加了前置和后置的打印逻辑。最后在main方法中,通过Proxy.newProxyInstance方法创建了代理对象,并调用代理对象的addUser方法。运行代码后,可以看到在目标方法执行前后分别打印了 “Before method execution” 和 “After method execution”。2.2 Cglib 动态代理Cglib(Code Generation Library)是一个高性能的代码生成库,它通过字节码操作技术在运行时动态生成目标类的子类,从而实现代理功能。与 JDK 动态代理不同,Cglib 代理不需要目标类实现接口,因此可以代理普通的类。Cglib 动态代理的核心类是net.sf.cglib.proxy.Enhancer和net.sf.cglib.proxy.MethodInterceptor。Enhancer类用于生成代理类,它提供了一系列方法来配置代理类的属性,比如设置父类(即目标类)、设置回调函数等。MethodInterceptor接口则用于定义方法的拦截逻辑,当调用代理对象的方法时,会触发MethodInterceptor的intercept方法,在这个方法中我们可以实现对目标方法的增强。下面是一个使用 Cglib 动态代理的代码示例:import net.sf.cglib.proxy.Enhancer;import net.sf.cglib.proxy.MethodInterceptor;import net.sf.cglib.proxy.MethodProxy;import java.lang.reflect.Method;class UserService {    public void addUser(String username) {        System.out.println("Adding user: " + username);    }}class MyMethodInterceptor implements MethodInterceptor {    @Override    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {        System.out.println("Before method execution");        Object result = proxy.invokeSuper(obj, args);        System.out.println("After method execution");        return result;    }}public class CglibProxyExample {    public static void main(String[] args) {        Enhancer enhancer = new Enhancer();        enhancer.setSuperclass(UserService.class);        enhancer.setCallback(new MyMethodInterceptor());        UserService proxy = (UserService) enhancer.create();        proxy.addUser("Bob");    }}一键获取完整项目代码java在这个示例中,首先定义了一个没有实现接口的UserService类。然后创建了MyMethodInterceptor类,实现了MethodInterceptor接口,在intercept方法中添加了前置和后置的打印逻辑。最后在main方法中,通过Enhancer类设置目标类为UserService,并设置回调函数为MyMethodInterceptor,调用create方法生成代理对象,并调用代理对象的addUser方法。运行代码后,同样可以看到在目标方法执行前后打印了相应的信息。2.3 两者对比与 Spring 的选择性能对比:JDK 动态代理:由于它基于反射机制,在创建代理对象和方法调用时会涉及到反射操作,因此性能相对较低。特别是在方法调用频繁的情况下,反射带来的开销会比较明显。不过,在代理对象创建较少且方法调用次数不多的场景下,其性能表现还是可以接受的。Cglib 动态代理:Cglib 通过字节码生成技术,直接在运行时生成目标类的子类,方法调用时直接调用子类的方法,避免了反射的开销,因此在性能上通常优于 JDK 动态代理,尤其是在大量创建代理对象和频繁调用方法的场景下,Cglib 的优势更加明显。适用场景对比:JDK 动态代理:适用于目标类已经实现了接口的情况,因为它必须依赖接口来创建代理对象。在 Java 的企业级开发中,很多服务层接口都采用这种方式,所以 JDK 动态代理在 Spring 的 AOP 中也被广泛应用于代理有接口的服务。Cglib 动态代理:适用于目标类没有实现接口,或者需要代理类的所有方法(包括非接口方法)的场景。例如,当我们需要对一些第三方库中的类进行增强,而这些类没有实现特定接口时,Cglib 就可以发挥作用。此外,由于 Cglib 的性能优势,对于一些对性能要求较高且目标类无接口的场景,也可以优先考虑使用 Cglib。Spring 的选择策略:在 Spring AOP 中,默认情况下,如果目标对象实现了接口,Spring 会优先使用 JDK 动态代理来创建代理对象;如果目标对象没有实现接口,Spring 则会使用 Cglib 动态代理。不过,Spring 也提供了配置选项,允许我们手动指定使用哪种代理方式。例如,通过在配置文件中设置proxy-target-class="true",可以强制 Spring 使用 Cglib 代理,即使目标对象实现了接口。这种配置在一些特殊场景下非常有用,比如当我们需要代理类的所有方法,包括那些在接口中没有定义的方法时。三、Spring AOP 实战演练3.1 环境搭建在开始使用 Spring AOP 之前,我们需要搭建一个 Spring 项目,并引入 Spring AOP 的相关依赖。如果你使用的是 Maven 项目管理工具,可以在pom.xml文件中添加以下依赖:<dependencies>    <!-- Spring Core -->    <dependency>        <groupId>org.springframework</groupId>        <artifactId>spring-context</artifactId>        <version>5.3.23</version>    </dependency>    <!-- Spring AOP -->    <dependency>        <groupId>org.springframework</groupId>        <artifactId>spring-aop</artifactId>        <version>5.3.23</version>    </dependency>    <!-- AspectJ Weaver -->    <dependency>        <groupId>org.aspectj</groupId>        <artifactId>aspectjweaver</artifactId>        <version>1.9.9.1</version>    </dependency></dependencies>一键获取完整项目代码xml上述配置中,spring-context是 Spring 的核心依赖,spring-aop提供了 Spring AOP 的支持,aspectjweaver是 AspectJ 的织入器,Spring AOP 默认使用 AspectJ 的切点表达式语言,因此需要引入这个依赖。如果你使用的是 Gradle,可以在build.gradle文件中添加以下依赖:dependencies {    implementation 'org.springframework:spring-context:5.3.23'    implementation 'org.springframework:spring-aop:5.3.23'    implementation 'org.aspectj:aspectjweaver:1.9.9.1'}一键获取完整项目代码groovy添加完依赖后,Maven 或 Gradle 会自动下载并管理这些依赖。3.2 定义切面在 Spring AOP 中,我们可以使用注解或 XML 配置的方式来定义切面。下面先介绍使用注解定义切面的方法:首先,创建一个切面类,并使用@Aspect注解标记它,表明这是一个切面。同时,使用@Component注解将其注册为 Spring 容器中的一个 Bean,这样 Spring 才能管理它。例如:import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Pointcut;import org.springframework.stereotype.Component;@Aspect@Componentpublic class LoggingAspect {    // 定义切点表达式,匹配com.example.service包下所有类的所有方法    @Pointcut("execution(* com.example.service.*.*(..))")    public void serviceMethods() {}    // 其他通知方法将在这里定义}一键获取完整项目代码java在上述代码中,@Pointcut注解定义了一个切点,切点表达式execution(* com.example.service.*.*(..))表示匹配com.example.service包下所有类的所有方法。serviceMethods方法本身没有实际的业务逻辑,它只是一个标识,用于在后续的通知中引用这个切点。接下来介绍使用 XML 配置定义切面的方法:首先,在 Spring 的配置文件(如applicationContext.xml)中,需要引入 AOP 的命名空间:<beans xmlns="http://www.springframework.org/schema/beans"       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"       xmlns:aop="http://www.springframework.org/schema/aop"       xsi:schemaLocation="http://www.springframework.org/schema/beans                           http://www.springframework.org/schema/beans/spring-beans.xsd                           http://www.springframework.org/schema/aop                           http://www.springframework.org/schema/aop/spring-aop.xsd">一键获取完整项目代码xml然后,定义切面类,并将其配置为 Spring 容器中的 Bean:<bean id="loggingAspect" class="com.example.aspect.LoggingAspect"/>一键获取完整项目代码xml接着,定义切点和通知:<aop:config>    <aop:aspect ref="loggingAspect">        <aop:pointcut id="serviceMethods" expression="execution(* com.example.service.*.*(..))"/>        <!-- 其他通知配置将在这里定义 -->    </aop:aspect></aop:config>一键获取完整项目代码xml在上述 XML 配置中,<aop:config>标签用于配置 AOP 相关的内容,<aop:aspect>标签引用了切面类loggingAspect,<aop:pointcut>标签定义了切点,表达式与注解方式中的切点表达式相同。3.3 通知类型详解前置通知(Before Advice):执行时机:在目标方法执行之前执行。使用场景:常用于权限检查、日志记录等操作。例如,在方法执行前检查用户是否有权限执行该方法,或者记录方法的调用信息。注解方式示例:import org.aspectj.lang.JoinPoint;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Before;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.stereotype.Component;@Aspect@Componentpublic class LoggingAspect {    private static final Logger logger = LoggerFactory.getLogger(LoggingAspect.class);    @Before("serviceMethods()")    public void logBefore(JoinPoint joinPoint) {        String methodName = joinPoint.getSignature().getName();        Object[] args = joinPoint.getArgs();        logger.info("方法 {} 开始执行,参数: {}", methodName, args);    }    @Pointcut("execution(* com.example.service.*.*(..))")    public void serviceMethods() {}}一键获取完整项目代码java在上述代码中,@Before注解表示这是一个前置通知,serviceMethods()是前面定义的切点,表示这个通知应用于com.example.service包下所有类的所有方法。logBefore方法中的JoinPoint参数可以获取到目标方法的签名和参数等信息。2. 后置通知(After Advice):执行时机:在目标方法执行之后执行,无论目标方法是否正常返回或抛出异常。使用场景:常用于资源清理等操作。例如,在方法执行后关闭数据库连接、释放文件资源等。注解方式示例:import org.aspectj.lang.JoinPoint;import org.aspectj.lang.annotation.After;import org.aspectj.lang.annotation.Aspect;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.stereotype.Component;@Aspect@Componentpublic class LoggingAspect {    private static final Logger logger = LoggerFactory.getLogger(LoggingAspect.class);    @After("serviceMethods()")    public void logAfter(JoinPoint joinPoint) {        String methodName = joinPoint.getSignature().getName();        logger.info("方法 {} 执行结束", methodName);    }    @Pointcut("execution(* com.example.service.*.*(..))")    public void serviceMethods() {}}一键获取完整项目代码java返回通知(After Returning Advice):执行时机:在目标方法正常返回后执行。使用场景:常用于对方法返回结果进行处理。例如,对返回结果进行缓存、格式化等操作。注解方式示例:import org.aspectj.lang.JoinPoint;import org.aspectj.lang.annotation.AfterReturning;import org.aspectj.lang.annotation.Aspect;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.stereotype.Component;@Aspect@Componentpublic class LoggingAspect {    private static final Logger logger = LoggerFactory.getLogger(LoggingAspect.class);    @AfterReturning(pointcut = "serviceMethods()", returning = "result")    public void logAfterReturning(JoinPoint joinPoint, Object result) {        String methodName = joinPoint.getSignature().getName();        logger.info("方法 {} 返回结果: {}", methodName, result);    }    @Pointcut("execution(* com.example.service.*.*(..))")    public void serviceMethods() {}}一键获取完整项目代码java在上述代码中,@AfterReturning注解的returning属性指定了一个参数名result,这个参数名与通知方法中的参数名相对应,用于接收目标方法的返回值。4. 异常通知(After Throwing Advice):执行时机:在目标方法抛出异常时执行。使用场景:常用于异常处理、日志记录等操作。例如,在方法抛出异常时记录异常信息,或者进行统一的异常处理。注解方式示例:import org.aspectj.lang.JoinPoint;import org.aspectj.lang.annotation.AfterThrowing;import org.aspectj.lang.annotation.Aspect;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.stereotype.Component;@Aspect@Componentpublic class LoggingAspect {    private static final Logger logger = LoggerFactory.getLogger(LoggingAspect.class);    @AfterThrowing(pointcut = "serviceMethods()", throwing = "ex")    public void logAfterThrowing(JoinPoint joinPoint, Exception ex) {        String methodName = joinPoint.getSignature().getName();        logger.error("方法 {} 抛出异常: {}", methodName, ex.getMessage());    }    @Pointcut("execution(* com.example.service.*.*(..))")    public void serviceMethods() {}}一键获取完整项目代码java在上述代码中,@AfterThrowing注解的throwing属性指定了一个参数名ex,用于接收目标方法抛出的异常对象。5. 环绕通知(Around Advice):执行时机:在目标方法执行前后都执行,可以完全控制目标方法的执行。使用场景:常用于性能统计、事务管理等操作。例如,统计方法的执行时间,或者在方法执行前后开启和提交事务。注解方式示例:import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.annotation.Around;import org.aspectj.lang.annotation.Aspect;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.stereotype.Component;@Aspect@Componentpublic class LoggingAspect {    private static final Logger logger = LoggerFactory.getLogger(LoggingAspect.class);    @Around("serviceMethods()")    public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {        long startTime = System.currentTimeMillis();        logger.info("方法 {} 开始执行", joinPoint.getSignature().getName());        try {            return joinPoint.proceed();        } finally {            long endTime = System.currentTimeMillis();            logger.info("方法 {} 执行结束,耗时: {} ms", joinPoint.getSignature().getName(), endTime - startTime);        }    }    @Pointcut("execution(* com.example.service.*.*(..))")    public void serviceMethods() {}}一键获取完整项目代码java在上述代码中,@Around注解的通知方法接受一个ProceedingJoinPoint参数,通过调用它的proceed()方法来执行目标方法。在调用proceed()方法前后添加的代码,分别在目标方法执行前和执行后执行。3.4 切点表达式切点表达式是 Spring AOP 中非常重要的一部分,它用于指定哪些方法会被切面所影响。Spring AOP 默认使用 AspectJ 的切点表达式语言,下面详细介绍其语法规则:基本语法结构:execution([修饰符模式] 返回值类型模式 [类名模式]方法名模式(参数模式)[异常模式])一键获取完整项目代码Plain其中,方括号内的部分是可选的。修饰符模式:用于匹配方法的修饰符,如public、private、protected等。可以使用通配符*匹配任意修饰符,通常情况下修饰符模式可以省略。返回值类型模式:用于匹配方法的返回值类型,*表示匹配任意返回值类型,也可以指定具体的返回值类型,如void、int、String等。类名模式:用于匹配类名,可以指定包名和类名相关信息。包名后若跟..表示当前包及其子包;类名用*表示匹配所有类。例如,com.example.service..*表示com.example.service包及其子包下的所有类。方法名模式:*表示匹配所有方法名;也可指定特定方法名,如get*表示以get开头的方法。参数模式:(..)表示匹配任意参数数量和类型;也可具体指定,如(int, String)表示方法有两个参数,分别为int类型和String类型 ,(int,..) 表示第一个参数是int类型,后面可以跟任意数量和类型参数。异常模式:用于匹配方法抛出的异常类型,较少使用,通常省略。通配符详解:*:匹配任意字符(除.外)。例如,com.*.service表示com包下任意子包下的service包。..:匹配任意子包或多个参数。在包名中使用时,表示当前包及其子包;在参数模式中使用时,表示任意数量和类型的参数。例如,com.example..*Service表示com.example包及其子包下所有以Service结尾的类;(..)表示任意参数。+:匹配指定类型及其子类。例如,java.util.List+表示匹配java.util.List接口及其所有实现类。常用示例:匹配指定包下所有类的所有方法:execution(* com.example.dao..*.*(..)),表示匹配com.example.dao包及其子包下所有类的所有方法,第一个*匹配任意返回值类型,第二个*匹配所有类,第三个* 匹配所有方法名,(..)匹配任意参数。匹配指定类的所有方法:execution(* com.example.service.UserService.*(..)),即匹配com.example.service.UserService类的所有方法 。匹配指定接口所有实现类的方法:execution(* com.example.dao.GenericDAO+.*(..)),+表示匹配GenericDAO接口的所有实现类的方法。匹配特定方法名开头的方法:execution(* save*(..)),表示匹配所有以save开头的方法,不限定返回值类型、类和参数。多表达式组合:多个 execution 表达式之间可以通过逻辑运算符组合:或(|| 或 or ):表示满足其中一个表达式即可。例如,execution(* com.example.service.UserService.add(..)) || execution(* com.example.service.UserService.delete(..)),表示匹配UserService类中的add方法或者delete方法。与(&& 或 and ):要求同时满足多个表达式。例如,execution(* com.example.service..*.*(..)) && args(String) ,表示匹配com.example.service包及其子包下所有类的方法,且方法参数包含String类型 。非(! 或 not ):对表达式取反。例如,!execution(* com.example.service.UserService.get*(..)) ,表示匹配除了UserService类中以get开头方法之外的其他方法。其他切点指示符:within 表达式:主要用于根据类型(类或包)匹配连接点。它更侧重类或包的范围匹配,而execution更侧重方法签名匹配。匹配指定包下的所有类:语法为within(包名..*),其中..表示当前包及其子包。例如,within(com.example.service..*)表示匹配com.example.service包及其子包下所有类的所有方法。匹配指定类:语法为within(全限定类名)。例如,within(com.example.service.UserService)表示只匹配com.example.service.UserService类的所有方法。@annotation 表达式:根据方法上是否存在特定注解来匹配连接点。当某方法标注指定注解,该方法执行就触发相应切面逻辑。自定义注解:首先定义一个自定义注解,例如:import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.METHOD)public @interface Loggable {}一键获取完整项目代码java用 @annotation 匹配带注解的方法:语法为@annotation(注解全限定名)。例如,@annotation(com.example.annotation.Loggable)表示匹配所有带有@Loggable注解的方法。import org.aspectj.lang.JoinPoint;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Before;import org.springframework.stereotype.Component;@Aspect@Componentpublic class LoggingAspect {    @Before("@annotation(com.example.annotation.Loggable)")    public void beforeLoggableMethod(JoinPoint joinPoint) {        System.out.println("Before method: " + joinPoint.getSignature().getName());    }}一键获取完整项目代码java在方法上使用注解:在需要增强的方法上添加自定义注解,例如:import com.example.annotation.Loggable;import org.springframework.stereotype.Service;@Servicepublic class UserService {    @Loggable    public void saveUser() {        System.out.println("Saving user...");    }}一键获取完整项目代码java当UserService#saveUser四、Spring AOP 的高级特性与应用场景4.1 引入(Introduction)引入是 Spring AOP 的一个高级特性,它允许我们在运行时为目标对象动态添加新的接口和实现,而不需要修改目标对象的源代码。这一特性在扩展现有类的功能时非常有用,比如我们可以为一个现有的类添加日志记录、缓存等功能,而无需对该类进行任何改动。在 Spring AOP 中,使用引入功能需要定义一个切面,并在切面中使用@DeclareParents注解。@DeclareParents注解有三个属性:value指定要引入接口的目标类或类的集合,使用切点表达式来表示;defaultImpl指定接口的默认实现类;defaultAnnotation指定一个可选的注解,用于标记哪些目标对象应该引入该接口(通常较少使用)。下面通过一个具体的代码示例来演示引入的使用:首先,定义一个要引入的接口及其实现类:// 定义要引入的接口public interface Cacheable {    void cache();}// 接口的实现类public class CacheableImpl implements Cacheable {    @Override    public void cache() {        System.out.println("Caching data...");    }}一键获取完整项目代码java然后,定义一个切面类,使用@DeclareParents注解将Cacheable接口引入到目标类:import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.DeclareParents;import org.springframework.stereotype.Component;@Aspect@Componentpublic class CacheAspect {    @DeclareParents(value = "com.example.service.*Service+", defaultImpl = CacheableImpl.class)    public static Cacheable cacheable;}一键获取完整项目代码java在上述代码中,@DeclareParents注解的value属性使用切点表达式com.example.service.*Service+表示com.example.service包下所有以Service结尾的类及其子类,defaultImpl属性指定了Cacheable接口的默认实现类为CacheableImpl。最后,在使用时,可以通过类型转换将目标对象转换为引入的接口类型,从而调用引入的方法:import com.example.service.UserService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.CommandLineRunner;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplicationpublic class Application implements CommandLineRunner {    @Autowired    private UserService userService;    public static void main(String[] args) {        SpringApplication.run(Application.class, args);    }    @Override    public void run(String... args) throws Exception {        Cacheable cacheable = (Cacheable) userService;        cacheable.cache();    }}一键获取完整项目代码java在上述代码中,将UserService对象转换为Cacheable类型,然后调用cache方法,就可以执行引入的缓存功能。通过引入,我们可以在不修改UserService类的情况下,为其添加新的功能,这大大提高了代码的可维护性和扩展性。4.2 多切面与切面优先级在实际应用中,可能会存在多个切面作用于同一个目标对象的情况。当多个切面作用于同一目标对象时,它们的执行顺序是有规则的。默认情况下,Spring AOP 会按照切面类名的字母顺序来决定切面的执行顺序,类名靠前的切面先执行。但这种默认的顺序在很多情况下并不能满足我们的需求,因此 Spring 提供了设置切面优先级的机制。在 Spring AOP 中,可以通过实现Ordered接口或使用@Order注解来设置切面的优先级。Ordered接口只有一个方法getOrder,返回值越小,表示优先级越高。@Order注解的使用则更加简洁,它可以直接标注在切面类上,其参数值越小,优先级越高。下面通过代码示例来演示如何设置切面优先级:首先,定义两个切面类:import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Before;import org.springframework.core.Ordered;import org.springframework.core.annotation.Order;import org.springframework.stereotype.Component;@Aspect@Component@Order(1) // 设置优先级为1,值越小优先级越高public class HighPriorityAspect {    @Before("execution(* com.example.service.*.*(..))")    public void highPriorityBefore() {        System.out.println("高优先级切面的前置通知");    }}一键获取完整项目代码java import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Before;import org.springframework.core.Ordered;import org.springframework.stereotype.Component;@Aspect@Componentpublic class LowPriorityAspect implements Ordered {    @Before("execution(* com.example.service.*.*(..))")    public void lowPriorityBefore() {        System.out.println("低优先级切面的前置通知");    }    @Override    public int getOrder() {        return 2; // 设置优先级为2    }}一键获取完整项目代码java在上述代码中,HighPriorityAspect使用@Order(1)注解设置优先级为 1,LowPriorityAspect实现Ordered接口,通过getOrder方法返回 2 来设置优先级为 2。因此,在目标方法执行前,HighPriorityAspect的前置通知会先执行,然后才执行LowPriorityAspect的前置通知。4.3 应用场景举例日志记录:在企业级应用中,我们通常需要记录方法的调用情况,包括方法的入参、返回值、执行时间等信息。使用 Spring AOP 可以轻松实现这一需求。通过定义一个日志切面,使用切点表达式匹配所有需要记录日志的方法,然后在前置通知中记录方法的入参和开始时间,在返回通知中记录返回值和执行时间,在异常通知中记录异常信息。这样,所有被匹配的方法在执行时都会自动记录日志,避免了在每个方法中手动添加日志代码,提高了代码的可维护性和可读性。例如:import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.annotation.Around;import org.aspectj.lang.annotation.Aspect;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.stereotype.Component;@Aspect@Componentpublic class LoggingAspect {    private static final Logger logger = LoggerFactory.getLogger(LoggingAspect.class);    @Around("execution(* com.example.service.*.*(..))")    public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {        long startTime = System.currentTimeMillis();        String methodName = joinPoint.getSignature().getName();        Object[] args = joinPoint.getArgs();        logger.info("开始执行方法 {},参数: {}", methodName, args);        try {            Object result = joinPoint.proceed();            long endTime = System.currentTimeMillis();            logger.info("方法 {} 执行完毕,返回值: {},执行时间: {} ms", methodName, result, endTime - startTime);            return result;        } catch (Exception e) {            logger.error("方法 {} 执行出错: {}", methodName, e.getMessage());            throw e;        }    }}一键获取完整项目代码java事务管理:在涉及数据库操作的应用中,事务管理是非常重要的。Spring AOP 可以将事务管理的逻辑从业务代码中分离出来,实现声明式事务管理。通过定义一个事务切面,使用切点表达式匹配需要进行事务管理的方法,然后在环绕通知中开启事务、执行业务方法、根据执行结果提交或回滚事务。这样,业务代码中就不需要显式地编写事务相关的代码,只需要在配置文件或注解中声明事务的属性,如事务的传播行为、隔离级别等,提高了代码的简洁性和事务管理的一致性。例如:import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.annotation.Around;import org.aspectj.lang.annotation.Aspect;import org.springframework.jdbc.datasource.DataSourceTransactionManager;import org.springframework.transaction.TransactionStatus;import org.springframework.transaction.support.DefaultTransactionDefinition;import org.springframework.stereotype.Component;@Aspect@Componentpublic class TransactionAspect {    private final DataSourceTransactionManager transactionManager;    public TransactionAspect(DataSourceTransactionManager transactionManager) {        this.transactionManager = transactionManager;    }    @Around("execution(* com.example.service.*.*(..)) && @annotation(org.springframework.transaction.annotation.Transactional)")    public Object transactionAround(ProceedingJoinPoint joinPoint) throws Throwable {        DefaultTransactionDefinition def = new DefaultTransactionDefinition();        TransactionStatus status = transactionManager.getTransaction(def);        try {            Object result = joinPoint.proceed();            transactionManager.commit(status);            return result;        } catch (Exception e) {            transactionManager.rollback(status);            throw e;        }    }}一键获取完整项目代码java权限控制:在 Web 应用中,我们需要对用户的操作进行权限控制,确保只有具备相应权限的用户才能访问特定的资源或执行特定的操作。Spring AOP 可以将权限控制的逻辑从业务代码中分离出来,实现统一的权限管理。通过定义一个权限切面,使用切点表达式匹配需要进行权限控制的方法,然后在前置通知中检查用户的权限,根据权限检查结果决定是否允许方法执行。这样,业务代码中就不需要重复编写权限检查的代码,提高了代码的安全性和可维护性。例如:import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.annotation.Around;import org.aspectj.lang.annotation.Aspect;import org.springframework.security.core.Authentication;import org.springframework.security.core.context.SecurityContextHolder;import org.springframework.stereotype.Component;@Aspect@Componentpublic class PermissionAspect {    @Around("execution(* com.example.controller.*.*(..))")    public Object permissionAround(ProceedingJoinPoint joinPoint) throws Throwable {        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();        if (authentication != null && authentication.isAuthenticated()) {            // 检查用户权限,这里只是示例,实际应用中需要根据具体的权限模型进行检查            String username = authentication.getName();            if ("admin".equals(username)) {                return joinPoint.proceed();            }        }        throw new RuntimeException("没有权限访问该资源");    }}一键获取完整项目代码java性能监控:在应用性能优化过程中,我们需要了解各个方法的执行时间,找出性能瓶颈。Spring AOP 可以帮助我们实现方法执行时间的监控。通过定义一个性能监控切面,使用切点表达式匹配需要监控的方法,在环绕通知中记录方法的开始时间和结束时间,计算并输出方法的执行耗时。这样,我们可以方便地对系统中各个方法的性能进行分析,从而有针对性地进行优化。例如:import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.annotation.Around;import org.aspectj.lang.annotation.Aspect;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.stereotype.Component;@Aspect@Componentpublic class PerformanceMonitorAspect {    private static final Logger logger = LoggerFactory.getLogger(PerformanceMonitorAspect.class);    @Around("execution(* com.example.service.*.*(..))")    public Object monitorPerformance(ProceedingJoinPoint joinPoint) throws Throwable {        long startTime = System.currentTimeMillis();        Object result = joinPoint.proceed();        long endTime = System.currentTimeMillis();        long executionTime = endTime - startTime;        logger.info("方法 {} 执行耗时: {} ms", joinPoint.getSignature().getName(), executionTime);        return result;    }}一键获取完整项目代码java五、总结与展望5.1 知识点回顾在本文中,我们深入探索了 Spring AOP 这一强大的技术领域。开篇我们阐述了 AOP 的基本概念,它作为一种编程范式,将横切关注点从核心业务逻辑中分离,极大地提升了代码的可维护性与复用性。Spring AOP 则是基于 AOP 思想在 Spring 框架中的具体实现,通过动态代理机制,在运行时为目标对象创建代理,实现对方法的增强。接着,我们详细剖析了 Spring AOP 底层依赖的动态代理机制,包括 JDK 动态代理和 Cglib 动态代理。JDK 动态代理基于反射,要求目标对象实现接口;Cglib 动态代理通过字节码生成技术,可代理普通类。Spring 会根据目标对象是否实现接口来自动选择合适的代理方式。在实战部分,我们一步步搭建 Spring AOP 的开发环境,通过注解和 XML 配置两种方式定义切面、切点以及各种通知类型,如前置通知、后置通知、返回通知、异常通知和环绕通知,每种通知都有其独特的执行时机和应用场景。同时,我们还深入学习了切点表达式的语法和使用,它能精准地指定哪些方法会被切面所影响。进一步地,我们探讨了 Spring AOP 的高级特性,如引入功能可以在运行时为目标对象动态添加新的接口和实现;多切面的使用中,我们了解了如何通过实现Ordered接口或使用@Order注解来设置切面的优先级,以控制多个切面的执行顺序。最后,我们列举了 Spring AOP 在日志记录、事务管理、权限控制和性能监控等多个实际应用场景中的具体应用。5.2 知识扩展AspectJ 与 Spring AOP:Spring AOP 虽然功能强大,但它与 AspectJ 有着紧密的联系和一些区别。AspectJ 是一个功能更为全面的 AOP 框架,它不仅支持运行时织入,还支持编译时织入和类加载时织入。编译时织入可以在编译阶段就将切面逻辑织入到字节码中,这样可以获得更好的性能,因为不需要在运行时动态生成代理对象。类加载时织入则是在类加载到 JVM 时进行织入。AspectJ 的切点表达式语言也更为丰富和强大,它可以匹配更多的连接点,如字段访问、构造函数调用等,而 Spring AOP 主要侧重于方法级别的拦截。在实际应用中,如果对性能要求极高,或者需要更细粒度的切面控制,可以考虑使用 AspectJ;而 Spring AOP 则更适合于一般的 Spring 项目,因为它与 Spring 框架的集成更加紧密,使用起来更加方便。AOP 在其他框架中的应用:除了 Spring 框架,AOP 在其他一些框架中也有广泛的应用。例如,在 Java EE 开发中,EJB(Enterprise JavaBeans)框架就使用了 AOP 的思想来实现事务管理、安全控制等功能。在 Web 开发中,Struts2 框架也可以通过插件的方式引入 AOP,实现对 Action 的增强,比如日志记录、权限检查等。在一些移动开发框架中,AOP 也被用于实现一些通用的功能,如性能监控、错误处理等,以提高应用的质量和可维护性。了解 AOP 在不同框架中的应用,可以帮助我们更好地理解 AOP 的通用性和重要性,以及如何在不同的技术栈中灵活运用 AOP 来解决实际问题。5.3 阅读资料推荐Spring 官方文档:Spring 官方文档是学习 Spring AOP 最权威的资料,它详细介绍了 Spring AOP 的各种功能、配置方式以及使用场景,并且会随着 Spring 版本的更新而及时更新,能够让我们了解到 Spring AOP 的最新特性和最佳实践。可以访问 Spring 官方网站(https://spring.io/projects/spring-framework)获取相关文档。《Spring 实战》:这本书是学习 Spring 框架的经典书籍,其中有专门的章节深入讲解 Spring AOP 的原理和实践,通过大量的代码示例和实际案例,帮助读者更好地理解和掌握 Spring AOP 的使用方法,对于想要深入学习 Spring AOP 的开发者来说是一本不可多得的好书。相关技术博客:在技术博客平台上,有许多技术专家和开发者分享了他们在使用 Spring AOP 过程中的经验和心得,如 InfoQ、开源中国等。通过阅读这些博客,可以了解到 Spring AOP 在实际项目中的应用案例、遇到的问题及解决方案,拓宽我们的技术视野,学习到一些实用的技巧和方法。————————————————原文链接:https://blog.csdn.net/weixin_63944437/article/details/155195732
  • [技术干货] 【JAVA 进阶】重生之霸道总裁手把手教我 Linux 实战技术
    前言Linux 作为开源操作系统的代表,在服务器、云计算、容器化等领域占据着绝对的主导地位。无论你是运维工程师、后端开发者,还是系统架构师,掌握 Linux 实战技能都是必不可少的核心能力。本文将通过理论结合实践的方式,带你深入了解 Linux 系统的核心概念和实战技巧,从基础环境搭建到高级系统管理,构建完整的 Linux 技能体系。在当今的技术环境中,Linux 不仅仅是一个操作系统,更是整个技术生态的基石。从 Web 服务器到大数据平台,从容器编排到人工智能训练,Linux 都扮演着至关重要的角色。掌握 Linux 实战技能,意味着你能够更好地理解和管理现代化的技术基础设施,为职业发展奠定坚实的基础。1. Linux 系统基础与环境搭建1.1 Linux 发行版选择与特点1.1.1 主流发行版对比分析Linux 生态系统中存在众多发行版,每个发行版都有其独特的特点和适用场景。理解不同发行版的特性对于选择合适的系统环境至关重要。Ubuntu 是基于 Debian 的发行版,以用户友好和社区支持著称。它采用 APT 包管理系统,拥有丰富的软件仓库和详细的文档。Ubuntu 的 LTS(长期支持)版本提供 5 年的安全更新,非常适合生产环境使用。CentOS/RHEL 系列是企业级 Linux 的代表,以稳定性和安全性见长。它们使用 YUM/DNF 包管理器,遵循严格的发布周期,确保系统的可靠性。Red Hat 的商业支持使其在企业环境中广受欢迎。Debian 是最古老的发行版之一,以其严格的自由软件政策和稳定性著称。它是许多其他发行版的基础,包管理系统 APT 的设计理念影响了整个 Linux 生态。# 查看当前系统发行版信息cat /etc/os-release# 查看内核版本uname -r# 查看系统架构uname -m# 查看发行版特定信息lsb_release -a  # Ubuntu/Debiancat /etc/redhat-release  # CentOS/RHEL一键获取完整项目代码bash1.1.2 企业级应用场景选择在企业环境中,发行版的选择往往需要考虑多个因素:技术支持、安全更新、软件兼容性、运维成本等。对于 Web 服务和应用开发,Ubuntu Server 提供了良好的平衡,既有丰富的软件包,又有相对较新的软件版本。对于关键业务系统,RHEL 或其免费替代品如 Rocky Linux、AlmaLinux 提供了更好的稳定性保证。容器化环境中,Alpine Linux 因其极小的体积(约 5MB)和安全性设计而备受青睐。它使用 musl libc 和 busybox,虽然可能存在一些兼容性问题,但在容器场景下优势明显。1.2 系统安装与初始化配置1.2.1 虚拟化环境搭建现代 Linux 学习和开发环境通常建立在虚拟化平台之上。VMware、VirtualBox、KVM 等虚拟化技术为我们提供了灵活的实验环境。# 检查系统是否支持虚拟化egrep -c '(vmx|svm)' /proc/cpuinfo# 安装 KVM 虚拟化环境(Ubuntu)sudo apt updatesudo apt install qemu-kvm libvirt-daemon-system libvirt-clients bridge-utils# 将用户添加到 libvirt 组sudo usermod -aG libvirt $USER# 验证 KVM 安装sudo systemctl status libvirtdvirsh list --all一键获取完整项目代码bash虚拟化环境的优势不仅在于资源隔离,更重要的是它提供了快照、克隆等功能,使得系统实验和恢复变得简单快捷。在学习过程中,你可以随时创建系统快照,在出现问题时快速恢复到之前的状态。1.2.2 系统基础配置优化新安装的 Linux 系统需要进行一系列基础配置,以确保系统的安全性和性能。# 更新系统包sudo apt update && sudo apt upgrade -y  # Ubuntu/Debiansudo yum update -y  # CentOS 7sudo dnf update -y  # CentOS 8+/Fedora# 配置时区sudo timedatectl set-timezone Asia/Shanghaitimedatectl status# 配置主机名sudo hostnamectl set-hostname myserverhostnamectl status# 配置静态 IP(示例)sudo vim /etc/netplan/01-netcfg.yaml  # Ubuntu 18.04+一键获取完整项目代码bash系统优化还包括内核参数调整、服务启动项管理、安全策略配置等。这些配置直接影响系统的性能和安全性,需要根据具体的应用场景进行调整。1.3 命令行基础与终端操作1.3.1 Shell 环境配置Shell 是用户与 Linux 系统交互的主要界面,掌握 Shell 的使用技巧对提高工作效率至关重要。# 查看当前使用的 Shellecho $SHELL# 查看系统可用的 Shellcat /etc/shells# 切换到 zsh(如果已安装)chsh -s /bin/zsh# 配置 bash 环境变量vim ~/.bashrc# 添加常用别名alias ll='ls -alF'alias la='ls -A'alias l='ls -CF'alias grep='grep --color=auto'# 重新加载配置source ~/.bashrc一键获取完整项目代码bash现代 Shell 环境通常配合 Oh My Zsh、Powerline 等工具使用,提供语法高亮、自动补全、主题美化等功能,大大提升命令行使用体验。1.3.2 常用命令实战演练Linux 命令行工具丰富多样,掌握核心命令的使用方法是 Linux 实战的基础。# 文件和目录操作ls -la /home/  # 详细列出目录内容find /var/log -name "*.log" -mtime -7  # 查找7天内修改的日志文件du -sh /var/*  # 查看目录大小df -h  # 查看磁盘使用情况# 文本处理grep -r "error" /var/log/  # 递归搜索错误信息sed 's/old/new/g' file.txt  # 替换文本awk '{print $1}' access.log | sort | uniq -c  # 统计访问IP# 进程管理ps aux | grep nginx  # 查看nginx进程top -p $(pgrep nginx)  # 监控特定进程kill -9 $(pgrep -f "python script.py")  # 强制终止进程一键获取完整项目代码bash这些命令的组合使用能够解决大部分日常运维任务,熟练掌握它们是成为 Linux 专家的必经之路。2. 文件系统与权限管理实战2.1 Linux 文件系统架构深度解析2.1.1 文件系统层次结构标准Linux 文件系统遵循 FHS(Filesystem Hierarchy Standard)标准,这种统一的目录结构使得不同发行版之间保持了良好的兼容性。根目录(/)是整个文件系统的起点,所有其他目录都是它的子目录。/bin 存放基本的用户命令,/sbin 存放系统管理命令,/etc 包含系统配置文件,/var 存储可变数据如日志文件,/home 是用户主目录的默认位置。# 查看根目录结构ls -la /# 查看各目录的作用man hier# 查看文件系统挂载信息mount | column -tdf -T  # 显示文件系统类型# 查看目录大小(按层级)du -h --max-depth=1 /一键获取完整项目代码bash理解文件系统结构对于系统管理至关重要。例如,当系统空间不足时,你需要知道哪些目录可能占用大量空间(如 /var/log、/tmp),以及如何安全地清理这些目录。2.1.2 inode 与文件存储机制inode(index node)是 Linux 文件系统的核心概念,它存储了文件的元数据信息,包括文件大小、权限、时间戳、数据块位置等,但不包含文件名。# 查看文件的 inode 信息ls -i filenamestat filename# 查看文件系统 inode 使用情况df -i# 查找相同 inode 的文件(硬链接)find /path -inum 12345# 创建硬链接和软链接ln original_file hard_linkln -s original_file soft_link# 查看链接信息ls -li original_file hard_link soft_link一键获取完整项目代码bash理解 inode 机制有助于解决一些常见问题,比如为什么删除大文件后磁盘空间没有释放(文件仍被进程占用),或者为什么 inode 耗尽会导致无法创建新文件。2.2 权限管理与访问控制2.2.1 传统权限模型详解Linux 的权限系统基于用户(User)、组(Group)、其他(Other)三个层次,每个层次都有读(r)、写(w)、执行(x)三种权限。# 查看文件权限ls -l filename# 修改文件权限(数字方式)chmod 755 script.sh  # rwxr-xr-xchmod 644 config.txt  # rw-r--r--# 修改文件权限(符号方式)chmod u+x script.sh  # 给所有者添加执行权限chmod g-w file.txt   # 移除组的写权限chmod o=r file.txt   # 设置其他用户只读权限# 修改文件所有者和组chown user:group filenamechgrp group filename# 递归修改目录权限chmod -R 755 /path/to/directorychown -R user:group /path/to/directory一键获取完整项目代码bash权限管理不仅涉及文件访问控制,还关系到系统安全。合理的权限设置能够防止未授权访问,减少安全风险。2.2.2 ACL 高级权限控制传统的 Linux 权限模型有时无法满足复杂的访问控制需求,ACL(Access Control List)提供了更细粒度的权限管理。# 检查文件系统是否支持 ACLmount | grep acl# 查看文件的 ACLgetfacl filename# 设置 ACL 权限setfacl -m u:username:rw filename  # 给特定用户读写权限setfacl -m g:groupname:r filename  # 给特定组读权限setfacl -m o::--- filename         # 移除其他用户的所有权限# 设置默认 ACL(对目录)setfacl -d -m u:username:rwx /path/to/directory# 移除 ACLsetfacl -x u:username filenamesetfacl -b filename  # 移除所有 ACL一键获取完整项目代码bashACL 在多用户环境和复杂权限需求场景中非常有用,比如需要给多个不同用户或组设置不同权限级别的情况。2.3 文件操作与管理实践2.3.1 批量文件处理技巧在实际工作中,经常需要对大量文件进行批量操作,掌握高效的批量处理技巧能够大大提高工作效率。# 批量重命名文件for file in *.txt; do    mv "$file" "${file%.txt}.bak"done# 使用 rename 命令(更强大)rename 's/\.txt$/.bak/' *.txt# 批量修改文件权限find /path -type f -name "*.sh" -exec chmod +x {} \;# 批量查找和替换文件内容find /path -name "*.conf" -exec sed -i 's/old_value/new_value/g' {} \;# 批量压缩文件find /var/log -name "*.log" -mtime +30 -exec gzip {} \;一键获取完整项目代码bash这些批量操作技巧在系统维护、日志管理、配置更新等场景中经常用到,熟练掌握能够显著提高运维效率。2.3.2 文件系统维护与优化文件系统的健康状态直接影响系统性能和数据安全,定期的维护和优化是必要的。# 检查文件系统错误sudo fsck /dev/sdb1# 查看文件系统详细信息tune2fs -l /dev/sdb1# 优化文件系统参数tune2fs -o journal_data_writeback /dev/sdb1# 查看磁盘 I/O 统计iostat -x 1# 监控文件系统使用情况watch -n 1 'df -h'# 清理系统临时文件sudo find /tmp -type f -atime +7 -deletesudo journalctl --vacuum-time=7d  # 清理系统日志一键获取完整项目代码bash定期的文件系统维护包括检查磁盘错误、清理临时文件、优化性能参数等,这些操作有助于保持系统的稳定运行。3. 进程管理与系统监控3.1 进程生命周期与管理机制3.1.1 进程创建与调度原理Linux 进程管理是操作系统的核心功能之一,理解进程的创建、调度和终止机制对于系统优化和故障排查至关重要。进程在 Linux 中通过 fork() 系统调用创建,新进程(子进程)是父进程的完整副本。进程调度器根据优先级、时间片等因素决定哪个进程获得 CPU 时间。# 查看进程树pstree -p# 查看进程详细信息ps auxps -ef# 查看特定进程的详细状态cat /proc/PID/statuscat /proc/PID/cmdline# 监控进程资源使用top -p PIDhtop  # 更友好的界面# 查看进程打开的文件lsof -p PID# 查看进程的内存映射cat /proc/PID/maps一键获取完整项目代码bash进程状态包括运行(R)、睡眠(S)、不可中断睡眠(D)、僵尸(Z)、停止(T)等。理解这些状态有助于诊断系统性能问题。3.1.2 进程间通信机制进程间通信(IPC)是多进程系统中的重要概念,Linux 提供了多种 IPC 机制,包括管道、信号、共享内存、消息队列等。# 查看系统 IPC 资源ipcs -a# 查看共享内存ipcs -m# 查看消息队列ipcs -q# 查看信号量ipcs -s# 清理 IPC 资源ipcrm -m shmid  # 删除共享内存ipcrm -q msgid  # 删除消息队列# 监控进程信号kill -l  # 列出所有信号kill -USR1 PID  # 发送用户定义信号一键获取完整项目代码bash在实际应用中,不同的 IPC 机制适用于不同的场景。管道适合简单的数据传递,共享内存适合大量数据交换,信号适合简单的通知机制。3.2 系统资源监控与性能分析3.2.1 CPU 与内存监控实战系统性能监控是运维工作的重要组成部分,及时发现和解决性能瓶颈能够保证系统的稳定运行。# CPU 使用率监控tophtopvmstat 1  # 每秒更新一次# 查看 CPU 详细信息lscpucat /proc/cpuinfo# 内存使用监控free -hcat /proc/meminfo# 查看内存使用详情ps aux --sort=-%mem | head -10  # 按内存使用排序pmap PID  # 查看进程内存映射# 系统负载监控uptimewcat /proc/loadavg一键获取完整项目代码bashCPU 使用率、内存使用率、系统负载是最重要的性能指标。负载平均值反映了系统的繁忙程度,通常应该低于 CPU 核心数。3.2.2 磁盘 I/O 性能分析磁盘 I/O 往往是系统性能的瓶颈,特别是在数据密集型应用中。# 磁盘 I/O 监控iostat -x 1iotop  # 按进程显示 I/O 使用情况# 查看磁盘使用情况df -hdu -sh /path/*# 监控磁盘 I/O 等待vmstat 1  # 关注 wa 列(I/O 等待时间)# 查看磁盘读写速度hdparm -t /dev/sda  # 测试磁盘读取速度dd if=/dev/zero of=testfile bs=1M count=1024  # 测试写入速度# 查看文件系统 I/O 统计cat /proc/diskstats一键获取完整项目代码bash高 I/O 等待时间通常表示磁盘性能瓶颈,可能需要优化应用程序的 I/O 模式或升级存储设备。3.3 服务管理与自动化运维3.3.1 Systemd 服务管理Systemd 是现代 Linux 发行版的标准初始化系统和服务管理器,它提供了强大的服务管理功能。# 查看系统服务状态systemctl statussystemctl list-units --type=service# 管理服务systemctl start nginxsystemctl stop nginxsystemctl restart nginxsystemctl reload nginx# 设置服务开机启动systemctl enable nginxsystemctl disable nginx# 查看服务日志journalctl -u nginxjournalctl -u nginx -f  # 实时查看日志# 创建自定义服务sudo vim /etc/systemd/system/myapp.service一键获取完整项目代码bash自定义服务配置示例:[Unit]Description=My ApplicationAfter=network.target[Service]Type=simpleUser=myuserWorkingDirectory=/opt/myappExecStart=/opt/myapp/start.shRestart=always[Install]WantedBy=multi-user.target一键获取完整项目代码iniSystemd 的优势在于并行启动、依赖管理、资源控制等功能,大大提高了系统启动速度和服务管理效率。3.3.2 定时任务与作业调度定时任务是自动化运维的重要工具,Linux 提供了 cron 和 systemd timer 两种主要的定时任务机制。# 管理 cron 任务crontab -l  # 列出当前用户的定时任务crontab -e  # 编辑定时任务# cron 时间格式:分 时 日 月 周# 示例:每天凌晨2点执行备份0 2 * * * /path/to/backup.sh# 查看系统级定时任务ls -la /etc/cron.*# 使用 systemd timersystemctl list-timerssystemctl status backup.timer# 创建 systemd timersudo vim /etc/systemd/system/backup.timer一键获取完整项目代码bashSystemd timer 配置示例:[Unit]Description=Daily Backup Timer[Timer]OnCalendar=dailyPersistent=true[Install]WantedBy=timers.target一键获取完整项目代码ini定时任务在日志轮转、系统备份、性能监控等场景中广泛应用,合理的任务调度能够减少人工干预,提高运维效率。4. 网络配置与服务管理4.1 网络基础配置与故障排查4.1.1 网络接口配置管理网络配置是 Linux 系统管理的重要组成部分,正确的网络配置是系统正常运行的基础。现代 Linux 系统使用不同的网络管理工具,Ubuntu 18.04+ 使用 Netplan,CentOS/RHEL 使用 NetworkManager 或传统的网络脚本。# 查看网络接口信息ip addr showip link showifconfig  # 传统命令# 查看路由表ip route showroute -n# 查看网络连接状态ss -tulnnetstat -tuln# 测试网络连通性ping -c 4 google.comtraceroute google.commtr google.com  # 结合 ping 和 traceroute# 查看 DNS 配置cat /etc/resolv.confnslookup google.comdig google.com一键获取完整项目代码bash网络故障排查通常遵循分层诊断的方法:首先检查物理连接,然后检查网络接口配置,再检查路由和 DNS 设置。4.1.2 路由与防火墙设置路由配置决定了数据包的转发路径,防火墙则控制网络访问的安全策略。# 添加静态路由sudo ip route add 192.168.2.0/24 via 192.168.1.1sudo route add -net 192.168.2.0/24 gw 192.168.1.1# 永久路由配置(Ubuntu)sudo vim /etc/netplan/01-netcfg.yaml# 防火墙管理(UFW - Ubuntu)sudo ufw statussudo ufw enablesudo ufw allow 22/tcpsudo ufw allow from 192.168.1.0/24sudo ufw deny 80/tcp# 防火墙管理(firewalld - CentOS/RHEL)sudo firewall-cmd --statesudo firewall-cmd --list-allsudo firewall-cmd --add-service=http --permanentsudo firewall-cmd --add-port=8080/tcp --permanentsudo firewall-cmd --reload# iptables 直接操作sudo iptables -L -nsudo iptables -A INPUT -p tcp --dport 22 -j ACCEPTsudo iptables -A INPUT -s 192.168.1.0/24 -j ACCEPT一键获取完整项目代码bash防火墙配置需要平衡安全性和可用性,过于严格的规则可能影响正常服务,过于宽松则存在安全风险。4.2 常用网络服务部署4.2.1 Web 服务器配置实战Web 服务器是最常见的网络服务之一,Apache 和 Nginx 是两个主流的 Web 服务器软件。# 安装 Nginxsudo apt install nginx  # Ubuntusudo yum install nginx  # CentOS# 启动和管理 Nginxsudo systemctl start nginxsudo systemctl enable nginxsudo systemctl status nginx# 测试配置文件语法sudo nginx -t# 重新加载配置sudo systemctl reload nginx# 查看 Nginx 进程和端口ps aux | grep nginxss -tuln | grep :80一键获取完整项目代码bashNginx 虚拟主机配置示例:server {    listen 80;    server_name example.com www.example.com;    root /var/www/example.com;    index index.html index.php;    location / {        try_files $uri $uri/ =404;    }    location ~ \.php$ {        fastcgi_pass unix:/var/run/php/php7.4-fpm.sock;        fastcgi_index index.php;        include fastcgi_params;    }}一键获取完整项目代码nginxWeb 服务器配置涉及虚拟主机、SSL 证书、负载均衡、缓存等多个方面,需要根据具体需求进行优化。4.2.2 数据库服务管理数据库是现代应用的核心组件,MySQL/MariaDB 和 PostgreSQL 是最常用的开源数据库系统。# 安装 MySQLsudo apt install mysql-server  # Ubuntusudo yum install mysql-server  # CentOS# 启动 MySQL 服务sudo systemctl start mysqlsudo systemctl enable mysql# 安全配置sudo mysql_secure_installation# 连接数据库mysql -u root -p# 查看数据库状态sudo systemctl status mysqlmysqladmin -u root -p statusmysqladmin -u root -p processlist# 备份和恢复mysqldump -u root -p database_name > backup.sqlmysql -u root -p database_name < backup.sql一键获取完整项目代码bash数据库性能监控:# 查看 MySQL 进程mysqladmin -u root -p processlist# 查看数据库状态mysql -u root -p -e "SHOW STATUS LIKE 'Threads%'"mysql -u root -p -e "SHOW STATUS LIKE 'Questions'"# 查看慢查询日志sudo tail -f /var/log/mysql/mysql-slow.log一键获取完整项目代码bash数据库管理包括性能优化、备份策略、安全配置等多个方面,需要根据应用特点制定相应的管理策略。4.3 网络安全与访问控制4.3.1 SSH 安全配置SSH 是远程管理 Linux 系统的主要方式,正确的 SSH 配置对系统安全至关重要。# SSH 服务管理sudo systemctl status sshsudo systemctl restart ssh# SSH 配置文件sudo vim /etc/ssh/sshd_config# 生成 SSH 密钥对ssh-keygen -t rsa -b 4096 -C "your_email@example.com"# 复制公钥到远程服务器ssh-copy-id user@remote_host# 使用密钥登录ssh -i ~/.ssh/id_rsa user@remote_host# 查看 SSH 连接日志sudo journalctl -u sshsudo tail -f /var/log/auth.log一键获取完整项目代码bashSSH 安全配置要点:# 禁用 root 直接登录PermitRootLogin no# 修改默认端口Port 2222# 禁用密码认证(仅使用密钥)PasswordAuthentication no# 限制登录用户AllowUsers user1 user2# 设置登录超时ClientAliveInterval 300ClientAliveCountMax 2一键获取完整项目代码bash4.3.2 网络流量监控网络流量监控有助于发现异常活动、优化网络性能和进行容量规划。# 实时网络流量监控iftop  # 按连接显示流量nethogs  # 按进程显示流量nload  # 简单的带宽监控# 网络统计信息cat /proc/net/devss -i  # 显示详细的套接字信息# 抓包分析sudo tcpdump -i eth0 -nsudo tcpdump -i eth0 port 80wireshark  # 图形界面抓包工具# 网络连接监控watch -n 1 'ss -tuln'netstat -i  # 接口统计信息一键获取完整项目代码bash网络监控脚本示例:#!/bin/bash# 网络流量监控脚本INTERFACE="eth0"LOG_FILE="/var/log/network_traffic.log"while true; do    RX_BYTES=$(cat /sys/class/net/$INTERFACE/statistics/rx_bytes)    TX_BYTES=$(cat /sys/class/net/$INTERFACE/statistics/tx_bytes)    TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')        echo "$TIMESTAMP RX: $RX_BYTES TX: $TX_BYTES" >> $LOG_FILE    sleep 60done一键获取完整项目代码bash5. Shell 脚本编程实战5.1 Shell 编程基础与语法5.1.1 变量与数据类型Shell 脚本是 Linux 系统管理和自动化的重要工具,掌握 Shell 编程能够大大提高工作效率。Shell 变量不需要声明类型,但理解不同类型的数据处理方式对编写高质量脚本很重要。#!/bin/bash# 变量定义和使用name="Linux"version=20.04readonly PI=3.14159echo "System: $name $version"echo "PI value: $PI"# 数组操作fruits=("apple" "banana" "orange")echo "First fruit: ${fruits[0]}"echo "All fruits: ${fruits[@]}"echo "Array length: ${#fruits[@]}"# 字符串操作text="Hello World"echo "Length: ${#text}"echo "Substring: ${text:0:5}"echo "Replace: ${text/World/Linux}"# 数值运算num1=10num2=20result=$((num1 + num2))echo "Sum: $result"# 使用 bc 进行浮点运算result=$(echo "scale=2; $num1 / 3" | bc)echo "Division: $result"一键获取完整项目代码bash变量作用域和环境变量的理解对于编写复杂脚本非常重要,特别是在多脚本协作的场景中。5.1.2 控制结构与函数控制结构是编程语言的核心,Shell 提供了完整的条件判断、循环和函数定义功能。#!/bin/bash# 条件判断check_file() {    local file=$1        if [[ -f "$file" ]]; then        echo "File $file exists"    elif [[ -d "$file" ]]; then        echo "$file is a directory"    else        echo "$file does not exist"    fi}# 循环结构process_files() {    local directory=$1        # for 循环    for file in "$directory"/*; do        if [[ -f "$file" ]]; then            echo "Processing: $(basename "$file")"        fi    done        # while 循环    local count=0    while [[ $count -lt 5 ]]; do        echo "Count: $count"        ((count++))    done}# 函数定义和调用backup_file() {    local source=$1    local backup_dir=${2:-/backup}        if [[ ! -d "$backup_dir" ]]; then        mkdir -p "$backup_dir"    fi        cp "$source" "$backup_dir/$(basename "$source").$(date +%Y%m%d)"    echo "Backup completed: $source"}# 错误处理safe_execute() {    local command=$1        if ! $command; then        echo "Error: Command failed - $command" >&2        return 1    fi}# 主程序main() {    check_file "/etc/passwd"    process_files "/tmp"    backup_file "/etc/hosts"}main "$@"一键获取完整项目代码bash函数的使用使得脚本更加模块化和可维护,良好的错误处理机制能够提高脚本的健壮性。5.2 实用脚本开发案例5.2.1 系统监控脚本系统监控脚本是运维工作中最常用的工具之一,它能够自动收集系统状态信息并在异常时发出警报。#!/bin/bash# 系统监控脚本SCRIPT_NAME="system_monitor"LOG_FILE="/var/log/${SCRIPT_NAME}.log"ALERT_EMAIL="admin@example.com"CPU_THRESHOLD=80MEMORY_THRESHOLD=85DISK_THRESHOLD=90# 日志函数log_message() {    local level=$1    local message=$2    local timestamp=$(date '+%Y-%m-%d %H:%M:%S')    echo "[$timestamp] [$level] $message" | tee -a "$LOG_FILE"}# CPU 监控check_cpu() {    local cpu_usage=$(top -bn1 | grep "Cpu(s)" | awk '{print $2}' | cut -d'%' -f1)    cpu_usage=${cpu_usage%.*}  # 去除小数部分        if [[ $cpu_usage -gt $CPU_THRESHOLD ]]; then        log_message "WARNING" "High CPU usage: ${cpu_usage}%"        send_alert "CPU usage is ${cpu_usage}%"    else        log_message "INFO" "CPU usage normal: ${cpu_usage}%"    fi}# 内存监控check_memory() {    local memory_info=$(free | grep Mem)    local total=$(echo $memory_info | awk '{print $2}')    local used=$(echo $memory_info | awk '{print $3}')    local usage=$((used * 100 / total))        if [[ $usage -gt $MEMORY_THRESHOLD ]]; then        log_message "WARNING" "High memory usage: ${usage}%"        send_alert "Memory usage is ${usage}%"    else        log_message "INFO" "Memory usage normal: ${usage}%"    fi}# 磁盘监控check_disk() {    while IFS= read -r line; do        local usage=$(echo "$line" | awk '{print $5}' | cut -d'%' -f1)        local mount=$(echo "$line" | awk '{print $6}')                if [[ $usage -gt $DISK_THRESHOLD ]]; then            log_message "WARNING" "High disk usage on $mount: ${usage}%"            send_alert "Disk usage on $mount is ${usage}%"        fi    done < <(df -h | grep -E '^/dev/')}# 发送警报send_alert() {    local message=$1    echo "$message" | mail -s "System Alert - $(hostname)" "$ALERT_EMAIL"}# 主监控函数main() {    log_message "INFO" "Starting system monitoring"        check_cpu    check_memory    check_disk        log_message "INFO" "System monitoring completed"}# 执行监控main "$@"一键获取完整项目代码bash这个监控脚本可以通过 cron 定期执行,实现自动化的系统监控和告警。5.2.2 自动化部署脚本自动化部署脚本能够标准化应用部署流程,减少人为错误,提高部署效率。#!/bin/bash# 自动化部署脚本APP_NAME="myapp"APP_DIR="/opt/$APP_NAME"BACKUP_DIR="/backup/$APP_NAME"GIT_REPO="https://github.com/user/myapp.git"SERVICE_NAME="$APP_NAME"# 颜色输出RED='\033[0;31m'GREEN='\033[0;32m'YELLOW='\033[1;33m'NC='\033[0m' # No Colorprint_status() {    local status=$1    local message=$2        case $status in        "INFO")            echo -e "${GREEN}[INFO]${NC} $message"            ;;        "WARNING")            echo -e "${YELLOW}[WARNING]${NC} $message"            ;;        "ERROR")            echo -e "${RED}[ERROR]${NC} $message"            ;;    esac}# 检查依赖check_dependencies() {    print_status "INFO" "Checking dependencies..."        local deps=("git" "systemctl" "nginx")    for dep in "${deps[@]}"; do        if ! command -v "$dep" &> /dev/null; then            print_status "ERROR" "Dependency not found: $dep"            exit 1        fi    done        print_status "INFO" "All dependencies satisfied"}# 备份当前版本backup_current() {    if [[ -d "$APP_DIR" ]]; then        print_status "INFO" "Backing up current version..."        local backup_name="$BACKUP_DIR/$(date +%Y%m%d_%H%M%S)"        mkdir -p "$backup_name"        cp -r "$APP_DIR"/* "$backup_name/"        print_status "INFO" "Backup completed: $backup_name"    fi}# 部署新版本deploy_app() {    print_status "INFO" "Deploying application..."        # 停止服务    if systemctl is-active --quiet "$SERVICE_NAME"; then        print_status "INFO" "Stopping service: $SERVICE_NAME"        sudo systemctl stop "$SERVICE_NAME"    fi        # 克隆或更新代码    if [[ -d "$APP_DIR/.git" ]]; then        print_status "INFO" "Updating existing repository..."        cd "$APP_DIR" && git pull origin main    else        print_status "INFO" "Cloning repository..."        sudo rm -rf "$APP_DIR"        sudo git clone "$GIT_REPO" "$APP_DIR"    fi        # 安装依赖    if [[ -f "$APP_DIR/requirements.txt" ]]; then        print_status "INFO" "Installing Python dependencies..."        pip install -r "$APP_DIR/requirements.txt"    fi        if [[ -f "$APP_DIR/package.json" ]]; then        print_status "INFO" "Installing Node.js dependencies..."        cd "$APP_DIR" && npm install    fi        # 设置权限    sudo chown -R www-data:www-data "$APP_DIR"    sudo chmod -R 755 "$APP_DIR"}# 启动服务start_services() {    print_status "INFO" "Starting services..."        sudo systemctl start "$SERVICE_NAME"    sudo systemctl enable "$SERVICE_NAME"        # 检查服务状态    if systemctl is-active --quiet "$SERVICE_NAME"; then        print_status "INFO" "Service started successfully: $SERVICE_NAME"    else        print_status "ERROR" "Failed to start service: $SERVICE_NAME"        exit 1    fi}# 健康检查health_check() {    print_status "INFO" "Performing health check..."        local max_attempts=30    local attempt=0        while [[ $attempt -lt $max_attempts ]]; do        if curl -f http://localhost:8080/health &> /dev/null; then            print_status "INFO" "Health check passed"            return 0        fi                ((attempt++))        sleep 2    done        print_status "ERROR" "Health check failed"    return 1}# 回滚函数rollback() {    print_status "WARNING" "Rolling back to previous version..."        local latest_backup=$(ls -t "$BACKUP_DIR" | head -n1)    if [[ -n "$latest_backup" ]]; then        sudo systemctl stop "$SERVICE_NAME"        sudo rm -rf "$APP_DIR"        sudo cp -r "$BACKUP_DIR/$latest_backup" "$APP_DIR"        sudo systemctl start "$SERVICE_NAME"        print_status "INFO" "Rollback completed"    else        print_status "ERROR" "No backup found for rollback"    fi}# 主部署流程main() {    print_status "INFO" "Starting deployment of $APP_NAME"        check_dependencies    backup_current    deploy_app    start_services        if health_check; then        print_status "INFO" "Deployment completed successfully"    else        print_status "ERROR" "Deployment failed, initiating rollback"        rollback        exit 1    fi}# 处理命令行参数case "${1:-deploy}" in    "deploy")        main        ;;    "rollback")        rollback        ;;    *)        echo "Usage: $0 {deploy|rollback}"        exit 1        ;;esac一键获取完整项目代码bash5.3 脚本优化与最佳实践5.3.1 性能优化技巧Shell 脚本的性能优化对于处理大量数据或频繁执行的脚本非常重要。#!/bin/bash# 性能优化示例# 1. 避免不必要的子进程# 慢速方法slow_method() {    for file in $(ls /path/*.txt); do        echo "Processing: $file"    done}# 快速方法fast_method() {    for file in /path/*.txt; do        [[ -f "$file" ]] && echo "Processing: $file"    done}# 2. 使用内置命令替代外部命令# 慢速方法count_lines_slow() {    local file=$1    wc -l < "$file"}# 快速方法count_lines_fast() {    local file=$1    local count=0    while IFS= read -r line; do        ((count++))    done < "$file"    echo $count}# 3. 批量处理而非逐个处理# 慢速方法process_files_slow() {    for file in *.log; do        grep "ERROR" "$file" > "${file}.errors"    done}# 快速方法process_files_fast() {    grep -l "ERROR" *.log | xargs -I {} sh -c 'grep "ERROR" "$1" > "$1.errors"' _ {}}# 4. 使用关联数组进行查找declare -A user_groupsuser_groups["alice"]="admin"user_groups["bob"]="user"user_groups["charlie"]="admin"# 快速查找用户组get_user_group() {    local user=$1    echo "${user_groups[$user]:-unknown}"}一键获取完整项目代码bash5.3.2 错误处理与调试良好的错误处理和调试机制是高质量脚本的重要特征。#!/bin/bash# 错误处理和调试示例# 设置严格模式set -euo pipefail  # 遇到错误立即退出,未定义变量报错,管道错误传播# 调试模式DEBUG=${DEBUG:-0}debug_log() {    [[ $DEBUG -eq 1 ]] && echo "[DEBUG] $*" >&2}# 错误处理函数error_exit() {    local message=$1    local code=${2:-1}    echo "ERROR: $message" >&2    exit $code}# 清理函数cleanup() {    local exit_code=$?    debug_log "Cleaning up temporary files"    rm -f /tmp/script_temp_*    exit $exit_code}# 注册清理函数trap cleanup EXITtrap 'error_exit "Script interrupted" 130' INT# 参数验证validate_parameters() {    [[ $# -lt 1 ]] && error_exit "Usage: $0 <input_file>"    [[ ! -f "$1" ]] && error_exit "Input file does not exist: $1"    [[ ! -r "$1" ]] && error_exit "Input file is not readable: $1"}# 安全的文件操作safe_file_operation() {    local source=$1    local destination=$2        # 创建临时文件    local temp_file=$(mktemp /tmp/script_temp_XXXXXX)        # 执行操作    if cp "$source" "$temp_file"; then        debug_log "File copied to temporary location"                # 验证文件完整性        if cmp -s "$source" "$temp_file"; then            mv "$temp_file" "$destination"            debug_log "File operation completed successfully"        else            error_exit "File integrity check failed"        fi    else        error_exit "Failed to copy file: $source"    fi}# 重试机制retry_command() {    local max_attempts=$1    local delay=$2    shift 2    local command=("$@")        local attempt=1    while [[ $attempt -le $max_attempts ]]; do        debug_log "Attempt $attempt of $max_attempts: ${command[*]}"                if "${command[@]}"; then            debug_log "Command succeeded on attempt $attempt"            return 0        fi                if [[ $attempt -lt $max_attempts ]]; then            debug_log "Command failed, retrying in ${delay}s"            sleep $delay        fi                ((attempt++))    done        error_exit "Command failed after $max_attempts attempts: ${command[*]}"}# 主函数main() {    validate_parameters "$@"        local input_file=$1    local output_file="${input_file}.processed"        debug_log "Processing file: $input_file"        # 使用重试机制执行网络操作    retry_command 3 5 curl -f "http://api.example.com/process" -d "@$input_file"        # 安全的文件操作    safe_file_operation "$input_file" "$output_file"        echo "Processing completed: $output_file"}# 执行主函数main "$@"一键获取完整项目代码bash6. 总结与展望6.1 知识点总结与扩展通过本文的深入学习,我们系统地掌握了 Linux 实战技能的核心内容。让我们回顾一下主要的知识点:系统基础层面,我们学习了不同 Linux 发行版的特点和选择策略,掌握了系统安装、配置和基本命令操作。这些基础技能是所有高级操作的前提,就像建筑的地基一样重要。文件系统管理方面,我们深入理解了 Linux 文件系统的层次结构、inode 机制、权限模型等核心概念。这些知识不仅帮助我们更好地管理文件和目录,还为理解系统性能和安全提供了理论基础。进程和系统监控领域,我们学习了进程生命周期、资源监控、服务管理等关键技能。这些技能在系统优化、故障排查、性能调优等场景中发挥着重要作用。网络配置和服务管理是现代 Linux 系统的重要组成部分,我们掌握了网络配置、服务部署、安全管理等实用技能,这些技能在云计算和微服务架构中尤为重要。Shell 脚本编程作为自动化运维的核心工具,我们学习了从基础语法到高级应用的完整技能体系,包括性能优化和错误处理等最佳实践。扩展学习方向:容器技术:Docker 和 Kubernetes 已成为现代应用部署的标准,建议深入学习容器编排和管理配置管理:Ansible、Puppet、Chef 等工具能够实现大规模系统的自动化配置管理监控和日志:Prometheus、Grafana、ELK Stack 等工具提供了完整的监控和日志分析解决方案云原生技术:学习云平台服务、微服务架构、服务网格等现代技术栈6.2 推荐阅读资料为了帮助大家进一步提升 Linux 技能,我推荐以下优质学习资源:经典书籍:《鸟哥的 Linux 私房菜》- Linux 入门经典,适合初学者系统学习《Linux 系统管理技术手册》- 深入的系统管理指南,适合进阶学习《高性能 Linux 服务器构建实战》- 专注于性能优化和服务器配置在线资源:Linux Foundation 官方文档和认证课程Red Hat 官方学习资源和实验室环境ArchWiki - 详细的技术文档和配置指南实践平台:VirtualBox/VMware - 本地虚拟化实验环境AWS/Azure/GCP - 云平台实践环境GitHub - 开源项目学习和贡献相关技术博文推荐:《Docker 容器化实战指南》- 深入学习容器技术《Kubernetes 集群管理实践》- 容器编排和管理《Ansible 自动化运维实战》- 配置管理和自动化部署《Prometheus 监控系统构建》- 现代监控解决方案6.3 技术讨论与思考Linux 技术的学习是一个持续的过程,随着技术的发展,新的挑战和机遇不断涌现。让我们思考几个值得探讨的问题:1. 传统运维 vs DevOps随着 DevOps 文化的普及,传统的运维模式正在发生变化。如何在保持系统稳定性的同时,提高部署频率和响应速度?Infrastructure as Code(IaC)如何改变我们管理基础设施的方式?2. 容器化对传统 Linux 管理的影响容器技术的普及是否意味着传统的 Linux 系统管理技能变得不那么重要?实际上,容器技术让我们更需要深入理解 Linux 内核、网络、存储等底层机制。3. 云原生时代的 Linux 技能在云原生环境中,Linux 系统往往是不可变的基础设施。这种变化对运维人员的技能要求有什么影响?如何适应这种新的运维模式?4. 安全性与便利性的平衡在追求自动化和效率的同时,如何确保系统的安全性?零信任网络架构对传统的 Linux 安全管理提出了哪些新要求?讨论话题:你在 Linux 学习和实践中遇到过哪些挑战?对于初学者,你认为最重要的 Linux 技能是什么?在你的工作环境中,Linux 自动化程度如何?还有哪些可以改进的地方?你如何看待 Linux 在边缘计算和物联网领域的应用前景?6.4 实践建议与行动计划学习 Linux 最重要的是实践,理论知识只有通过实际操作才能真正掌握。以下是一些实践建议:阶段性学习计划:第一阶段(1-2个月):基础巩固搭建个人实验环境(虚拟机或云服务器)熟练掌握基本命令和文件操作完成简单的系统配置任务第二阶段(2-3个月):进阶应用学习服务管理和网络配置编写基础的 Shell 脚本尝试部署简单的 Web 应用第三阶段(3-6个月):专业提升深入学习性能优化和故障排查掌握自动化运维工具参与开源项目或实际项目实践项目建议:个人博客系统:从零开始部署 LAMP/LNMP 环境监控系统:搭建 Prometheus + Grafana 监控平台自动化脚本:编写系统备份、日志分析等实用脚本容器化应用:将传统应用容器化并部署到 Kubernetes6.5 社区参与与持续学习Linux 是一个开源社区驱动的项目,参与社区是提升技能的重要途径:参与方式:贡献开源项目代码或文档参加本地 Linux 用户组活动在技术论坛分享经验和解答问题撰写技术博客记录学习心得认证考试:Red Hat Certified System Administrator (RHCSA)Linux Professional Institute Certification (LPIC)CompTIA Linux+这些认证不仅能验证你的技能水平,还能为职业发展提供有力支持。6.6 结语与交流邀请Linux 的世界博大精深,本文只是为大家提供了一个入门和进阶的指南。真正的专家之路需要持续的学习、实践和思考。技术在不断发展,但 Linux 的核心理念——开放、自由、协作——始终不变。掌握 Linux 不仅仅是学会一个操作系统,更是拥抱开源文化,培养系统性思维的过程。————————————————原文链接:https://blog.csdn.net/weixin_63944437/article/details/154282632
  • [技术干货] 【Java 进阶】重生之这次我要彻底掌握 Java 中的各种流
    引言在Java编程的世界中,IO流操作是每个开发者都必须掌握的核心技能。从简单的文件读写到复杂的网络通信,从基础的字节操作到高效的NIO编程,Java流操作贯穿了整个应用开发的生命周期。随着Java技术的不断发展,从传统的BIO(Blocking IO)到现代的NIO(Non-blocking IO),再到NIO.2的异步IO,Java为开发者提供了丰富而强大的IO处理能力。掌握这些流操作不仅能够提升代码的性能和可维护性,更是成为高级Java开发者的必备技能。本文将深入探讨Java中各种流操作的原理、特点和实际应用,通过丰富的代码示例和实战场景,帮助读者全面掌握Java流操作的精髓。第一章:Java IO流概述与基础概念1.1 IO流的基本概念与分类Java IO流是Java程序与外部世界进行数据交换的桥梁。从概念上讲,流是一个连续的数据序列,数据可以从源头流向目的地。1.1.1 流的基本分类Java IO流主要可以从以下几个维度进行分类:按数据流向分类:输入流(Input Stream):从数据源读取数据到程序中输出流(Output Stream):从程序向目的地写入数据按处理数据单位分类:字节流(Byte Stream):以字节为单位处理数据,适合处理二进制文件字符流(Character Stream):以字符为单位处理数据,适合处理文本文件// 字节流示例public class ByteStreamExample {    public static void copyFile(String source, String target) throws IOException {        try (FileInputStream fis = new FileInputStream(source);             FileOutputStream fos = new FileOutputStream(target)) {                        byte[] buffer = new byte[1024];            int bytesRead;            while ((bytesRead = fis.read(buffer)) != -1) {                fos.write(buffer, 0, bytesRead);            }        }    }}// 字符流示例public class CharacterStreamExample {    public static void copyTextFile(String source, String target) throws IOException {        try (FileReader reader = new FileReader(source);             FileWriter writer = new FileWriter(target)) {                        char[] buffer = new char[1024];            int charsRead;            while ((charsRead = reader.read(buffer)) != -1) {                writer.write(buffer, 0, charsRead);            }        }    }}一键获取完整项目代码java1.1.2 流的功能分类按功能分类:节点流(Node Stream):直接与数据源或目的地连接处理流(Processing Stream):对其他流进行包装,提供额外功能// 节点流示例 - 直接操作文件FileInputStream nodeStream = new FileInputStream("data.txt");// 处理流示例 - 为节点流添加缓冲功能BufferedInputStream processingStream = new BufferedInputStream(nodeStream);一键获取完整项目代码java1.2 流的层次结构与设计模式1.2.1 字节流层次结构Java字节流的核心是InputStream和OutputStream两个抽象类:// InputStream层次结构核心方法public abstract class InputStream implements Closeable {    // 核心抽象方法    public abstract int read() throws IOException;        // 批量读取方法    public int read(byte b[]) throws IOException {        return read(b, 0, b.length);    }        // 带偏移量的批量读取    public int read(byte b[], int off, int len) throws IOException {        // 默认实现,子类可以重写以提高效率        if (b == null) {            throw new NullPointerException();        } else if (off < 0 || len < 0 || len > b.length - off) {            throw new IndexOutOfBoundsException();        } else if (len == 0) {            return 0;        }        int c = read();        if (c == -1) {            return -1;        }        b[off] = (byte)c;        int i = 1;        try {            for (; i < len ; i++) {                c = read();                if (c == -1) {                    break;                }                b[off + i] = (byte)c;            }        } catch (IOException ee) {        }        return i;    }}一键获取完整项目代码java1.2.2 装饰器模式在IO流中的应用Java IO流大量使用了装饰器模式,通过层层包装来增强流的功能:public class DecoratorPatternExample {    public static void demonstrateDecorator() throws IOException {        // 基础节点流        FileInputStream fileStream = new FileInputStream("data.txt");                // 添加缓冲功能        BufferedInputStream bufferedStream = new BufferedInputStream(fileStream);                // 添加数据解析功能        DataInputStream dataStream = new DataInputStream(bufferedStream);                // 现在可以高效地读取各种数据类型        int intValue = dataStream.readInt();        double doubleValue = dataStream.readDouble();        String stringValue = dataStream.readUTF();                dataStream.close(); // 关闭最外层流会自动关闭内层流    }}一键获取完整项目代码java1.3 IO流的选择策略1.3.1 性能考虑因素选择合适的IO流需要考虑多个因素:public class StreamSelectionStrategy {        // 小文件快速读取    public static String readSmallFile(String filename) throws IOException {        try (BufferedReader reader = Files.newBufferedReader(Paths.get(filename))) {            return reader.lines().collect(Collectors.joining("\n"));        }    }        // 大文件分块处理    public static void processLargeFile(String filename) throws IOException {        try (BufferedReader reader = Files.newBufferedReader(Paths.get(filename))) {            String line;            int lineCount = 0;            while ((line = reader.readLine()) != null) {                processLine(line);                if (++lineCount % 10000 == 0) {                    System.out.println("Processed " + lineCount + " lines");                }            }        }    }        private static void processLine(String line) {        // 处理单行数据    }        // 二进制文件高效复制    public static void copyBinaryFile(String source, String target) throws IOException {        try (FileChannel sourceChannel = FileChannel.open(Paths.get(source), StandardOpenOption.READ);             FileChannel targetChannel = FileChannel.open(Paths.get(target),                  StandardOpenOption.CREATE, StandardOpenOption.WRITE)) {                        sourceChannel.transferTo(0, sourceChannel.size(), targetChannel);        }    }}一键获取完整项目代码java第二章:字节流详解与实战应用2.1 InputStream核心类详解2.1.1 FileInputStream文件字节输入流FileInputStream是最常用的字节输入流,用于从文件中读取字节数据:public class FileInputStreamExample {        // 基础文件读取    public static void basicFileRead(String filename) throws IOException {        FileInputStream fis = null;        try {            fis = new FileInputStream(filename);            int byteData;            while ((byteData = fis.read()) != -1) {                System.out.print((char) byteData);            }        } finally {            if (fis != null) {                fis.close();            }        }    }        // 使用try-with-resources的改进版本    public static void improvedFileRead(String filename) throws IOException {        try (FileInputStream fis = new FileInputStream(filename)) {            byte[] buffer = new byte[1024];            int bytesRead;            while ((bytesRead = fis.read(buffer)) != -1) {                System.out.write(buffer, 0, bytesRead);            }        }    }        // 读取文件的特定部分    public static byte[] readFileSegment(String filename, long offset, int length)             throws IOException {        try (RandomAccessFile raf = new RandomAccessFile(filename, "r")) {            raf.seek(offset);            byte[] data = new byte[length];            int bytesRead = raf.read(data);            if (bytesRead < length) {                return Arrays.copyOf(data, bytesRead);            }            return data;        }    }}一键获取完整项目代码java2.1.2 ByteArrayInputStream内存字节流ByteArrayInputStream允许将字节数组作为输入源:public class ByteArrayInputStreamExample {        public static void demonstrateByteArrayStream() throws IOException {        String data = "Hello, Java IO Streams!";        byte[] bytes = data.getBytes(StandardCharsets.UTF_8);                try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes)) {            // 读取前5个字节            byte[] buffer = new byte[5];            int bytesRead = bais.read(buffer);            System.out.println("Read: " + new String(buffer, 0, bytesRead));                        // 标记当前位置            bais.mark(10);                        // 继续读取            int nextByte = bais.read();            System.out.println("Next byte: " + (char) nextByte);                        // 重置到标记位置            bais.reset();                        // 再次读取            nextByte = bais.read();            System.out.println("After reset: " + (char) nextByte);        }    }}一键获取完整项目代码java2.2 OutputStream核心类详解2.2.1 FileOutputStream文件字节输出流public class FileOutputStreamExample {        // 基础文件写入    public static void writeToFile(String filename, String content) throws IOException {        try (FileOutputStream fos = new FileOutputStream(filename)) {            byte[] bytes = content.getBytes(StandardCharsets.UTF_8);            fos.write(bytes);            fos.flush(); // 确保数据写入磁盘        }    }        // 追加模式写入    public static void appendToFile(String filename, String content) throws IOException {        try (FileOutputStream fos = new FileOutputStream(filename, true)) {            fos.write(content.getBytes(StandardCharsets.UTF_8));            fos.write(System.lineSeparator().getBytes());        }    }        // 分块写入大量数据    public static void writeDataInChunks(String filename, byte[] data) throws IOException {        try (FileOutputStream fos = new FileOutputStream(filename);             BufferedOutputStream bos = new BufferedOutputStream(fos, 8192)) {                        int chunkSize = 1024;            for (int i = 0; i < data.length; i += chunkSize) {                int length = Math.min(chunkSize, data.length - i);                bos.write(data, i, length);                                // 每写入1MB数据就刷新一次                if (i % (1024 * 1024) == 0) {                    bos.flush();                }            }        }    }}一键获取完整项目代码java2.3 文件字节流实战应用2.3.1 文件复制工具实现public class FileCopyUtility {        // 基础文件复制    public static void copyFile(String source, String target) throws IOException {        try (FileInputStream fis = new FileInputStream(source);             FileOutputStream fos = new FileOutputStream(target)) {                        byte[] buffer = new byte[4096];            int bytesRead;            while ((bytesRead = fis.read(buffer)) != -1) {                fos.write(buffer, 0, bytesRead);            }        }    }        // 带进度显示的文件复制    public static void copyFileWithProgress(String source, String target) throws IOException {        File sourceFile = new File(source);        long totalBytes = sourceFile.length();        long copiedBytes = 0;                try (FileInputStream fis = new FileInputStream(source);             FileOutputStream fos = new FileOutputStream(target)) {                        byte[] buffer = new byte[8192];            int bytesRead;            while ((bytesRead = fis.read(buffer)) != -1) {                fos.write(buffer, 0, bytesRead);                copiedBytes += bytesRead;                                // 显示进度                int progress = (int) ((copiedBytes * 100) / totalBytes);                System.out.printf("\rProgress: %d%% (%d/%d bytes)",                     progress, copiedBytes, totalBytes);            }            System.out.println("\nCopy completed!");        }    }        // 多线程文件复制    public static void copyFileMultiThreaded(String source, String target, int threadCount)             throws IOException, InterruptedException {        File sourceFile = new File(source);        long fileSize = sourceFile.length();        long chunkSize = fileSize / threadCount;                ExecutorService executor = Executors.newFixedThreadPool(threadCount);        CountDownLatch latch = new CountDownLatch(threadCount);                try (RandomAccessFile sourceRaf = new RandomAccessFile(source, "r");             RandomAccessFile targetRaf = new RandomAccessFile(target, "rw")) {                        targetRaf.setLength(fileSize); // 预分配文件大小                        for (int i = 0; i < threadCount; i++) {                long start = i * chunkSize;                long end = (i == threadCount - 1) ? fileSize : (i + 1) * chunkSize;                                executor.submit(() -> {                    try {                        copyFileChunk(sourceRaf, targetRaf, start, end - start);                    } catch (IOException e) {                        e.printStackTrace();                    } finally {                        latch.countDown();                    }                });            }                        latch.await();        } finally {            executor.shutdown();        }    }        private static void copyFileChunk(RandomAccessFile source, RandomAccessFile target,                                     long offset, long length) throws IOException {        synchronized (source) {            source.seek(offset);        }                byte[] buffer = new byte[8192];        long remaining = length;                while (remaining > 0) {            int toRead = (int) Math.min(buffer.length, remaining);            int bytesRead;                        synchronized (source) {                bytesRead = source.read(buffer, 0, toRead);            }                        if (bytesRead == -1) break;                        synchronized (target) {                target.seek(offset + (length - remaining));                target.write(buffer, 0, bytesRead);            }                        remaining -= bytesRead;        }    }}一键获取完整项目代码java2.4 数据流与对象流深度应用2.4.1 DataInputStream和DataOutputStream数据流提供了读写Java基本数据类型的便捷方法:public class DataStreamExample {        // 写入各种数据类型    public static void writeDataTypes(String filename) throws IOException {        try (DataOutputStream dos = new DataOutputStream(                new BufferedOutputStream(new FileOutputStream(filename)))) {                        // 写入各种基本数据类型            dos.writeBoolean(true);            dos.writeByte(127);            dos.writeShort(32767);            dos.writeInt(2147483647);            dos.writeLong(9223372036854775807L);            dos.writeFloat(3.14159f);            dos.writeDouble(2.718281828459045);            dos.writeUTF("Hello, DataStream!");                        // 写入数组            int[] numbers = {1, 2, 3, 4, 5};            dos.writeInt(numbers.length);            for (int number : numbers) {                dos.writeInt(number);            }        }    }        // 读取各种数据类型    public static void readDataTypes(String filename) throws IOException {        try (DataInputStream dis = new DataInputStream(                new BufferedInputStream(new FileInputStream(filename)))) {                        // 按写入顺序读取            boolean boolValue = dis.readBoolean();            byte byteValue = dis.readByte();            short shortValue = dis.readShort();            int intValue = dis.readInt();            long longValue = dis.readLong();            float floatValue = dis.readFloat();            double doubleValue = dis.readDouble();            String stringValue = dis.readUTF();                        System.out.printf("Boolean: %b, Byte: %d, Short: %d%n",                 boolValue, byteValue, shortValue);            System.out.printf("Int: %d, Long: %d%n", intValue, longValue);            System.out.printf("Float: %f, Double: %f%n", floatValue, doubleValue);            System.out.printf("String: %s%n", stringValue);                        // 读取数组            int arrayLength = dis.readInt();            int[] numbers = new int[arrayLength];            for (int i = 0; i < arrayLength; i++) {                numbers[i] = dis.readInt();            }            System.out.println("Array: " + Arrays.toString(numbers));        }    }}一键获取完整项目代码java2.4.2 ObjectInputStream和ObjectOutputStream对象流支持Java对象的序列化和反序列化:// 可序列化的用户类class User implements Serializable {    private static final long serialVersionUID = 1L;        private String name;    private int age;    private transient String password; // transient字段不会被序列化        public User(String name, int age, String password) {        this.name = name;        this.age = age;        this.password = password;    }        // getter和setter方法    public String getName() { return name; }    public void setName(String name) { this.name = name; }    public int getAge() { return age; }    public void setAge(int age) { this.age = age; }    public String getPassword() { return password; }    public void setPassword(String password) { this.password = password; }        @Override    public String toString() {        return String.format("User{name='%s', age=%d, password='%s'}",             name, age, password);    }}public class ObjectStreamExample {        // 序列化对象到文件    public static void serializeObjects(String filename) throws IOException {        List<User> users = Arrays.asList(            new User("Alice", 25, "secret123"),            new User("Bob", 30, "password456"),            new User("Charlie", 35, "mypassword")        );                try (ObjectOutputStream oos = new ObjectOutputStream(                new BufferedOutputStream(new FileOutputStream(filename)))) {                        oos.writeObject(users);            oos.writeInt(42); // 可以混合写入其他数据            oos.writeUTF("Serialization completed");        }    }        // 从文件反序列化对象    @SuppressWarnings("unchecked")    public static void deserializeObjects(String filename) throws IOException, ClassNotFoundException {        try (ObjectInputStream ois = new ObjectInputStream(                new BufferedInputStream(new FileInputStream(filename)))) {                        List<User> users = (List<User>) ois.readObject();            int number = ois.readInt();            String message = ois.readUTF();                        System.out.println("Deserialized users:");            users.forEach(System.out::println);            System.out.println("Number: " + number);            System.out.println("Message: " + message);        }    }        // 自定义序列化控制    public static class CustomSerializableClass implements Serializable {        private static final long serialVersionUID = 1L;                private String data;        private transient int computedValue;                public CustomSerializableClass(String data) {            this.data = data;            this.computedValue = data.hashCode();        }                // 自定义序列化方法        private void writeObject(ObjectOutputStream oos) throws IOException {            oos.defaultWriteObject(); // 序列化非transient字段            oos.writeInt(data.length()); // 序列化额外信息        }                // 自定义反序列化方法        private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {            ois.defaultReadObject(); // 反序列化非transient字段            int dataLength = ois.readInt(); // 读取额外信息            this.computedValue = data.hashCode(); // 重新计算transient字段                        System.out.println("Custom deserialization: data length = " + dataLength);        }                @Override        public String toString() {            return String.format("CustomSerializableClass{data='%s', computedValue=%d}",                 data, computedValue);        }    }}一键获取完整项目代码java第三章:字符流深入分析与使用场景3.1 Reader字符输入流体系3.1.1 FileReader文件字符读取FileReader是处理文本文件的首选字符流:public class FileReaderExample {        // 基础文件读取    public static String readTextFile(String filename) throws IOException {        StringBuilder content = new StringBuilder();        try (FileReader reader = new FileReader(filename, StandardCharsets.UTF_8)) {            char[] buffer = new char[1024];            int charsRead;            while ((charsRead = reader.read(buffer)) != -1) {                content.append(buffer, 0, charsRead);            }        }        return content.toString();    }        // 逐行读取文件    public static List<String> readFileLines(String filename) throws IOException {        List<String> lines = new ArrayList<>();        try (BufferedReader reader = new BufferedReader(                new FileReader(filename, StandardCharsets.UTF_8))) {            String line;            while ((line = reader.readLine()) != null) {                lines.add(line);            }        }        return lines;    }        // 使用Java 8 Stream API读取文件    public static void processFileWithStream(String filename) throws IOException {        try (BufferedReader reader = Files.newBufferedReader(Paths.get(filename))) {            reader.lines()                  .filter(line -> !line.trim().isEmpty())                  .map(String::toUpperCase)                  .forEach(System.out::println);        }    }}一键获取完整项目代码java3.1.2 StringReader和CharArrayReader这些内存字符流用于处理字符串和字符数组:public class MemoryCharacterStreamExample {        public static void demonstrateStringReader() throws IOException {        String text = "Line 1\nLine 2\nLine 3\nLine 4";                try (StringReader stringReader = new StringReader(text);             BufferedReader bufferedReader = new BufferedReader(stringReader)) {                        String line;            int lineNumber = 1;            while ((line = bufferedReader.readLine()) != null) {                System.out.printf("Line %d: %s%n", lineNumber++, line);            }        }    }        public static void demonstrateCharArrayReader() throws IOException {        char[] charArray = "Hello, Character Streams!".toCharArray();                try (CharArrayReader reader = new CharArrayReader(charArray)) {            // 标记支持测试            if (reader.markSupported()) {                reader.mark(5);                                // 读取前5个字符                char[] buffer = new char[5];                int charsRead = reader.read(buffer);                System.out.println("First 5 chars: " + new String(buffer, 0, charsRead));                                // 重置并重新读取                reader.reset();                charsRead = reader.read(buffer);                System.out.println("After reset: " + new String(buffer, 0, charsRead));            }        }    }}一键获取完整项目代码java3.2 Writer字符输出流体系3.2.1 FileWriter文件字符写入public class FileWriterExample {        // 基础文件写入    public static void writeTextFile(String filename, String content) throws IOException {        try (FileWriter writer = new FileWriter(filename, StandardCharsets.UTF_8)) {            writer.write(content);            writer.flush();        }    }        // 格式化写入    public static void writeFormattedData(String filename, List<Person> persons) throws IOException {        try (BufferedWriter writer = new BufferedWriter(                new FileWriter(filename, StandardCharsets.UTF_8))) {                        // 写入表头            writer.write("Name,Age,Email");            writer.newLine();                        // 写入数据            for (Person person : persons) {                writer.write(String.format("%s,%d,%s",                     person.getName(), person.getAge(), person.getEmail()));                writer.newLine();            }        }    }        // 追加写入日志    public static void appendLog(String logFile, String message) throws IOException {        try (FileWriter writer = new FileWriter(logFile, StandardCharsets.UTF_8, true)) {            String timestamp = LocalDateTime.now().format(                DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));            writer.write(String.format("[%s] %s%n", timestamp, message));        }    }}// 辅助类class Person {    private String name;    private int age;    private String email;        public Person(String name, int age, String email) {        this.name = name;        this.age = age;        this.email = email;    }        // getter方法    public String getName() { return name; }    public int getAge() { return age; }    public String getEmail() { return email; }}一键获取完整项目代码java3.3 字符编码与转换流3.3.1 InputStreamReader和OutputStreamWriter转换流是字节流和字符流之间的桥梁:public class ConversionStreamExample {        // 指定编码读取文件    public static String readFileWithEncoding(String filename, String encoding) throws IOException {        try (InputStreamReader reader = new InputStreamReader(                new FileInputStream(filename), encoding);             BufferedReader bufferedReader = new BufferedReader(reader)) {                        return bufferedReader.lines()                                 .collect(Collectors.joining(System.lineSeparator()));        }    }        // 指定编码写入文件    public static void writeFileWithEncoding(String filename, String content, String encoding)             throws IOException {        try (OutputStreamWriter writer = new OutputStreamWriter(                new FileOutputStream(filename), encoding);             BufferedWriter bufferedWriter = new BufferedWriter(writer)) {                        bufferedWriter.write(content);        }    }        // 编码转换工具    public static void convertFileEncoding(String sourceFile, String targetFile,                                          String sourceEncoding, String targetEncoding)             throws IOException {        try (BufferedReader reader = new BufferedReader(                new InputStreamReader(new FileInputStream(sourceFile), sourceEncoding));             BufferedWriter writer = new BufferedWriter(                new OutputStreamWriter(new FileOutputStream(targetFile), targetEncoding))) {                        String line;            while ((line = reader.readLine()) != null) {                writer.write(line);                writer.newLine();            }        }    }        // 检测文件编码(简化版本)    public static String detectEncoding(String filename) throws IOException {        try (FileInputStream fis = new FileInputStream(filename)) {            byte[] bom = new byte[4];            int bytesRead = fis.read(bom);                        if (bytesRead >= 3) {                if (bom[0] == (byte) 0xEF && bom[1] == (byte) 0xBB && bom[2] == (byte) 0xBF) {                    return "UTF-8";                }                if (bom[0] == (byte) 0xFF && bom[1] == (byte) 0xFE) {                    return "UTF-16LE";                }                if (bom[0] == (byte) 0xFE && bom[1] == (byte) 0xFF) {                    return "UTF-16BE";                }            }                        // 默认返回系统编码            return System.getProperty("file.encoding");        }    }}一键获取完整项目代码java3.4 打印流与格式化输出3.4.1 PrintWriter高级应用public class PrintWriterExample {        // 格式化输出到文件    public static void generateReport(String filename, List<SalesRecord> records) throws IOException {        try (PrintWriter writer = new PrintWriter(                new BufferedWriter(new FileWriter(filename, StandardCharsets.UTF_8)))) {                        // 输出报表头            writer.println("=".repeat(60));            writer.printf("%-20s %10s %15s %10s%n", "Product", "Quantity", "Unit Price", "Total");            writer.println("=".repeat(60));                        double grandTotal = 0;            for (SalesRecord record : records) {                double total = record.getQuantity() * record.getUnitPrice();                writer.printf("%-20s %10d %15.2f %10.2f%n",                     record.getProduct(), record.getQuantity(), record.getUnitPrice(), total);                grandTotal += total;            }                        writer.println("-".repeat(60));            writer.printf("%-46s %10.2f%n", "Grand Total:", grandTotal);            writer.println("=".repeat(60));                        // 检查是否有错误            if (writer.checkError()) {                System.err.println("Error occurred while writing to file");            }        }    }        // 多目标输出    public static void multiTargetOutput(String message) throws IOException {        // 同时输出到控制台和文件        try (PrintWriter fileWriter = new PrintWriter(                new FileWriter("output.log", StandardCharsets.UTF_8, true))) {                        PrintWriter consoleWriter = new PrintWriter(System.out, true);                        String timestamp = LocalDateTime.now().format(                DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));            String formattedMessage = String.format("[%s] %s", timestamp, message);                        // 输出到控制台            consoleWriter.println(formattedMessage);                        // 输出到文件            fileWriter.println(formattedMessage);        }    }}// 辅助类class SalesRecord {    private String product;    private int quantity;    private double unitPrice;        public SalesRecord(String product, int quantity, double unitPrice) {        this.product = product;        this.quantity = quantity;        this.unitPrice = unitPrice;    }        // getter方法    public String getProduct() { return product; }    public int getQuantity() { return quantity; }    public double getUnitPrice() { return unitPrice; }}一键获取完整项目代码java第四章:缓冲流性能优化与最佳实践4.1 字节缓冲流性能分析4.1.1 BufferedInputStream性能优化public class BufferedStreamPerformance {        // 性能对比测试    public static void performanceComparison(String filename) throws IOException {        byte[] testData = generateTestData(1024 * 1024); // 1MB测试数据                // 写入测试数据        try (FileOutputStream fos = new FileOutputStream(filename)) {            fos.write(testData);        }                // 测试无缓冲读取        long startTime = System.nanoTime();        readWithoutBuffer(filename);        long unbufferedTime = System.nanoTime() - startTime;                // 测试缓冲读取        startTime = System.nanoTime();        readWithBuffer(filename);        long bufferedTime = System.nanoTime() - startTime;                // 测试自定义缓冲区大小        startTime = System.nanoTime();        readWithCustomBuffer(filename, 8192);        long customBufferedTime = System.nanoTime() - startTime;                System.out.printf("Unbuffered read: %.2f ms%n", unbufferedTime / 1_000_000.0);        System.out.printf("Buffered read: %.2f ms%n", bufferedTime / 1_000_000.0);        System.out.printf("Custom buffered read: %.2f ms%n", customBufferedTime / 1_000_000.0);        System.out.printf("Buffered is %.2fx faster than unbuffered%n",             (double) unbufferedTime / bufferedTime);    }        private static void readWithoutBuffer(String filename) throws IOException {        try (FileInputStream fis = new FileInputStream(filename)) {            int byteData;            while ((byteData = fis.read()) != -1) {                // 模拟处理            }        }    }        private static void readWithBuffer(String filename) throws IOException {        try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(filename))) {            int byteData;            while ((byteData = bis.read()) != -1) {                // 模拟处理            }        }    }        private static void readWithCustomBuffer(String filename, int bufferSize) throws IOException {        try (BufferedInputStream bis = new BufferedInputStream(                new FileInputStream(filename), bufferSize)) {            int byteData;            while ((byteData = bis.read()) != -1) {                // 模拟处理            }        }    }        private static byte[] generateTestData(int size) {        byte[] data = new byte[size];        new Random().nextBytes(data);        return data;    }}一键获取完整项目代码java4.1.2 缓冲区大小优化策略public class BufferSizeOptimization {        // 动态缓冲区大小调整    public static void adaptiveBufferCopy(String source, String target) throws IOException {        File sourceFile = new File(source);        long fileSize = sourceFile.length();                // 根据文件大小选择缓冲区大小        int bufferSize = calculateOptimalBufferSize(fileSize);                try (BufferedInputStream bis = new BufferedInputStream(                new FileInputStream(source), bufferSize);             BufferedOutputStream bos = new BufferedOutputStream(                new FileOutputStream(target), bufferSize)) {                        byte[] buffer = new byte[bufferSize];            int bytesRead;            while ((bytesRead = bis.read(buffer)) != -1) {                bos.write(buffer, 0, bytesRead);            }        }    }        private static int calculateOptimalBufferSize(long fileSize) {        if (fileSize < 1024) {            return 512; // 小文件使用小缓冲区        } else if (fileSize < 1024 * 1024) {            return 4096; // 中等文件使用4KB缓冲区        } else if (fileSize < 10 * 1024 * 1024) {            return 8192; // 大文件使用8KB缓冲区        } else {            return 16384; // 超大文件使用16KB缓冲区        }    }        // 缓冲区大小基准测试    public static void benchmarkBufferSizes(String filename) throws IOException {        int[] bufferSizes = {1024, 2048, 4096, 8192, 16384, 32768, 65536};                System.out.println("Buffer Size\tRead Time (ms)\tWrite Time (ms)");        System.out.println("-".repeat(50));                for (int bufferSize : bufferSizes) {            long readTime = benchmarkRead(filename, bufferSize);            long writeTime = benchmarkWrite(filename + ".copy", bufferSize);                        System.out.printf("%d\t\t%.2f\t\t%.2f%n",                 bufferSize, readTime / 1_000_000.0, writeTime / 1_000_000.0);        }    }        private static long benchmarkRead(String filename, int bufferSize) throws IOException {        long startTime = System.nanoTime();                try (BufferedInputStream bis = new BufferedInputStream(                new FileInputStream(filename), bufferSize)) {            byte[] buffer = new byte[bufferSize];            while (bis.read(buffer) != -1) {                // 读取数据            }        }                return System.nanoTime() - startTime;    }        private static long benchmarkWrite(String filename, int bufferSize) throws IOException {        byte[] testData = new byte[1024 * 1024]; // 1MB测试数据        new Random().nextBytes(testData);                long startTime = System.nanoTime();                try (BufferedOutputStream bos = new BufferedOutputStream(                new FileOutputStream(filename), bufferSize)) {            bos.write(testData);        }                return System.nanoTime() - startTime;    }}一键获取完整项目代码java4.2 字符缓冲流高效应用4.2.1 BufferedReader高级技巧public class BufferedReaderAdvanced {        // 大文件逐行处理    public static void processLargeTextFile(String filename, Function<String, String> processor)             throws IOException {        try (BufferedReader reader = new BufferedReader(                new FileReader(filename, StandardCharsets.UTF_8), 16384)) {                        String line;            int lineNumber = 0;            while ((line = reader.readLine()) != null) {                lineNumber++;                String processedLine = processor.apply(line);                                if (processedLine != null) {                    System.out.printf("Line %d: %s%n", lineNumber, processedLine);                }                                // 每处理10000行显示进度                if (lineNumber % 10000 == 0) {                    System.out.printf("Processed %d lines...%n", lineNumber);                }            }        }    }        // 文件内容搜索    public static List<SearchResult> searchInFile(String filename, String searchTerm)             throws IOException {        List<SearchResult> results = new ArrayList<>();                try (BufferedReader reader = new BufferedReader(                new FileReader(filename, StandardCharsets.UTF_8))) {                        String line;            int lineNumber = 0;            while ((line = reader.readLine()) != null) {                lineNumber++;                int index = line.toLowerCase().indexOf(searchTerm.toLowerCase());                if (index != -1) {                    results.add(new SearchResult(lineNumber, index, line));                }            }        }                return results;    }        // 文件统计信息    public static FileStatistics analyzeTextFile(String filename) throws IOException {        FileStatistics stats = new FileStatistics();                try (BufferedReader reader = new BufferedReader(                new FileReader(filename, StandardCharsets.UTF_8))) {                        String line;            while ((line = reader.readLine()) != null) {                stats.incrementLineCount();                stats.addCharacterCount(line.length());                stats.addWordCount(countWords(line));                                if (line.trim().isEmpty()) {                    stats.incrementEmptyLineCount();                }            }        }                return stats;    }        private static int countWords(String line) {        return line.trim().isEmpty() ? 0 : line.trim().split("\\s+").length;    }}// 辅助类class SearchResult {    private int lineNumber;    private int position;    private String line;        public SearchResult(int lineNumber, int position, String line) {        this.lineNumber = lineNumber;        this.position = position;        this.line = line;    }        @Override    public String toString() {        return String.format("Line %d, Position %d: %s", lineNumber, position, line);    }}class FileStatistics {    private int lineCount = 0;    private int emptyLineCount = 0;    private long characterCount = 0;    private long wordCount = 0;        public void incrementLineCount() { lineCount++; }    public void incrementEmptyLineCount() { emptyLineCount++; }    public void addCharacterCount(int count) { characterCount += count; }    public void addWordCount(int count) { wordCount += count; }        @Override    public String toString() {        return String.format("Lines: %d, Empty Lines: %d, Characters: %d, Words: %d",             lineCount, emptyLineCount, characterCount, wordCount);    }}一键获取完整项目代码java4.3 缓冲区大小优化策略4.3.1 内存使用与性能平衡public class MemoryPerformanceBalance {        // 内存敏感的文件处理    public static void memoryEfficientProcessing(String filename, long maxMemoryUsage)             throws IOException {        // 根据内存限制计算缓冲区大小        int bufferSize = calculateBufferSize(maxMemoryUsage);                try (BufferedReader reader = new BufferedReader(                new FileReader(filename, StandardCharsets.UTF_8), bufferSize)) {                        String line;            int processedLines = 0;            long memoryUsed = 0;                        while ((line = reader.readLine()) != null) {                // 估算内存使用量                memoryUsed += line.length() * 2; // 字符占用2字节                                processLine(line);                processedLines++;                                // 定期检查内存使用                if (processedLines % 1000 == 0) {                    if (memoryUsed > maxMemoryUsage * 0.8) {                        System.gc(); // 建议垃圾回收                        memoryUsed = 0; // 重置计数                    }                }            }        }    }        private static int calculateBufferSize(long maxMemoryUsage) {        // 缓冲区不超过最大内存的10%        long maxBufferSize = maxMemoryUsage / 10;        return (int) Math.min(maxBufferSize, 65536); // 最大64KB    }        private static void processLine(String line) {        // 模拟行处理    }        // 自适应缓冲区管理    public static class AdaptiveBuffer {        private int currentBufferSize;        private final int minBufferSize;        private final int maxBufferSize;        private long lastOperationTime;                public AdaptiveBuffer(int minSize, int maxSize) {            this.minBufferSize = minSize;            this.maxBufferSize = maxSize;            this.currentBufferSize = minSize;        }                public int getBufferSize() {            return currentBufferSize;        }                public void recordOperationTime(long operationTime) {            // 根据操作时间调整缓冲区大小            if (operationTime > lastOperationTime * 1.2 && currentBufferSize < maxBufferSize) {                // 性能下降,增加缓冲区                currentBufferSize = Math.min(currentBufferSize * 2, maxBufferSize);            } else if (operationTime < lastOperationTime * 0.8 && currentBufferSize > minBufferSize) {                // 性能提升,可以减少缓冲区以节省内存                currentBufferSize = Math.max(currentBufferSize / 2, minBufferSize);            }                        lastOperationTime = operationTime;        }    }}一键获取完整项目代码java4.4 内存映射文件与零拷贝技术4.4.1 MappedByteBuffer应用public class MemoryMappedFileExample {        // 内存映射文件读取    public static void readWithMemoryMapping(String filename) throws IOException {        try (RandomAccessFile file = new RandomAccessFile(filename, "r");             FileChannel channel = file.getChannel()) {                        long fileSize = channel.size();            MappedByteBuffer buffer = channel.map(FileChannel.MapMode.READ_ONLY, 0, fileSize);                        // 直接从内存读取            byte[] data = new byte[1024];            while (buffer.hasRemaining()) {                int bytesToRead = Math.min(data.length, buffer.remaining());                buffer.get(data, 0, bytesToRead);                processData(data, bytesToRead);            }        }    }        // 内存映射文件写入    public static void writeWithMemoryMapping(String filename, byte[] data) throws IOException {        try (RandomAccessFile file = new RandomAccessFile(filename, "rw");             FileChannel channel = file.getChannel()) {                        MappedByteBuffer buffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, data.length);            buffer.put(data);            buffer.force(); // 强制写入磁盘        }    }        // 大文件分段映射    public static void processLargeFileWithMapping(String filename) throws IOException {        try (RandomAccessFile file = new RandomAccessFile(filename, "r");             FileChannel channel = file.getChannel()) {                        long fileSize = channel.size();            long mappingSize = 64 * 1024 * 1024; // 64MB分段                        for (long position = 0; position < fileSize; position += mappingSize) {                long size = Math.min(mappingSize, fileSize - position);                MappedByteBuffer buffer = channel.map(FileChannel.MapMode.READ_ONLY, position, size);                                processBuffer(buffer);                                // 显示进度                double progress = (double) (position + size) / fileSize * 100;                System.out.printf("Progress: %.2f%%%n", progress);            }        }    }        private static void processData(byte[] data, int length) {        // 处理数据    }        private static void processBuffer(MappedByteBuffer buffer) {        // 处理映射缓冲区        while (buffer.hasRemaining()) {            byte b = buffer.get();            // 处理字节        }    }        // 零拷贝文件传输    public static void zeroCopyTransfer(String source, String target) throws IOException {        try (FileChannel sourceChannel = FileChannel.open(Paths.get(source), StandardOpenOption.READ);             FileChannel targetChannel = FileChannel.open(Paths.get(target),                  StandardOpenOption.CREATE, StandardOpenOption.WRITE)) {                        long transferred = 0;            long fileSize = sourceChannel.size();                        while (transferred < fileSize) {                long bytesTransferred = sourceChannel.transferTo(transferred,                     fileSize - transferred, targetChannel);                transferred += bytesTransferred;                                // 显示进度                double progress = (double) transferred / fileSize * 100;                System.out.printf("Transfer progress: %.2f%%%n", progress);            }        }    }}一键获取完整项目代码java第五章:高级流操作与NIO新特性5.1 NIO核心组件详解5.1.1 Channel通道基础Java NIO引入了Channel概念,提供了更高效的IO操作方式:public class ChannelBasics {        // FileChannel基础操作    public static void fileChannelBasics(String filename) throws IOException {        // 读取操作        try (FileChannel readChannel = FileChannel.open(Paths.get(filename), StandardOpenOption.READ)) {            ByteBuffer buffer = ByteBuffer.allocate(1024);                        while (readChannel.read(buffer) > 0) {                buffer.flip(); // 切换到读模式                                while (buffer.hasRemaining()) {                    System.out.print((char) buffer.get());                }                                buffer.clear(); // 清空缓冲区,准备下次读取            }        }                // 写入操作        try (FileChannel writeChannel = FileChannel.open(Paths.get(filename + ".copy"),                 StandardOpenOption.CREATE, StandardOpenOption.WRITE)) {                        String content = "Hello, NIO Channel!";            ByteBuffer buffer = ByteBuffer.wrap(content.getBytes(StandardCharsets.UTF_8));                        while (buffer.hasRemaining()) {                writeChannel.write(buffer);            }        }    }        // Channel之间的数据传输    public static void channelToChannelTransfer(String source, String target) throws IOException {        try (FileChannel sourceChannel = FileChannel.open(Paths.get(source), StandardOpenOption.READ);             FileChannel targetChannel = FileChannel.open(Paths.get(target),                  StandardOpenOption.CREATE, StandardOpenOption.WRITE)) {                        // 方法1:transferFrom            targetChannel.transferFrom(sourceChannel, 0, sourceChannel.size());                        // 方法2:transferTo(在另一个方法中演示)            // sourceChannel.transferTo(0, sourceChannel.size(), targetChannel);        }    }        // 随机访问文件    public static void randomAccessWithChannel(String filename) throws IOException {        try (FileChannel channel = FileChannel.open(Paths.get(filename),                 StandardOpenOption.READ, StandardOpenOption.WRITE)) {                        // 在文件末尾写入数据            long fileSize = channel.size();            ByteBuffer buffer = ByteBuffer.wrap("\nAppended text".getBytes());            channel.write(buffer, fileSize);                        // 读取文件开头的数据            buffer = ByteBuffer.allocate(10);            channel.read(buffer, 0);            buffer.flip();                        System.out.println("First 10 bytes: " + StandardCharsets.UTF_8.decode(buffer));        }    }}一键获取完整项目代码java5.2 Channel通道操作实战5.2.1 网络Channel应用public class NetworkChannelExample {        // NIO服务器示例    public static void startNIOServer(int port) throws IOException {        try (ServerSocketChannel serverChannel = ServerSocketChannel.open()) {            serverChannel.bind(new InetSocketAddress(port));            serverChannel.configureBlocking(false);                        Selector selector = Selector.open();            serverChannel.register(selector, SelectionKey.OP_ACCEPT);                        System.out.println("NIO Server started on port " + port);                        while (true) {                if (selector.select() == 0) {                    continue;                }                                Set<SelectionKey> selectedKeys = selector.selectedKeys();                Iterator<SelectionKey> iterator = selectedKeys.iterator();                                while (iterator.hasNext()) {                    SelectionKey key = iterator.next();                    iterator.remove();                                        if (key.isAcceptable()) {                        handleAccept(key, selector);                    } else if (key.isReadable()) {                        handleRead(key);                    }                }            }        }    }        private static void handleAccept(SelectionKey key, Selector selector) throws IOException {        ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();        SocketChannel clientChannel = serverChannel.accept();                if (clientChannel != null) {            clientChannel.configureBlocking(false);            clientChannel.register(selector, SelectionKey.OP_READ);            System.out.println("Client connected: " + clientChannel.getRemoteAddress());        }    }        private static void handleRead(SelectionKey key) throws IOException {        SocketChannel clientChannel = (SocketChannel) key.channel();        ByteBuffer buffer = ByteBuffer.allocate(1024);                try {            int bytesRead = clientChannel.read(buffer);            if (bytesRead > 0) {                buffer.flip();                String message = StandardCharsets.UTF_8.decode(buffer).toString();                System.out.println("Received: " + message);                                // 回显消息                ByteBuffer response = ByteBuffer.wrap(("Echo: " + message).getBytes());                clientChannel.write(response);            } else if (bytesRead == -1) {                // 客户端断开连接                System.out.println("Client disconnected");                key.cancel();                clientChannel.close();            }        } catch (IOException e) {            System.err.println("Error reading from client: " + e.getMessage());            key.cancel();            clientChannel.close();        }    }        // NIO客户端示例    public static void connectToNIOServer(String host, int port, String message) throws IOException {        try (SocketChannel channel = SocketChannel.open()) {            channel.connect(new InetSocketAddress(host, port));                        // 发送消息            ByteBuffer buffer = ByteBuffer.wrap(message.getBytes(StandardCharsets.UTF_8));            channel.write(buffer);                        // 读取响应            buffer = ByteBuffer.allocate(1024);            int bytesRead = channel.read(buffer);                        if (bytesRead > 0) {                buffer.flip();                String response = StandardCharsets.UTF_8.decode(buffer).toString();                System.out.println("Server response: " + response);            }        }    }}一键获取完整项目代码java5.3 Buffer缓冲区高级应用5.3.1 ByteBuffer高级操作public class ByteBufferAdvanced {        // ByteBuffer的各种创建方式    public static void bufferCreationMethods() {        // 分配堆内存缓冲区        ByteBuffer heapBuffer = ByteBuffer.allocate(1024);                // 分配直接内存缓冲区(更高效,但创建成本高)        ByteBuffer directBuffer = ByteBuffer.allocateDirect(1024);                // 包装现有字节数组        byte[] array = new byte[1024];        ByteBuffer wrappedBuffer = ByteBuffer.wrap(array);                // 包装数组的一部分        ByteBuffer partialBuffer = ByteBuffer.wrap(array, 10, 100);                System.out.println("Heap buffer: " + heapBuffer.isDirect());        System.out.println("Direct buffer: " + directBuffer.isDirect());    }        // Buffer的标记和重置    public static void bufferMarkAndReset() {        ByteBuffer buffer = ByteBuffer.allocate(20);                // 写入一些数据        buffer.put("Hello".getBytes());        System.out.println("After put: position=" + buffer.position() + ", limit=" + buffer.limit());                // 标记当前位置        buffer.mark();                // 继续写入        buffer.put(" World".getBytes());        System.out.println("After more put: position=" + buffer.position());                // 重置到标记位置        buffer.reset();        System.out.println("After reset: position=" + buffer.position());                // 切换到读模式        buffer.flip();                // 读取数据        byte[] data = new byte[buffer.remaining()];        buffer.get(data);        System.out.println("Read data: " + new String(data));    }        // Buffer的切片和复制    public static void bufferSliceAndDuplicate() {        ByteBuffer original = ByteBuffer.allocate(20);        original.put("Hello World Test".getBytes());        original.flip();                // 移动到位置5        original.position(5);                // 创建切片(共享数据,但有独立的位置标记)        ByteBuffer slice = original.slice();        System.out.println("Slice remaining: " + slice.remaining());                // 创建副本(共享数据,但有独立的位置标记)        original.rewind();        ByteBuffer duplicate = original.duplicate();        System.out.println("Duplicate remaining: " + duplicate.remaining());                // 修改切片会影响原始缓冲区        slice.put(0, (byte) 'X');        original.rewind();        byte[] result = new byte[original.remaining()];        original.get(result);        System.out.println("After slice modification: " + new String(result));    }}一键获取完整项目代码java5.3.2 多种Buffer类型应用public class MultipleBufferTypes {        // IntBuffer示例    public static void intBufferExample() {        IntBuffer intBuffer = IntBuffer.allocate(10);                // 写入整数        for (int i = 1; i <= 5; i++) {            intBuffer.put(i * 10);        }                intBuffer.flip();                // 读取整数        while (intBuffer.hasRemaining()) {            System.out.println("Int value: " + intBuffer.get());        }    }        // CharBuffer与字符串处理    public static void charBufferExample() {        CharBuffer charBuffer = CharBuffer.allocate(50);                // 写入字符        charBuffer.put("Hello, NIO CharBuffer!");        charBuffer.flip();                // 转换为字符串        String result = charBuffer.toString();        System.out.println("CharBuffer content: " + result);                // 使用CharBuffer进行字符串操作        charBuffer.rewind();        StringBuilder sb = new StringBuilder();        while (charBuffer.hasRemaining()) {            char c = charBuffer.get();            sb.append(Character.toUpperCase(c));        }        System.out.println("Uppercase: " + sb.toString());    }        // ByteBuffer与其他类型Buffer的转换    public static void bufferViewExample() {        ByteBuffer byteBuffer = ByteBuffer.allocate(40);                // 写入不同类型的数据        byteBuffer.putInt(42);        byteBuffer.putDouble(3.14159);        byteBuffer.putLong(123456789L);                byteBuffer.flip();                // 读取数据        int intValue = byteBuffer.getInt();        double doubleValue = byteBuffer.getDouble();        long longValue = byteBuffer.getLong();                System.out.printf("Values: int=%d, double=%.5f, long=%d%n",             intValue, doubleValue, longValue);                // 创建视图Buffer        byteBuffer.rewind();        IntBuffer intView = byteBuffer.asIntBuffer();        System.out.println("Int view capacity: " + intView.capacity());    }}一键获取完整项目代码java5.4 Selector选择器与多路复用5.4.1 Selector基础应用public class SelectorBasics {        // 基础Selector使用    public static void basicSelectorUsage() throws IOException {        Selector selector = Selector.open();                // 创建ServerSocketChannel        ServerSocketChannel serverChannel = ServerSocketChannel.open();        serverChannel.bind(new InetSocketAddress(8080));        serverChannel.configureBlocking(false);                // 注册到Selector        SelectionKey serverKey = serverChannel.register(selector, SelectionKey.OP_ACCEPT);                System.out.println("Server started, waiting for connections...");                while (true) {            // 阻塞等待事件            int readyChannels = selector.select();                        if (readyChannels == 0) {                continue;            }                        Set<SelectionKey> selectedKeys = selector.selectedKeys();            Iterator<SelectionKey> keyIterator = selectedKeys.iterator();                        while (keyIterator.hasNext()) {                SelectionKey key = keyIterator.next();                keyIterator.remove();                                if (key.isAcceptable()) {                    handleAccept(key, selector);                } else if (key.isReadable()) {                    handleRead(key);                } else if (key.isWritable()) {                    handleWrite(key);                }            }        }    }        private static void handleAccept(SelectionKey key, Selector selector) throws IOException {        ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();        SocketChannel clientChannel = serverChannel.accept();                if (clientChannel != null) {            clientChannel.configureBlocking(false);                        // 为客户端通道附加一个缓冲区            ByteBuffer buffer = ByteBuffer.allocate(1024);            SelectionKey clientKey = clientChannel.register(selector, SelectionKey.OP_READ);            clientKey.attach(buffer);                        System.out.println("New client connected: " + clientChannel.getRemoteAddress());        }    }        private static void handleRead(SelectionKey key) throws IOException {        SocketChannel channel = (SocketChannel) key.channel();        ByteBuffer buffer = (ByteBuffer) key.attachment();                try {            int bytesRead = channel.read(buffer);                        if (bytesRead > 0) {                buffer.flip();                byte[] data = new byte[buffer.remaining()];                buffer.get(data);                String message = new String(data, StandardCharsets.UTF_8);                                System.out.println("Received: " + message);                                // 准备回写数据                buffer.clear();                buffer.put(("Echo: " + message).getBytes(StandardCharsets.UTF_8));                buffer.flip();                                // 注册写事件                key.interestOps(SelectionKey.OP_WRITE);                            } else if (bytesRead == -1) {                // 客户端关闭连接                System.out.println("Client disconnected");                key.cancel();                channel.close();            }        } catch (IOException e) {            System.err.println("Error reading from client: " + e.getMessage());            key.cancel();            channel.close();        }    }        private static void handleWrite(SelectionKey key) throws IOException {        SocketChannel channel = (SocketChannel) key.channel();        ByteBuffer buffer = (ByteBuffer) key.attachment();                try {            channel.write(buffer);                        if (!buffer.hasRemaining()) {                // 写完了,重新注册读事件                buffer.clear();                key.interestOps(SelectionKey.OP_READ);            }        } catch (IOException e) {            System.err.println("Error writing to client: " + e.getMessage());            key.cancel();            channel.close();        }    }}一键获取完整项目代码java5.4.2 高性能NIO服务器实现public class HighPerformanceNIOServer {        private final int port;    private final Selector selector;    private final ServerSocketChannel serverChannel;    private volatile boolean running = false;        public HighPerformanceNIOServer(int port) throws IOException {        this.port = port;        this.selector = Selector.open();        this.serverChannel = ServerSocketChannel.open();                serverChannel.bind(new InetSocketAddress(port));        serverChannel.configureBlocking(false);        serverChannel.register(selector, SelectionKey.OP_ACCEPT);    }        public void start() throws IOException {        running = true;        System.out.println("High-performance NIO server started on port " + port);                while (running) {            try {                int readyChannels = selector.select(1000); // 1秒超时                                if (readyChannels == 0) {                    continue;                }                                processSelectedKeys();                            } catch (IOException e) {                System.err.println("Error in server loop: " + e.getMessage());                break;            }        }                cleanup();    }        private void processSelectedKeys() throws IOException {        Set<SelectionKey> selectedKeys = selector.selectedKeys();        Iterator<SelectionKey> iterator = selectedKeys.iterator();                while (iterator.hasNext()) {            SelectionKey key = iterator.next();            iterator.remove();                        if (!key.isValid()) {                continue;            }                        try {                if (key.isAcceptable()) {                    handleAccept(key);                } else if (key.isReadable()) {                    handleRead(key);                } else if (key.isWritable()) {                    handleWrite(key);                }            } catch (IOException e) {                System.err.println("Error handling key: " + e.getMessage());                closeChannel(key);            }        }    }        private void handleAccept(SelectionKey key) throws IOException {        ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();        SocketChannel clientChannel = serverChannel.accept();                if (clientChannel != null) {            clientChannel.configureBlocking(false);                        // 创建客户端会话            ClientSession session = new ClientSession(clientChannel);            SelectionKey clientKey = clientChannel.register(selector, SelectionKey.OP_READ);            clientKey.attach(session);                        System.out.println("Client connected: " + clientChannel.getRemoteAddress());        }    }        private void handleRead(SelectionKey key) throws IOException {        ClientSession session = (ClientSession) key.attachment();        SocketChannel channel = session.getChannel();                int bytesRead = channel.read(session.getReadBuffer());                if (bytesRead > 0) {            session.processReadData();                        // 如果有数据要写回,注册写事件            if (session.hasDataToWrite()) {                key.interestOps(key.interestOps() | SelectionKey.OP_WRITE);            }                    } else if (bytesRead == -1) {            System.out.println("Client disconnected: " + channel.getRemoteAddress());            closeChannel(key);        }    }        private void handleWrite(SelectionKey key) throws IOException {        ClientSession session = (ClientSession) key.attachment();        SocketChannel channel = session.getChannel();                session.writeData();                // 如果没有更多数据要写,取消写事件        if (!session.hasDataToWrite()) {            key.interestOps(key.interestOps() & ~SelectionKey.OP_WRITE);        }    }        private void closeChannel(SelectionKey key) {        try {            key.cancel();            key.channel().close();        } catch (IOException e) {            System.err.println("Error closing channel: " + e.getMessage());        }    }        public void stop() {        running = false;    }        private void cleanup() throws IOException {        selector.close();        serverChannel.close();        System.out.println("Server stopped");    }        // 客户端会话类    private static class ClientSession {        private final SocketChannel channel;        private final ByteBuffer readBuffer;        private final ByteBuffer writeBuffer;                public ClientSession(SocketChannel channel) {            this.channel = channel;            this.readBuffer = ByteBuffer.allocate(1024);            this.writeBuffer = ByteBuffer.allocate(1024);        }                public SocketChannel getChannel() {            return channel;        }                public ByteBuffer getReadBuffer() {            return readBuffer;        }                public void processReadData() {            readBuffer.flip();                        if (readBuffer.hasRemaining()) {                // 简单的回显处理                writeBuffer.clear();                writeBuffer.put("Echo: ".getBytes());                writeBuffer.put(readBuffer);                writeBuffer.flip();            }                        readBuffer.clear();        }                public boolean hasDataToWrite() {            return writeBuffer.hasRemaining();        }                public void writeData() throws IOException {            channel.write(writeBuffer);        }    }}一键获取完整项目代码java第六章:总结与展望6.1 知识点总结与技术扩展6.1.1 核心知识点回顾通过本文的深入学习,我们全面掌握了Java流操作的核心技术:基础流操作体系:字节流:以InputStream/OutputStream为核心的字节处理体系,适合处理二进制数据字符流:以Reader/Writer为核心的字符处理体系,专门优化文本数据处理转换流:InputStreamReader/OutputStreamWriter作为字节流和字符流的桥梁性能优化策略:缓冲流:通过BufferedInputStream/BufferedOutputStream显著提升IO性能缓冲区大小调优:根据文件大小和系统资源动态调整缓冲区内存映射:利用MappedByteBuffer实现零拷贝,提升大文件处理效率高级NIO特性:Channel通道:提供双向数据传输能力,支持异步操作Buffer缓冲区:灵活的数据容器,支持多种数据类型Selector选择器:实现单线程管理多个通道,提升并发性能6.1.2 技术扩展与深化响应式流处理:// 结合Java 9+ Flow API的响应式流处理public class ReactiveStreamExample {    public static void processFileReactively(String filename) {        Flow.Publisher<String> publisher = subscriber -> {            try (BufferedReader reader = Files.newBufferedReader(Paths.get(filename))) {                reader.lines().forEach(subscriber::onNext);                subscriber.onComplete();            } catch (IOException e) {                subscriber.onError(e);            }        };                // 订阅处理        publisher.subscribe(new Flow.Subscriber<String>() {            @Override            public void onSubscribe(Flow.Subscription subscription) {                subscription.request(Long.MAX_VALUE);            }                        @Override            public void onNext(String item) {                System.out.println("Processing: " + item);            }                        @Override            public void onError(Throwable throwable) {                System.err.println("Error: " + throwable.getMessage());            }                        @Override            public void onComplete() {                System.out.println("Processing completed");            }        });    }}一键获取完整项目代码java云原生IO操作:对象存储集成:与AWS S3、阿里云OSS等云存储服务的流式交互分布式文件系统:HDFS、GlusterFS等分布式存储的Java客户端应用容器化部署:Docker环境下的IO性能优化和资源限制处理6.2 学习资源与参考资料6.2.1 官方文档与权威资料Oracle官方文档:Java IO Tutorial - Oracle官方IO教程NIO.2 File API - 新文件API详解Java NIO Selector - Selector API文档经典技术书籍:《Java NIO》by Ron Hitchens - NIO编程权威指南《Netty in Action》by Norman Maurer - 基于NIO的网络编程框架《Java并发编程实战》by Brian Goetz - 并发环境下的IO操作6.2.2 实战项目与开源框架高性能IO框架:Netty:基于NIO的异步事件驱动网络应用框架Vert.x:响应式应用开发工具包Reactor Netty:Spring WebFlux底层的响应式网络库文件处理工具:Apache Commons IO:丰富的IO工具类库Google Guava:包含优秀的IO辅助工具Apache Tika:文件类型检测和内容提取6.2.3 性能测试与监控工具// JMH基准测试示例@BenchmarkMode(Mode.AverageTime)@OutputTimeUnit(TimeUnit.MILLISECONDS)@State(Scope.Benchmark)public class IOPerformanceBenchmark {        private static final String TEST_FILE = "benchmark_test.txt";    private static final int FILE_SIZE = 1024 * 1024; // 1MB        @Setup    public void setup() throws IOException {        // 创建测试文件        try (FileOutputStream fos = new FileOutputStream(TEST_FILE)) {            byte[] data = new byte[FILE_SIZE];            new Random().nextBytes(data);            fos.write(data);        }    }        @Benchmark    public void testBufferedRead() throws IOException {        try (BufferedInputStream bis = new BufferedInputStream(                new FileInputStream(TEST_FILE))) {            byte[] buffer = new byte[8192];            while (bis.read(buffer) != -1) {                // 读取数据            }        }    }        @Benchmark    public void testNIORead() throws IOException {        try (FileChannel channel = FileChannel.open(Paths.get(TEST_FILE))) {            ByteBuffer buffer = ByteBuffer.allocate(8192);            while (channel.read(buffer) != -1) {                buffer.clear();            }        }    }        @TearDown    public void cleanup() {        new File(TEST_FILE).delete();    }}一键获取完整项目代码java6.3 技术发展趋势与实践建议6.3.1 未来技术趋势Project Loom与虚拟线程:Java 19+引入的虚拟线程将革命性地改变IO编程模式,使得传统的阻塞IO也能获得高并发性能。GraalVM与原生编译:通过GraalVM将Java应用编译为原生可执行文件,显著提升IO密集型应用的启动速度和内存效率。云原生与Serverless:函数计算:AWS Lambda、阿里云函数计算等环境下的IO优化容器化:Kubernetes环境下的存储卷和网络IO管理边缘计算:IoT设备和边缘节点的轻量级IO处理6.3.2 最佳实践建议性能优化原则:选择合适的流类型:根据数据特性选择字节流或字符流合理使用缓冲:避免频繁的系统调用,提升整体性能资源管理:使用try-with-resources确保资源正确释放异步处理:对于高并发场景,优先考虑NIO或异步IO代码质量保证:// 推荐的IO操作模板public class IOBestPractices {        // 标准的文件读取模板    public static String readFileContent(Path filePath) throws IOException {        try (BufferedReader reader = Files.newBufferedReader(filePath, StandardCharsets.UTF_8)) {            return reader.lines()                         .collect(Collectors.joining(System.lineSeparator()));        }    }        // 安全的文件写入模板    public static void writeFileContent(Path filePath, String content) throws IOException {        try (BufferedWriter writer = Files.newBufferedWriter(filePath,                 StandardCharsets.UTF_8, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)) {            writer.write(content);        }    }        // 大文件处理模板    public static void processLargeFile(Path filePath, Consumer<String> lineProcessor) throws IOException {        try (Stream<String> lines = Files.lines(filePath, StandardCharsets.UTF_8)) {            lines.forEach(lineProcessor);        }    }}一键获取完整项目代码java6.4 互动与讨论6.4.1 深度思考问题让我们一起探讨几个有趣的技术问题:性能权衡:在什么情况下直接内存(DirectByteBuffer)比堆内存缓冲区更有优势?如何量化这种性能差异?并发安全:多线程环境下使用同一个FileChannel是否安全?如何设计线程安全的文件操作工具类?内存管理:处理超大文件(如几十GB的日志文件)时,如何平衡内存使用和处理效率?错误处理:在网络IO操作中,如何优雅地处理连接中断、超时等异常情况?性能监控:如何设计一个通用的IO性能监控框架,实时跟踪应用的IO操作效率?6.4.2 实战挑战项目挑战1:高性能日志分析器设计一个能够实时分析大型日志文件的工具,要求:支持多种日志格式实时统计关键指标内存使用控制在合理范围支持分布式部署挑战2:文件同步工具实现一个类似rsync的文件同步工具,包含:增量同步算法网络传输优化断点续传功能冲突解决策略6.4.3 技术交流与分享加入技术社区:GitHub:分享你的IO工具项目,参与开源贡献Stack Overflow:回答Java IO相关问题,帮助其他开发者技术博客:分享你的实践经验和性能优化心得持续学习建议:关注Java新版本的IO改进特性学习其他语言的IO模型(如Go的goroutine、Rust的async/await)深入理解操作系统的IO机制结语Java流操作是每个Java开发者必须掌握的核心技能。从传统的BIO到现代的NIO,从简单的文件读写到复杂的网络通信,IO操作贯穿了整个应用开发的生命周期。————————————————原文链接:https://blog.csdn.net/weixin_63944437/article/details/153959389
  • [技术干货] 【JavaScript】filter 方法的详解与实战
    一、filter 方法核心概念1.1 定义与本质filter 是 JavaScript 数组的内置迭代方法,属于 ES5 标准(2009 年发布),用于创建一个新数组,其元素是原数组中满足指定条件的所有元素。它的核心本质是 “筛选与过滤”,通过回调函数对数组元素进行条件判断,保留符合条件的元素并返回新数组。1.2 核心特性纯函数特性:filter 不会修改原数组,所有筛选操作均基于原数组的副本进行,最终返回全新数组迭代性:遍历原数组的每一个元素,执行相同的条件判断逻辑返回新数组:即使筛选结果为空,也会返回空数组而非 undefined 或 null回调执行时机:仅对数组中已初始化的索引元素执行回调,未初始化的空槽(empty)会被跳过1.3 应用场景filter 是前端开发中最常用的数组方法之一,典型应用场景包括:数据筛选(如表格数据过滤、搜索结果匹配)数据清洗(如去除空值、过滤无效数据)数组提纯(如提取特定类型元素、去重处理)条件分组(如按状态拆分数组、按类型分类数据)二、filter 语法与参数解析2.1 基本语法// 基础语法const newArray = array.filter(callback(element[, index[, array]])[, thisArg]);一键获取完整项目代码javascript2.2 参数详解2.2.1 回调函数(callback)必须参数,用于定义筛选条件的函数,返回一个布尔值:返回 true:当前元素会被保留到新数组中返回 false:当前元素会被排除回调函数接收三个参数:element:当前正在处理的数组元素(必须)index:当前元素的索引值(可选)array:调用 filter 方法的原数组(可选)2.2.2 thisArg(可选)可选参数,指定回调函数执行时的 this 指向。若不提供此参数,回调函数中的 this 在非严格模式下指向全局对象(window/global),严格模式下为 undefined。2.3 返回值返回一个新的数组,包含所有通过回调函数筛选的元素。新数组的长度由符合条件的元素数量决定,元素顺序与原数组保持一致。2.4 语法示例// 完整参数使用示例const numbers = [10, 20, 30, 40, 50];const context = { threshold: 30 };// 使用thisArg指定回调中的thisconst filtered = numbers.filter(function(element, index, array) {     console.log(`当前元素:${element},索引:${index},原数组:${array}`);     return element > this.threshold; // this指向context对象}, context);console.log(filtered); // 输出:[40, 50]一键获取完整项目代码javascript三、filter 基础用法全解析3.1 过滤基本数据类型数组3.1.1 过滤数字数组// 示例1:筛选偶数const nums = [1, 2, 3, 4, 5, 6, 7, 8];const evenNums = nums.filter(num => num % 2 === 0);console.log(evenNums); // 输出:[2, 4, 6, 8]// 示例2:筛选指定范围的数字const scores = [85, 92, 78, 65, 98, 59, 88];const passScores = scores.filter(score => score >= 80);console.log(passScores); // 输出:[85, 92, 98, 88]// 示例3:筛选非NaN的数字const mixedNums = [12, NaN, 34, NaN, 56, undefined, null];const validNums = mixedNums.filter(num => !isNaN(num) && num !== null && num !== undefined);console.log(validNums); // 输出:[12, 34, 56]一键获取完整项目代码javascript3.1.2 过滤字符串数组// 示例1:筛选长度大于3的字符串const words = ["apple", "cat", "banana", "dog", "grape"];const longWords = words.filter(word => word.length > 3);console.log(longWords); // 输出:["apple", "banana", "grape"]// 示例2:筛选包含指定字符的字符串const names = ["张三", "李四", "王五", "张晓明", "赵丽"];const zhangNames = names.filter(name => name.includes("张"));console.log(zhangNames); // 输出:["张三", "张晓明"]// 示例3:筛选非空字符串const mixedStrs = ["hello", "", "world", "  ", "javascript", null];const validStrs = mixedStrs.filter(str => typeof str === "string" && str.trim() !== "");console.log(validStrs); // 输出:["hello", "world", "javascript"]一键获取完整项目代码javascript3.2 过滤对象数组对象数组是开发中最常用的场景,filter 可基于对象的任意属性进行筛选。// 数据源:用户列表const users = [     { id: 1, name: "张三", age: 25, gender: "male", role: "admin" },     { id: 2, name: "李四", age: 17, gender: "female", role: "user" },     { id: 3, name: "王五", age: 32, gender: "male", role: "admin" },     { id: 4, name: "赵六", age: 28, gender: "male", role: "user" },     { id: 5, name: "钱七", age: 16, gender: "female", role: "user" }];// 示例1:筛选成年用户(age >= 18)const adultUsers = users.filter(user => user.age >= 18);console.log(adultUsers); // 输出:id为1、3、4的用户// 示例2:筛选管理员用户(role = "admin")const adminUsers = users.filter(user => user.role === "admin");console.log(adminUsers); // 输出:id为1、3的用户// 示例3:多条件筛选(男性且成年)const adultMaleUsers = users.filter(user => user.gender === "male" && user.age >= 18);console.log(adultMaleUsers); // 输出:id为1、3、4的用户// 示例4:基于嵌套属性筛选(假设有地址属性)const usersWithAddress = [     { id: 1, name: "张三", address: { province: "广东", city: "深圳" } },     { id: 2, name: "李四", address: { province: "广东", city: "广州" } },     { id: 3, name: "王五", address: { province: "浙江", city: "杭州" } }];const guangdongUsers = usersWithAddress.filter(user => user.address.province === "广东");console.log(guangdongUsers); // 输出:id为1、2的用户一键获取完整项目代码javascript3.3 过滤特殊值与空值处理数组中的 null、undefined、空对象等特殊值是数据清洗的常见需求。// 数据源:包含特殊值的混合数组const mixedArray = [     123,     null,     "hello",     undefined,     { name: "张三" },     "",     0,     NaN,     [],     {},     function() {},     true];// 示例1:筛选非null且非undefined的值const nonNullUndefined = mixedArray.filter(item => item !== null && item !== undefined);console.log(nonNullUndefined);    // 输出:[123, "hello", { name: "张三" }, "", 0, NaN, [], {}, function() {}, true]// 示例2:筛选有效数字(排除NaN、0)const validNumbers = mixedArray.filter(item => typeof item === "number" && !isNaN(item) && item !== 0);console.log(validNumbers); // 输出:[123]// 示例3:筛选非空对象(排除空对象、数组)const nonEmptyObjects = mixedArray.filter(item =>         typeof item === "object" &&         item !== null &&         Object.keys(item).length > 0);console.log(nonEmptyObjects); // 输出:[{ name: "张三" }]// 示例4:筛选真值(排除falsy值)const truthyValues = mixedArray.filter(Boolean);console.log(truthyValues);    // 输出:[123, "hello", { name: "张三" }, function() {}, true]一键获取完整项目代码javascript3.4 过滤类数组对象filter 是数组的方法,但可通过 Array.prototype.filter.call() 或 Array.from() 应用于类数组对象(如 arguments、NodeList 等)。// 示例1:处理arguments对象function filterEvenNumbers() {     // 将arguments转换为数组后使用filter     return Array.from(arguments).filter(num => num % 2 === 0);}const evenNums = filterEvenNumbers(1, 2, 3, 4, 5, 6);console.log(evenNums); // 输出:[2, 4, 6]// 示例2:处理DOM元素集合(NodeList)// 实际环境中需在浏览器端运行// 获取所有按钮元素,筛选出禁用的按钮// const buttons = document.querySelectorAll("button");// const disabledButtons = Array.prototype.filter.call(buttons, btn => btn.disabled);// console.log(disabledButtons); // 输出:所有禁用的按钮元素// 示例3:处理字符串(字符串也是类数组)const str = "javascript";const vowels = Array.from(str).filter(char => ["a", "e", "i", "o", "u"].includes(char));console.log(vowels); // 输出:["a", "a", "i"]一键获取完整项目代码javascript四、filter 高级使用技巧4.1 结合其他数组方法链式调用filter 常与 map、reduce、sort 等方法结合,实现复杂的数据处理逻辑。// 数据源:商品列表const products = [     { id: 1, name: "手机", category: "电子", price: 3999, stock: 50 },     { id: 2, name: "衬衫", category: "服装", price: 199, stock: 120 },     { id: 3, name: "电脑", category: "电子", price: 6999, stock: 30 },     { id: 4, name: "裤子", category: "服装", price: 299, stock: 80 },     { id: 5, name: "耳机", category: "电子", price: 499, stock: 100 }];// 示例1:filter + map:筛选电子类商品并提取名称和价格const electronicProducts = products     .filter(product => product.category === "电子")     .map(product => ({ name: product.name, price: product.price }));console.log(electronicProducts);// 输出:[{name: "手机", price: 3999}, {name: "电脑", price: 6999}, {name: "耳机", price: 499}]// 示例2:filter + reduce:计算服装类商品的总库存const clothingStockTotal = products     .filter(product => product.category === "服装")     .reduce((total, product) => total + product.stock, 0);console.log(clothingStockTotal); // 输出:200// 示例3:filter + sort + map:筛选价格>1000的商品,按价格排序,提取名称const expensiveProducts = products     .filter(product => product.price > 1000)     .sort((a, b) => a.price - b.price)     .map(product => product.name);console.log(expensiveProducts); // 输出:["手机", "电脑"]一键获取完整项目代码javascript4.2 实现数组去重利用 filter 结合 indexOf 或 includes 可实现数组去重,适用于简单数据类型数组。// 示例1:基础去重(利用indexOf)const duplicateNums = [1, 2, 2, 3, 3, 3, 4, 5, 5];const uniqueNums1 = duplicateNums.filter((num, index, array) => {     // 只保留第一次出现的元素(indexOf返回第一次出现的索引)     return array.indexOf(num) === index;});console.log(uniqueNums1); // 输出:[1, 2, 3, 4, 5]// 示例2:去重优化(利用includes)const duplicateStrs = ["a", "b", "a", "c", "b", "d"];const uniqueStrs = duplicateStrs.filter((str, index, array) => {     // 检查当前元素是否已在前面出现过     return !array.slice(0, index).includes(str);});console.log(uniqueStrs); // 输出:["a", "b", "c", "d"]// 示例3:对象数组去重(基于id属性)const duplicateUsers = [     { id: 1, name: "张三" },     { id: 2, name: "李四" },     { id: 1, name: "张三" },     { id: 3, name: "王五" }];const uniqueUsers = duplicateUsers.filter((user, index, array) => {     // 收集前面已出现的id,判断当前id是否存在     const existingIds = array.slice(0, index).map(item => item.id);     return !existingIds.includes(user.id);});console.log(uniqueUsers); // 输出:id为1、2、3的用户一键获取完整项目代码javascript4.3 嵌套数组的过滤处理多维数组时,可结合递归或 flat 方法与 filter 配合使用。// 示例1:二维数组过滤(直接遍历)const twoDArray = [     [1, 2, 3],     [4, 5, 6],     [7, 8, 9]];// 筛选所有子数组中的偶数const evenNumbers = twoDArray.map(subArray =>         subArray.filter(num => num % 2 === 0));console.log(evenNumbers); // 输出:[[2], [4, 6], [8]]// 示例2:二维数组扁平过滤(先扁平再过滤)const flatEvenNumbers = twoDArray     .flat() // 先将二维数组转为一维数组     .filter(num => num % 2 === 0);console.log(flatEvenNumbers); // 输出:[2, 4, 6, 8]// 示例3:深层嵌套数组过滤(递归实现)const deepArray = [     1,     [2, [3, 4], 5],     [6, [7, [8, 9]]]];// 递归扁平化函数function flattenArray(arr) {     return arr.reduce((acc, item) =>           Array.isArray(item) ? acc.concat(flattenArray(item)) : acc.concat(item), []);}// 筛选大于5的数字const filteredDeepNums = flattenArray(deepArray).filter(num => num > 5);console.log(filteredDeepNums); // 输出:[6, 7, 8, 9]一键获取完整项目代码javascript4.4 动态条件筛选通过动态生成筛选条件,实现灵活的多条件组合筛选。// 数据源:图书列表const books = [     { id: 1, title: "JavaScript高级程序设计", category: "编程", price: 99, publishYear: 2020 },     { id: 2, title: "深入理解计算机系统", category: "计算机", price: 128, publishYear: 2018 },     { id: 3, title: "React设计模式与最佳实践", category: "编程", price: 79, publishYear: 2021 },     { id: 4, title: "算法导论", category: "计算机", price: 118, publishYear: 2019 },     { id: 5, title: "TypeScript实战", category: "编程", price: 89, publishYear: 2022 }];// 动态筛选函数:接收筛选条件对象,返回符合条件的图书function filterBooks(books, conditions) {     return books.filter(book => {       // 遍历所有筛选条件,全部满足才返回true       return Object.entries(conditions).every(([key, value]) => {         // 若条件值为数组,判断是否包含(适用于多选项)         if (Array.isArray(value)) {           return value.includes(book[key]);         }         // 若条件值为函数,执行函数判断         if (typeof value === "function") {           return value(book[key]);         }         // 默认精确匹配         return book[key] === value;       });     });}// 示例1:筛选编程类且价格<100的图书const condition1 = {     category: "编程",     price: price => price < 100};const result1 = filterBooks(books, condition1);console.log(result1); // 输出:id为1、3、5的图书// 示例2:筛选2020年后出版的计算机或编程类图书const condition2 = {     category: ["计算机", "编程"],     publishYear: year => year >= 2020};const result2 = filterBooks(books, condition2);console.log(result2); // 输出:id为1、3、5的图书一键获取完整项目代码javascript4.5 在 TypeScript 中使用 filterTypeScript 中使用 filter 时,可通过类型守卫实现更精确的类型推断。// 示例1:基础类型过滤const mixedValues: (number | string | boolean)[] = [1, "2", 3, "4", true, 5];// 筛选数字类型,TypeScript可自动推断结果为number[]const numbersOnly = mixedValues.filter(value => typeof value === "number");// 示例2:对象类型过滤(类型守卫)interface User {     id: number;     name: string;     role: "admin" | "user" | "guest";}interface Product {     id: number;     name: string;     price: number;}// 联合类型数组const items: (User | Product)[] = [     { id: 1, name: "张三", role: "admin" },     { id: 2, name: "手机", price: 3999 },     { id: 3, name: "李四", role: "user" },     { id: 4, name: "电脑", price: 6999 }];// 自定义类型守卫函数function isUser(item: User | Product): item is User {     return "role" in item;}function isProduct(item: User | Product): item is Product {     return "price" in item;}// 筛选用户类型,结果类型为User[]const users = items.filter(isUser);// 筛选商品类型,结果类型为Product[]const products = items.filter(isProduct);一键获取完整项目代码javascript4.6 利用 thisArg 绑定上下文当筛选逻辑需要访问外部对象的属性时,可通过 thisArg 参数绑定上下文。// 示例:根据配置对象筛选数组const config = {     minScore: 60,     maxScore: 100,     subject: "math"};// 学生成绩数据const studentScores = [     { name: "张三", math: 85, english: 72 },     { name: "李四", math: 58, english: 88 },     { name: "王五", math: 92, english: 65 },     { name: "赵六", math: 45, english: 90 }];// 筛选指定科目成绩在[minScore, maxScore]范围内的学生const qualifiedStudents = studentScores.filter(function(student) {     const score = student[this.subject];     return score >= this.minScore && score <= this.maxScore;}, config); // 绑定this为config对象console.log(qualifiedStudents); // 输出:张三、王五(math成绩合格)一键获取完整项目代码javascript五、filter 实战开发案例5.1 实战案例 1:前端表格数据筛选实现一个支持多条件筛选的商品表格,包含价格区间、分类筛选、库存状态筛选。<!-- HTML结构(仅作演示,实际需结合前端框架) --><!--    <div class="filter-panel">     <select id="category-filter">       <option value="all">所有分类</option>       <option value="electronics">电子产品</option>       <option value="clothing">服装</option>       <option value="home">家居用品</option>     </select>     <input type="number" id="min-price" placeholder="最低价格">     <input type="number" id="max-price" placeholder="最高价格">     <select id="stock-filter">       <option value="all">所有库存</option>       <option value="in-stock">有库存</option>       <option value="out-stock">无库存</option>     </select>     <button id="filter-btn">筛选</button></div><table id="product-table">     <!-- 表格内容动态生成 --></table>--><script>// 1. 模拟商品数据const productData = [     { id: 1, name: "智能手机", category: "electronics", price: 3999, stock: 50, brand: "小米" },     { id: 2, name: "纯棉衬衫", category: "clothing", price: 199, stock: 0, brand: "优衣库" },     { id: 3, name: "笔记本电脑", category: "electronics", price: 6999, stock: 20, brand: "苹果" },     { id: 4, name: "沙发", category: "home", price: 2999, stock: 8, brand: "宜家" },     { id: 5, name: "牛仔裤", category: "clothing", price: 299, stock: 35, brand: "李维斯" },     { id: 6, name: "台灯", category: "home", price: 99, stock: 0, brand: "飞利浦" },     { id: 7, name: "耳机", category: "electronics", price: 499, stock: 100, brand: "华为" },     { id: 8, name: "床单", category: "home", price: 159, stock: 42, brand: "水星家纺" }];// 2. 筛选逻辑实现function filterProducts(products, filters) {     return products.filter(product => {       // 分类筛选       if (filters.category!== "all" && product.category!== filters.category) {         return false;       }       // 价格区间筛选       if (filters.minPrice!== "" && product.price < Number(filters.minPrice)) {         return false;       }       if (filters.maxPrice!== "" && product.price > Number(filters.maxPrice)) {         return false;       }       // 库存筛选       if (filters.stock === "in-stock" && product.stock === 0) {         return false;       }       if (filters.stock === "out-stock" && product.stock > 0) {         return false;       }       return true;     });}// 3. 绑定筛选事件(模拟)function handleFilter() {     // 模拟获取筛选条件(实际从DOM元素获取)     const filters = {       category: "electronics", // 从下拉框获取       minPrice: "1000", // 从输入框获取       maxPrice: "8000", // 从输入框获取       stock: "in-stock" // 从下拉框获取     };     // 执行筛选     const filteredProducts = filterProducts(productData, filters);             // 渲染筛选结果(实际需生成表格HTML)     console.log("筛选结果:", filteredProducts);     // 输出:[{id:1,...}, {id:3,...}, {id:7,...}]}// 模拟点击筛选按钮handleFilter();</script>一键获取完整项目代码javascript5.2 实战案例 2:接口返回数据清洗处理后端接口返回的复杂数据,筛选有效数据并格式化输出。// 1. 模拟后端接口返回数据const apiResponse = {     code: 200,     message: "success",     data: {       users: [         { id: 1, name: "张三", age: 25, email: "zhangsan@example.com", status: 1 },         { id: 2, name: "", age: 0, email: "lisi@example.com", status: 0 },         { id: 3, name: "王五", age: -5, email: "wangwu.example.com", status: 1 },         { id: 4, name: "赵六", age: 32, email: "zhaoliu@example.com", status: 1 },         { id: null, name: "钱七", age: 28, email: "", status: 2 },         { id: 6, name: "孙八", age: 45, email: "sunba@example.com", status: 1 }       ],       total: 6     }};// 2. 数据清洗工具函数const emailRegex = /^[^s@]+@[^s@]+.[^s@]+$/;function cleanUserData(rawData) {     // 边界判断:确保数据存在     if (!rawData || rawData.code!== 200 ||!rawData.data?.users) {       return { cleanedUsers: [], count: 0 };     }     const cleanedUsers = rawData.data.users       // 筛选有效用户:id存在且为数字、name非空、age为正整数、email格式正确、status为1      .filter(user =>             typeof user.id === "number" &&             user.id > 0 &&             typeof user.name === "string" &&             user.name.trim()!== "" &&             typeof user.age === "number" &&             user.age > 0 &&             emailRegex.test(user.email) &&             user.status === 1       )       // 格式化用户数据      .map(user => ({         userId: user.id,         userName: user.name.trim(),         userAge: user.age,         userEmail: user.email.toLowerCase()       }));     return {       cleanedUsers,       count: cleanedUsers.length     };}// 3. 执行数据清洗const { cleanedUsers, count } = cleanUserData(apiResponse);console.log(`有效用户数:${count}`); // 输出:3console.log("清洗后的用户数据:", cleanedUsers);// 输出:id为1、4、6的用户(已格式化)一键获取完整项目代码javascript5.3 实战案例 3:数组结构转换与筛选将扁平数组转换为树形结构,并筛选出包含指定节点的子树。// 1. 模拟扁平结构的部门数据const departments = [     { id: 1, name: "技术部", parentId: 0 },     { id: 2, name: "产品部", parentId: 0 },     { id: 3, name: "前端开发", parentId: 1 },     { id: 4, name: "后端开发", parentId: 1 },     { id: 5, name: "UI设计", parentId: 2 },     { id: 6, name: "产品经理", parentId: 2 },     { id: 7, name: "React开发", parentId: 3 },     { id: 8, name: "Vue开发", parentId: 3 },     { id: 9, name: "Java开发", parentId: 4 },     { id: 10, name: "测试部", parentId: 0 }];// 2. 扁平转树形结构函数function buildTree(nodes, parentId = 0) {     return nodes       // 筛选当前父节点的子节点      .filter(node => node.parentId === parentId)       // 递归构建子树      .map(node => ({         ...node,         children: buildTree(nodes, node.id)       }));}// 3. 筛选包含指定节点的子树function filterTreeByNode(tree, targetNodeId) {     // 遍历树节点     return tree.filter(node => {       // 若当前节点是目标节点,保留       if (node.id === targetNodeId) {         return true;       }       // 若当前节点有子节点,递归筛选子节点       if (node.children && node.children.length > 0) {         const filteredChildren = filterTreeByNode(node.children, targetNodeId);         // 若子节点中包含目标节点,更新子节点并保留当前节点         if (filteredChildren.length > 0) {           node.children = filteredChildren;           return true;         }       }       // 既不是目标节点,子节点也不包含目标节点,排除       return false;     });}// 4. 执行转换与筛选// 构建完整树形结构const fullTree = buildTree(departments);console.log("完整部门树:", fullTree);// 筛选包含"React开发"(id=7)的子树const filteredTree = filterTreeByNode(fullTree, 7);console.log("筛选后的部门树:", filteredTree);// 输出:技术部 -> 前端开发 -> React开发 的层级结构一键获取完整项目代码javascript六、filter 常见问题与坑点6.1 原数组不变的特性filter 不会修改原数组,而是返回新数组。若试图通过 filter 修改原数组元素,会导致意外结果。// 错误示例:试图通过filter修改原数组const numbers = [1, 2, 3, 4, 5];const filtered = numbers.filter(num => {     num *= 2; // 修改原数组元素     return num > 5;});console.log(filtered); // 输出:[3, 4, 5](实际是6、8、10的判断结果)console.log(numbers); // 输出:[1, 2, 6, 8, 10](原数组被修改,造成副作用)// 正确做法:筛选与修改分离const correctFiltered = numbers    .filter(num => num > 2.5) // 先筛选    .map(num => num * 2); // 再修改console.log(correctFiltered); // 输出:[12, 16, 20]console.log(numbers); // 输出:[1, 2, 6, 8, 10](原数组保持不变)一键获取完整项目代码javascript6.2 this 指向问题若未指定 thisArg,回调函数中的 this 指向可能不符合预期,尤其是在箭头函数与普通函数混用的场景。// 问题示例:this指向错误const filterConfig = {     threshold: 5};const numbers = [1, 3, 5, 7, 9];// 普通函数作为回调,this指向全局对象const filtered1 = numbers.filter(function(num) {     return num > this.threshold; // this.threshold 为undefined});console.log(filtered1); // 输出:[](错误结果)// 解决方案1:指定thisArg参数const filtered2 = numbers.filter(function(num) {     return num > this.threshold;}, filterConfig);console.log(filtered2); // 输出:[7, 9](正确结果)// 解决方案2:使用箭头函数(继承外部this)const filtered3 = numbers.filter(num => num > filterConfig.threshold);console.log(filtered3); // 输出:[7, 9](正确结果)一键获取完整项目代码javascript6.3 空数组与 undefined 的处理filter 对空数组返回空数组,对包含 undefined 的数组会将其视为 falsy 值过滤。// 示例1:空数组处理const emptyArray = [];const filteredEmpty = emptyArray.filter(item => item > 0);console.log(filteredEmpty); // 输出:[](无错误,返回空数组)// 示例2:包含undefined的数组const arrayWithUndefined = [1, undefined, 3, undefined, 5];// 直接筛选会排除undefinedconst filteredUndefined1 = arrayWithUndefined.filter(item => item);console.log(filteredUndefined1); // 输出:[1, 3, 5]// 如需保留undefined,需显式判断const filteredUndefined2 = arrayWithUndefined.filter(item => item!== undefined);console.log(filteredUndefined2); // 输出:[1, 3, 5](同上,因为undefined !== undefined为false)// 如需筛选出undefined,需反向判断const onlyUndefined = arrayWithUndefined.filter(item => item === undefined);console.log(onlyUndefined); // 输出:[undefined, undefined]一键获取完整项目代码javascript6.4 NaN 的过滤问题由于 NaN !== NaN,直接判断 NaN 会导致筛选失败,需使用 isNaN 函数。// 问题示例:直接判断NaN失败const arrayWithNaN = [1, 2, NaN, 3, NaN, 4];// 错误:NaN !== NaN,无法筛选出NaNconst filteredNaN1 = arrayWithNaN.filter(item => item === NaN);console.log(filteredNaN1); // 输出:[]// 正确示例:使用isNaN函数const filteredNaN2 = arrayWithNaN.filter(item => isNaN(item));console.log(filteredNaN2); // 输出:[NaN, NaN]// 筛选非NaN值const nonNaNValues = arrayWithNaN.filter(item => !isNaN(item));console.log(nonNaNValues); // 输出:[1, 2, 3, 4]一键获取完整项目代码javascript6.5 与 find 方法的区别filter 与 find 容易混淆,核心区别在于返回值:filter 返回所有符合条件的元素数组,find 返回第一个符合条件的元素。const users = [     { id: 1, name: "张三", age: 25 },     { id: 2, name: "李四", age: 30 },     { id: 3, name: "王五", age: 25 }];// 使用filter:返回所有25岁的用户(数组)const usersAge25 = users.filter(user => user.age === 25);console.log(usersAge25); // 输出:[{id:1,...}, {id:3,...}]// 使用find:返回第一个25岁的用户(对象)const firstUserAge25 = users.find(user => user.age === 25);console.log(firstUserAge25); // 输出:{id:1, name: "张三", age:25}// 性能差异:find找到第一个匹配项后停止遍历,filter需遍历整个数组// 如需获取单个元素,优先使用find一键获取完整项目代码javascript6.6 稀疏数组的处理filter 会跳过数组中的空槽(empty),只处理已初始化的元素。// 创建稀疏数组(索引1和3为空槽)const sparseArray = [1,, 3,, 5];console.log(sparseArray.length); // 输出:5// filter跳过空槽const filteredSparse = sparseArray.filter(item => item > 2);console.log(filteredSparse); // 输出:[3, 5](空槽被跳过)// 验证空槽是否被处理const checkedSparse = sparseArray.filter((item, index) => {     console.log(`索引${index}:${item}`);     return true;});// 输出:// 索引0:1// 索引2:3// 索引4:5// 空槽索引1和3未被处理一键获取完整项目代码javascript七、filter 性能优化策略7.1 避免在回调中执行复杂操作回调函数中的复杂计算会增加迭代成本,尤其是大数据量数组。应将复杂操作移到 filter 外部。// 优化前:回调中执行复杂计算const largeArray = Array.from({ length: 100000 }, (_, i) => i);// 回调中包含复杂的数学计算console.time("optimize-before");const resultBefore = largeArray.filter(num => {     // 复杂计算:质数判断     if (num <= 1) return false;     for (let i = 2; i <= Math.sqrt(num); i++) {       if (num % i === 0) return false;     }     return true;});console.timeEnd("optimize-before"); // 耗时较长// 优化后:将复杂计算封装为纯函数,减少回调复杂度(对性能影响较小,但代码更清晰)// 进一步优化:使用缓存或提前计算(若计算结果可复用)function isPrime(num) {     if (num <= 1) return false;     for (let i = 2; i <= Math.sqrt(num); i++) {       if (num % i === 0) return false;     }     return true;}console.time("optimize-after");const resultAfter = largeArray.filter(isPrime);console.timeEnd("optimize-after"); // 耗时与优化前相近,但代码可维护性更高一键获取完整项目代码javascript7.2 大数据量下的筛选优化当数组长度超过 10 万时,可考虑分批次筛选或结合其他方法优化。// 大数据量筛选优化:分批次处理(避免阻塞主线程)async function filterLargeArray(largeArray, filterFn, batchSize = 10000) {     const result = [];     const totalLength = largeArray.length;             for (let i = 0; i < totalLength; i += batchSize) {       // 分批次处理       const batch = largeArray.slice(i, i + batchSize);       const filteredBatch = batch.filter(filterFn);       result.push(...filteredBatch);                 // 每处理一批次,让出主线程(避免UI阻塞)       await new Promise(resolve => setTimeout(resolve, 0));     }             return result;}// 使用示例const hugeArray = Array.from({ length: 500000 }, (_, i) => i);filterLargeArray(hugeArray, num => num % 100 === 0)    .then(filteredResult => {       console.log(`筛选结果数量:${filteredResult.length}`); // 输出:5000     });一键获取完整项目代码javascript7.3 提前退出筛选(替代方案)filter 必须遍历整个数组,若只需找到部分符合条件的元素,可使用 for 循环提前退出。// 场景:从大数据量中筛选前100个符合条件的元素const largeArray = Array.from({ length: 100000 }, (_, i) => i);// 方案1:使用filter(需遍历整个数组)console.time("filter-full");const filterResult = largeArray.filter(num => num % 100 === 0).slice(0, 100);console.timeEnd("filter-full"); // 遍历10万次// 方案2:使用for循环(找到100个后提前退出)console.time("for-early-exit");const forResult = [];for (let i = 0; i < largeArray.length; i++) {     if (largeArray[i] % 100 === 0) {       forResult.push(largeArray[i]);       // 找到100个后退出循环       if (forResult.length === 100) break;     }}console.timeEnd("for-early-exit"); // 仅遍历10000次左右,性能提升显著一键获取完整项目代码javascript7.4 避免不必要的类型转换在回调中频繁进行类型转换会影响性能,应提前统一转换类型。// 优化前:回调中频繁转换类型const stringNumbers = Array.from({ length: 100000 }, (_, i) => i.toString());console.time("convert-in-callback");const convertedResult1 = stringNumbers.filter(str => {     const num = Number(str); // 每次回调都进行类型转换     return num > 50000;});console.timeEnd("convert-in-callback");// 优化后:提前转换类型,再筛选console.time("convert-before-filter");const numbers = stringNumbers.map(str => Number(str)); // 一次性转换const convertedResult2 = numbers.filter(num => num > 50000);console.timeEnd("convert-before-filter"); // 性能更优,尤其是大数据量一键获取完整项目代码javascript八、filter 与相似方法的区别8.1 filter vs mapfilter:用于筛选元素,返回符合条件的元素组成的新数组,数组长度可能减少map:用于转换元素,返回与原数组长度相同的新数组,元素值被转换const numbers = [1, 2, 3, 4, 5];// filter:筛选偶数(长度减少)const filtered = numbers.filter(num => num % 2 === 0);console.log(filtered); // 输出:[2, 4]// map:将数字翻倍(长度不变)const mapped = numbers.map(num => num * 2);console.log(mapped); // 输出:[2, 4, 6, 8, 10]// 组合使用:先筛选再转换const combined = numbers.filter(num => num % 2 === 0).map(num => num * 2);console.log(combined); // 输出:[4, 8]一键获取完整项目代码javascript8.2 filter vs findfilter:返回所有符合条件的元素数组(可能为空)find:返回第一个符合条件的元素(无符合条件元素时返回 undefined)const users = [     { id: 1, name: "张三", age: 25 },     { id: 2, name: "李四", age: 30 },     { id: 3, name: "王五", age: 25 }];// filter:返回所有25岁用户const allAge25 = users.filter(user => user.age === 25);console.log(allAge25); // 输出:[{id:1,...}, {id:3,...}]// find:返回第一个25岁用户const firstAge25 = users.find(user => user.age === 25);console.log(firstAge25); // 输出:{id:1,...}// 无符合条件时的返回值const allAge40 = users.filter(user => user.age === 40);console.log(allAge40); // 输出:[]const firstAge40 = users.find(user => user.age === 40);console.log(firstAge40); // 输出:undefined一键获取完整项目代码javascript8.3 filter vs reducefilter:专注于筛选元素,功能单一reduce:功能更强大,可实现筛选、汇总、转换等多种功能const numbers = [1, 2, 3, 4, 5, 6];// 使用filter筛选偶数const filterEven = numbers.filter(num => num % 2 === 0);console.log(filterEven); // 输出:[2, 4, 6]// 使用reduce实现筛选偶数const reduceEven = numbers.reduce((acc, num) => {     if (num % 2 === 0) {       acc.push(num);     }     return acc;}, []);console.log(reduceEven); // 输出:[2, 4, 6]// reduce的额外能力:筛选并计算总和const evenSum = numbers.reduce((sum, num) => {     return num % 2 === 0? sum + num : sum;}, 0);console.log(evenSum); // 输出:12(2+4+6)一键获取完整项目代码javascript8.4 filter vs some/everyfilter:返回符合条件的元素数组some:判断是否存在符合条件的元素,返回布尔值(找到第一个即停止)every:判断所有元素是否都符合条件,返回布尔值(找到第一个不符合即停止)const scores = [85, 92, 78, 65, 98];// filter:返回所有及格分数(>=60)const passedScores = scores.filter(score => score >= 60);console.log(passedScores); // 输出:[85, 92, 78, 65, 98]// some:判断是否有满分(100分)const hasPerfectScore = scores.some(score => score === 100);console.log(hasPerfectScore); // 输出:false// every:判断是否所有分数都及格const allPassed = scores.every(score => score >= 60);console.log(allPassed); // 输出:true// 性能差异:some/every可提前退出,filter需遍历全部一键获取完整项目代码javascript总结JavaScript 的 filter 方法是数组处理的核心工具之一,其纯函数特性、简洁语法和强大的筛选能力使其在前端开发中应用广泛。本文从基础概念出发,详细解析了 filter 的语法参数、基础用法、高级技巧,并通过实战案例展示了其在实际开发中的应用,同时总结了常见问题与性能优化策略。掌握 filter 方法的关键在于:理解其 “筛选 - 返回新数组” 的核心逻辑熟练结合其他数组方法实现复杂数据处理注意 this 指向、空值处理等常见坑点根据场景选择合适的优化策略合理运用 filter 方法可以大幅简化数据筛选代码,提高开发效率和代码可读性。在实际开发中,应结合具体需求,灵活搭配其他数组方法,构建高效、可维护的前端数据处理逻辑。————————————————原文链接:https://blog.csdn.net/weixin_60526471/article/details/153619654
  • [技术干货] 【Java Web学习 | 第五篇】CSS(4) -盒子模型
    CSS盒子模型在网页布局的世界里,CSS盒子模型是一切布局的基础。无论是简单的文本段落还是复杂的页面组件,都可以被看作是一个个矩形的"盒子"。掌握盒子模型的工作原理,是成为前端开发者的必备技能。本文将从基础概念出发,通过实例代码详细解析盒子模型的核心要素及实战技巧。1. 什么是CSS盒子模型?CSS盒子模型是一种将HTML元素视为矩形盒子的布局模型,每个盒子由四个部分组成:内容区(content):元素实际内容所在的区域内边距(padding):内容区与边框之间的空间边框(border):围绕内边距和内容区的线条外边距(margin):盒子与其他盒子之间的空间这四个部分共同决定了元素在页面中的大小和位置,是页面布局的三大核心之一(另外两个是浮动和定位)。2. 边框(border):盒子的"外衣"边框是盒子最外层的可见边界,由三个属性共同定义:宽度、样式和颜色。边框的基本属性#div1 {  width: 300px;  height: 200px;  border-width: 10px;      /* 边框宽度 */  border-style: solid;    /* 边框样式 */  border-color: pink;     /* 边框颜色 */}运行项目并下载源码css也可以使用简写形式,顺序可以任意调整:#div1 {  border: 10px solid pink;  /* 宽度 样式 颜色 */}运行项目并下载源码css边框样式是必须指定的属性,常用值包括:solid:实线边框dashed:虚线边框dotted:点线边框单边边框设置我们可以单独设置盒子四个方向的边框,利用CSS的层叠性可以简化代码:#div2 {  width: 200px;  height: 200px;  border: 5px solid blue;  /* 先设置四边为蓝色 */  border-top: 5px solid red; /* 再单独设置上边框为红色 */}运行项目并下载源码css边框对盒子大小的影响重要提示:边框会增加盒子的实际大小。例如,一个宽度为100px的盒子,如果设置了5px的边框,其实际宽度将变为110px(100 + 5×2)。解决方案:测量盒子时不包含边框如果测量包含边框,需从width/height中减去边框宽度的两倍表格细线边框表格边框默认会有重叠问题,使用border-collapse: collapse可以合并相邻边框:table, td, th {  border: 1px solid pink;  border-collapse: collapse; /* 合并相邻边框 */}运行项目并下载源码css3. 内边距(padding):内容与边框的缓冲带内边距用于控制内容区与边框之间的距离,让内容不会紧贴边框。内边距的基本用法内边距有四个方向的属性:padding-top:上内边距padding-right:右内边距padding-bottom:下内边距padding-left:左内边距也可以使用简写形式,规则如下:.div3 {  /* 1个值:上下左右都为5px */  /* padding: 5px; */    /* 2个值:上下5px,左右10px */  /* padding: 5px 10px; */    /* 3个值:上5px,左右10px,下20px */  /* padding: 5px 10px 20px; */    /* 4个值:上5px,右10px,下20px,左30px(顺时针方向) */  padding: 5px 10px 20px 30px;}运行项目并下载源码css内边距对盒子大小的影响与边框类似,指定了宽高的盒子设置内边距会使盒子变大。例如,一个200×200px的盒子,设置10px内边距后,实际大小会变为220×220px。解决方案:如果需要保持盒子总大小不变,应从width/height中减去内边距的总和。内边距的实用技巧当导航栏中每个菜单项的字数不同时,使用内边距代替固定宽度可以实现更灵活的布局:#nav {  height: 41px;  line-height: 41px; /* 垂直居中 */  background-color: #fcfcfc;}#nav a {  display: inline-block;  padding: 0 20px; /* 左右内边距撑开盒子 */  text-decoration: none;}运行项目并下载源码css这种方式能保证每个菜单项的内容与边缘距离一致,且能自适应内容长度。内边距不影响盒子大小的特殊情况如果盒子没有指定width/height属性,设置内边距不会撑开盒子大小:.div5 {  width: 300px;  height: 100px;  background-color: pink;}.div5 p {  padding: 50px; /* p元素没有指定宽高,不会超出父元素 */}运行项目并下载源码css4. 外边距(margin):盒子之间的距离外边距控制盒子与其他盒子之间的距离,其语法与内边距类似。外边距的基本用法外边距同样有四个方向的属性:margin-top:上外边距margin-right:右外边距margin-bottom:下外边距margin-left:左外边距简写规则与padding完全一致,这里不再赘述。外边距的典型应用:水平居中外边距最常用的技巧之一是 实现块级元素的水平居中,需要满足两个条件:盒子必须指定宽度(width)左右外边距都设置为auto.header {  width: 500px;  height: 200px;  background-color: skyblue;  margin: 100px auto; /* 上下100px,左右自动居中 */}运行项目并下载源码css对于行内元素或行内块元素,水平居中需要给其父元素设置text-align: center。外边距合并问题使用margin定义垂直方向的外边距时,可能会出现外边距合并的现象 --> 当父元素没有设置 “阻隔”(如边框、内边距、overflow 等)时,父元素的margin-top会与子元素的margin-top合并,表现为:父元素会 “吸收” 子元素的上外边距,两者的外边距合并为一个(取较大值)。视觉上,子元素的上外边距 “转移” 到了父元素身上,导致父元素跟着子元素一起向下移动(看起来像 “塌陷”)。例如:#father {  width: 400px;  height: 400px;  background-color: purple;  margin-top: 50px;}#son {  width: 200px;  height: 200px;  background-color: pink;  margin-top: 100px; /* 会导致父元素一起下移 */}运行项目并下载源码css显示效果【本来子元素按照margin-top:100px 应该从父元素的上顶部分开,但实际并没有,而是父元素跟者子元素一起下来了(塌陷)】:解决方法:为父元素定义上边框(可设为透明)border: 1px solid transparent;为父元素定义上内边距 padding: 1px;为父元素添加overflow: hidden(推荐,不改变盒子大小)#father {  /* 其他样式不变 */  overflow: hidden; /* 解决外边距塌陷 */}运行项目并下载源码css显示效果:清除默认内外边距网页中很多元素(如ul、p等)会自带默认的内外边距,且不同浏览器表现不一致。因此,布局前通常会先清除这些默认样式:* {  padding: 0; /* 清除内边距 */  margin: 0;  /* 清除外边距 */}运行项目并下载源码css注意:行内元素为了兼容性,建议只设置左右方向的内外边距,上下方向可能不起作用(转换为块级或行内块元素后可正常使用)。综合代码演示<!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <meta name="viewport" content="width=device-width, initial-scale=1.0">    <title>盒子模型</title>    <!-- 页面布局三大核心:盒子模型、浮动、定位 -->    <!--    谓盒子模型:就是把HTML页面中的布局元素看做是一个矩形的盒子,也就是一个盛装内容的容器            CSS盒子模型本质上是一个盒子,封装周围的HTML元素,包括:边框、外边距、内边距和实际内容    -->    <style>        /* 注意,这句话应为我们使用CSS时的第一句话        * {            padding: 0; 清除内边距             margin: 0;  清除外边距        }         */        /*  <!-- 111.border -->        <!-- border可以设置元素的边框,边框由三部分组成:边框宽度(粗细)、边框样式、边框颜色            border: border-width | border-style | border-color;                 边框属性简写(复合写法):border-width border-style border-color;(没有顺序要求)        -->         */        #div1 {            width: 300px;            height: 200px;            /* 1.border-width 边框的粗细,一般情况下都用 px */            border-width: 10px;            /* 2.border-style 边框的样式 solid(实现边框) dashed(虚线边框) dotted(点线边框) */            border-style: solid;            /* 3.border-color */            border-color: pink;            /* 4.边框的简写 */            /* border: 10px solid pink; */        }        /* 5.边框分开写法: border-top/left/right/bottom: 5px solid red */        /* 注意:11.边框会额外增加盒子的实际大小,比如 width:100px的盒子,若有border: 5px solid red ,则盒子的实际宽度为100+2*5                解决方案:1. 测量盒子的时候不要量边框                         2. 如果测量的时候包含了边框,则需要width/height减去边框宽度*2(若两边都有边框)        */        #div2 {            width: 200px;            height: 200px;            /* 给定一个200*200的盒子,设置上边框为红色,其余边为蓝色 */            /* border-top: 5px solid red;            border-bottom: 5px solid blue;            border-left: 5px solid blue;            border-right: 5px solid blue; */            /* 这种写法更好,合理运用层叠性(就近原则) */            border: 5px solid blue;            border-top: 5px solid red;        }        /* 6.表格细线边框 (边框与边框之间会有像素融合问题,5px+5px=10px)*/        /* 解决:border-collapse: collapse(合并) 相邻边框合并在一起*/        table {            width: 500px;            height: 250px;            text-align: center;        }        table,        td,        th {            border: 1px solid pink;            /* 合并相邻的边框 */            border-collapse: collapse;        }        /* 7.内边距 padding 盒子里面的内容默认适合盒子的边缘紧挨着的,这时就需要padding来进行调节            11.padding 属性用于设置内边距,即边框和内容之间的距离                1.padding-left 左内边距                2.padding-right 右内边距                3.padding-top 上内边距                4.padding-bottom 下内边距            22.padding复合写法(简写)                1.padding: 5px;  1个值,代表上下左右都有5像素内边距                2.padding: 5px 10px; 2个值,代表上下内边是5像素,左右内边距是10像素                3.padding: 5px 10px 20px; 3个值,代表上内边距是5像素,左右内边距是10像素,下内边距是20像素                4.padding: 5px 10px 20px 30px; 4个值,上是5像素,右10像素,下20像素,左30像素 顺时针(上右下左)            33.padding会影响盒子的实际大小,也就是说,如果盒子已经有了宽度和高度,此时再指定内边框,会撑大盒子                解决方案:如果要保证盒子跟效果图大小一致,则让width/height减去多出来的内边距大小即可(注意两边的内边距都要考虑进去)        */        .div3 {            width: 200px;            height: 200px;            background-color: pink;            /* 在浏览器右键点击检查,把鼠标移动到对应的位置,内边距部分会变为跟背景不一样的颜色 */            /* padding-left: 5px;            padding-top: 5px;            padding-right: 5px;            padding-bottom: 5px; */            /* 内边距简写 */            /* padding: 5px; */            /* padding: 5px 10px; */            /* padding: 5px 10px 20px; */            padding: 5px 10px 20px 30px;        }        /* 7.1 内边距的应用:当网页中每个导航栏(盒子)里面的字数不一样多时,我们就不要统一给每一个盒子设定宽度了              直接用padding撑开盒子,就可以实现即使内容长度不一样,内容与盒子之间的边距也一样        */        #nav {            height: 41px;            border-top: 3px solid #ff8500;            border-bottom: 1px solid #edeef0;            background-color: #fcfcfc;            /* 运用了继承 a继承来自div的行高 */            line-height: 41px;        }        #nav a {            /* height: 41px; 由于a属于行内元素,这样设置无效,此时必须要转换 行内元素->行内块元素(不能装换成块级元素,因为其无法一行显示多个) */            display: inline-block;            height: 41px;            padding: 0 20px;            font-size: 15px;            color: #4c4c4c;            text-decoration: none;        }        /* 伪类选择器 */        #nav a:hover {            background-color: #eee;            color: #ff8500;        }        /* 7.2 padding不会影响盒子大小的情况            如果盒子本身没有指定width/height属性,则此时padding就不会撑开盒子大小(对应没有设置的属性)            总结:孩子有边框,孩子变大了,但是父亲不受影响,也就是父盒子里面的子盒子会受内边框影响,但是父盒子不会发生变化        */        .div5 {            width: 300px;            height: 100px;            background-color: pink;        }        .div5 p {            /* p的width不会变成300+30*2=360px,在自身width没有指定的情况下,不会超出父亲的范围 */            /* width: 100%; */                        padding:30px;        }        /* 8. 外边距(margin) 控制盒子和盒子之间的距离            属性:  1.margin-left 左外边距                    2.margin-right 右外边距                    3.margin-top 上外边距                    4.margin-bottom 下外边距            注意:margin简写方式代表的意义和padding完全一致        */        .div6 {            height: 200px;            width: 100px;            background-color: pink;        }        /* #one {            margin-bottom: 20px;        } */        #two {            margin-top: 20px;            margin-left: 20px;            margin-bottom: 50px;        }        /* 8.1 外边距的典型应用            外边距可以让块级盒子水平居中(盒子默认左侧对齐),但是必须满足两个条件:                1.盒子必须指定了宽度(width),否则它就跟浏览器/父盒子一样宽了                2.盒子左右的外边距都设置为 auto            以下三种写法都可以:                1.margin-left: auto; margin-right: auto;                2.margin: auto; (上下左右都auto了)                3.(常用)margin: 0px auto; (上下外边距为0px 左右auto)            注意:以上方法是让块级元素居中,行内元素或者行内块元素水平居中给其 父元素 添加 text-align: center; 即可        */        .header {            width: 500px;            height: 200px;            background-color: skyblue;            /* 简写法:上下外边距为100px,左右外边距auto实现块级元素自动水平居中 */            margin: 100px auto;            /* text-align: center 使行内元素和行内块元素(img)都会居中对齐 */            text-align: center;        }        #span1 {            margin: 0 auto;            /* margin: 0 auto;  不起效果,其只对块级元素起作用                给其父元素添加 text-align: center才对            */        }        /* 8.2  使用margin定义块元素的垂直外边距时,可能会出现外边距的合并        嵌套块元素垂直外边距的塌陷(父盒子跟者子盒子一起塌下来了):            对于两个嵌套关系(父子关系)的块级元素,父元素有上边距同时子元素也有上边距,此时父元素会塌陷较大的外边距值                解决方案:                    1.可以为父元素定义上边框                    2.可以为父元素定义上内边框                    3.可以为父元素添加 overflow: hidden         */        #father {            width: 400px;            height: 400px;            background-color: purple;            margin-top: 50px;            /* 父元素塌陷解决 */            /* 1.  transparent(透明)*/            /* border: 1px solid transparent; */            /* 2. */            /* padding: 1px; */            /* 3. 最好,不会改变盒子的大小,上面两种都有影响*/            overflow: hidden;            /* 4.还有其他方法,比如 浮动、固定、绝对定位 这些的盒子不会有塌陷问题 */        }        #son {            width: 200px;            height: 200px;            background-color: pink;            /* 本来子元素按照margin-top:100px 应该从父元素的上顶部分开,但实际并没有,而是父元素跟者子元素一起下来了(塌陷) */            margin-top: 100px;        }        /* 9. 内外边距的清除  */        /*  网页很多元素都会自带默认的内外边距,而且不同浏览器默认的也不一致。            因此我们在布局前,要先清除网页元素的内外边距                 *{                    padding: 0; 清除内边距                    margin: 0; 清除外边距                }           !注意:                 行内元素为了照顾兼容性,尽量只设置左右内外边距,不要设置上下内外边距(即使设置也不起效果)。但是转换为块级元素和行内元素就可以        */        /* ! 这句话也是我们CSS的第一行代码  !*/        * {            padding: 0;            margin: 0;        }        #span2 {            background-color: pink;            margin: 20px;        }    </style></head><body>    <!-- 测试边框各属性 -->    <div id="div1"></div>    <br />    <!-- 测试边框分开写法 -->    <div id="div2"></div>    <!-- 测试表格细线边框 -->    <table align="center" border="1" cellpadding="0" cellspacing="0">        <thead> <!--注意不要把<thead>和<th>搞混 前一个是表结构标签,使表格层次看着更清晰,而后者是类似加粗版的<td>-->            <tr>                <th>排名</th>                <th>关键词</th>                <th>趋势</th>                <th>进入搜索</th>                <th>最近七日</th>                <th>相关链接</th>            </tr>        </thead>        <tbody>            <tr>                <td>1</td>                <td>鬼吹灯</td>                <td><img src="picture/down.jpeg" /></td>                <td>345</td>                <td>123</td>                <td><a href="#">贴吧</a> <a href="#">图片</a> <a href="#">百科</a></td>            </tr>            <tr>                <td>2</td>                <td>盗墓笔记</td>                <td><img src="picture/down.jpeg" /></td>                <td>124</td>                <td>123421</td>                <td><a href="#" target="_blank">贴吧</a> <a href="#">图片</a> <a href="#">百科</a></td>            </tr>            <tr>                <td>3</td>                <td>西游记</td>                <td><img src="picture/up.jpeg" /></td>                <td>212</td>                <td>3213</td>                <td><a href="#" target="_blank">贴吧</a> <a href="#">图片</a> <a href="#">百科</a></td>            </tr>            <tr>                <td>4</td>                <td>甄嬛传</td>                <td><img src="picture/up.jpeg" /></td>                <td>2343</td>                <td>243343</td>                <td><a href="#" target="_blank">贴吧</a> <a href="#">图片</a> <a href="#">百科</a></td>            </tr>        </tbody>    </table>    <!-- 盒子模型内边距测试 -->    <div class="div3">这是盒子模型的内边距测试 天王盖地虎,宝塔镇河妖妖怪阿斯蒂芬哈喽</div>    <!-- 内边距的应用 -->    <div id="nav">        <a href="">新浪微博</a>        <a href="">手机新浪网</a>        <a href="">移动客户端</a>        <a href="">微博</a>        <a href="">交友网</a>    </div>    <!-- 测试padding不会影响盒子大小的情况 -->    <div class="div5">        <p>        </p>    </div>    换行    <br>    <!-- 测试盒子模型之外边距 -->    <div class="div6" id="one"></div>    <div class="div6" id="two"></div>    <!-- 外边距的应用 -->    <div class="header">        <span id="span1">里面的文字,为行内元素</span>        <img src="picture/up.jpeg" />    </div>    <br>    <!-- 外边距的合并 -->    <div id="father">        <div id="son"></div>    </div>    <br />    <!-- 内外边距的清除 -->    <!-- 网页很多元素都会自带默认的内外边距,而且不同浏览器默认的也不一致。         因此我们在布局前,要先清除网页元素的内外边距     -->    12345    <ul>        <li>            你好        </li>    </ul>    <span id="span2">行内元素尽量只设置左右的内外边距</span></body></html>运行项目并下载源码html显示效果:CSS美化三剑客:圆角边框、盒子阴影与文字阴影在网页设计中,细节决定品质。圆角边框、盒子阴影和文字阴影这三个CSS属性,虽然看似简单,却能瞬间提升页面的精致度和立体感。1. 圆角边框(border-radius):告别生硬直角默认情况下,HTML元素的边框都是直角的,显得生硬刻板。border-radius属性通过设置"圆角半径",能让元素边缘呈现平滑的弧形效果,是现代UI设计的基础。核心语法:圆角的本质圆角效果的原理是在元素的每个角落绘制一个圆形(或椭圆),圆形与边框的交集形成弧形边缘。border-radius的值就是这个圆形的半径,值越大,圆角越明显。/* 基础语法 */selector {  border-radius: length; /* 可以是px、%等单位 */}运行项目并下载源码css实战案例:从矩形到圆形圆形效果当元素是正方形时,将border-radius设置为宽度的50%,即可得到完美圆形:.yuanxing {  width: 300px;  height: 300px; /* 宽高相等的正方形 */  background-color: pink;  border-radius: 50%; /* 半径=宽高的一半,形成圆形 */}运行项目并下载源码css圆角矩形对于长方形,将border-radius设置为高度的一半,可得到两侧半圆的胶囊形:.juxing {  width: 300px;  height: 100px; /* 高度是宽度的1/3 */  background-color: pink;  border-radius: 50px; /* 50px = 100px高度的一半 */}运行项目并下载源码css自定义不规则圆角border-radius支持为四个角分别设置不同半径,顺序为左上角→右上角→右下角→左下角(顺时针):.radius1 {  width: 200px;  height: 200px;  background-color: pink;  /* 单独设置左上角圆角 */  border-top-left-radius: 30px;  /* 也可简写:border-radius: 10px 20px 30px 40px; */}运行项目并下载源码css实用技巧单位选择:px适合固定尺寸的圆角,%适合响应式设计(随元素大小自动调整)兼容处理:现代浏览器均支持,但老旧浏览器(如IE8及以下)不支持,需谨慎使用常见场景:按钮、头像、卡片组件、输入框等需要柔和边缘的元素2. 盒子阴影(box-shadow):给元素添加立体感现实世界中,物体总会因为光线产生阴影,box-shadow属性正是通过模拟这种光影效果,让平面的元素产生立体感。语法解析selector {  box-shadow: h-shadow v-shadow blur spread color inset;}运行项目并下载源码css各参数含义:h-shadow:必需,水平阴影位置(正值向右,负值向左)v-shadow:必需,垂直阴影位置(正值向下,负值向上)blur:可选,模糊距离(值越大,阴影越模糊)spread:可选,阴影尺寸(正值扩大阴影,负值缩小)color:可选,阴影颜色(常用rgba设置半透明)inset:可选,将外阴影改为内阴影(默认是外阴影,不可写outset)实战案例:交互增强效果基础阴影效果为元素添加轻微阴影,增强层次感:.shadow1 {  width: 200px;  height: 200px;  background-color: pink;  /* 水平11px、垂直17px、模糊14px、半透明黑色阴影 */  box-shadow: 11px 17px 14px -4px rgba(0, 0, 0, 0.3);}运行项目并下载源码csshover交互阴影鼠标悬浮时显示阴影,提升交互体验:.shadow1 {  width: 200px;  height: 200px;  background-color: pink;  transition: box-shadow 0.3s; /* 平滑过渡 */}.shadow1:hover {  /* 鼠标经过时显示阴影 */  box-shadow: 11px 17px 14px -4px rgba(0, 0, 0, 0.3);}运行项目并下载源码css注意事项阴影不占用空间:不会影响其他元素的布局,也不会撑开父容器多重阴影:用逗号分隔可添加多个阴影,实现复杂效果性能考量:过多或过大的阴影可能影响页面渲染性能,需适度使用3. 文字阴影(text-shadow):让文字更有质感文字作为页面的核心内容,适当的阴影能增强可读和视觉冲击力。text-shadow的用法与box-shadow类似,但作用对象是文字。语法解析selector {  text-shadow: h-shadow v-shadow blur color;}运行项目并下载源码css参数与盒子阴影基本一致,只是没有spread(阴影尺寸)和inset(内阴影)属性。实战案例:突出标题文字#character {  font-size: 50px;  color: orange;  font-weight: 700;  /* 水平5px、垂直5px、模糊6px、半透明黑色阴影 */  text-shadow: 5px 5px 6px rgba(0, 0, 0, 0.3);}运行项目并下载源码css这段代码会让文字产生轻微的立体感,同时与背景形成更好的区分度。创意用法发光效果:使用与文字同色的阴影,设置较大的模糊值.glow-text {  color: white;  text-shadow: 0 0 10px #fff, 0 0 20px #fff, 0 0 30px #ff00de;}运行项目并下载源码css立体字效果:叠加多个方向的阴影,模拟光照层次感4. 综合实战:打造精致卡片组件将三个属性结合使用,能快速提升组件质感。例如一个产品卡片:.product-card {  width: 300px;  padding: 20px;  background: orange;  border-radius: 10px; /* 圆角边框 */  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5); /* 轻微外阴影 */}.product-card h3 {   font-size: 20px;   text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5); /* 文字阴影 */}运行项目并下载源码css这样的卡片会显得精致且有层次,远胜于生硬的直角和扁平效果。综合代码演示<!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <meta name="viewport" content="width=device-width, initial-scale=1.0">    <title>圆角边框+盒子阴影+文字阴影</title>    <style>        /* 111.圆角边框  */        /* border-radius: length; 设置元素的外边框圆角 radius(圆的半径),圆与边框的交集形成的圆角效果             参数值可以为 数值 或 百分比 的形式                1.如果盒子是个正方形,想要设置为一个圆,把数值修改为高度或宽度的一半即可,或者直接写为50%                2.如果盒子是个矩形,设置为高度的一半就可以                3.该属性是一个简写属性,可以跟四个值,分别代表左上角、右上角、右下角、左下角                4.每个角分开写:border-top-left-radius、border-top-right-radius(top必须在前面)、                               border-bottom-right-radius、border-bottom-lef-radius(bottom也要在前面)        */        /* 圆形  radiu设置为盒子宽度的一半即可*/        .yuanxing {            width: 300px;            height: 300px;            background-color: pink;            /* border-radius: 150px; */            /* 50% 就是宽度和高度的一半 */            border-radius: 50%;        }        /* 圆角矩形 radius设置为盒子高度的一半*/        .juxing {            width: 300px;            height: 100px;            background-color: pink;            border-radius: 50px;        }        /* 每个角设置不同的radius */        .radius1 {            width: 200px;            height: 200px;            background-color: pink;            /* border-radius: 10px 20px 30px 40px; */            /* border-radius: 10px 40px; */            border-top-left-radius: 30px;        }        /* 222.盒子阴影  */        /* 语法:box-shadow: h-shadow v-shadow blur spread color inset                h-shadow 必需。水平阴影的位置,允许负值(数值越大,影子越往右边偏移)                v-shadow 必需。垂直阴影的位置,允许负值(数值越大,影子越往下偏移)                blur 可选。模糊距离(影子是虚的还是实的,数值越大,影子越模糊)                spread 可选。阴影的尺寸                color 可选。阴影的颜色                inset 可选。将外部阴影(outset)改为内部阴影            注意;                1.默认是外阴影(outset),但是不可以书写这个单词,否则将导致阴影无效                2.盒子阴影不占空间,不会影响其他盒子的排列位置        */        .shadow1 {            width: 200px;            height: 200px;            background-color: pink;            margin: 100px auto;            /* rgba半透明化 */            /* box-shadow: 11px 17px 14px -4px rgba(0, 0, 0, 0.3); */        }        .shadow1:hover {            /* 原先盒子没有影子,当鼠标经过盒子时就添加阴影效果 */            box-shadow: 11px 17px 14px -4px rgba(0, 0, 0, 0.3);        }        /* 文字阴影 */        /* 语法: text-shadow: h-shadow v-shadow blur color;                h-shadow 必需。水平阴影的位置,允许负值                v-shadow 必需。竖直阴影的位置,允许负值                blur 可选。模糊的距离)                color 可选。模糊的颜(数值越大阴影越虚)        */        #character {            font-size: 50px;            color: orange;            font-weight: 700;            text-shadow: 5px 5px 6px rgba(0, 0, 0, 0.3);        }          .glow-text {            color: aqua;            font-size: 100px;            text-shadow: 0 0 10px #fff, 0 0 20px #fff, 0 0 30px aqua;          }          .product-card {            width: 300px;            padding: 20px;            background: orange;            border-radius: 10px; /* 圆角边框 */            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5); /* 轻微外阴影 */          }        .product-card h3 {            font-size: 20px;            text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5); /* 文字阴影 */        }    </style></head><body>    <!-- 圆形的做法  -->    圆形    <div class="yuanxing"></div>    <p>分隔</p>    <!-- 圆角矩形 -->    圆角矩形    <div class="juxing"></div>    <p>分隔</p>    <div class="radius1"></div>    <p>分隔</p>    <!-- 盒子阴影测试 -->    <div class="shadow1"></div>    <!-- 文字阴影测试 -->    <div id="character">你是阴影,我是火影</div>    <div class="glow-text">glow测试</div>    <div class="product-card">        <h3>大夏境内,诸神禁行</h3>    </div></body></html>运行项目并下载源码html————————————————原文链接:https://blog.csdn.net/2401_86760859/article/details/154315541
  • [技术干货] 【Java Web学习 | 第八篇】JavaScript(2) 基础知识2
    JavaScript 运算符与流程控制全解析在 JavaScript 中,运算符和流程控制是实现逻辑处理的基础。本文在前文基础上补充for循环内容,全面讲解比较运算符、逻辑运算符、条件判断语句(if、switch)及循环语句(while、for),帮助你掌握 JavaScript 的逻辑构建能力。一、运算符:自增、比较与逻辑1. 自增运算符(++)自增运算符分为前缀(++i)和后缀(i++),核心区别在于返回值时机:后缀自增(i++):先返回当前值,再自增前缀自增(++i):先自增,再返回新值let i = 1;console.log(i++ + ++i + i); // 结果为7// 解析:// 1. i++ 先返回1,i变为2// 2. ++i 先自增为3,返回3// 3. 此时i=3,总和:1+3+3=7一键获取完整项目代码javascript2. 比较运算符用于判断值的关系,返回布尔值,需重点区分==与===:运算符    特点    示例    结果==    只比较值(隐式转换类型)    2 == "2"    true(字符串转数字)===    比较值和类型(无转换)    2 === "2"    false(类型不同)!=    只比较值不相等    2 != "2"    false(值相等)!==    比较值或类型不相等    2 !== "2"    true(类型不同)特殊规则:字符串按字符编码比较(如"a" < "b"为true)NaN与任何值比较都返回false(包括自身)console.log(2 == "2"); // true(隐式转换)console.log(2 === "2"); // false(类型不同)console.log("aabgg" < "bzzzz"); // true(首字符"a"<"b")一键获取完整项目代码javascript3. 逻辑运算符组合多个条件判断,返回布尔值:运算符    描述    示例    结果&&    逻辑与(两边都真才真)    3<5 && 3<9    true两条竖线(打不出来)    逻辑或(至少一边真则真)    3<5 两条竖线 3>100    true!    逻辑非(取反)    !(2>5)    true真值/假值规则:假值:0、""、null、undefined、NaN真值:除假值外的所有值(如非空字符串、非0数字)console.log(!9); // false(9是真值)console.log(!""); // true(空字符串是假值)一键获取完整项目代码javascript二、条件判断语句1. if 语句根据条件执行代码块,条件会隐式转换为布尔值:// 语法if (条件) {  // 条件为真时执行} else {  // 条件为假时执行}// 示例:判断空格字符串是否为真if (" ") { // 空格字符串是真值(非空)  console.log(true); // 输出:true} else {  console.log(false);}一键获取完整项目代码javascript2. 三目运算符if-else的简写形式:条件 ? 表达式1 : 表达式2// 判断2与"2"是否绝对相等document.write(2 === "2" ? "相等" : "不等"); // 输出:不等一键获取完整项目代码javascript3. switch 语句用于多条件等值判断(使用===比较),需配合break防止穿透:let num5 = 3;switch (num5) {  case 1:    console.log("选了1");    break;  case 2:    console.log("选了2");    break;  case 3:    console.log("选了3"); // 匹配成功,输出此句    break;  default:    console.log("无匹配项");}// 输出:选了3一键获取完整项目代码javascript三、循环语句1. while 循环根据条件重复执行代码块,continue可跳过本次循环:let n = 5;while (n--) { // n从5递减到0  if (n === 3) continue; // 跳过n=3的循环  document.write(`执行第${n}次<br/>`);}// 输出:// 执行第4次// 执行第2次// 执行第1次// 执行第0次一键获取完整项目代码javascript2. for 循环更灵活的循环方式,适合已知循环次数的场景,语法:for (初始化; 条件; 更新) {  // 循环体}一键获取完整项目代码javascript示例:基本用法// 输出1-5for (let i = 1; i <= 5; i++) {  console.log(i); // 依次输出1、2、3、4、5}一键获取完整项目代码javascript总结本文全面讲解了 JavaScript 核心运算符和流程控制:自增运算符的前缀/后缀差异影响返回值比较运算符中===比==更严格(检查类型)逻辑运算符依赖真值/假值转换条件判断:if适合区间判断,switch适合等值判断循环:while适合未知次数,for适合已知次数,灵活使用continue和break————————————————原文链接:https://blog.csdn.net/2401_86760859/article/details/154581317
  • [技术干货] Java IO 流进阶:Buffer 与 Channel 核心概念解析及与传统 IO 的本质区别
     在 Java IO 编程中,传统的字节流与字符流大家都不陌生,但当面对高并发、大文件处理等场景时,NIO(New IO)中的 Buffer 与 Channel 逐渐成为性能优化的关键。本文将深入剖析 Buffer 与 Channel 的核心概念,通过对比传统 IO 流,带你理解它们为何能显著提升 IO 效率,并配合直观的图示帮你建立清晰的认知。一、传统 IO 流的局限性:为什么需要 Buffer/Channel?        在了解 Buffer 与 Channel 之前,我们先回顾传统 IO 流的工作方式。传统 IO 流分为字节流(InputStream/OutputStream) 和字符流(Reader/Writer),其核心特点可概括为:单向传输:流是单向的,输入流只能读,输出流只能写,如FileInputStream只能从文件读数据,FileOutputStream只能向文件写数据。阻塞操作:读写操作是阻塞的,当调用read()或write()时,线程会一直等待数据传输完成,期间无法做其他事情。直接操作数据:数据通过流直接传输,没有中间缓冲层,每次读写都可能触发底层系统调用(如磁盘 IO 或网络 IO),而系统调用的开销是很大的。我们用一张图直观展示传统 IO 流的工作模式: 传统 IO 的瓶颈:在高并发场景下,频繁的系统调用和线程阻塞会导致资源浪费(如线程上下文切换),而单向传输也限制了数据操作的灵活性。为解决这些问题,JDK 1.4 引入了 NIO,其中 Buffer(缓冲区)和 Channel(通道)是核心组件。二、Buffer:数据的 "临时仓库"        Buffer 是 NIO 中用于存储数据的容器,本质是一块内存区域,可以理解为 "数据的临时仓库"。所有数据的读写都必须通过 Buffer 完成,这与传统 IO 直接操作流的方式截然不同。2.1 Buffer 的核心属性Buffer 有三个核心属性,决定了其读写状态,这是理解 Buffer 的关键:capacity(容量):Buffer 的最大容量(初始化后不可变),即最多能存储多少数据(如 1024 字节)。position(位置):当前操作的位置(类似指针)。写数据时:position 从 0 开始,每写入一个数据,position+1,最大为 capacity-1。读数据时:position 从 0 开始,每读取一个数据,position+1,最大为 limit-1。limit(限制):当前可操作的数据边界。写模式下:limit = capacity(最多写到容量上限)。读模式下:limit = 写模式结束时的 position(最多读到实际写入的数据量)。此外,还有一个可选属性mark(标记),用于记录某个位置,方便后续通过reset()回到该位置。2.2 Buffer 的工作流程(以读文件为例)写模式:从 Channel 读取数据到 Buffer,此时 position 从 0 开始递增,直到数据写完(position = 实际写入量)。切换读模式:调用flip()方法,将 limit 设为当前 position,position 重置为 0,准备读取数据。读模式:从 Buffer 读取数据到程序,position 从 0 开始递增,直到 limit(即实际写入量)。清空 / 重用:调用clear()(清空缓冲区,position=0,limit=capacity)或compact()(保留未读完的数据,将其移到缓冲区开头),准备下次写入。用图示展示 Buffer 的状态变化: 2.3 常见 Buffer 类型Java 为不同数据类型提供了对应的 Buffer 实现(除 boolean 外):ByteBuffer(最常用,处理字节数据)CharBuffer、ShortBuffer、IntBuffer、LongBuffer、FloatBuffer、DoubleBuffer        其中ByteBuffer支持直接内存(堆外内存)分配,通过allocateDirect(int capacity)创建,减少了 JVM 堆内存与 native 内存之间的复制,适合大文件或频繁 IO 场景。三、Channel:双向的数据通道Channel(通道)是 NIO 中数据传输的 "通道",类似于传统 IO 中的流,但有本质区别:双向性:Channel 是双向的,既可以读也可以写(通过isReadable()/isWritable()判断),而流是单向的。基于 Buffer 操作:Channel 必须配合 Buffer 使用,数据的读写都通过 Buffer 完成(read(Buffer)和write(Buffer))。支持非阻塞:部分 Channel(如SocketChannel、ServerSocketChannel)支持非阻塞模式,配合 Selector 可实现高效的多路复用。可异步关闭:Channel 可以被异步关闭,且关闭后相关操作会立即终止。3.1 常见 Channel 类型FileChannel:用于文件读写,只能在阻塞模式下工作。SocketChannel:用于 TCP 客户端网络通信,支持非阻塞。ServerSocketChannel:用于 TCP 服务器端监听连接,支持非阻塞。DatagramChannel:用于 UDP 协议的数据传输,支持非阻塞。3.2 Channel 与 Buffer 的协作流程以文件读写为例,Channel 与 Buffer 的交互流程如下:打开 Channel(如FileChannel)。创建 Buffer(如ByteBuffer)。读操作:Channel 将数据写入 Buffer(channel.read(buffer))。切换 Buffer 为读模式(buffer.flip())。从 Buffer 读取数据到程序(buffer.get())。写操作:程序将数据写入 Buffer(buffer.put())。切换 Buffer 为写模式(buffer.flip()或buffer.compact())。Channel 从 Buffer 读取数据并写入目标(channel.write(buffer))。关闭 Channel 和清理 Buffer。用图示展示这一过程: 四、Buffer/Channel 与传统 IO 流的核心区别为了更清晰地对比,我们用表格总结两者的关键差异:特性    传统 IO 流    Buffer/Channel (NIO)数据传输方式    直接通过流传输,无缓冲层    必须通过 Buffer 间接传输方向性    单向(输入流只读,输出流只写)    双向(Channel 可同时读写)阻塞性    阻塞 IO(操作时线程等待)    支持非阻塞 IO(配合 Selector)效率    频繁系统调用,效率低    批量操作减少系统调用,效率高适用场景    简单 IO、低并发场景    高并发、大文件、网络 IO 场景操作粒度    字节 / 字符级(单次操作一个数据)    缓冲区级(单次操作一批数据)核心差异本质:传统 IO 是 "流导向",NIO 是 "缓冲区导向"。缓冲区导向通过批量处理数据减少了用户态与内核态的切换(系统调用),而非阻塞特性则避免了线程在 IO 等待时的资源浪费,这也是 NIO 在高并发场景下性能更优的根本原因。五、简单示例:传统 IO 与 NIO 读写文件对比5.1 传统 IO 文件复制// 传统IO流实现文件复制try (InputStream in = new FileInputStream("source.txt");     OutputStream out = new FileOutputStream("target.txt")) {    byte[] buffer = new byte[1024];    int len;    while ((len = in.read(buffer)) != -1) { // 每次读1024字节到临时数组        out.write(buffer, 0, len); // 直接写入输出流    }} catch (IOException e) {    e.printStackTrace();}一键获取完整项目代码java5.2 NIO(Buffer/Channel)文件复制// NIO(Buffer+Channel)实现文件复制try (FileChannel inChannel = new FileInputStream("source.txt").getChannel();     FileChannel outChannel = new FileOutputStream("target.txt").getChannel()) {    ByteBuffer buffer = ByteBuffer.allocateDirect(1024); // 直接内存缓冲区    while (inChannel.read(buffer) != -1) { // 从通道读数据到缓冲区        buffer.flip(); // 切换为读模式        outChannel.write(buffer); // 从缓冲区写数据到通道        buffer.clear(); // 清空缓冲区,准备下次读取    }} catch (IOException e) {    e.printStackTrace();}一键获取完整项目代码java对比分析:虽然两者都用到了 "缓冲区"(传统 IO 的 byte 数组也是一种缓冲),但 NIO 的 Buffer 是与 Channel 深度结合的抽象,提供了更精细的状态管理(position/limit),且FileChannel支持transferTo()/transferFrom()方法直接在通道间传输数据(零拷贝),效率远高于传统 IO。六、总结        Buffer 与 Channel 是 Java NIO 的核心组件,它们通过 "缓冲区导向" 和 "双向通道" 的设计,解决了传统 IO 流在高并发场景下的效率问题:Buffer:作为数据的临时仓库,通过 position/limit/capacity 管理数据读写状态,支持批量操作,减少系统调用。Channel:作为双向数据通道,必须配合 Buffer 使用,支持非阻塞模式,提升了 IO 操作的灵活性和效率。        在实际开发中,简单场景(如小文件读写)用传统 IO 更简洁,而高并发、大文件或网络编程场景(如 Netty 框架)则应优先考虑 NIO 的 Buffer 与 Channel,以获得更好的性能。        希望本文能帮助你理清 Buffer/Channel 与传统 IO 的区别,为后续深入学习 NIO(如 Selector、非阻塞模式)打下基础。如果有疑问,欢迎在评论区交流!————————————————原文链接:https://blog.csdn.net/qq_40303030/article/details/152888895
  • [技术干货] JavaSE重点总结后篇
    一、面向对象1、深拷贝和其那拷贝的区别        在 Java 中,深拷贝(Deep Copy)和浅拷贝(Shallow Copy)是两种拷贝对象的方式,它们在拷贝对象的方式上有很大不同。          浅拷贝会创建一个新对象,但这个新对象的属性(字段)和原对象的属性完全相同。如果属性是基本数据类型,拷贝的是基本数据类型的值;如果属性是引用类型,拷贝的是引用地址,因此新旧对象共享同一个引用对象。        浅拷贝的实现方式为:实现 Cloneable 接口并重写 clone() 方法。class Person implements Cloneable {    String name;    int age;    Address address;     public Person(String name, int age, Address address) {        this.name = name;        this.age = age;        this.address = address;    }     @Override    protected Object clone() throws CloneNotSupportedException {        return super.clone();    }} class Address {    String city;     public Address(String city) {        this.city = city;    }} public class Main {    public static void main(String[] args) throws CloneNotSupportedException {        Address address = new Address("张家口");        Person person1 = new Person("寻星探路", 18, address);        Person person2 = (Person) person1.clone();         System.out.println(person1.address == person2.address); // true    }}一键获取完整项目代码java        深拷贝也会创建一个新对象,但会递归地复制所有的引用对象,确保新对象和原对象完全独立。新对象与原对象的任何更改都不会相互影响。深拷贝的实现方式有:手动复制所有的引用对象,或者使用序列化与反序列化。①、手动拷贝class Person {    String name;    int age;    Address address;     public Person(String name, int age, Address address) {        this.name = name;        this.age = age;        this.address = address;    }     public Person(Person person) {        this.name = person.name;        this.age = person.age;        this.address = new Address(person.address.city);    }} class Address {    String city;     public Address(String city) {        this.city = city;    }} public class Main {    public static void main(String[] args) {        Address address = new Address("张家口");        Person person1 = new Person("寻星探路", 18, address);        Person person2 = new Person(person1);         System.out.println(person1.address == person2.address); // false    }}一键获取完整项目代码java②、序列化与反序列化import java.io.*; class Person implements Serializable {    String name;    int age;    Address address;     public Person(String name, int age, Address address) {        this.name = name;        this.age = age;        this.address = address;    }     public Person deepClone() throws IOException, ClassNotFoundException {        ByteArrayOutputStream bos = new ByteArrayOutputStream();        ObjectOutputStream oos = new ObjectOutputStream(bos);        oos.writeObject(this);         ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());        ObjectInputStream ois = new ObjectInputStream(bis);        return (Person) ois.readObject();    }} class Address implements Serializable {    String city;     public Address(String city) {        this.city = city;    }} public class Main {    public static void main(String[] args) throws IOException, ClassNotFoundException {        Address address = new Address("张家口");        Person person1 = new Person("寻星探路", 18, address);        Person person2 = person1.deepClone();         System.out.println(person1.address == person2.address); // false    }}一键获取完整项目代码java2、Java创建对象有哪几种方式?Java 有四种创建对象的方式:①、new 关键字创建,这是最常见和直接的方式,通过调用类的构造方法来创建对象。Person person = new Person();一键获取完整项目代码java②、反射机制创建,反射机制允许在运行时创建对象,并且可以访问类的私有成员,在框架和工具类中比较常见。Class clazz = Class.forName("Person");Person person = (Person) clazz.newInstance();一键获取完整项目代码java③、clone 拷贝创建,通过 clone 方法创建对象,需要实现 Cloneable 接口并重写 clone 方法。Person person = new Person();Person person2 = (Person) person.clone();一键获取完整项目代码java④、序列化机制创建,通过序列化将对象转换为字节流,再通过反序列化从字节流中恢复对象。需要实现 Serializable 接口。Person person = new Person();ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("person.txt"));oos.writeObject(person);ObjectInputStream ois = new ObjectInputStream(new FileInputStream("person.txt"));Person person2 = (Person) ois.readObject();一键获取完整项目代码java二、String1、String 和StringBuilder、StringBuffer 的区别?        String 和StringBuilder、StringBuffer在 Java 中都是用于处理字符串的,它们之间的区别是,String 是不可变的,平常开发用得最多,当遇到大量字符串连接时,就用 StringBuilder,它不会生成很多新的对象,StringBuffer 和 StringBuilder 类似,但每个方法上都加了 synchronized 关键字,所以是线程安全的。 String的特点String类的对象是不可变的。也就是说,一旦一个String对象被创建,它所包含的字符串内容是不可改变的。每次对String对象进行修改操作(如拼接、替换等)实际上都会生成一个新的String对象,而不是修改原有对象。这可能会导致内存和性能开销,尤其是在大量字符串操作的情况下。StringBuilder的特点StringBuilder提供了一系列的方法来进行字符串的增删改查操作,这些操作都是直接在原有字符串对象的底层数组上进行的,而不是生成新的 String 对象。StringBuilder不是线程安全的。这意味着在没有外部同步的情况下,它不适用于多线程环境。相比于String,在进行频繁的字符串修改操作时,StringBuilder能提供更好的性能。 Java 中的字符串连+操作其实就是通过StringBuilder实现的。StringBuffer的特点   StringBuffer和StringBuilder类似,但StringBuffer是线程安全的,方法前面都加了synchronized关键字。使用场景String:适用于字符串内容不会改变的场景,比如说作为 HashMap 的 key。StringBuilder:适用于单线程环境下需要频繁修改字符串内容的场景,比如在循环中拼接或修改字符串,是 String 的完美替代品。StringBuffer:现在已经不怎么用了,因为一般不会在多线程场景下去频繁的修改字符串内容2、String 是不可变类吗?        String 是不可变的,这意味着一旦一个 String 对象被创建,其存储的文本内容就不能被改变。这是因为:①、不可变性使得 String 对象在使用中更加安全。因为字符串经常用作参数传递给其他 Java 方法,例如网络连接、打开文件等。        如果 String 是可变的,这些方法调用的参数值就可能在不知不觉中被改变,从而导致网络连接被篡改、文件被莫名其妙地修改等问题。②、不可变的对象因为状态不会改变,所以更容易进行缓存和重用。字符串常量池的出现正是基于这个原因。        当代码中出现相同的字符串字面量时,JVM 会确保所有的引用都指向常量池中的同一个对象,从而节约内存。③、因为 String 的内容不会改变,所以它的哈希值也就固定不变。这使得 String 对象特别适合作为 HashMap 或 HashSet 等集合的键,因为计算哈希值只需要进行一次,提高了哈希表操作的效率。如何保证String不可变?        第一,String 类内部使用一个私有的字符数组来存储字符串数据。这个字符数组在创建字符串时被初始化,之后不允许被改变。private final char value[];一键获取完整项目代码java        第二,String 类没有提供任何可以修改其内容的公共方法,像 concat 这些看似修改字符串的操作,实际上都是返回一个新创建的字符串对象,而原始字符串对象保持不变。public String concat(String str) {    if (str.isEmpty()) {        return this;    }    int len = value.length;    int otherLen = str.length();    char buf[] = Arrays.copyOf(value, len + otherLen);    str.getChars(buf, len);    return new String(buf, true);}一键获取完整项目代码java        第三,String 类本身被声明为 final,这意味着它不能被继承。这防止了子类可能通过添加修改方法来改变字符串内容的可能性。public final class String一键获取完整项目代码java三、异常处理1、Java中的异常体系?        Java 中的异常处理机制用于处理程序运行过程中可能发生的各种异常情况,通常通过 try-catch-finally 语句和 throw 关键字来实现。   Throwable 是 Java 语言中所有错误和异常的基类。它有两个主要的子类:Error 和 Exception,这两个类分别代表了 Java 异常处理体系中的两个分支。        Error 类代表那些严重的错误,这类错误通常是程序无法处理的。比如,OutOfMemoryError 表示内存不足,StackOverflowError 表示栈溢出。这些错误通常与 JVM 的运行状态有关,一旦发生,应用程序通常无法恢复。        Exception 类代表程序可以处理的异常。它分为两大类:编译时异常(Checked Exception)和运行时异常(Runtime Exception)。①、编译时异常(Checked Exception):这类异常在编译时必须被显式处理(捕获或声明抛出)。        如果方法可能抛出某种编译时异常,但没有捕获它(try-catch)或没有在方法声明中用 throws 子句声明它,那么编译将不会通过。例如:IOException、SQLException 等。②、运行时异常(Runtime Exception):这类异常在运行时抛出,它们都是 RuntimeException 的子类。对于运行时异常,Java 编译器不要求必须处理它们(即不需要捕获也不需要声明抛出)。        运行时异常通常是由程序逻辑错误导致的,如 NullPointerException、IndexOutOfBoundsException 等。2、异常的处理方式①、遇到异常时可以不处理,直接通过throw 和 throws 抛出异常,交给上层调用者处理。throws 关键字用于声明可能会抛出的异常,而 throw 关键字用于抛出异常。public void test() throws Exception {    throw new Exception("抛出异常");}一键获取完整项目代码java②、使用 try-catch 捕获异常,处理异常。try {    //包含可能会出现异常的代码以及声明异常的方法}catch(Exception e) {    //捕获异常并进行处理}finally {    //可选,必执行的代码}一键获取完整项目代码javacatch和finally的异常可以同时抛出吗?        如果 catch 块抛出一个异常,而 finally 块中也抛出异常,那么最终抛出的将是 finally 块中的异常。catch 块中的异常会被丢弃,而 finally 块中的异常会覆盖并向上传递。public class Example {    public static void main(String[] args) {        try {            throw new Exception("Exception in try");        } catch (Exception e) {            throw new RuntimeException("Exception in catch");        } finally {            throw new IllegalArgumentException("Exception in finally");        }    }}一键获取完整项目代码javatry 块首先抛出一个 Exception。控制流进入 catch 块,catch 块中又抛出了一个 RuntimeException。但是在 finally 块中,抛出了一个 IllegalArgumentException,最终程序抛出的异常是 finally 块中的 IllegalArgumentException。        虽然 catch 和 finally 中的异常不能同时抛出,但可以手动捕获 finally 块中的异常,并将 catch 块中的异常保留下来,避免被覆盖。常见的做法是使用一个变量临时存储 catch 中的异常,然后在 finally 中处理该异常:public class Example {    public static void main(String[] args) {        Exception catchException = null;        try {            throw new Exception("Exception in try");        } catch (Exception e) {            catchException = e;            throw new RuntimeException("Exception in catch");        } finally {            try {                throw new IllegalArgumentException("Exception in finally");            } catch (IllegalArgumentException e) {                if (catchException != null) {                    System.out.println("Catch exception: " + catchException.getMessage());                }                System.out.println("Finally exception: " + e.getMessage());            }        }    }}一键获取完整项目代码java 四、I/O1、Java中IO流分为几种?        Jaa IO 流的划分可以根据多个维度进行,包括数据流的方向(输入或输出)、处理的数据单位(字节或字符)、流的功能以及流是否支持随机访问等。按照数据流方向进行划分?输入流(Input Stream):从源(如文件、网络等)读取数据到程序。输出流(Output Stream):将数据从程序写出到目的地(如文件、网络、控制台等)。按处理数据单位如何划分?字节流(Byte Streams):以字节为单位读写数据,主要用于处理二进制数据,如音频、图像文件等。字符流(Character Streams):以字符为单位读写数据,主要用于处理文本数据。按功能如何划分?节点流(Node Streams):直接与数据源或目的地相连,如 FileInputStream、FileOutputStream。处理流(Processing Streams):对一个已存在的流进行包装,如缓冲流 BufferedInputStream、BufferedOutputStream。管道流(Piped Streams):用于线程之间的数据传输,如 PipedInputStream、PipedOutputStream。IO流用到了什么设计模式?        用到了——装饰器模式,装饰器模式的核心思想是在不改变原有对象结构的前提下,动态地给对象添加新的功能。         具体到 Java IO 中,InputStream 和 OutputStream 这些抽象类定义了基本的读写操作,然后通过各种装饰器类来增强功能。比如 BufferedInputStream 给基础的输入流增加了缓冲功能,DataInputStream 增加了读取基本数据类型的能力,它们都是对基础流的装饰和增强。InputStream input = new BufferedInputStream(    new DataInputStream(        new FileInputStream("data.txt")    ));一键获取完整项目代码java        这里 FileInputStream 提供基本的文件读取能力,DataInputStream 装饰它增加了数据类型转换功能,BufferedInputStream 再装饰它增加了缓冲功能。每一层装饰都在原有功能基础上增加新特性,而且可以灵活组合。        我对装饰器模式的理解是它很好地体现了“组合优于继承”的设计原则。优势在于运行时动态组合功能,而且遵循开闭原则,可以在不修改现有代码的情况下增加新功能。Java 缓冲区溢出,如何预防?        Java 缓冲区溢出主要是由于向缓冲区写入的数据超过其能够存储的数据量。可以采用这些措施来避免:①、合理设置缓冲区大小:在创建缓冲区时,应根据实际需求合理设置缓冲区的大小,避免创建过大或过小的缓冲区。②、控制写入数据量:在向缓冲区写入数据时,应该控制写入的数据量,确保不会超过缓冲区的容量。Java 的 ByteBuffer 类提供了remaining()方法,可以获取缓冲区中剩余的可写入数据量。import java.nio.ByteBuffer; public class ByteBufferExample {     public static void main(String[] args) {        // 模拟接收到的数据        byte[] receivedData = {1, 2, 3, 4, 5};        int bufferSize = 1024;  // 设置一个合理的缓冲区大小         // 创建ByteBuffer        ByteBuffer buffer = ByteBuffer.allocate(bufferSize);         // 写入数据之前检查容量是否足够        if (buffer.remaining() >= receivedData.length) {            buffer.put(receivedData);        } else {            System.out.println("Not enough space in buffer to write data.");        }         // 准备读取数据:将limit设置为当前位置,position设回0        buffer.flip();         // 读取数据        while (buffer.hasRemaining()) {            byte data = buffer.get();            System.out.println("Read data: " + data);        }         // 清空缓冲区以便再次使用        buffer.clear();    }}一键获取完整项目代码java2、有了字节流为什么还要有字符流?        其实字符流是由 Java 虚拟机将字节转换得到的,问题就出在这个过程还比较耗时,并且,如果我们不知道编码类型就很容易出现乱码问题。        所以, I/O 流就干脆提供了一个直接操作字符的接口,方便我们平时对字符进行流操作。如果音频文件、图片等媒体文件用字节流比较好,如果涉及到字符的话使用字符流比较好。文本存储是字节流还是字符流,视频文件呢?        在计算机中,文本和视频都是按照字节存储的,只是如果是文本文件的话,我们可以通过字符流的形式去读取,这样更方面的我们进行直接处理。        比如说我们需要在一个大文本文件中查找某个字符串,可以直接通过字符流来读取判断。        处理视频文件时,通常使用字节流(如 Java 中的FileInputStream、FileOutputStream)来读取或写入数据,并且会尽量使用缓冲流(如BufferedInputStream、BufferedOutputStream)来提高读写效率。3、BIO、NIO、AIO 之间的区别?BIO:采用阻塞式 I/O 模型,线程在执行 I/O 操作时被阻塞,无法处理其他任务,适用于连接数较少的场景。NIO:采用非阻塞 I/O 模型,线程在等待 I/O 时可执行其他任务,通过 Selector 监控多个 Channel 上的事件,适用于连接数多但连接时间短的场景。AIO:使用异步 I/O 模型,线程发起 I/O 请求后立即返回,当 I/O 操作完成时通过回调函数通知线程,适用于连接数多且连接时间长的场景。五、序列化1、什么是序列化?什么是反序列化?        序列化(Serialization)是指将对象转换为字节流的过程,以便能够将该对象保存到文件、数据库,或者进行网络传输。        反序列化(Deserialization)就是将字节流转换回对象的过程,以便构建原始对象。 解释一下序列化的过程和作用序列化过程通常涉及到以下几个步骤:第一步,实现 Serializable 接口。public class Person implements Serializable {    private String name;    private int age;     // 省略构造方法、getters和setters}一键获取完整项目代码java第二步,使用 ObjectOutputStream 来将对象写入到输出流中。ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("person.ser"));一键获取完整项目代码java第三步,调用 ObjectOutputStream 的 writeObject 方法,将对象序列化并写入到输出流中。Person person = new Person("寻星探路", 18);out.writeObject(person);一键获取完整项目代码java2、有几种序列化方式?Java 对象序列化 :Java 原生序列化方法即通过 Java 原生流(InputStream 和 OutputStream 之间的转化)的方式进行转化,一般是对象输出流 ObjectOutputStream和对象输入流ObjectInputStream。Json 序列化:这个可能是我们最常用的序列化方式,Json 序列化的选择很多,一般会使用 jackson 包,通过 ObjectMapper 类来进行一些操作,比如将对象转化为 byte 数组或者将 json 串转化为对象。ProtoBuff 序列化:ProtocolBuffer 是一种轻便高效的结构化数据存储格式,ProtoBuff 序列化对象可以很大程度上将其压缩,可以大大减少数据传输大小,提高系统性能。六、网络编程1、了解过Socket网络套接字吗?        Socket 是网络通信的基础,表示两台设备之间通信的一个端点。Socket 通常用于建立 TCP 或 UDP 连接,实现进程间的网络通信。  一个简单的 TCP 客户端:class TcpClient {    public static void main(String[] args) throws IOException {        Socket socket = new Socket("127.0.0.1", 8080); // 连接服务器        BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));        PrintWriter out = new PrintWriter(socket.getOutputStream(), true);         out.println("Hello, Server!"); // 发送消息        System.out.println("Server response: " + in.readLine()); // 接收服务器响应         socket.close();    }}一键获取完整项目代码javaTCP 服务端:class TcpServer {    public static void main(String[] args) throws IOException {        ServerSocket serverSocket = new ServerSocket(8080); // 创建服务器端Socket        System.out.println("Server started, waiting for connection...");        Socket socket = serverSocket.accept(); // 等待客户端连接        System.out.println("Client connected: " + socket.getInetAddress());         BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));        PrintWriter out = new PrintWriter(socket.getOutputStream(), true);         String message;        while ((message = in.readLine()) != null) {            System.out.println("Received: " + message);            out.println("Echo: " + message); // 回送消息        }         socket.close();        serverSocket.close();    }}一键获取完整项目代码javaRPC框架了解吗?        RPC是一种协议,允许程序调用位于远程服务器上的方法,就像调用本地方法一样。RPC 通常基于 Socket 通信实现。RPC,Remote Procedure Call,远程过程调用        RPC 框架支持高效的序列化(如 Protocol Buffers)和通信协议(如 HTTP/2),屏蔽了底层网络通信的细节,开发者只需关注业务逻辑即可。  常见的 RPC 框架包括:gRPC:基于 HTTP/2 和 Protocol Buffers。Dubbo:阿里开源的分布式 RPC 框架,适合微服务场景。Spring Cloud OpenFeign:基于 REST 的轻量级 RPC 框架。Thrift:Apache 的跨语言 RPC 框架,支持多语言代码生成。七、泛型1、Java泛型了吗?        泛型主要用于提高代码的类型安全,它允许在定义类、接口和方法时使用类型参数,这样可以在编译时检查类型一致性,避免不必要的类型转换和类型错误。        没有泛型的时候,像 List 这样的集合类存储的是 Object 类型,导致从集合中读取数据时,必须进行强制类型转换,否则会引发 ClassCastException。List list = new ArrayList();list.add("hello");String str = (String) list.get(0);  // 必须强制类型转换一键获取完整项目代码java泛型一般有三种使用方式:泛型类、泛型接口、泛型方法。  1.泛型类://此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型//在实例化泛型类时,必须指定T的具体类型public class Generic<T>{     private T key;     public Generic(T key) {        this.key = key;    }     public T getKey(){        return key;    }}一键获取完整项目代码java如何实例化泛型类:Generic<Integer> genericInteger = new Generic<Integer>(123456);一键获取完整项目代码java2.泛型接口 :public interface Generator<T> {    public T method();}一键获取完整项目代码java实现泛型接口,指定类型:class GeneratorImpl<T> implements Generator<String>{    @Override    public String method() {        return "hello";    }}一键获取完整项目代码java3.泛型方法 :   public static < E > void printArray( E[] inputArray )   {         for ( E element : inputArray ){            System.out.printf( "%s ", element );         }         System.out.println();    }一键获取完整项目代码java使用:// 创建不同类型数组: Integer, Double 和 CharacterInteger[] intArray = { 1, 2, 3 };String[] stringArray = { "Hello", "World" };printArray( intArray  );printArray( stringArray  );一键获取完整项目代码java泛型常用的通配符有哪些?常用的通配符为: T,E,K,V,?? 表示不确定的 java 类型T (type) 表示具体的一个 java 类型K V (key value) 分别代表 java 键值中的 Key ValueE (element) 代表 Element什么是泛型擦除?所谓的泛型擦除,官方名叫“类型擦除”。Java 的泛型是伪泛型,这是因为 Java 在编译期间,所有的类型信息都会被擦掉。也就是说,在运行的时候是没有泛型的。例如这段代码,往一群猫里放条狗:LinkedList<Cat> cats = new LinkedList<Cat>();LinkedList list = cats;  // 注意我在这里把范型去掉了,但是list和cats是同一个链表!list.add(new Dog());  // 完全没问题!一键获取完整项目代码java        因为 Java 的范型只存在于源码里,编译的时候给你静态地检查一下范型类型是否正确,而到了运行时就不检查了。上面这段代码在 JRE(Java运行环境)看来和下面这段没区别:LinkedList cats = new LinkedList();  // 注意:没有范型!LinkedList list = cats;list.add(new Dog());一键获取完整项目代码java为什么要类型擦除呢?        主要是为了向下兼容,因为 JDK5 之前是没有泛型的,为了让 JVM 保持向下兼容,就出了类型擦除这个策略。八、反射1、什么是反射?原理?应用?        反射允许 Java 在运行时检查和操作类的方法和字段。通过反射,可以动态地获取类的字段、方法、构造方法等信息,并在运行时调用方法或访问字段。比如创建一个对象是通过 new 关键字来实现的:Person person = new Person();一键获取完整项目代码java        Person 类的信息在编译时就确定了,那假如在编译期无法确定类的信息,但又想在运行时获取类的信息、创建类的实例、调用类的方法,这时候就要用到反射。        反射功能主要通过 java.lang.Class 类及 java.lang.reflect 包中的类如 Method, Field, Constructor 等来实现。  比如说我们可以动态加载类并创建对象:String className = "java.util.Date";Class<?> cls = Class.forName(className);Object obj = cls.newInstance();System.out.println(obj.getClass().getName());一键获取完整项目代码java比如说我们可以这样来访问字段和方法:// 加载并实例化类Class<?> cls = Class.forName("java.util.Date");Object obj = cls.newInstance(); // 获取并调用方法Method method = cls.getMethod("getTime");Object result = method.invoke(obj);System.out.println("Time: " + result); // 访问字段Field field = cls.getDeclaredField("fastTime");field.setAccessible(true); // 对于私有字段需要这样做System.out.println("fastTime: " + field.getLong(obj));一键获取完整项目代码java反射有哪些应用场景?①、Spring 框架就大量使用了反射来动态加载和管理 Bean。Class<?> clazz = Class.forName("com.example.MyClass");Object instance = clazz.newInstance();一键获取完整项目代码java②、Java 的动态代理机制就使用了反射来创建代理类。代理类可以在运行时动态处理方法调用,这在实现 AOP 和拦截器时非常有用。InvocationHandler handler = new MyInvocationHandler();MyInterface proxyInstance = (MyInterface) Proxy.newProxyInstance(    MyInterface.class.getClassLoader(),    new Class<?>[] { MyInterface.class },    handler);一键获取完整项目代码java③、JUnit 和 TestNG 等测试框架使用反射机制来发现和执行测试方法。反射允许框架扫描类,查找带有特定注解(如 @Test)的方法,并在运行时调用它们。Method testMethod = testClass.getMethod("testSomething");testMethod.invoke(testInstance);一键获取完整项目代码java④、最常见的是写通用的工具类,比如对象拷贝工具。比如说 BeanUtils、MapStruct 等等,能够自动拷贝两个对象之间的同名属性,就是通过反射来实现的。 反射的原理是什么?        每个类在加载到 JVM 后,都会在方法区生成一个对应的 Class 对象,这个对象包含了类的所有元信息,比如字段、方法、构造器、注解等。        通过这个 Class 对象,我们就能在运行时动态地创建对象、调用方法、访问字段。反射的优缺点是什么?        反射的优点还是很明显的。首先是能够在运行时动态操作类和对象。其次是能够编写通用的代码,一套代码可以处理不同类型的对象。还有就是能够突破访问限制,访问 private 字段和方法,这在反编译场景下很有用。        但反射的缺点也不少。最明显的是性能问题,反射操作比直接调用要慢很多,因为需要在运行时解析类信息、进行类型检查、权限验证等。        其次是反射能够绕过访问控制,访问和修改 private 成员,这会破坏类的封装。九、Lambda表达式1、Lambda表达式了解多少?        Lambda 表达式主要用于提供一种简洁的方式来表示匿名方法,使 Java 具备了函数式编程的特性。比如说我们可以使用 Lambda 表达式来简化线程的创建:new Thread(() -> System.out.println("Hello World")).start();一键获取完整项目代码java这比以前的匿名内部类要简洁很多。        所谓的函数式编程,就是把函数作为参数传递给方法,或者作为方法的结果返回。比如说我们可以配合 Stream 流进行数据过滤:List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);List<Integer> evenNumbers = numbers.stream()    .filter(n -> n % 2 == 0)    .collect(Collectors.toList());一键获取完整项目代码java        其中 n -> n % 2 == 0 就是一个 Lambda 表达式。表示传入一个参数 n,返回 n % 2 == 0 的结果。————————————————原文链接:https://blog.csdn.net/2402_83476639/article/details/155102993
  • [技术干货] Java外功精要(3)——Spring配置文件和mybatis
    1.配置文件1.1 概述计算机配置文件:用于存储系统、应用程序的设置信息,通常以文本或结构化数据格式(如JSON、XML、INI等)保存。其核心功能包括但不限于:参数定制:允许用户或管理员调整软件或硬件的运行参数环境适配:根据不同设备或场景加载特定配置(如开发/生产环境)持久化存储:确保重启后设置仍生效SpringBoot配置文件:SpringBoot支持多种类型的配置文件,常见的格式包括properties、yaml和yml,主要用于集中管理应用程序的各种配置参数,简化部署和开发过程中的环境切换YAML和YML本质上是相同的文件格式,只是文件扩展名的不同,两者在功能和使用上没有区别1.2 propertiesproperties配置文件是最早期的配置⽂件格式,也是创建SpringBoot项⽬默认的配置⽂件采用常见的键值对格式(key=value)支持#开头的注释#应用程序名称spring.application.name=configuration#应用程序端口号server.port=8080#数据库连接信息spring.datasource.url=jdbc:mysql://127.0.0.1:3306/database_name?characterEncoding=utf8&useSSL=falsespring.datasource.username=rootspring.datasource.password=root一键获取完整项目代码properties1.3 yml采用键值对格式(key: value),冒号后必须有空格数据序列化格式,通过缩进表示层级关系支持#开头的注释spring:  application:    #应用程序名称    name: configuration  #数据库连接信息  datasource:    url: jdbc:mysql://127.0.0.1:3306/database_name?characterEncoding=utf8&useSSL=false    username: root    password: root#应用程序端口号server:  port: 8080一键获取完整项目代码yaml1.4 优缺点对比properties优点:语法简单直观,采用key=value形式,适合初学者快速上手与Java生态兼容性极强缺点:缺乏层次结构,复杂配置时容易冗余。上述配置数据库连接信息时spring.datasource前缀冗余不支持数据类型定义,所有值均为字符串,需手动转换yml优点:层次化结构清晰,通过缩进表示层级,适合复杂配置场景支持数据类型(如布尔值、数字),减少手动类型转换缺点:格式错误易导致解析失败(容易忽略冒号后空格)部分旧版工具链兼容性较差,需额外依赖解析库注:SpringBoot同时支持两种格式,混合使用时若key重复,properties优先级高于yml1.5 @Value注解作用:是Spring框架提供了一个@Value注解(org.springframework.beans.factory.annotation.Value),用于将外部配置文件中的值注入到Spring管理的Bean中示例:(properties和yml的读取方式相同)package org.example.configuration.config;import org.springframework.beans.factory.annotation.Value;import org.springframework.context.annotation.Configuration;@Configurationpublic class Config {    @Value("${spring.application.name}")    private String applicationName;    @Value("${server.port}")    private Integer port;    @Value("${spring.datasource.url}")    private String url;    @Value("${spring.datasource.username}")    private String username;    @Value("${spring.datasource.password}")    private String password;    public void print() {        System.out.println("applicationName=" + applicationName);        System.out.println("port=" + port);        System.out.println("url=" + url);        System.out.println("username=" + username);        System.out.println("password=" + password);    }}package org.example.configuration;import org.example.configuration.config.Config;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.context.ApplicationContext;@SpringBootApplicationpublic class ConfigurationApplication {    public static void main(String[] args) {        ApplicationContext context = SpringApplication.run(ConfigurationApplication.class, args);        Config config = context.getBean(Config.class);        config.print();    }}一键获取完整项目代码java运行结果:applicationName=configurationport=8080url=jdbc:mysql://127.0.0.1:3306/database_name?characterEncoding=utf8&useSSL=falseusername=rootpassword=root2.mybatis2.1 概述MyBatis是一款优秀的持久层框架,支持自定义 SQL、存储过程、高级映射以及多种配置方式。它消除了几乎所有的JDBC代码和参数的手动设置以及结果集的检索支持存储过程:指的是数据库管理系统(DBMS)允许用户创建、存储和执行存储过程的能力。存储过程是一组预编译的SQL语句,存储在数据库中,可以被应用程序调用执行支持高级映射:指通过配置或注解实现复杂SQL查询结果与Java对象之间的灵活转换。其核心目标是简化数据库关联操作,提升开发效率支持多种配置方式:mybatis支持注解和xml两种配置方式2.2 前置操作引入依赖:Spring Web,Mybatis Framework,MySQL Driver,Lombok在application.properties/yml中添加数据库连接信息:#应用程序名称spring.application.name=configuration#应用程序端口号server.port=8080#数据库连接信息spring.datasource.url=jdbc:mysql://127.0.0.1:3306/database_name?characterEncoding=utf8&useSSL=falsespring.datasource.username=rootspring.datasource.password=root#自动驼峰转换mybatis.configuration.map-underscore-to-camel-case=true一键获取完整项目代码propertiesspring:  application:    #应用程序名称    name: configuration  #数据库连接信息  datasource:    url: jdbc:mysql://127.0.0.1:3306/database_name?characterEncoding=utf8&useSSL=false    username: root    password: root#应用程序端口号server:  port: 8080mybatis:  configuration:     map-underscore-to-camel-case: true #自动驼峰转换一键获取完整项目代码yamlSQL命名规范:采用下划线分隔单词(如order_detail)Java命名规范:大驼峰/小驼峰2.3 注解2.3.1 配置1.创建一个接口,并使用 @Mapper注解 修饰import org.apache.ibatis.annotations.Mapper;@Mapperpublic interface BlogMapper {    //其他代码}一键获取完整项目代码java@Mapper注解:允许开发者直接在接口方法上通过注解配置SQL语句,无需编写XML映射文件。适用于简单SQL场景,能显著减少配置量2.初始化数据create table blog (id int primary key auto_increment,name varchar(128),age int);insert into blog values (null,'刘备',30),(null,'关羽',28),(null,'张飞',25);一键获取完整项目代码sql3.创建对应实体类import lombok.Data;@Datapublic class PersonInfo {    private Integer id;    private String name;    private Integer age;    public PersonInfo(Integer id, String name, Integer age) {        this.id = id;        this.name = name;        this.age = age;    }    public PersonInfo() {    }}一键获取完整项目代码sql2.3.2 CRUDimport com.example.spring_mybatis.model.PersonInfo;import org.apache.ibatis.annotations.*;@Mapperpublic interface BlogMapper {    @Select("select * from blog")    List<PersonInfo> getPersonInfoAll();    @Insert("insert into blog values (#{id},#{name},#{age})")    Integer addPerson(PersonInfo person);    @Update("update blog set name = #{name},age = #{age} where id = #{id}")    Integer updatePerson(PersonInfo personInfo);    @Delete("delete from blog where id = #{id}")    Integer deletePerson(Integer id);}一键获取完整项目代码sql按住alt+insert,可在test目录下生成以上方法的测试方法import com.example.spring_mybatis.model.PersonInfo;import lombok.extern.slf4j.Slf4j;import org.junit.jupiter.api.Test;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.context.SpringBootTest;import java.util.List;@SpringBootTest@Slf4jclass BlogMapperTest {    private final BlogMapper blogMapper;    @Autowired    public BlogMapperTest(BlogMapper blogMapper) {        this.blogMapper = blogMapper;    }    @Test    void getPersonInfoAll() {        List<PersonInfo> personInfoAll = blogMapper.getPersonInfoAll();        log.info("查询成功,personInfoAll:{}",personInfoAll.toString());        //查询成功,personInfoAll:[PersonInfo(id=1, name=刘备, age=30),        //PersonInfo(id=2, name=关羽, age=28),        //PersonInfo(id=3, name=张飞, age=25)]    }    @Test    void addPerson() {        Integer ret = blogMapper.addPerson(new PersonInfo(null, "赵云", 25));        log.info("添加成功,影响行数:{}",ret.toString());//添加成功,影响行数:1    }    @Test    void updatePerson() {        Integer ret = blogMapper.updatePerson(new PersonInfo(1, "刘备", 35));        log.info("更新成功,影响行数:{}",ret.toString());//更新成功,影响行数:1    }    @Test    void deletePerson() {        Integer ret = blogMapper.deletePerson(4);        log.info("删除成功,影响行数:{}",ret.toString());//删除成功,影响行数:1    }}一键获取完整项目代码sql2.3.3 @Param作用:用于在Mapper接口方法中为形式参数指定名称。当方法有多个参数时,通过该注解明确SQL中引用的参数名,避免依赖参数顺序@Mapperpublic interface BlogMapper {    //@Param    @Update("update blog set name = #{name},age = #{age} where id = #{id}")    Integer updatePersonInfo(@Param("id") Integer userId,@Param("name") String userName,@Param("age") Integer userAge);}一键获取完整项目代码java@SpringBootTest@Slf4jclass BlogMapperTest {    private final BlogMapper blogMapper;    @Autowired    public BlogMapperTest(BlogMapper blogMapper) {        this.blogMapper = blogMapper;    }    @Test    void updatePersonInfo() {        Integer ret = blogMapper.updatePersonInfo(1, "刘玄德", 30);        log.info("更新成功,影响行数:{}",ret.toString());//更新成功,影响行数:1    }}一键获取完整项目代码java2.4 xml2.4.1 配置1.创建一个接口,并使用 @Mapper注解 修饰import org.apache.ibatis.annotations.Mapper;@Mapperpublic interface BlogXMLMapper {    //其他代码}一键获取完整项目代码java2.配置mybatis的xml文件路径mybatis:  mapper-locations: classpath:mybatis/**Mapper.xml #配置mybatis的xml文件路径  #标识位于resources/mybatis路径下任何以Mapper结尾的xml文件为mybatis的配置文件一键获取完整项目代码xml3.在resources/mybatis路径下创建以Mapper结尾的xml文件,并添加如下代码<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"><!--namespace:用于指定该XML文件对应的Java接口或类的全限定名--><mapper namespace="com.example.spring_mybatis.mapper_blog.BlogXMLMapper"></mapper>一键获取完整项目代码xml2.4.2 示例在接口中声明方法import com.example.spring_mybatis.model_blog.PersonInfo;import org.apache.ibatis.annotations.Mapper;import java.util.List;@Mapperpublic interface BlogXMLMapper {    List<PersonInfo> getPersonInfoAll();}一键获取完整项目代码java在对应xml文件中实现接口方法id:是MyBatis映射文件中SQL语句的唯一标识符。需与Mapper接口中的方法名一致,保证映射正确resultType:指定SQL查询结果映射的Java对象类型,需为全限定类名<mapper namespace="com.example.spring_mybatis.mapper_blog.BlogXMLMapper">    <select id="getPersonInfoAll" resultType="com.example.spring_mybatis.model_blog.PersonInfo">        select * from blog    </select></mapper>一键获取完整项目代码xml按住alt+insert,可在test目录下生成以上方法的测试方法import com.example.spring_mybatis.model_blog.PersonInfo;import lombok.extern.slf4j.Slf4j;import org.junit.jupiter.api.Test;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.context.SpringBootTest;import java.util.List;@SpringBootTest@Slf4jclass BlogXMLMapperTest {    private final BlogXMLMapper blogXMLMapper;    @Autowired    public BlogXMLMapperTest(BlogXMLMapper blogXMLMapper) {        this.blogXMLMapper = blogXMLMapper;    }    @Test    void getPersonInfoAll() {        List<PersonInfo> personInfoAll = blogXMLMapper.getPersonInfoAll();        log.info("查询成功,personInfoAll:{}",personInfoAll.toString());    }}一键获取完整项目代码java运行结果:2.5 动态SQL动态SQL:指在程序运行时根据条件或参数动态生成的SQL语句。与静态SQL相比,动态SQL更具灵活性,适用于需要根据不同条件构建查询的场景。例如,在某些web/app进行账号注册时会出现非必填选项mybatis的注解和xml两种方式都能实现动态SQL,但xml较为方便,所以下文使用xml来实现动态SQL2.5.1 trim标签作用:用于自定义字符串截取规则。包含四个属性:prefix:最终结果添加前缀suffix:最终结果添加后缀prefixOverrides:去除首部指定内容suffixOverrides:去除尾部指定内容2.5.2 if标签作用:用于条件判断,通常在where或set语句中使用。当test表达式的值为true时,包含标签内的SQL片段    <insert id="addPersonInfo">        insert into blog        <trim prefix="(" suffix=")" suffixOverrides=",">            <if test="id != null">                id,            </if>            <if test="name != null">                name,            </if>            <if test="age != null">                age,            </if>        </trim>        values        <trim prefix="(" suffix=")" suffixOverrides=",">            <if test="id != null">                #{id},            </if>            <if test="name != null">                #{name},            </if>            <if test="age != null">                #{age},            </if>        </trim>    </insert>一键获取完整项目代码xml2.5.3 where标签作用:替代SQL中的where关键字。当if条件成立时才会加入SQL片段,并自动去除第一个子句的and/or    <select id="getPersonInfoByNameAndAge">        select * from blog        <where>            <if test="name != null">                and name = #{name}            </if>            <if test="age != null">                and age = #{age}            </if>        </where>    </select>一键获取完整项目代码xml2.5.4 set标签作用:用于update语句。当if条件成立时才会加入SQL片段,并自动去除最后一个子句的逗号、    <update id="updatePersonInfo">        update blog        <set>            <if test="name != null">                name = #{name},            </if>            <if test="age != null">                age = #{age},            </if>        </set>        <where>            and id = #{id}        </where>    </update>一键获取完整项目代码xml2.5.5 foreach标签作用:用于集合遍历。主要属性:collection:集合参数名item:当前元素变量名open/close:包围符号separator:分隔符    @Test    void getPersonInfoById() {        ArrayList<Integer> ids = new ArrayList<>();        ids.add(1);        ids.add(2);        ids.add(3);        List<PersonInfo> personInfoById = blogXMLMapper.getPersonInfoById(ids);        System.out.println(personInfoById);    }一键获取完整项目代码java    <select id="getPersonInfoById" resultType="com.example.spring_mybatis.model_blog.PersonInfo">        select * from blog        where id in        <foreach collection="ids" item="id" open="(" close=")" separator=",">            #{id}        </foreach>    </select>一键获取完整项目代码xml2.5.6 include标签作用:用于引用SQL片段,通过refid指定要引用的片段id。需配合sql标签使用,实现代码复用    <sql id="collection">        id,name,age    </sql>    <select id="getPersonInfoAll" resultType="com.example.spring_mybatis.model_blog.PersonInfo">        select        <include refid="collection">        </include>        from blog    </select>一键获取完整项目代码xml2.6 主键返回主键返回:指在数据库插入操作后,自动获取刚插入记录的主键值。在mybatis中使用注解和xml都能获取到返回的主键1.注解实现@Mapperpublic interface BlogMapper {    @Options(useGeneratedKeys = true,keyProperty = "id")    @Insert("insert into blog values (#{id},#{name},#{age})")    Integer addPerson(PersonInfo person);}@SpringBootTest@Slf4jclass BlogMapperTest {    private final BlogMapper blogMapper;    @Autowired    public BlogMapperTest(BlogMapper blogMapper) {        this.blogMapper = blogMapper;    }        @Test    void addPerson() {        PersonInfo personInfo = new PersonInfo(null, "黄忠", 60);        Integer ret = blogMapper.addPerson(personInfo);        log.info("添加成功,影响行数:{},返回的主键值:{}",ret.toString(),personInfo.getId());//添加成功,影响行数:1,返回的主键值:6    }}一键获取完整项目代码java 2.通过xml实现@SpringBootTest@Slf4jclass BlogXMLMapperTest {    private final BlogXMLMapper blogXMLMapper;    @Autowired    public BlogXMLMapperTest(BlogXMLMapper blogXMLMapper) {        this.blogXMLMapper = blogXMLMapper;    }        @Test    void addPersonInfo() {        PersonInfo personInfo = new PersonInfo();        personInfo.setAge(40);        personInfo.setName("曹操");        Integer ret = blogXMLMapper.addPersonInfo(personInfo);        log.info("添加成功,影响行数:{},返回的主键值:{}",ret.toString(),personInfo.getId());//添加成功,影响行数:1,返回的主键值:7    }}一键获取完整项目代码java    <insert id="addPersonInfo" useGeneratedKeys="true" keyProperty="id">        insert into blog        <trim prefix="(" suffix=")" suffixOverrides=",">            <if test="id != null">                id,            </if>            <if test="name != null">                name,            </if>            <if test="age != null">                age,            </if>        </trim>        values        <trim prefix="(" suffix=")" suffixOverrides=",">            <if test="id != null">                #{id},            </if>            <if test="name != null">                #{name},            </if>            <if test="age != null">                #{age},            </if>        </trim>    </insert>一键获取完整项目代码xml2.7 预编译/即时SQL预编译SQL(Prepared Statements):SQL语句在程序运行前被预先编译并存储在数据库中。执行时只需传递参数,无需重新编译SQL语句安全性高:通过参数化查询避免SQL注入攻击。参数化查询是一种将SQL语句与用户输入数据分离的数据库操作方式,查询语句中使用占位符(如?、@param等)代替直接拼接用户输入,执行时通过预编译机制将参数动态绑定到占位符位置性能优化:编译一次,多次执行,减少数据库开销即时SQL(Dynamic SQL):在程序运行时动态生成并立即编译执行,每次执行都可能涉及完整的SQL解析和编译过程灵活性高:可根据运行时条件动态拼接SQL语句潜在风险:直接拼接用户输入可能导致SQL注入性能开销:每次执行需重新编译#占位符会使用预编译机制,将参数值安全地绑定到SQL语句中,防止SQL注入攻击。MyBatis会将#替换为?,然后通过JDBC的预编译功能设置参数值$占位符直接进行字符串替换,将参数值拼接到SQL语句中,不会进行预编译或转义处理SQL注入攻击:当恶意输入" 'or 1 = '1 "时1.预编译SQL@Select("select * from blog where name= #{name}")List<UserInfo> queryByName(String name)一键获取完整项目代码java预编译SQL会根据参数的类型判断是否需要加引号,上述name参数是String类型,需要加引号,这就是参数化查询的作用。最终SQL:select * from blog where name = " 'or 1='1 "; # 整个 'or 1='1 会作为name的值一键获取完整项目代码sql2.即时SQL@Select("select * from blog where name= '${name}'")List<UserInfo> queryByName(String name)一键获取完整项目代码java即时SQL不会判断参数类型从而是否添加引号,所以需要手动加上单引号。最终SQL:select * from blog where name = ''or 1 = '1'; # 因为1=1是恒等式,所以该表的数据会被全部查询出来,这就是SQL注入————————————————原文链接:https://blog.csdn.net/2401_89167985/article/details/152804543
  • [技术干货] 用 Notion + 自动化打造你的个人知识管理系统
    你是不是也这样:看到好文章先收藏,结果再也没打开过;笔记散落在微信、网页、本地文档里,想找时怎么也找不到;学了一堆东西,却没法串联起来形成自己的知识体系?其实,问题不在你不够努力,而在缺乏一个趁手的系统。一、Notion的优点Notion 不是唯一选择,但它有几个独特优势:自由度高:数据库、看板、日历、列表随意组合双向链接:轻松建立知识点之间的关联(类似 Roam Research)全平台同步:手机、电脑、网页端实时更新免费够用:个人使用基本功能完全免费API 开放:可接入自动化工具(如 Zapier、Make)更重要的是——它足够简单,能让你坚持用下去。二、三库一体一个有效的 PKM 系统不需要复杂,关键在于输入 → 整理 → 输出的闭环。我们用三个核心数据库实现:1. 收件箱(Inbox)—— 临时存放所有碎片信息用途:快速保存灵感、链接、摘录,不求完美,只求不丢字段建议:内容(文本/URL)来源(如“知乎”、“播客”、“会议记录”)状态(待处理 / 已归档)创建时间 技巧:在手机桌面放 Notion 快捷方式,看到好内容 5 秒内存入收件箱。2. 知识库(Knowledge Base)—— 结构化沉淀核心内容用途:经过整理的、可长期复用的知识单元(类似维基)字段建议:标题标签(多选:技术/心理学/产品…)相关主题(关联到其他知识条目)原始来源(链接回 Inbox 条目)最后复习时间 关键:每条知识尽量独立、原子化。例如不要写“机器学习笔记”,而是拆成“梯度下降原理”、“过拟合解决方法”等小条目。3. 项目/行动库(Projects & Actions)—— 驱动实践与输出用途:把知识转化为具体行动(写作、做项目、分享)字段建议:任务名称关联知识(多对多链接到 Knowledge Base)截止日期状态(规划中 / 进行中 / 已完成)三、从一条微博到知识卡片假设你在微博看到一段关于“费曼学习法”的精彩解释:步骤 1:快速存入收件箱打开 Notion 手机 App点击“+”新建页面,粘贴原文 + 微博链接标记来源为“微博”,状态为“待处理”步骤 2:每日整理(10 分钟即可)打开收件箱,筛选“待处理”阅读内容,用自己的话重写核心观点(避免直接收藏)在知识库中新建页面《费曼学习法》,填写:核心步骤:1. 选择概念 2. 教给别人 3. 发现漏洞 4. 简化类比关联已有知识:《认知负荷理论》《主动回忆》添加标签:#学习方法 #教育步骤 3:驱动输出在项目库中创建任务:“写一篇费曼学习法实践心得”关联《费曼学习法》知识条目设置一周后提醒这样,一条碎片信息就完成了从“看到”到“内化”再到“输出”的完整旅程。四、减少手动操作1. 用浏览器插件一键保存网页安装Notion Web Clipper ,点击即可将当前网页保存到指定数据库(如收件箱),自动带标题、URL、截图。2. 邮件自动转 Notion(适合订阅内容)通过 Notion API + Zapier:规则:收到发件人邮件动作:自动创建 Inbox 条目,内容为邮件正文3. 微信内容自动同步(进阶)使用工具如 WeChat Notion Sync(开源项目),将微信“文件传输助手”或特定群聊消息自动转发到 Notion(需自建服务)。 知识管理不是为了囤积信息,而是构建一个能自我生长的认知外脑。Notion 只是工具,真正的价值在于你如何用它建立输入、思考、输出的正向循环。 
总条数:692 到第
上滑加载中