• [技术干货] Docker安装Java 17
    使用Docker容器内安装的JDK进行Java 17开发与部署的完整指南引言在当今的软件开发领域,Docker已经成为容器化技术的标准,广泛应用于各种应用和服务的部署与管理。对于Java开发者来说,使用Docker来安装JDK(Java Development Kit)不仅提供了便捷和高效的环境配置方式,还能确保开发、测试和生产环境的一致性。本文将详细介绍如何在Docker容器内安装JDK,并进行Java项目的开发与部署。一、Docker与JDK的基本概念1. Docker简介Docker是一个开源的容器化平台,允许开发者将应用程序及其依赖项打包成一个可移植的容器。这使得应用程序可以在任何支持Docker的环境中无缝运行。2. JDK简介JDK(Java Development Kit)是Java开发的核心工具包,包含了Java运行时环境(JRE)、编译器、调试器以及其他工具。二、准备工作1. 安装Docker确保你的系统中已经安装了Docker。你可以从Docker官网下载并安装适合你操作系统的Docker版本。2. 选择JDK版本根据你的项目需求选择合适的JDK版本。本文将以JDK 17为例进行说明。三、拉取JDK镜像首先,我们需要从Docker Hub拉取JDK镜像。打开终端或命令提示符,执行以下命令:docker pull openjdk:17-jdk  AI生成项目这条命令会从Docker Hub下载JDK 17的镜像文件。四、创建数据挂载目录为了方便管理和持久化数据,我们需要在宿主机上创建数据挂载目录。执行以下命令:mkdir -p /opt/project  mkdir -p /opt/project/log  AI生成项目这些目录将用于存放项目文件和日志。五、启动JDK容器并挂载项目接下来,我们将启动一个JDK容器,并将项目文件挂载到容器中。假设你的项目JAR文件名为server-1.0.0.jar,执行以下命令:docker run -d -p 8091:8091 -v /opt/project/server-1.0.0.jar:/myapp/server-1.0.0.jar --name myjavaapp openjdk:17-jdk  AI生成项目这条命令的解释如下:-d:在后台运行容器。-p 8091:8091:将容器的8091端口映射到主机的8091端口。-v /opt/project/server-1.0.0.jar:/myapp/server-1.0.0.jar:将宿主机的/opt/project/server-1.0.0.jar文件挂载到容器的/myapp目录下。--name myjavaapp:为容器命名为myjavaapp。openjdk:17-jdk:使用我们之前拉取的JDK 17镜像。六、在容器中运行Java项目进入容器并运行你的Java项目。首先,获取容器的ID或名称:docker ps  AI生成项目然后,使用以下命令进入容器:docker exec -it myjavaapp /bin/bash  AI生成项目在容器内,导航到挂载的目录并运行你的JAR文件:cd /myapp  java -jar server-1.0.0.jar  AI生成项目七、Docker命令实用指南1. 镜像管理拉取镜像:docker pull openjdk:11构建镜像:docker build -t myapp:latest .列出镜像:docker images删除镜像:docker rmi myapp:latest2. 容器管理运行容器:docker run -d -p 8080:8080 --name myappcontainer myapp:latest列出容器:docker ps列出所有容器:docker ps -a启动容器:docker start myappcontainer停止容器:docker stop myappcontainer删除容器:docker rm myappcontainer八、使用Docker Compose进行多服务部署对于复杂的应用,可能需要多个服务协同工作。Docker Compose可以帮助你定义和运行多容器Docker应用程序。创建一个docker-compose.yml文件:version: '3'  services:    java-app:      image: openjdk:17-jdk      ports:        - "8091:8091"      volumes:        - /opt/project:/myapp      command: java -jar /myapp/server-1.0.0.jar    mysql:      image: mysql:5.7      environment:        MYSQL_ROOT_PASSWORD: rootpassword  AI生成项目然后,使用以下命令启动所有服务:docker-compose up -d  AI生成项目九、监控与日志管理1. 查看容器日志使用以下命令查看容器的日志:docker logs myjavaapp  AI生成项目2. 监控资源使用使用以下命令监控容器的资源使用情况:docker stats myjavaapp  AI生成项目十、总结通过本文的介绍,你已经学会了如何在Docker容器内安装JDK,并进行Java项目的开发与部署。使用Docker不仅可以简化环境配置,还能提高应用的便携性和一致性。希望这些内容对你有所帮助,如果你有任何问题或经验分享,欢迎在评论区留言。————————————————原文链接:https://blog.csdn.net/m0_52647839/article/details/145807537
  • [技术干货] JavaEE初阶-线程和进程之间的区别
    进程和线程的区别:1..进程是资源调度的基本单位。而线程是处理器实行调度的基本单位2.每个进程都有独立的内存资源,而线程之间的内存资源是共享的3.进程的调度开销要比线程的开销大很多,所以线程又被称作轻量级进程,调度开销要比进程低很多。4.进程的并发执行时当一个进程崩溃时5.线程上下文的的切换要比进程上下文的切换快得多 。总结根本区别:进程是操作系统资源分配的基本单位,而线程是处理器任务调度和执行的基本单位(也可以理解为进程当中的一条执行流程)资源开销:每个进程都有独立的代码和数据空间(程序上下文),程序之间的切换会有较大的开销;线程可以看做轻量级的进程,同一类线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计数器(PC),线程之间切换的开销小。包含关系:如果一个进程内有多个线程,则执行过程不是一条线的,而是多条线(线程)共同完成的;线程是进程的一部分,所以线程也被称为轻权进程或者轻量级进程。内存分配:同一进程的线程共享本进程的地址空间和资源,而进程之间的地址空间和资源是相互独立的影响关系:一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃整个进程都死掉。所以多进程要比多线程健壮。执行过程:每个独立的进程有程序运行的入口、顺序执行序列和程序出口。但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制,两者均可并发执行调度和切换:线程上下文切换比进程上下文切换要快得多。————————————————原文链接:https://blog.csdn.net/qq_53283658/article/details/127145029
  • [技术干货] JavaEE----多线程
    1.进程的状态public class Test {    public static void main(String[] args) throws InterruptedException {        Thread t=new Thread(()->{         });        System.out.println(t.getState());        //线程已经创建,但是没有开始执行,这个时候的状态就是new状态                        t.start();        t.join();                        //TERMINATED就是线程结束之后的这个线程的状态:terminated        System.out.println(t.getState());     } }AI生成项目java运行 下面的这个就是在我们的t线程里面设计一个死循环,这个时候我们就可以使用getstate获取到这个时候的状态就是我们的runnable状态的; 下面的这个是对于timed-waiting状态的演示: 2.线程的安全引入线程的安全问题:主要就是这个线程调度的随机性; 下面的这个里面,我们是对于这个全局的静态变量是count=0,我们在这个主方法里面对于这个count分别加上50000次,这个时候我们正常情况下结果应该是100000,但是这个打印结果不是100000,主要就是因为这个调度器的执行问题导致的这个结果不是100000; 实际上,这个count进行++的时候,需要经过三个步骤,分别是 load:把内存里面的数据进行读取到CPU寄存器里面去; add:把这个寄存器里面的数据count++; save:把这个寄存器里面的计算之后的数据放到这个内存里面去; 为什么计算之后的这个结果不是100000呢,下面我们画一下这个计算的过程: 如果是下面的这个情况,我们的两个线程的三步骤都是连续的,这个时候我们的count就会被加上去,这个时候就是2; 但是如果下面的这个情况,就是两个线程之间的这个三步操作就会失效,只有一个会发挥作用,因为其中的一个过程被中断了: 3.线程安全的问题产生原因1.操作系统里面对于线程的调度是随机的(抢占式执行); ​ 我们想要解决问题肯定不可以从这个入手,因为这个是取决于我们的操作系统,我们很难对于这个抢占 式执行的现状进行控制; 2.两个线程,针对于一个变量进行修改; ​ 上面的这个就是属于这个情况,两个线程都是针对于这个count进行操作,因此这个时候因为这个调度执 行的步骤可能会被切断,因此这个时候就会出现问题; 3.修改操作不是原子的; ​ 什么是原子:上面的这个count++就不是原子的,原子的简单的就可以理解为这个操作的步骤是一步到位 ​ 还是需要分为多次进行执行,上面的这个load,add,save需要分为三个步骤进行执行,因此这个就不是原 子的; ​ 假设我们的这个步骤一步就可以完成,这个时候我们就把这个操作叫做原子的; 4.内存可见性问题; 5指令重排序问题;(4,5)我们暂时没有遇到,因此这个地方不进行过多的介绍; 4.synchronized关键字的引入4.1修饰代码块我们的这个synchronized实际上就是对于这个操作进行加锁的操作,只有我们的一个线程的三个步骤全部执行完毕之后,我们的另外一个线程才会被执行,相当于我们的t1对于这个全局的变量++的时候,这个就是出于上锁的状态,其他的线程无法进行操作,只有当我们的这个线程执行完毕之后,这个锁被释放掉,也就是开锁,这个时候我们的其他的线程才可以继续执行; 这样的操作保证了这个不同的线程之间的这个操作的独立性,就不会出现上面介绍的一个线程的三个步骤被另外一个线程打断,出现两个线程的操作交叉执行的问题;就是这个load,add,save就是各走各的,而且三个过程是连续的,不会被中断;   除了上面的这个synchronized修饰代码块之外,我们的这个synchronized还是可以修饰我们的静态方法和我们的实例方法的: 4.2修饰实例方法所谓的这个实例方法,其实就是为了和我们的静态方法进行区分,就是一个类里面的普通的成员方法,下面的这个就是我们的synchronized修饰我们的实例方法,下面的两个本质是等效的,因为这个synchronized修饰我们的实例方法本质上就是对于这个this锁对象进行操作,这个时候的锁对象就是我们的this; 因此这样来讲,上面的操作和我们的下面的这个修饰方法就是一样的,只不过我们的这个下面的写法里面,把这个锁对象隐藏了起来; 4.3修饰静态方法下面的两个写法就是一样的,就是我们的synchronized关键字修饰我们的静态成员方法,相当于这个代码块里面的这个参数就是我们的类对象; 我们的代码里面定义了一个类,那么这个里面就一定会有一个类对象,而且一个类只会有一个类对象,不会有多个的; public class Test {    public static void main(String[] args) {        synchronized public static void increase(){         }         public static increase2(){            synchronized (counter.class){                            }        }    }}AI生成项目java运行 4.4对象头介绍synchrinozed修饰的这个锁是存在于我们的这个对象头里面的,那么什么是对象头: 对象头就是我们进行这个对象的创建的时候,一个对象会有自己的内存空间,在这个内存空间里面,除了我们自己对于这个对象定义的属性,这个对象还会有些默认的属性,这个默认的属性就是在我们的对象头里面的; 在对象头里面,就有属性是存放说明我们的这个对象是不是加上了锁的; 4.5死锁-可重入的特性什么是可重入 ,就是对于一个对象,我们连续加锁,这个时候不会出现死锁的情况; 我们使用下面的这个案例对于死锁进行说明: 死锁就是像下面的这个情况一样,我们连续对哦与一个锁对象多次加锁,这个时候就会出现死锁,具体的讲就是线程被卡死了; synchronized(locker){synchronized(locker){.......}}AI生成项目java运行为什么会出现下面的这个死锁的情况,就是我们的第一次加锁的时候,我们的第二次操作正常情况下是进不去的,需要第一次的这个吧这个锁打开之后我们才可以第二次进入,但是下面的这个情况下我们的这个锁想要打开,只有等到这个操作执行完,就是这个代码块执行完,也就是执行到我们下面的这个示例代码的第二个}位置才可以,但是想要执行到这个第二个}位置,必须要执行这个第二次的加锁的操作,这个就是矛盾的地方; 因此这个时候想要加锁,但是这个执行又无法结束,因此这个时候就会出现线程卡死的情况,也就是我们说的死锁现象,为了处理这个问题,synchronized关键字引入了这个可重入的特性,就是对于这个相同的锁对象,我们可以重入,就是反复的入,也就是反复地加锁,这个是可以被允许的; 当然,这个死锁的现象是针对于这个相同的锁对象多次加锁,这个时候才可能会出现死锁的情况,如果每一次加锁针对的锁对象不是一样的,这个时候是不会出现我们的死锁现象的; synchronized(locker){    //下面的这个是针对于一个新的锁对象进行加锁,这个时候肯定不会出现死锁的情况,无论是不是可重入的synchronized(locker2){.......}}AI生成项目java运行那么,在这个可重入的特性下,我们的这个锁什么时候打开呢,正确答案是,直到所有的这个锁全部加上之后,直到我们的这个最外层大括号的时候,这个锁才会被打开; 具体到下面的这个情况,就是执行到最后一个}的时候,这个锁才会被打开,这个过程里面,我们会不断的进行计数,就是这个锁一共加上了几层,即n++,打开的时候,也会不断的对于这个n–直到这个走到最后一个}的时候,这个时候的n=0,也就是我们释放锁的时候; synchronized(locker){synchronized(locker){synchronized(locker){synchronized(locker){synchronized(locker){..........}}}}}AI生成项目 5.关于死锁的分析总结5.1死锁的分析1.一个对象,被连续两次上锁,这个时候如果是不可重入锁,就会发生死锁的现象; 2.两个对象,两把锁,这个时候无论是不是可重入的,都会发生这个死锁现象; 这个经典案例就是我们的钥匙落在了车里,车钥匙落在了家里,这个时候就会出现思索的现象; 这个时候家和车就是两个对象,我们的车钥匙和家钥匙就是锁,这个时候出现的情况就是我们的死锁的现象; 3.N个对象,M把锁,这个时候就是上面的两个对象两把锁的扩展,这个时候更加容易出现死锁的现象; 最经典的N歌对象,M把锁的问题就是我们的哲学家就餐问题: 这个时候我们是使用5个滑稽作为案例的,没有画出来很多,我们的滑稽在就餐的时候,每一个人都是拿走的自己的最近的一个筷子,这个时候,每一个人只有一个,谁都无法就餐,所有的人就是阻塞的状态,这个时候就会出现死锁的现象;(下面我们会介绍这个解决的方案);   5.2死锁成因的必要条件死锁的成因,需要满足下面的这四个条件,并且是同时满足的: 1.互斥使用(死锁的基本特性):当一个线程有一把锁之后,另外一个线程想要使用这个锁,需要进行阻塞等待; 2.不可抢占(死锁的基本特性):当一个锁被线程1拿到之后,线程2只能等待这个线程1主动地进行释放,否则只能处于等待的状态; 3.请求保持(代码结构):一个线程尝试获取多把锁,先拿到第一把锁之后,尝试获取第二把锁,获取这个锁的时候,第一把锁不会被释放; 4.循环等待/环路等待:等待之间的依赖关系,形成了换; 例如一个例子:钥匙锁在了车里,车钥匙锁在了家里;这个就是一个循环的环路等待,这个结果就是一个死循环,也是死锁的一个成因; 5.3死锁的解决方案解决死锁问题的核心就是要破坏上面的必要条件,但是这个里面的第一和第二个必要条件就是我们的锁的特性,因此这个不需要进行考虑,我们主要针对于3,4两个必要条件进行解决; 针对于3这个现象,我们需要进行这个代码结构的调整,不要把两个加锁的代码放到一个代码块里面去; 针对于4这个现象,我们需要进行编号操作,可以有效的解决这个问题: 还是使用这个哲学家的就餐问题,我们进行编号之后,让每一个滑稽取出来这个最小的编号的筷子(自己面前的两个筷子里面的最小的),这样的话,我们的问题就解决了;   我们可以分析一下这个就餐的过程,我们的拿筷子的情况如图所示,每一个人拿的都是自己的这个面前的两个里面的最小的编号,我们的最后一个滑稽取筷子的时候,1和5相比,肯定是1小,这个时候他就不可以取走这个5编号的筷子,这个时候的1已经是被和他相邻的这个滑稽取走了,因此这个时候,只能等待人家用完; 因此这个时候我们的左上角的这个滑稽就可以拿到这个5开始就餐,放下筷子之后,我们的左下角的这个滑稽拿到这个4号筷子吃饭,以此类推,直到我们的右上角的这个滑稽放下筷子,这个时候我们的最上面的这个滑稽就可以吃饭了,这个线程的死锁问题就被解决了;  定是1小,这个时候他就不可以取走这个5编号的筷子,这个时候的1已经是被和他相邻的这个滑稽取走了,因此这个时候,只能等待人家用完; 因此这个时候我们的左上角的这个滑稽就可以拿到这个5开始就餐,放下筷子之后,我们的左下角的这个滑稽拿到这个4号筷子吃饭,以此类推,直到我们的右上角的这个滑稽放下筷子,这个时候我们的最上面的这个滑稽就可以吃饭了,这个线程的死锁问题就被解决了———————————————— 原文链接:https://blog.csdn.net/binhyun/article/details/143165733
  • [技术干货] Javaee 多线程 --进程和线程之间的区别和联系
    进程和线程进程进程:是正在执行的程序,是资源分配的基本单位,具有独立的地址空间操作系统会为其分配CPU和内存线程线程:引入线程是为了解决进程开销大,浪费资源的情景,并且多进程并发效率比较低线程是调度执行的基本单位线程之间会相互影响,一个线程挂了,会影响到整个进程都异常结束,线程也自然会结束进程和线程的区别进程包含线程,一个进程里面有多个线程或者是一个线程进程和线程都是用来实现并发编程场景的,但是线程比进程更轻量和高效同一个进程的线程之间共用同一份资源(内存和硬盘),省去了申请资源的开销进程和进程之间都是独立存在的,不会相互影响,同一个进程中,线程和线程之间会相互影响(线程安全问题 + 线程出现异常)进程是分配资源的基本单位,线程是调度执行的基本单位创建线程的五种写法继承Thread,重写runpackage Thread;class MyThread extends Thread{    public void run(){        // 这个是线程的入口方法        while(true) {            System.out.println("hello Thread!");            try {                Thread.sleep(1000);            } catch (InterruptedException e) {                throw new RuntimeException(e);            }        }    }}// 创建线程public class Demo1 {    public static void main(String[] args) throws InterruptedException {        Thread thread = new MyThread();        // 使用start方法可以间接调用run方法        // start和 run都时Thread的成员        // run 只是线程的入口(描述了线程要做什么事情)        // start才是真正调用了系统的API,在系统中创建出了线程,让线程调用 run        thread.start();        // 从这句开始程序就并发执行,一边执行hello main,一边执行hello Thread        // 兵分两路进行执行        // 并发 == 并行 + 并发        while(true){            System.out.println("hello main!");            Thread.sleep(1000);        }        // 先执行main,再执行的是Thread,先执行主线程    }}AI生成项目java运行实现Runnable(接口),重写runpackage Thread;class MyRunable implements Runnable{    public void run(){        while(true){            System.out.println("hello thread!");            try {                Thread.sleep(1000);            } catch (InterruptedException e) {                throw new RuntimeException(e);            }        }    }}public class Demo2 {    public static void main(String[] args) throws InterruptedException {        // 这个接口就是用来实现多态的        Runnable myRunable = new MyRunable();        Thread thread = new Thread(myRunable);        thread.start();        while(true){            System.out.println("hello main!");            Thread.sleep(1000);        }    }}AI生成项目java运行继承Thread,重写run,但是使用匿名内部类使用匿名内部类的方式创建出线程package Thread;public class Demo3 {    public static void main(String[] args) {        Thread thread = new Thread(){            public void run(){                while(true){                    System.out.println("hello Thread!");                    try {                        Thread.sleep(1000);                    } catch (InterruptedException e) {                        throw new RuntimeException(e);                    }                }            }        };        thread.start();        while(true){            System.out.println("hello main!");            try {                Thread.sleep(1000);            } catch (InterruptedException e) {                throw new RuntimeException(e);            }        }    }}AI生成项目java运行实现Runnable(接口),重写run,但是使用匿名内部类package Thread;public class Demo4 {    public static void main(String[] args) {        // 法一:创建实例       Runnable runnable = new Runnable(){            public void run(){                System.out.println("hello Thread!");                try {                    Thread.sleep(1000);                } catch (InterruptedException e) {                    throw new RuntimeException(e);                }            }        };        // 法二:创建匿名对象       Thread thread = new Thread(new Runnable(){            public void run(){                System.out.println("hello Thread!");                try {                    Thread.sleep(1000);                } catch (InterruptedException e) {                    throw new RuntimeException(e);                }            }        });        // Thread thread = new Thread(runnable);        thread.start();        while(true){            System.out.println("hello main!");            try {                Thread.sleep(1000);            } catch (InterruptedException e) {                throw new RuntimeException(e);            }        }    }}AI生成项目java运行使用lambda表达式lambda表达式相当于是匿名内部类的替换写法package Thread;public class Demo5 {    public static void main(String[] args) {        Thread thread = new Thread(()->{           while(true){               System.out.println("hello Thread!");               try {                   Thread.sleep(1000);               } catch (InterruptedException e) {                   throw new RuntimeException(e);               }           }        });        thread.start();        while(true){            System.out.println("hello main!");            try {                Thread.sleep(1000);            } catch (InterruptedException e) {                throw new RuntimeException(e);            }        }    }}AI生成项目java运行请说明Thread类中run和start的区别从方法的区别,及运行结果的区别分别说明1. start方法可以用来启动一个新的线程,run方法只是一个普通的方法,在主线程中执行2.start方法只能调用一次,run方法可以调用多次3.调用start方法会执行新的线程,新线程和主线程并发执行,run方法只是线程的入口,start方法调用了系统API,start方法创建出了线程,让线程再调用run方法4.run方法和主线程同步执行,start方法启动的线程和主线程异步执行5.run方法按顺序执行,start方法调用的线程中的代码执行顺序由线程调度器决定,顺序不确定————————————————原文链接:https://blog.csdn.net/2301_79722622/article/details/149054854
  • [技术干货] 布隆过滤器的应用场景
    布隆过滤器(Bloom Filter)因其空间效率高、查询速度快的特点,被广泛应用于需要快速判断元素是否存在的场景,尤其适合允许一定误判率但不能漏判的情况。以下是其典型应用场景及具体案例:1. 缓存穿透防护问题恶意请求或高频查询不存在的数据(如 id=-1、未爬取的 URL),导致缓存和数据库均未命中,直接穿透到后端存储,引发性能雪崩。解决方案在缓存前加一层布隆过滤器,存储所有可能存在的键(如数据库主键、URL)。查询流程:先查布隆过滤器 → 若不存在,直接返回空结果(避免穿透)。若可能存在,再查缓存或数据库。案例电商系统:防止用户频繁查询不存在的商品 ID。爬虫系统:避免重复请求不存在的网页。2. URL 去重问题爬虫需要抓取海量网页,但需避免重复抓取同一 URL,否则浪费资源且可能陷入循环。解决方案用布隆过滤器存储已抓取的 URL,每次抓取前先检查是否存在。优势:相比哈希表或数据库,布隆过滤器节省大量内存(例如存储 1 亿 URL 仅需约 120MB 内存,误判率 1%)。案例Google 爬虫:早期使用布隆过滤器去重,后升级为更高效的变种(如 Counting Bloom Filter)。新闻聚合应用:避免重复抓取相同新闻源的链接。3. 垃圾邮件/恶意内容过滤问题需要快速判断邮件发送者、IP 或内容是否在黑名单中,但黑名单可能包含数百万条记录。解决方案用布隆过滤器存储黑名单(如垃圾邮件发送者的邮箱、恶意 IP),实时过滤请求。误判处理:若布隆过滤器返回“可能存在”,再通过精确查询(如数据库)确认。案例SpamAssassin:开源垃圾邮件过滤工具使用布隆过滤器加速黑名单检查。CDN 防护:拦截已知恶意请求的 IP。4. 区块链与加密货币问题区块链节点需要快速验证交易或地址是否有效,但全量存储所有地址不现实(如比特币地址约 2^160 个)。解决方案用布隆过滤器存储已使用的地址或交易哈希,节点间同步时过滤无效数据。比特币轻客户端:通过布隆过滤器向全节点请求与自己钱包相关的交易,减少数据传输量。案例Ethereum:使用布隆过滤器优化日志查询。Zcash:隐私交易中过滤无关数据。5. 数据库与大数据查询优化问题在大数据集(如用户行为日志、点击流)中查询某元素是否存在时,直接扫描全表效率极低。解决方案用布隆过滤器预过滤不可能存在的数据,减少磁盘 I/O 或网络传输。Hive/Spark:支持将布隆过滤器下推到存储层(如 HDFS),加速查询。案例Facebook:用布隆过滤器优化 HBase 的 Scan 操作,避免全表扫描。Elasticsearch:通过布隆过滤器加速 exists 查询。6. 推荐系统与用户行为分析问题需要快速判断用户是否已对某商品点赞、收藏或浏览过,但用户行为数据量巨大。解决方案用布隆过滤器存储用户行为记录(如 user_id + item_id 的哈希),实时去重或推荐。冷启动优化:对新用户快速生成近似行为画像。案例Netflix:用布隆过滤器记录用户观看历史,优化推荐算法。TikTok:实时过滤用户已刷过的视频。7. 分布式系统与一致性哈希问题分布式缓存(如 Redis Cluster)中,节点需要快速判断键是否属于本机分片,避免跨节点查询。解决方案用布隆过滤器存储本节点负责的键范围,减少哈希计算或网络开销。变种应用:结合一致性哈希环,优化数据迁移时的键分配。案例Twitter:用布隆过滤器加速 Timeline 服务的缓存查询。Akamai:CDN 节点用布隆过滤器路由请求到正确的边缘服务器。8. 网络安全与入侵检测问题需要实时检测网络流量中是否包含恶意签名(如病毒特征、攻击模式),但签名库可能包含数百万条规则。解决方案用布隆过滤器存储恶意签名,快速过滤正常流量,减少深度检测(DPI)的负载。误判处理:对布隆过滤器标记的可疑流量进行进一步分析。案例Snort:开源入侵检测系统使用布隆过滤器加速规则匹配。Cloudflare:用布隆过滤器拦截已知的 DDoS 攻击流量。9. 生物信息学与基因测序问题基因测序数据量极大(如人类基因组约 30 亿碱基对),需要快速查找特定序列是否存在。解决方案用布隆过滤器存储已知基因片段,加速测序数据的比对和分析。应用场景:疾病关联分析、进化树构建。案例BWA/Bowtie:主流基因比对工具使用布隆过滤器优化索引查询。COVID-19 测序:快速筛选病毒变异位点。10. 游戏开发与反作弊问题需要实时检测玩家行为是否异常(如外挂、刷分),但行为模式库可能包含大量规则。解决方案用布隆过滤器存储作弊行为特征(如特定按键序列、网络包模式),实时拦截可疑操作。动态更新:通过后台服务定期更新布隆过滤器的作弊规则。案例PUBG Mobile:用布隆过滤器检测外挂脚本。Steam:反作弊系统(VAC)使用布隆过滤器优化行为分析。总结:布隆过滤器的适用性场景是否适用原因需要快速判断存在性✔️查询时间复杂度 O(k),与数据量无关。允许一定误判率✔️误判率可通过调整参数控制(如 1% 或更低)。数据量极大(亿级以上)✔️空间效率远高于哈希表或数据库,内存占用低。不支持删除或动态更新❌(需变种)传统布隆过滤器不支持删除,可用 Counting Bloom Filter 或 Cuckoo Filter。需要零误判❌误判率不可为 0,需结合精确查询(如数据库)使用。推荐实践:单机场景:优先使用 Guava 的 BloomFilter 或 RedisBloom。分布式场景:结合 Redis、HBase 或自定义分片实现。高并发场景:注意布隆过滤器的线程安全性(Guava 实现是线程安全的)。通过合理应用布隆过滤器,可以显著提升系统性能,尤其在处理海量数据的场景下。
  • [技术干货] java布隆过滤器
    Java 中的布隆过滤器(Bloom Filter)是一种空间效率极高的概率型数据结构,用于快速判断一个元素是否可能存在于集合中(可能存在误判,但绝不会漏判)。它特别适合处理大规模数据且允许一定误判率的场景,如缓存穿透防护、URL去重、垃圾邮件过滤等。一、布隆过滤器核心原理1. 数据结构位数组(Bit Array):初始时所有位为 0,长度通常为 m。哈希函数集合:k 个独立的哈希函数,每个函数将元素映射到位数组的某个位置。2. 操作流程添加元素:用 k 个哈希函数计算元素的 k 个位置。将这些位置的位全部设为 1。查询元素:用同样的 k 个哈希函数计算 k 个位置。如果所有位置均为 1,则元素可能存在;否则一定不存在。3. 误判率误判(False Positive):元素不在集合中,但布隆过滤器返回“可能存在”。误判率公式:[p \approx \left(1 - e^{-\frac{kn}{m}}\right)^k]n:已添加的元素数量。m:位数组长度。k:哈希函数数量。优化目标:通过调整 m 和 k,使误判率 p 最小化(通常 k ≈ m/n * ln2 时最优)。二、Java 实现方式1. Guava 的 BloomFilter(推荐)Google Guava 库提供了开箱即用的布隆过滤器实现,支持自动计算最优参数。依赖引入<dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>32.1.3-jre</version> <!-- 使用最新版本 --> </dependency> 代码示例import com.google.common.hash.BloomFilter; import com.google.common.hash.Funnels; import java.nio.charset.StandardCharsets; public class BloomFilterDemo { public static void main(String[] args) { // 1. 创建布隆过滤器:预期插入100万个元素,误判率控制在1%以内 BloomFilter<String> bloomFilter = BloomFilter.create( Funnels.stringFunnel(StandardCharsets.UTF_8), 1_000_000, // 预期元素数量 0.01 // 误判率 ); // 2. 添加元素 bloomFilter.put("apple"); bloomFilter.put("banana"); // 3. 查询元素 System.out.println(bloomFilter.mightContain("apple")); // true System.out.println(bloomFilter.mightContain("orange")); // false(可能误判为true) // 4. 序列化(可选) // byte[] bytes = bloomFilter.toByteArray(); // BloomFilter<String> restored = BloomFilter.create( // Funnels.stringFunnel(StandardCharsets.UTF_8), bytes); } } 关键参数说明Funnels.stringFunnel():指定元素类型(如 String、Long 等)。expectedInsertions:预期插入的元素数量(需预估准确,否则影响误判率)。fpp(False Positive Probability):误判率(值越小,位数组越大)。2. Redis 的布隆过滤器(分布式场景)如果需要在分布式系统中使用布隆过滤器,可以通过 Redis 模块 RedisBloom 实现。依赖引入<dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>4.4.3</version> </dependency> <dependency> <groupId>io.rebloom</groupId> <artifactId>rebloom</artifactId> <version>2.2.0</version> </dependency> 代码示例import redis.clients.jedis.Jedis; import redis.clients.jedis.params.SetParams; public class RedisBloomFilterDemo { public static void main(String[] args) { try (Jedis jedis = new Jedis("localhost", 6379)) { // 1. 创建布隆过滤器(key="my_bloom", 预期元素数=1000, 误判率=0.01) jedis.bfReserve("my_bloom", 0.01, 1000); // 2. 添加元素 jedis.bfAdd("my_bloom", "apple"); jedis.bfAdd("my_bloom", "banana"); // 3. 查询元素 System.out.println(jedis.bfExists("my_bloom", "apple")); // true System.out.println(jedis.bfExists("my_bloom", "orange")); // false } } } 三、布隆过滤器的应用场景缓存穿透防护问题:恶意请求查询不存在的数据(如 id=-1),导致缓存和数据库均未命中。解决方案:在缓存前加一层布隆过滤器,若元素不存在则直接返回空结果。URL去重爬虫系统中快速判断 URL 是否已抓取。垃圾邮件过滤判断邮件发送者是否在黑名单中(允许少量误判)。区块链地址校验快速检查地址是否有效(避免全量存储)。四、布隆过滤器的局限性不支持删除操作传统布隆过滤器无法删除元素(需使用变种如 Counting Bloom Filter)。误判率不可为 0需根据业务容忍度调整参数。空间效率与误判率的权衡误判率越低,位数组越大(例如 1% 误判率约需 9.6 位/元素)。五、替代方案对比方案查询时间空间效率支持删除误判率布隆过滤器O(k)高❌可控布谷鸟过滤器O(k)更高✔️更低哈希表O(1)低✔️0Redis 集合O(1)中✔️0选择建议:需要极致空间效率且允许误判 → 布隆过滤器。需要支持删除 → 布谷鸟过滤器或 Redis 集合。需要零误判 → 哈希表或 数据库。总结单机场景:优先使用 Guava 的 BloomFilter,简单高效。分布式场景:选择 RedisBloom 或自研基于 Redis 的布隆过滤器。关键参数:根据预期元素数量和误判率调整 m 和 k(Guava 已自动优化)。通过合理使用布隆过滤器,可以显著提升系统性能,尤其在处理海量数据的场景下。
  • [技术干货] 【Java篇】一气化三清:类的实例化与封装的智慧之道
    对象的构造及初始化5.1 如何初始化对象通过前面知识点的学习我们知道,在Java方法内部定义一个局部变量时,必须要初始化,否则会编译失败。 public static void main(String[] args) {int a;System.out.println(a);}// Error:(26, 28) java: 可能尚未初始化变量aAI生成项目java运行 要让上述代码通过编译,非常简单,只需在正式使用变量之前给它设置初始值即可。 public static void main(String[] args) {    Date d = new Date();    d.printDate();    d.setDate(2021,6,9);    d.printDate();}// 代码可以正常通过编译AI生成项目java运行 如果是对象,就需要调用之前写的 setDate 方法将具体的日期设置到对象中。 通过上述例子我们发现两个问题: 问题1:每次对象创建好后调用 setDate 方法设置具体日期显得比较麻烦,那么对象该如何初始化?问题2:局部变量必须初始化才能使用,而字段声明之后没有给值依然可以使用,这是因为字段具有默认初始值。为了解决问题1,Java引入了 构造方法,使得对象在创建时就能完成初始化操作。 5.2 构造方法构造方法(也称为构造器)是一种特殊的成员方法,其主要作用是初始化对象。 5.2.1 构造方法的概念public class Date {    public int year;    public int month;    public int day;    // 构造方法:    // 名字与类名相同,没有返回值类型,设置为void也不行    // 一般情况下使用public修饰    // 在创建对象时由编译器自动调用,并且在对象的生命周期内只调用一次    public Date(int year, int month, int day){        this.year = year;        this.month = month;        this.day = day;        System.out.println("Date(int,int,int)方法被调用了");    }    public void printDate(){        System.out.println(year + "-" + month + "-" + day);    }    public static void main(String[] args) {        // 此处创建了一个Date类型的对象,并没有显式调用构造方法        Date d = new Date(2021,6,9); // 输出Date(int,int,int)方法被调用了        d.printDate(); // 2021-6-9    }}AI生成项目java运行 构造方法的特点是: 名字必须与类名相同,且没有返回值类型(连void都不行)。在创建对象时由编译器自动调用,并且在对象的生命周期内只调用一次。注意:构造方法的作用是对对象中的成员进行初始化,并不负责给对象开辟内存空间。5.2.2 构造方法的特性构造方法具有如下特性: 名字必须与类名完全相同没有返回值类型,即使设置为void也不行创建对象时由编译器自动调用,且在对象生命周期内只调用一次(就像人的出生,每个人只能出生一次)支持重载:同一个类中可以定义多个构造方法,只要参数列表不同即可示例代码1:带参构造方法 public class Date {    public int year;    public int month;    public int day;        // 构造方法:名字与类名相同,没有返回值类型,使用public修饰    // 在创建对象时由编译器自动调用,并且在对象的生命周期内只调用一次    public Date(int year, int month, int day) {        this.year = year;        this.month = month;        this.day = day;        System.out.println("Date(int, int, int)方法被调用了");    }        public void printDate() {        System.out.println(year + "-" + month + "-" + day);    }        public static void main(String[] args) {        // 此处创建了一个Date类型的对象,并没有显式调用构造方法        Date d = new Date(2021, 6, 9); // 输出:Date(int, int, int)方法被调用了        d.printDate();                // 输出:2021-6-9    }}AI生成项目java运行 示例代码2:无参构造方法 public class Date {    public int year;    public int month;    public int day;        // 无参构造方法:给成员变量设置默认初始值    public Date() {        this.year = 1900;        this.month = 1;        this.day = 1;    }}AI生成项目java运行 上述两个构造方法名字相同但参数列表不同,构成了方法的重载。 如果用户没有显式定义构造方法,编译器会生成一个默认的无参构造方法。注意:一旦用户显式定义了构造方法,编译器就不会再生成默认构造方法了 示例代码3:仅定义带参构造方法时默认构造方法不会生成 // 带有三个参数的构造方法public Date(int year, int month, int day) {    this.year = year;    this.month = month;    this.day = day;} public void printDate(){    System.out.println(year + "-" + month + "-" + day);} public static void main(String[] args) {    Date d = new Date(); // 编译期报错,因为没有无参构造方法    d.printDate();}AI生成项目java运行 示例代码4:只有无参构造方法的情况 public class Date {    public int year;    public int month;    public int day;        public void printDate(){        System.out.println(year + "-" + month + "-" + day);    }        public static void main(String[] args) {        Date d = new Date();        d.printDate();    }}AI生成项目java运行 示例代码5:只有带参构造方法的情况 public class Date {    public int year;    public int month;    public int day;        public Date(int year, int month, int day) {        this.year = year;        this.month = month;        this.day = day;    }}AI生成项目java运行 构造方法中可以通过 this(…) 调用其他构造方法来简化代码。注意:this(…) 必须是构造方法中的第一条语句,否则编译器会报错。示例代码6:正确使用this(…)实现构造器链 public class Date {    public int year;    public int month;    public int day;        // 无参构造方法 -- 内部调用带参构造方法实现初始化    // 注意:this(1900, 1, 1);必须是构造方法中的第一条语句    public Date(){        // System.out.println(year); // 若取消注释则编译会失败        this(1900, 1, 1);        // 以下赋值代码被省略,因为已在带参构造方法中完成初始化        // this.year = 1900;        // this.month = 1;        // this.day = 1;    }        // 带有三个参数的构造方法    public Date(int year, int month, int day) {        this.year = year;        this.month = month;        this.day = day;    }}AI生成项目java运行 注意:构造方法中的 this(…) 调用不能形成循环,否则会导致编译错误。 public Date(){this(1900,1,1);} public Date(int year, int month, int day) {this();}/*无参构造器调用三个参数的构造器,而三个参数构造器有调用无参的构造器,形成构造器的递归调用编译报错:Error:(19, 12) java: 递归构造器调用*/AI生成项目java运行 在大多数情况下,我们使用 public 来修饰构造方法,但在特殊场景下(如实现单例模式)可能会使用 private 修饰构造方法。5.3 默认初始化在上文中提到的第二个问题:为什么局部变量在使用前必须初始化,而成员变量可以不初始化? 要搞清楚这个过程,就需要知道 new 关键字背后所发生的一些事情: Date d = new Date(2021,6,9);AI生成项目java运行1在程序员看来只是一句简单的语句,但 JVM 层面需要做好多事情。下面简单介绍下: 检测对象对应的类是否被加载,如果没有则加载为对象分配内存空间并先默认初始化处理并执行类中的 init 方法初始化分配好的空间 (说明:多个线程同时申请资源,JVM 要保证分配给对象的空间内干净。)即:对象空间被申请好之后,对象中包含的成员已经设置好了初始值,比如:Java中的 成员变量 会由 JVM 自动赋予默认值(如int类型为0,boolean类型为false等),而局部变量则需要显式初始化才能使用。 Java为不同的数据类型提供了默认值,具体如下所示: 数据类型 默认值byte 0char ‘\u0000’short 0int 0long 0Lboolean falsefloat 0.0fdouble 0.0dreference null这些默认值确保了即使对象的成员变量没有显式初始化,也不会发生错误,成员变量会有一个初始的稳定状态。 设置对象头信息(关于对象内存模型后面会介绍) 调用构造方法,给对象中各个成员赋值 5.4 就地初始化就地初始化是指在成员变量声明时直接为它们赋初值。这种方法在代码的简洁性上具有优势,可以避免每次创建对象时重复设置成员变量的值。 代码示例: public class Date {    public int year = 1900;  // 就地初始化    public int month = 1;    // 就地初始化    public int day = 1;      // 就地初始化     public Date() {        // 构造方法在此处不需要再次初始化year、month、day,它们已经有默认值    }        public void printDate() {        System.out.println(year + "-" + month + "-" + day);    }     public static void main(String[] args) {        Date d = new Date();        d.printDate();  // 输出:1900-1-1    }}AI生成项目java运行 在这个例子中,year、month 和 day 的值在声明时就被初始化为1900、1和1,确保了每次创建对象时这些值已经存在。 六、封装6.1 封装的概念封装是面向对象编程的三大特性之一,其核心思想是将数据和操作数据的方法结合在一起,并对外隐藏实现细节。例如,电脑作为一个复杂的设备,用户只需通过开关、键盘和鼠标等接口进行交互,而不必关心内部CPU、显卡等工作原理。   简单来说就是套壳屏蔽细节。 6.2 访问限定符访问修饰符说明 public:可以理解为一个人的外部接口,能被外部访问。protected:主要是给继承使用,子类继承后就能访问到。default(不写修饰符时即为默认访问修饰符):对于同一包内的类可见,不同包则不可见。private:只能在当前类中访问。(这部分要介绍完继承后才能完全理解)  【说明】protected 主要是给继承使用;default 只能给同一个包内使用;按照自己的理解去记忆。 示例代码: public class Computer {    private String cpu;    private String brand;    private String memory;    private String screen;     public Computer(String brand, String cpu, String memory, String screen) {        this.brand = brand;        this.cpu = cpu;        this.memory = memory;        this.screen = screen;    }     public void boot() {        System.out.println("开机");    }     public void shutDown() {        System.out.println("关机");    }}AI生成项目java运行 public class TestComputer {    public static void main(String[] args) {        Computer c = new Computer("华为", "i9", "16G", "4K");        c.boot();        c.shutDown();    }}AI生成项目java运行 注意:一般情况下,成员变量通常设置为 private,成员方法设置为 public。 6.3 封装扩展之包6.3.1 包的概念包(Package)用于对类进行分组管理,有助于解决类名冲突并提高代码的组织性。例如,将相似功能的类归为同一包。 为了更好的管理类,把多个类收集在一起成为一组,称为软件包  6.3.3导入包在Java中,如果我们需要使用不在默认 java.lang 包中的类或接口,就需要使用 import 关键字来导入。例如,我们想使用 java.util.Date 这个类,就可以这样做: import java.util.Date; public class TestImport {    public static void main(String[] args) {        Date d = new Date();        System.out.println(d);    }}AI生成项目java运行 这样就可以正常创建 Date 对象并使用。 6.3.3全类名当不同包中存在同名的类时(如 java.util.Date 与 java.sql.Date 都叫 Date),可能会引发冲突。这时,可以使用 全类名(Fully Qualified Name)来指定使用哪个类: public class TestFullName {    public static void main(String[] args) {        // 使用全类名来区分两个Date类        java.util.Date d1 = new java.util.Date();        java.sql.Date d2 = new java.sql.Date(System.currentTimeMillis());                System.out.println(d1);        System.out.println(d2);    }}AI生成项目java运行 通过在创建对象时加上包名,就可以区分来自 java.util 和 java.sql 的 Date 类。 6.3.4 静态导入从 Java 5 开始,支持使用 静态导入(import static)的方式将某个类中的 静态成员(常量或方法) 导入到当前类中,从而在调用时可以省略类名。 示例代码: import static java.lang.Math.PI;import static java.lang.Math.random; public class TestStaticImport {    public static void main(String[] args) {        System.out.println(PI);      // 直接使用PI常量        System.out.println(random()); // 直接使用random()方法    }}AI生成项目java运行 如果不使用静态导入,则需要写成: System.out.println(Math.PI);System.out.println(Math.random());AI生成项目java运行 6.3.5 IDE工具中的包结构当我们使用 IntelliJ IDEA 或 Eclipse 等 IDE 工具时,会在项目结构中直观地看到包名与文件夹一一对应。 包名一般使用 小写 的域名反写形式(如 com.example.project),在 IDE 中会对应层级文件夹结构。在同一个包下,可以放置多个类文件,便于组织与管理。在实际开发中,合理划分包结构能让项目更易于维护和理解。 6.3.6 包的访问权限控制举例Computer类位于com.bit.demo1包中,TestComputer位于com.bit.demo2包中: package com.bit.demo1; public class Computer {    private String cpu;        // cpu    private String memory;     // 内存    public String screen;      // 屏幕    String brand;              // 品牌     public Computer(String brand, String cpu, String memory, String screen) {        this.brand = brand;        this.cpu = cpu;        this.memory = memory;        this.screen = screen;    }     public void PowerOff(){        System.out.println("关机~~~");    }     public void SurfInternet(){        System.out.println("上网~~~");    }}AI生成项目java运行 ////////////////////////////////////package com.bit.demo2; import com.bit.demo1.Computer; public class TestComputer {    public static void main(String[] args) {        Computer p = new Computer("HW", "i7", "8G", "13*14");         System.out.println(p.screen);   // 公有属性,可以被其他包访问        // System.out.println(p.cpu);    // 私有属性,不能被其他包访问        // System.out.println(p.brand);  // brand是default,不允许被其他包中的类访问    }}AI生成项目java运行 注意:如果去掉前面的Computer类中的public修饰符,代码也会编译失败。 6.3.7 常见的包java.lang:系统常用基础类(String,Object),此包从JDK1.1后自动导入。java.lang.reflect:Java反射机制包;java.net:进行网络编程开发包;java.sql:进行数据库开发的包;javax.util:Java提供的工具程序包(集合类等)非常重要;javalio.io:编程程序包。注意事项: import 和 C++ 的 #include 差别很大. C++ 必须 #include 来引入其他文件内容, 但是 Java 不需要.import 只是为了写代码的时候更方便. 区别如下: Java 的 import:仅在编译时为你提供类或接口的简写路径,使你可以直接使用类名而不用写出完整的包名。实际上,编译器会在编译过程中通过类路径(classpath)去查找相应的类文件,而不会把代码插入到当前文件中。 C/C++ 的 #include:预处理器会在编译之前将头文件的内容直接拷贝进源代码文件中,这种方式实际上是将文件的内容“粘贴”到包含它的文件里。 因此,Java 的 import 只是一种简化引用的机制,并不涉及代码的复制。 七、总结与展望在本篇文章中,我们围绕对象的构造与初始化、封装、包的管理以及访问权限等内容展开了详细讲解,帮助大家深入理解Java面向对象编程的核心概念。 7.1 总结对象的构造与初始化 构造方法:通过构造方法,我们可以在创建对象时立即为其成员赋值,保证对象在使用前处于有效状态。构造器重载与this调用:支持多个构造方法以及构造器链,让对象初始化更加灵活和简洁。默认初始化与就地初始化:成员变量会自动获得默认值,同时也可以在声明时直接赋值,避免重复代码。封装 核心思想:将数据与操作数据的方法绑定在一起,通过访问限定符(public、private、protected、default)隐藏内部实现细节。访问控制:合理使用访问修饰符保护数据安全,提供公开接口供外部使用,增强了程序的健壮性和安全性。包的管理与导入 包的概念:通过将相关类归为一组,实现代码的模块化管理和命名空间隔离,避免类名冲突。import语句:在编译时提供类名简写路径,与C/C++的#include机制不同,import不进行代码复制,仅起到标识和简化引用的作用。7.2 总结深入static成员在后续的内容中,我们将更详细地探讨static关键字的使用,包括静态变量、静态方法及其在类中的意义,帮助大家更好地理解类级别的共享特性。 代码块与初始化块将介绍类中的初始化块和静态代码块,讨论它们在对象创建过程中的执行顺序和作用,为进一步掌握对象生命周期奠定基础。 内部类与匿名类内部类是一种特殊的类定义方式,它能够更紧密地绑定外部类的成员,将在未来篇章中深入剖析其用法和设计思想。 面向对象的设计原则随着对类和对象理解的深入,我们也将探讨更多面向对象设计的原则和模式,帮助大家构建更健壮、可维护的Java应用程序———————————————— 原文链接:https://blog.csdn.net/2301_79849925/article/details/146015106
  • [技术干货] Java中文件操作和IO
     前言:在 Java 中,输入输出(I/O)是常见的操作,字节流和字符流是处理文件和数据的核心类,本文将介绍 InputStream、OutputStream、Reader 和 Writer 类的基本用法。这里是秋刀鱼不做梦的BLOG想要了解更多内容可以访问我的主页秋刀鱼不做梦-CSDN博客在正式开始讲解之前,先让我们看一下本文大致的讲解内容:目录1.File 类        (1)构造方法        【1】File(String pathname):最简单的方式,直接传入文件的路径        【2】File(String parent, String child):我们也可以先给出父目录,然后再给出子文件或子目录的名称        【3】File(File parent, String child):首先先创建一个 File 对象表示父目录,然后用它来构建子文件或子目录        (2)File中的方法【1】获取文件信息类【2】判断文件状态类【3】文件与目录操作【4】列出目录内容2.数据流类        (1)InputStream 类        (2)OutputStream 类        (3)Reader 类        (4)Writer 类1.File 类        首先先让我们了解一下Java中的File 类,在 Java 中,File 类是我们用来操作文件和目录的工具,虽然它并不能直接读取或写入文件的内容,但它提供了很多方法,让我们能够管理文件系统,比如检查文件是否存在、获取文件信息、创建文件和目录、删除文件等等,简而言之,它是与文件打交道时的一个基础类。        (1)构造方法        初步了解了File类是个什么东西之后,那么我们在Java中如何去创建File类呢?常见的创建方式有如下三种:        【1】File(String pathname):最简单的方式,直接传入文件的路径File file = new File("path/to/file.txt");AI生成项目java运行        【2】File(String parent, String child):我们也可以先给出父目录,然后再给出子文件或子目录的名称File file = new File("path/to", "file.txt");AI生成项目java运行        【3】File(File parent, String child):首先先创建一个 File 对象表示父目录,然后用它来构建子文件或子目录File parentDir = new File("path/to");File file = new File(parentDir, "file.txt");AI生成项目java运行        通过上述方法,我们就可以方便地根据路径创建 File 对象,之后就可以对这些文件或目录进行各种操作了!!!        (2)File中的方法         File类中提供了很多方法,能帮助我们做各种文件和目录的操作,比如获取文件信息、检查文件状态、创建和删除文件等等,我们来看看其中一些最常用的方法:【1】获取文件信息方法        getName():返回文件的名称,不包括路径。File file = new File("path/to/file.txt");System.out.println(file.getName());  // 输出 file.txtAI生成项目java运行        getAbsolutePath():返回文件的绝对路径,这个路径不管你在哪个目录下都能访问到文件。System.out.println(file.getAbsolutePath());AI生成项目java运行        getPath():返回文件的路径,可能是相对路径,也可能是绝对路径,取决于创建 File 对象时传入的是什么路径System.out.println(file.getPath());AI生成项目java运行        getParent():返回文件的父目录,如果文件在根目录或者没有父目录,这个方法会返回null。System.out.println(file.getParent());AI生成项目【2】判断文件状态方法        exists():检查文件或目录是否存在,如果存在,返回 true,否则返回 false。if (file.exists()) {    System.out.println("文件存在");} else {    System.out.println("文件不存在");}AI生成项目java运行        isFile():判断它是否是一个文件,如果是文件,返回 true,如果是目录,返回 false。if (file.isFile()) {    System.out.println("是文件");}AI生成项目java运行                isDirectory():判断它是否是一个目录,如果是目录,返回 true,如果是文件,返回 false。if (file.isDirectory()) {    System.out.println("是目录");}AI生成项目java运行【3】文件与目录操作方法        createNewFile():用来创建一个新文件,如果文件已经存在,这个方法会返回 false。try {    if (file.createNewFile()) {        System.out.println("文件创建成功");    } else {        System.out.println("文件已存在");    }} catch (IOException e) {    e.printStackTrace();}AI生成项目java运行        mkdir():创建一个目录。如果目录已存在,返回 false,如果成功创建,返回 true。File dir = new File("path/to/directory");if (dir.mkdir()) {    System.out.println("目录创建成功");}AI生成项目java运行【4】列出目录内容方法        listFiles():返回一个 File 数组,包含目录下所有的文件和子目录,如果这个 File 对象代表的不是目录,返回 null。File dir = new File("path/to/directory");File[] files = dir.listFiles();if (files != null) {    for (File f : files) {        System.out.println(f.getName());    }}AI生成项目java运行        这样我们就大致的了解了File中的常用方法了!!!2.数据流类        在 Java 中,处理文件和数据流的输入输出是非常常见的操作,为了让这些操作更加高效,Java 提供了字节流和字符流的不同方式,这些类分为 InputStream 和 OutputStream,以及 Reader 和 Writer,接下来让我们一一讲解一下:        (1)InputStream 类        InputStream 是所有字节输入流的超类,它负责从外部读取字节数据,你可以把它想象成一个“读取器”,它帮助你从磁盘文件、网络连接或者内存等地方读取数据。常用方法:        ——read():从输入流中读取一个字节并返回它,如果流的末尾已经到达,则返回 -1InputStream inputStream = new FileInputStream("file.txt");int byteData = inputStream.read();while (byteData != -1) {    System.out.print((char) byteData); // 转换为字符并输出    byteData = inputStream.read();    // 继续读取下一个字节}inputStream.close(); // 别忘了关闭流AI生成项目java运行        代码解释:这个例子逐个字节读取文件内容,直到到达文件末尾。我们将每个字节转换为字符并打印出来        ——read(byte[] b):一次性读取多个字节到字节数组 b 中,返回实际读取的字节数,如果已经到达流的末尾,它返回 -1。byte[] buffer = new byte[1024];int bytesRead = inputStream.read(buffer);System.out.println("读取了 " + bytesRead + " 个字节");inputStream.close();AI生成项目java运行        代码解释:这个例子中我们使用read方法将数据读到了buffer这个数组中,并返回了读取到的字节数。        ——read(byte[] b, int off, int len):从字节数组 b 的 off 偏移量开始,最多读取 len 个字节,返回实际读取的字节数。byte[] buffer = new byte[1024];int bytesRead = inputStream.read(buffer, 0, 100); // 从数组开头读取 100 字节inputStream.close();AI生成项目java运行        代码解释:和read(byte[] b)方法类似,只不过我们只读取了0到100字节而已至此,我们就了解了InputStream类的常用方法了!!!        (2)OutputStream 类        与 InputStream 类相对应,OutputStream 负责将数据写入输出流,它可以用于文件、网络或内存等目标的写入。常用方法:        ——write(int b):将一个字节的数据写入输出流,需要注意,write() 方法接受的是一个 int 类型的参数,它会自动转换为字节。OutputStream outputStream = new FileOutputStream("output.txt");outputStream.write(65); // 写入字节 'A'(ASCII 码为 65)outputStream.close();AI生成项目java运行        代码解释:这个例子将字节 65 写入文件。        ——write(byte[] b):将字节数组中的数据写入输出流。byte[] data = "Hello".getBytes();outputStream.write(data);outputStream.close();AI生成项目java运行        代码解释:我们将字符串 "Hello" 转换成字节数组,然后写入文件。        ——write(byte[] b, int off, int len):从字节数组 b 的 off 偏移量开始,最多写入 len 个字节。byte[] data = "HelloWorld".getBytes();outputStream.write(data, 0, 5); // 写入 "Hello"outputStream.close();AI生成项目java运行        代码解释:这段代码将 "HelloWorld" 字符串中的前 5 个字节写入文件        ——flush():在某些情况下,写入的数据会被暂时保存在缓冲区中,直到缓冲区满了才会被写入,如果你需要强制将缓冲区中的数据立即写入目标,可以使用 flush()。outputStream.flush();AI生成项目java运行以上就是OutputStream 类的常见方法了!!!        (3)Reader 类        如果你要处理文本文件中的字符数据,Reader 类是一个非常方便的选择,它是所有字符输入流的超类,专门用来处理字符而不是字节。常用方法:        ——read():读取一个字符并返回它的 Unicode 值。如果已到文件末尾,返回 -1。Reader reader = new FileReader("file.txt");int charData = reader.read();while (charData != -1) {    System.out.print((char) charData); // 转换为字符并输出    charData = reader.read();    // 继续读取下一个字符}reader.close();AI生成项目java运行        代码解释:这个例子将逐字符读取文件内容,直到遇到文件的末尾。        ——read(char[] cbuf):一次性读取多个字符并将它们存入字符数组 cbuf 中,返回实际读取的字符数。char[] buffer = new char[1024];int charsRead = reader.read(buffer);System.out.println("读取了 " + charsRead + " 个字符");reader.close();AI生成项目java运行        代码解释:我们使用一个char数组来接收读取到的数据,并且read方法返回了读取到的个数        ——read(char[] cbuf, int off, int len):从字符数组 cbuf 的 off 偏移量开始,最多读取 len 个字符。char[] buffer = new char[1024];int charsRead = reader.read(buffer, 0, 100); // 从数组开头读取 100 个字符reader.close();AI生成项目java运行        代码解释:我们使用了char数组来接收读取的数据,只读取了前100个字节至此,我们就了解了Reader类的常用方法了!!!        (4)Writer 类        与 Reader 类相对应,Writer 类用于将字符数据写入目标输出流,它专门用于处理字符数据,避免了字节流处理文本时的编码问题。常用方法:        ——write(int c):将一个字符的 Unicode 值写入到输出流。Writer writer = new FileWriter("output.txt");writer.write(65); // 写入字符 'A'writer.close();AI生成项目java运行        代码解释:这段代码将字符 'A' 写入到文件中。        ——write(char[] cbuf):将字符数组中的数据一次性写入输出流。char[] data = "Hello".toCharArray();writer.write(data);writer.close();AI生成项目java运行       代码解释:这里我们将字符串 "Hello" 转换为字符数组,然后写入文件        ——write(char[] cbuf, int off, int len):从字符数组 cbuf 的 off 偏移量开始,最多写入 len 个字符。char[] data = "HelloWorld".toCharArray();writer.write(data, 0, 5); // 写入 "Hello"writer.close();AI生成项目java运行         代码解释: 这段代码将 "HelloWorld" 字符串中的前 5 个字符写入文件        ——flush():与字节流类似,Writer 类也有 flush() 方法,可以将缓冲区中的数据强制写入文件。writer.flush();AI生成项目java运行以上就是Writer类的常见方法了————————————————原文链接:https://blog.csdn.net/2302_80198073/article/details/144897687
  • [技术干货] Java中WSDL解析的完整实践指南
    简介:解析WSDL文件是开发基于Web服务的应用程序中的关键任务。本文将详细介绍如何使用Java以及相关库如Apache CXF和Axis2来解析WSDL文档,并实现SOAP请求与响应处理。文章首先会指导如何引入必要的库,然后深入解析WSDL文件获取服务接口、操作和消息结构。接着,通过示例代码展示如何创建服务代理,发送SOAP请求,并接收与解析SOAP响应。最后,本文也会解释如何处理主流Web服务框架生成的WSDL,并提供详细的示例代码。掌握这些知识能够帮助开发者在Java环境中构建健壮的Web服务客户端。1. WSDL文件解析的必要性WSDL(Web Services Description Language)文件是Web服务的标准描述语言,它定义了服务接口和绑定方式,使得开发者能够理解和使用远程服务。随着SOA(Service Oriented Architecture)的普及,WSDL文件的解析成为了软件开发中不可或缺的一环。1.1 WSDL文件的作用WSDL文件在Web服务交互中扮演着至关重要的角色。它不仅定义了服务的网络地址(即endpoint),还详细描述了服务可以执行的操作,包括每个操作的输入输出消息格式。服务提供者通过WSDL文件对外公开其服务的接口,服务消费者则通过解析WSDL文件来了解如何与服务进行交互,这样双方就能够基于共同的理解进行通信。1.2 解析WSDL文件的必要性解析WSDL文件的必要性体现在以下几个方面:接口透明性 :通过解析WSDL文件,开发者无需访问服务的源代码,即可了解服务的功能和如何调用,这大大提高了系统的封装性和模块化。服务的自动化 :开发工具可以自动读取WSDL文件,生成客户端代理代码,从而加快开发进程,并减少因手动编码导致的错误。跨平台交互 :WSDL基于XML,它是一种可跨平台、跨语言的描述语言,有助于实现不同系统之间的互操作性。灵活性和可扩展性 :WSDL文件可以描述复杂的网络服务协议和结构,使得服务可以灵活地添加新的功能而不影响现有的客户端实现。总之,WSDL文件是Web服务沟通的桥梁,而解析WSDL文件是理解和实现这一沟通的基础。接下来的章节将详细介绍如何使用Apache CXF和Axis2等工具来解析WSDL文件,并进一步探讨WSDL的结构及其在实际开发中的应用。2. 使用Apache CXF解析WSDLApache CXF是一个功能强大的开源服务框架,它提供了全面的Web服务支持,包括对WSDL的解析。在本章节中,我们将深入了解Apache CXF框架如何帮助我们解析WSDL文件,并在实践中应用它。我们不仅会覆盖基本的使用方法,还会讨论在解析过程中可能遇到的挑战,并提供解决方案。2.1 Apache CXF概述2.1.1 CXF框架的架构和特点Apache CXF(从前称为 Celtix 和 XFire)是一个全功能的开源服务框架,它使得Web服务的创建和消费变得简单。CXF的核心特点包括其服务总线架构,支持多种传输协议和数据格式,以及对多种行业标准的支持,特别是SOAP和WSDL。CXF的关键组件包括:Frontend API :用于配置和启动Web服务。Backend API :用于处理服务调用的底层细节。DataBinding :处理XML和Java对象之间的映射。JBI Container :提供对Java Business Integration的支持。CXF的架构被设计为模块化和可扩展,允许开发者根据需要轻松地添加或替换组件。2.1.2 CXF与WSDL的关系及其作用WSDL文件对于Web服务的描述至关重要,Apache CXF提供了读取和解析WSDL文件的能力。CXF利用WSDL文件来识别服务操作、消息格式、传输协议等关键信息,进而在客户端和服务端之间建立通信机制。CXF支持WSDL的最新版本,它能够:生成WSDL文件,基于Java接口和服务实现。读取WSDL文件,用于在不直接访问代码的情况下了解服务的结构和功能。提供WSDL的动态解析功能,用于在运行时动态创建代理和客户端。2.2 CXF解析WSDL的实践2.2.1 解析WSDL的代码实现接下来,让我们通过一个实际的代码例子来展示如何使用Apache CXF来解析一个WSDL文件。假设我们有一个名为 example.wsdl 的WSDL文件,我们希望了解如何获取这个WSDL文件中的服务定义和操作。首先,你需要在你的项目中包含Apache CXF的依赖库。如果你使用Maven,可以添加以下依赖到你的 pom.xml 文件中:<dependency>    <groupId>org.apache.cxf</groupId>    <artifactId>cxf-rt-frontend-jaxws</artifactId>    <version>3.4.4</version></dependency><dependency>    <groupId>org.apache.cxf</groupId>    <artifactId>cxf-rt-transports-http</artifactId>    <version>3.4.4</version></dependency>AI生成项目xml然后,你可以使用以下Java代码来解析WSDL文件:import org.apache.cxf.helpers.IOUtils;import org.apache.cxf.wsdl.WSDLManager;import org.apache.cxf.wsdl.model.WSDLModel;import org.apache.cxf.wsdl.model.WSDLModelFactory;import org.apache.cxf.wsdl.model.WSDLPart; import java.io.StringReader;import java.util.Map; public class WSDLParserExample {    public static void main(String[] args) {        try {            // 加载WSDL文件            String wsdlContent = IOUtils.toString(                WSDLParserExample.class.getResourceAsStream("/example.wsdl"), "UTF-8");            WSDLModelFactory factory = WSDLManager.getInstance().getModelFactory();            WSDLModel model = factory.newWSDLModel(new StringReader(wsdlContent));             // 获取服务和端口类型            Map<String, Object> services = model.getServiceMap();            System.out.println("Service(s):");            services.keySet().forEach(System.out::println);             Map<String, Object> portTypes = model.getPortTypeMap();            System.out.println("\nPortType(s):");            portTypes.keySet().forEach(System.out::println);             // 打印所有消息            model.getMessagingMap().keySet().forEach(System.out::println);        } catch (Exception e) {            e.printStackTrace();        }    }}AI生成项目java运行2.2.2 分析WSDL结构和生成的服务接口在上述代码中,我们首先通过 IOUtils.toString() 方法读取了WSDL文件的内容,并创建了一个 WSDLModel 实例。这个实例代表了整个WSDL文档的结构和内容。通过调用 model.getServiceMap() 和 model.getPortTypeMap() ,我们可以检索到定义在WSDL中的所有服务和端口类型。此外, model.getMessagingMap().keySet() 帮助我们获取了WSDL中定义的所有消息。解析WSDL文件后,我们可以进一步生成服务接口。Apache CXF提供了一个代码生成工具,可以基于WSDL文件自动生成Java接口和实现类。这允许开发者快速开始使用Web服务,而无需手动编写大量的绑定和代理代码。解析和代码生成之后,下一步是创建服务代理类实例,用于调用远程Web服务。这个过程将在下一章详细介绍。3. 使用Axis2解析WSDLAxis2是一个功能强大且灵活的Web服务平台,用于创建、部署和管理Web服务。与旧版本的Axis相比,Axis2对WSDL解析和Web服务的实现进行了优化,提高了性能和可用性。深入了解Axis2框架如何解析WSDL文件,并通过API与WSDL元素进行交互,将有助于开发者更好地利用这一工具。3.1 Axis2框架解析WSDL机制3.1.1 Axis2的架构和工作原理Axis2架构建立在模块化的设计理念之上,可以灵活地添加或移除组件来扩展功能。其核心组件包括:Axis2 Engine :处理消息的接收与发送,并协调整个请求处理流程。ServiceRepository :存储Web服务的信息,包括WSDL文件解析结果。Transport Sender/Receiver :负责与底层传输层通信,如HTTP、SMTP等。在解析WSDL文件时,Axis2首先加载WSDL文档,并将其内容解析为内部数据结构。这些数据结构随后可用于生成服务端代码或客户端代理。3.1.2 Axis2对WSDL文件的处理流程Axis2处理WSDL文件的过程可以分为以下几个步骤:WSDL文件加载 :Axis2读取WSDL文件内容。WSDL解析 :利用其提供的解析器将WSDL文档解析为DOM结构。服务和绑定生成 :从解析的DOM结构中提取服务描述,包括端点、消息和绑定。服务类生成 :Axis2可使用内置的代码生成器根据WSDL描述生成服务类的Java代码。3.2 Axis2解析WSDL的实战演练3.2.1 配置和初始化Axis2环境在开始解析WSDL之前,需要先配置好Axis2环境。这通常包括添加Axis2的核心库到项目的构建路径中,配置XML解析器和相关的依赖库。<!-- pom.xml --><dependency>    <groupId>org.apache.axis2</groupId>    <artifactId>axis2</artifactId>    <version>1.8.2</version></dependency>AI生成项目xml接下来,初始化一个Axis2服务的 Service 对象:import org.apache.axis2.context.ServiceContext;import org.apache.axis2.service.Service; // ... ServiceContext serviceContext = ServiceContext.createContext();Service service = new Service(serviceContext);AI生成项目java运行3.2.2 Axis2解析WSDL实例代码解析假设我们有一个WSDL文件 MyService.wsdl ,以下是如何使用Axis2来解析这个WSDL文件并获取其服务和绑定信息的示例:import org.apache.axis2.description.AxisService;import org.apache.axis2.description.AxisEndpoint;import org.apache.axis2.description.WSDL2JavaAxisOperation;import org.apache.axis2.description.WSDL2JavaAxisMessage;import org.apache.axis2.transport.http.HTTPConstants;import org.apache.axis2.util.MatrixUtil;import org.apache.axiom.om.OMElement;import org.apache.axiom.om.OMAbstractDocument;import org.apache.axiom.om.util.AXIOMUtil;import org.apache.axiom.soap.SOAPFactory;import org.apache.axiom.soap.SOAPFactoryImpl;import org.apache.axiom.soap.SOAPFault;import org.apache.axiom.soap.SOAPHeader;import org.apache.axiom.soap.SOAPEnvelope;import org.apache.axiom.soap.SOAPBody;import org.w3c.dom.Document; // 加载WSDL文档Document wsdlDoc = AXIOMUtil.stringToOM("wsdl_file_path").getDocument(); // 创建WSDL2JavaAxisOperation对象WSDL2JavaAxisOperation operation = new WSDL2JavaAxisOperation();operation.setWSDLDocument((OMAbstractDocument) wsdlDoc);operation.setServiceName("MyService");operation.setPortName("MyPort");operation.setOperationName("myOperation");operation.setAxis2ServiceDescription((AxisService) service.getDescription()); // 获取操作的输入消息OMElement inputMessage = operation.getInputMessage();// 获取操作的输出消息OMElement outputMessage = operation.getOutputMessage(); // 解析输入消息WSDL2JavaAxisMessage inputMessageParser = new WSDL2JavaAxisMessage(inputMessage, operation);inputMessageParser.setAxis2ServiceDescription((AxisService) service.getDescription());inputMessageParser.setAxis2OperationDescription((WSDL2JavaAxisOperation) operation);inputMessageParser.parse(); // 解析输出消息WSDL2JavaAxisMessage outputMessageParser = new WSDL2JavaAxisMessage(outputMessage, operation);outputMessageParser.setAxis2ServiceDescription((AxisService) service.getDescription());outputMessageParser.setAxis2OperationDescription((WSDL2JavaAxisOperation) operation);outputMessageParser.parse(); // 获取服务端点AxisEndpoint endpoint = service.getAxis2Endpoint(operation); // 设置消息格式SOAPFactory factory = new SOAPFactoryImpl();SOAPEnvelope env = factory.getDefaultEnvelope();SOAPHeader header = env.getHeader();SOAPBody body = env.getBody();AI生成项目java运行在上述代码中,我们首先通过 AXIOMUtil.stringToOM 方法将WSDL文件内容转换为AXIOM的 OMDocument 对象。之后,我们创建了一个 WSDL2JavaAxisOperation 对象来解析WSDL文档中特定操作的详细信息。通过调用 parse 方法,我们可以获取到输入和输出消息的相关信息,并将其解析为可用的Java对象。最后,通过 AxisEndpoint 对象,我们可以得到对应操作的端点信息,为后续的服务调用打下基础。需要注意的是,解析WSDL文件只是与Web服务交互的起点。在实际应用中,还需要将解析得到的端点信息和消息格式应用到服务调用中,执行实际的服务请求和响应处理。4. WSDL文件结构解析4.1 WSDL文件结构概览4.1.1 WSDL的根元素和定义域WSDL(Web Services Description Language)文件是一种基于XML的语言,用于描述网络服务。它是Web服务通信协议的基础,用于定义如何与特定的服务进行交互。WSDL文件的根元素是 <definitions> ,它通常包含命名空间声明、消息定义、端口类型、绑定和服务描述等关键部分。<definitions name="HelloWorldService"             targetNamespace="http://example.com/wsdl/"             xmlns="http://schemas.xmlsoap.org/wsdl/"             xmlns:ns="http://example.com/wsdl/"             xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/">    <!-- 其他定义 --></definitions>AI生成项目xml在上面的示例中, <definitions> 元素定义了服务的名称 name ,目标命名空间 targetNamespace ,以及必须的命名空间 xmlns 。命名空间将用于区分WSDL文件中的不同元素和类型定义。4.1.2 WSDL中的服务接口和服务实现WSDL文件描述了Web服务的两个主要方面:服务接口和其服务实现。服务接口定义了客户如何与服务进行通信,包括所支持的操作以及消息的格式。服务实现则详细说明了接口的具体部署位置,通常是通过绑定到网络地址(URL)来实现。服务接口部分通常包含如下内容:<message> :定义了操作的输入和输出消息。<portType> :将操作组合成接口,并定义了服务可以执行的操作。<binding> :描述了如何将 portType 绑定到具体的通信协议。<service> :为 portType 的集合提供了一个网络可寻址的端点。服务实现则通过 <port> 元素,结合 <binding> 和一个网络地址,指明了具体的服务地址。4.2 WSDL核心元素详解4.2.1 <portType> 的作用和属性<portType> 是WSDL文件的核心元素之一,它定义了Web服务可以执行的操作集,每个操作代表一个服务可以执行的方法。<portType name="HelloWorldPortType">    <operation name="sayHello">        <input message="ns:sayHelloRequest"/>        <output message="ns:sayHelloResponse"/>    </operation></portType>AI生成项目xml在上述代码中, <portType> 定义了一个名为 HelloWorldPortType 的端口类型,其中包含一个名为 sayHello 的操作。操作 sayHello 通过 <input> 和 <output> 标签定义了输入和输出消息。4.2.2 <binding> 的作用和绑定细节<binding> 元素用于将 portType 与特定的通信协议绑定,并规定了消息的编码和传输细节。例如,将 portType 绑定到SOAP协议,可以指定使用HTTP传输。<binding name="HelloWorldBinding" type="ns:HelloWorldPortType">    <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>    <operation name="sayHello">        <soap:operation soapAction="urn:sayHello"/>        <input>            <soap:body use="literal"/>        </input>        <output>            <soap:body use="literal"/>        </output>    </operation></binding>AI生成项目xml在 <binding> 中, <soap:binding> 标签指定了绑定的风格(如 document )和传输协议(如HTTP)。每个操作被 <soap:operation> 详细规定, <soap:body> 标签说明了消息体是如何被编码和封装的。4.2.3 <message> 的作用和消息结构<message> 元素描述了操作的消息结构,包括输入和输出消息。消息被定义为一系列参数,每个参数在WSDL中是一个 <part> 元素。<types>    <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">        <!-- 定义消息的数据类型 -->    </xs:schema></types>$message name="sayHelloRequest">    <part name="name" type="xs:string"/></message>$message name="sayHelloResponse">    <part name="greeting" type="xs:string"/></message>AI生成项目xml在上述代码中,我们定义了两种消息类型: sayHelloRequest 和 sayHelloResponse 。每种消息类型由一个或多个 <part> 组成,每个 <part> 定义了一个消息片段的名称和类型。 <types> 部分包含了XML模式定义,用于说明这些 <part> 的数据类型。通过深入解析WSDL文件结构,开发者可以更好地理解Web服务的通信协议和交互方式。这不仅有助于服务的实现与集成,还能在解决服务交互问题时提供重要的参考依据。在下一章节中,我们将探讨使用Axis2框架解析WSDL文件的机制和实战演练。5. 创建服务代理类实例5.1 服务代理类的作用和实现服务代理类的概念和功能服务代理类是一种设计模式,它允许客户端通过一个间接层与远程服务进行交互,而不需要直接处理网络通信的复杂性。代理类负责创建实际的Web服务客户端,并封装了网络通信和数据处理的所有细节。在企业级应用中,服务代理类提供了以下主要功能:封装网络通信细节 :隐藏了与远程服务通信的细节,包括请求的构造、SOAP消息的封装、网络连接的建立以及响应的接收。提高代码可维护性 :由于所有通信逻辑被封装在代理类中,因此在服务接口或协议发生变化时,只需修改代理类,而不必修改客户端的业务逻辑代码。提供服务接口抽象 :代理类可以实现服务接口,使得客户端可以像调用本地方法一样调用远程服务,降低业务逻辑与远程通信的耦合。异常处理和日志记录 :服务代理类可以负责处理服务调用过程中可能出现的异常,并记录相关的交互日志,方便调试和问题追踪。使用代码创建服务代理类实例以下是如何使用Java语言和JAX-WS API创建服务代理类实例的一个示例:import javax.xml.namespace.QName;import javax.xml.ws.Service;import java.net.URL; public class ServiceProxyExample {    public static void main(String[] args) {        try {            // WSDL文档的URL            String wsdlURL = "http://example.com/service?wsdl";            // 定义命名空间和端口名称            QName serviceQName = new QName("http://example.com/", "ServiceName");            QName portQName = new QName("http://example.com/", "PortName");            // 使用JAX-WS API加载服务定义并创建服务对象            Service service = Service.create(new URL(wsdlURL), serviceQName);            // 获取绑定接口            YourServiceInterface servicePort = service.getPort(portQName, YourServiceInterface.class);            // 使用servicePort调用Web服务方法            ResultType response = servicePort.yourWebServiceMethod(parameterValue);            // 输出响应结果            System.out.println("Service response: " + response);        } catch (Exception e) {            e.printStackTrace();        }    }}AI生成项目java运行在上述代码中,我们首先通过指定的WSDL文档URL创建了一个服务对象 Service 。然后,我们使用服务对象的 getPort 方法来获取绑定到具体服务操作的代理对象 YourServiceInterface 。需要注意的是, YourServiceInterface 应该是由WSDL文件自动生成的Java接口,代表了远程服务端点。接下来,我们就可以像调用本地方法一样使用 servicePort 对象调用远程服务的方法了。5.2 服务代理类的配置与调用配置服务代理类的参数服务代理类的配置通常涉及设置网络连接参数、安全凭据以及其他与服务交互相关的属性。这些配置可以在创建服务代理实例时通过不同的方法进行,具体取决于使用的框架和环境。以下是一些常见的配置参数示例:// 配置代理地址System.setProperty("http.proxyHost", "proxy.example.com");System.setProperty("http.proxyPort", "8080"); // 配置用户名和密码String username = "user";String password = "pass";service Credential usernamePasswordCredential = new UsernamePasswordCredential(username, password);service.getRequestContext().put("security.username", username);service.getRequestContext().put("security.password", password);AI生成项目java运行实现服务调用和异常处理当调用远程Web服务时,通常需要处理多种异常情况,比如网络错误、服务不可用或者数据格式不匹配等问题。使用服务代理类时,可以利用Java异常处理机制来捕获和处理这些异常。以下是如何实现服务调用和异常处理的示例:try {    // 假设ResultType是远程服务方法返回的类型    ResultType result = servicePort.yourWebServiceMethod(parameterValue);    // 处理成功响应的逻辑    System.out.println("Success: " + result);} catch (WebServiceException e) {    // 处理Web服务异常    System.err.println("WebServiceException: " + e.getMessage());} catch (ProcessingException e) {    // 处理消息处理异常    System.err.println("ProcessingException: " + e.getMessage());} catch (RemoteException e) {    // 处理远程调用异常    System.err.println("RemoteException: " + e.getMessage());} catch (Exception e) {    // 处理其他通用异常    System.err.println("Exception: " + e.getMessage());}AI生成项目java运行在这个示例中,我们使用了 try-catch 语句来捕获和处理 WebServiceException 、 ProcessingException 和 RemoteException 等异常。这些异常通常由服务框架抛出,用于表示特定的远程服务调用问题。我们还添加了一个通用的 Exception 捕获,用于处理不属于前面特定类型的其他异常情况。通过上述步骤,我们可以创建并使用服务代理类实例,调用远程Web服务并处理可能出现的异常情况。下一章将详细讲解如何发送SOAP请求到远程服务器,并在本章的基础上进一步加深对Web服务交互过程的理解。6. 发送SOAP请求的方法SOAP(Simple Object Access Protocol)是一种基于XML的协议,用于在网络上进行分布式计算。在Web服务领域,SOAP是实现应用程序之间通信的一种常用方式。本章将深入探讨如何构造SOAP请求消息,并通过编程方式发送这些请求到远程Web服务端点,并处理响应。6.1 SOAP消息的构成和结构SOAP消息主要由三个部分组成:信封(Envelope),头部(Header)和正文(Body)。它们各自承载了不同的信息和功能,定义了整个消息的结构和语义。6.1.1 SOAP消息的格式和组成部分SOAP消息的信封部分是所有SOAP消息必须包含的元素,它定义了消息的开始和结束,以及消息的其他组成部分。一个典型的SOAP信封元素结构如下:<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"                  xmlns:xsd="http://www.w3.org/2001/XMLSchema"                  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">    <soapenv:Header>        <!--SOAP头部信息-->    </soapenv:Header>    <soapenv:Body>        <!--SOAP正文信息-->    </soapenv:Body></soapenv:Envelope>AI生成项目xml头部(Header)部分是可选的,用于传递应用特定的信息,如安全凭证或事务信息。而正文(Body)部分是必须的,它包含了实际的消息内容,通常是一个或多个XML元素。6.1.2SOAP消息中的头部和正文解析在头部中,可以定义多个子元素,它们都是 <soapenv:Header> 的子节点,这些子节点定义了头部信息的细节。例如:<soapenv:Header>    <ns2:Security xmlns:ns2="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" SOAP-ENV:mustUnderstand="1">        <!--安全信息-->    </ns2:Security></soapenv:Header>AI生成项目xml正文部分通常包含的是实际的调用信息,比如Web服务操作的名称和参数。例如:<soapenv:Body>    <ns2:GetWeather xmlns:ns2="http://example.com/weather">        <!--请求的参数-->    </ns2:GetWeather></soapenv:Body>AI生成项目xml6.2 发送SOAP请求的实践操作实际发送SOAP请求涉及到客户端和服务端的交互。客户端需要构造一个符合WSDL定义的SOAP消息,并通过HTTP协议发送到服务端。服务端处理完毕后,将响应消息以同样方式返回给客户端。6.2.1 配置和生成SOAP请求为了发送SOAP请求,可以使用各种编程语言提供的库和API。以下是一个使用Java语言配置和生成SOAP请求的例子:import javax.xml.soap.MessageFactory;import javax.xml.soap.SOAPConnection;import javax.xml.soap.SOAPConnectionFactory;import javax.xml.soap.SOAPMessage;import javax.xml.soap.SOAPPart; public class SoapClient {    public static void main(String[] args) {        try {            MessageFactory messageFactory = MessageFactory.newInstance();            SOAPMessage message = messageFactory.createMessage();            SOAPPart soapPart = message.getSOAPPart();            String serverURI = "http://example.com/weather?wsdl";            SOAPEnvelope envelope = soapPart.getEnvelope();            SOAPBody soapBody = envelope.getBody();            String tns = "http://example.com/weather";            SOAPElement requestElement = soapBody.addChildElement("GetWeather", "ns2", tns);            // 创建请求参数...            SOAPConnection connection = SOAPConnectionFactory.newInstance().createConnection();            message.saveChanges();            URL endpoint = new URL(serverURI);            SOAPMessage response = connection.call(message, endpoint);            // 输出响应内容...        } catch (Exception e) {            e.printStackTrace();        }    }}AI生成项目java运行在这个例子中,我们首先创建了一个SOAP消息,然后为该消息添加了一个请求体,并通过SOAP连接发送到服务器。6.2.2 发送请求并处理响应发送请求之后,需要处理从服务器返回的响应。这通常涉及解析响应消息,并从中提取所需的信息。以下是处理响应的代码逻辑:// 继续上面的例子System.out.println("Status code: " + response.getResponseCode());System.out.println("Response: " + response.getContentDescription());InputStream is = response.getContentDescription();// 使用SAX或者DOM解析响应消息...AI生成项目java运行上述代码将响应消息的内容输出到控制台,并展示了获取响应状态码的方式。实际应用中,更常用的是使用XML解析器来解析SOAP响应,如DOM或SAX解析器。在实际的工作中,使用服务代理类实例发送SOAP请求会更加方便,因为它抽象了SOAP消息的构造过程和底层通信细节,使得开发人员可以更加专注于业务逻辑的实现。通过这一章的学习,您应该已经了解了SOAP请求的基本构成,以及如何在实际项目中构造和发送SOAP请求。在下一章,我们将深入解析SOAP响应消息的结构和技术。7. 解析SOAP响应的技术SOAP响应消息是Web服务交互过程中的重要部分,通常包含服务器的响应数据以及可能发生的任何错误信息。解析这些响应消息是确保应用正确处理服务器返回信息的关键步骤。本章将深入探讨SOAP响应消息的结构分析以及使用不同技术提取数据的方法。7.1 SOAP响应消息的结构分析SOAP响应消息具有一定的结构,正确理解和分析这些结构是提取有用信息的基础。7.1.1 解析SOAP响应中的数据SOAP响应通常包含一个Envelope元素,其下有两个子元素:Header和Body。Header通常用于包含消息处理的元数据,而Body包含了实际的响应信息。<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">  <soap:Header/>  <soap:Body>    <ns2:getCountryResponse xmlns:ns2="http://example.com/">      <return>USA</return>    </ns2:getCountryResponse>  </soap:Body></soap:Envelope>AI生成项目xml在上述XML片段中, <return> 元素包含了服务器返回的实际数据。解析这部分数据,我们需要关注Body内的内容。7.1.2 处理SOAP响应中的错误信息在SOAP响应中,错误信息通常被封装在 <Fault> 元素内。开发者需要检测此元素的存在,并解析其子元素如 <faultcode> , <faultstring> 等来获取错误详情。<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">  <soap:Body>    <soap:Fault>      <faultcode>soap:Server</faultcode>      <faultstring>Server was unable to read request</faultstring>    </soap:Fault>  </soap:Body></soap:Envelope>AI生成项目xml在这个例子中,服务器表明它无法读取请求,开发者可以根据这个信息进行进一步的调试。7.2 提取SOAP响应数据的技术解析SOAP响应数据常用的技术有DOM和SAX。这两种技术各有特点,适用于不同的场景。7.2.1 使用DOM解析技术提取数据DOM(Document Object Model)是解析和操作XML的一种方式,它将XML文档转换为树形结构,并允许开发者以编程方式访问和修改文档内容。DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();DocumentBuilder builder = factory.newDocumentBuilder();Document doc = builder.parse(new InputSource(new StringReader(soapResponse)));NodeList nodes = doc.getElementsByTagName("return");for (int i = 0; i < nodes.getLength(); i++) {    Node node = nodes.item(i);    System.out.println("Value: " + node.getTextContent());}AI生成项目java运行上述Java代码使用DOM解析SOAP响应中的数据。7.2.2 使用SAX解析技术提取数据SAX(Simple API for XML)是一种基于事件的XML解析技术,适用于处理大型文件,因为它不需要将整个文档加载到内存中。class MyHandler extends DefaultHandler {    @Override    public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {        if ("return".equals(qName)) {            System.out.println("Value: ");        }    }    @Override    public void characters(char[] ch, int start, int length) throws SAXException {        System.out.print(new String(ch, start, length));    }} XMLReader reader = XMLReaderFactory.createXMLReader();MyHandler handler = new MyHandler();reader.setContentHandler(handler);reader.parse(new InputSource(new StringReader(soapResponse)));AI生成项目java运行在上述Java代码中,我们定义了一个自定义的处理器来响应SAX事件,并输出所需的数据。SAX的解析速度快,内存使用效率高,但在处理复杂的XML文档结构时,可能需要更多的代码来跟踪当前状态和上下文。本章介绍了如何分析SOAP响应消息的结构,并详细讲解了使用DOM和SAX两种技术从SOAP响应中提取数据的方法。理解这两种技术的区别和适用场景是确保正确解析响应并有效利用数据的关键。通过结合实际的例子和代码演示,本章旨在帮助IT从业者更好地理解SOAP响应解析的核心技术,从而为后续的服务交互奠定坚实的基础。本文还有配套的精品资源,点击获取 简介:解析WSDL文件是开发基于Web服务的应用程序中的关键任务。本文将详细介绍如何使用Java以及相关库如Apache CXF和Axis2来解析WSDL文档,并实现SOAP请求与响应处理。文章首先会指导如何引入必要的库,然后深入解析WSDL文件获取服务接口、操作和消息结构。接着,通过示例代码展示如何创建服务代理,发送SOAP请求,并接收与解析SOAP响应。最后,本文也会解释如何处理主流Web服务框架生成的WSDL,并提供详细的示例代码。掌握这些知识能够帮助开发者在Java环境中构建健壮的Web服务客户端。————————————————原文链接:https://blog.csdn.net/weixin_42594427/article/details/149395353
  • [技术干货] 基于Java的不固定长度字符集在指定宽度和自适应模型下图片绘制生成实战
    前言        在当今数字化与信息化飞速发展的时代,图像的生成与处理技术正日益成为众多领域关注的焦点。从创意设计到数据可视化,从游戏开发到人工智能辅助创作,高效、精准且具有高度适应性的图像生成方案有着广泛而迫切的需求。Java 作为一种强大、稳定且广泛应用的编程语言,在图像绘制领域也发挥着不可忽视的作用。         在GIS领域,比如图例的生成就会面对以上的问题。由于在进行字符标注时无法预测文本的长度,因此我们需要能有一种自适应文本长度的生成方法,但是同时,也有可能我们需要指定一种宽度从而对字符文本进行绘制的需要。如下两图所示:  自适应宽度生成示意图指定宽度生成示意图          本实战旨在深入探讨基于 Java 的不固定长度字符集在指定宽度和自适应模型下图片绘制生成的方法与技巧。不固定长度字符集为图片绘制带来了独特的挑战与机遇。一方面,其灵活多变的字符组合方式能够创造出丰富多样、极具个性化的图像效果,为创意表达提供了广阔空间;另一方面,如何在保证图像整体协调性与美观性的前提下,合理安排不同长度字符在指定宽度内的布局,实现自适应模型下的高效绘制,需要深入研究与实践。         通过本次实战,我们期望为读者提供一套完整、实用且具有创新性的基于 Java 的图片绘制解决方案,帮助读者提升在图像生成领域的技术能力,激发他们在数字创作方面的灵感与潜力,从而在各自的应用场景中创造出更具价值与吸引力的图像作品,为推动图像技术的发展与应用贡献一份力量。 一、需求介绍        在面向地理空间的图例生成过程,我们通常会遇到以下两种情况:第一种是需要指定宽度,比如要求在宽度为200px的图片中,将指定的文字在图片中生成。第二种就是需要根据指定列,即一行展示几列,然后自适应的生成固定宽度的图片。本节将主要介绍这两个需求。这里我们需要展示的是一些不一定长的字符串集合,模拟展示以下这些地名数据,如下所示: String[] demoTexts = {  " 项目管理", "软件开发", "数据分析","人工智能", "云计算", "网络安全",  "用户体验", "测试验证", "运维部署", "昆明市","曲靖市","玉溪市",  "保山市","昭通市","丽江市","普洱市","临沧市","楚雄彝族自治州",  "红河哈尼族彝族自治州","文山壮族苗族自治州","西双版纳傣族自治州",  "湘西土家族苗族自治州","深圳市","保亭黎族苗族自治县",  "阿坝藏族羌族自治州","黔西南布依族苗族自治州","克孜勒苏柯尔克孜自治州",  "双江拉祜族佤族布朗族傣族自治县","积石山保安族东乡族撒拉族自治县","中国石油集团东方地球物理勘探有限责任公司霸州基地管理处居委会",  "天津市蓟州区京津州河科技产业园管理委员会虚拟社区","窑街煤电集团民勤县瑞霖生态农林有限责任公司生活区","沈阳市于洪区红旗土地股份合作经营有限公司生活区",  "大理白族自治州","德宏傣族景颇族自治州","怒江傈僳族自治州","迪庆藏族自治州"};AI生成项目java运行 1、指定宽度生成        指定宽度生成,即我们对目标成果的图片宽度是有要求的,比如宽度指定为200px。核心需求如下:         固定总宽度模式 平均分配列宽:根据总宽度和列数计算每列可用宽度 自动换行:根据列数自动计算行数 文本截断:超长文本添加省略号 2、指定列自适应生成        自适应列宽模式 动态计算列宽:根据每列中最长的条目确定列宽,遍历所有文本,计算每个条目(矩形+间距+文本)的总宽度,确定最大宽度作为图像宽度。 计算高度:基于行数和字体高度计算总高度 自动换行:根据列数自动计算行数 保持完整显示:不截断文本 二、Java生成实现        本小节将根据上面的生成需求来具体讲解如何进行详细的生成。java生成的实现分成三个部分,第一部分是介绍两个公共方法,第二部分介绍如何按照指定宽度生成,第三部分介绍如何进行自适应生成,通过代码实例的方法进行讲解。 1、公共方法        为了方便对对绘制的文字展示得更加美观,这里我们每进行一次绘制就修改画笔的颜色。因此需要一个生成指定颜色的方法,在java中生成Color对象,并且转为十六进制的颜色表示,核心方法如下: /*** - 将color转十六进制字符串* @param color* @return*/public static String Color2String(Color color) {// 获取 RGB 颜色值,格式为 0x00RRGGBB    int rgb = color.getRGB();    // 将 RGB 转换为十六进制字符串,去掉前两位的透明度部分(如果是纯不透明颜色)    String hexColor = "#" + Integer.toHexString(rgb & 0xffffff);    return hexColor;}AI生成项目java运行         根据不同字符串生成均匀分布的颜色方法如下: // 生成可区分颜色(HSV色环均匀分布)private static Color[] generateDistinctColors(int count) {    Color[] colors = new Color[count];    float goldenRatio = 0.618033988749895f;  // 黄金分割比例    float saturation = 0.8f;  // 饱和度    float brightness = 0.9f;  // 亮度    for (int i = 0; i < count; i++) {        float hue = (i * goldenRatio) % 1.0f;        colors[i] = Color.getHSBColor(hue, saturation, brightness);    }    return colors;}AI生成项目java运行          以上两个方法在指定宽度生成和自适应生成中均会使用到,因此在此简单列出来。 2、指定宽度生成按指定宽度生成的核心方法如下: // 固定总宽度模式public static BufferedImage createFixedColumnsImage(String[] texts, int columns,                                                       int totalWidth, Font font,                                                       int padding, int columnSpacing,                                                       int rowSpacing) {    BufferedImage tempImg = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);    Graphics2D tempG = tempImg.createGraphics();    tempG.setFont(font);    FontMetrics fm = tempG.getFontMetrics();            final int RECT_SIZE = 10;    final int ENTRY_SPACING = 5;            // 生成颜色序列    Color[] colors = generateDistinctColors(texts.length);     // 计算列宽    int availableWidth = totalWidth - padding * 2 - (columns - 1) * columnSpacing;    int columnWidth = availableWidth / columns;    int textMaxWidth = columnWidth - RECT_SIZE - ENTRY_SPACING;     // 处理文本    List<String> processedTexts = new ArrayList<>();    for (String text : texts) {        processedTexts.add(truncateText(text, textMaxWidth, fm));    }     // 计算总高度    int rows = (int) Math.ceil((double)texts.length / columns);    int totalHeight = padding * 2 + rows * (fm.getHeight() + rowSpacing) - rowSpacing;     // 创建图像    BufferedImage image = new BufferedImage(totalWidth, totalHeight, BufferedImage.TYPE_INT_ARGB);    Graphics2D g = image.createGraphics();    setupGraphics(g, font);     // 绘制背景    g.setColor(Color.WHITE);    g.fillRect(0, 0, totalWidth, totalHeight);     // 绘制条目    int yBase = padding + fm.getAscent();    int[] columnX = new int[columns];    for (int i = 0; i < columns; i++) {       columnX[i] = padding + i * (columnWidth + columnSpacing);    }     for (int i = 0; i < processedTexts.size(); i++) {       g.setColor(colors[i]);       int col = i % columns;       int row = i / columns;                   int y = yBase + row * (fm.getHeight() + rowSpacing);       int rectY = y - fm.getAscent() + (fm.getHeight() - RECT_SIZE)/2;                   // 绘制矩形       g.fillRect(columnX[col], rectY, RECT_SIZE, RECT_SIZE);                   // 绘制文本       g.drawString(processedTexts.get(i),                 columnX[col] + RECT_SIZE + ENTRY_SPACING,                 y);    }     g.dispose();    tempG.dispose();    return image;}AI生成项目java运行         由于在指定宽度的生成方式中,绘制的图片宽度是固定的,而文字的字数是不固定的。因此在绘制的过程中,需要对超长的文本进行截取,超长的部分将使用省略号来进行展示。对超长文本字符进行截断处理的方法如下: private static String truncateText(String text, int maxWidth, FontMetrics fm) {    if (fm.stringWidth(text) <= maxWidth) return text;    int ellipsisWidth = fm.stringWidth("...");    int availableWidth = maxWidth - ellipsisWidth;    int length = text.length();    while (length > 0 && fm.stringWidth(text.substring(0, length)) > availableWidth) {        length--;    }    return length > 0 ? text.substring(0, length) + "..." : "";}AI生成项目java运行          生成指定宽度的图片调用方法如下: // 生成固定宽度图片(400px宽,2列)BufferedImage fixedImage = createFixedColumnsImage(     demoTexts, 2, 400,     new Font("宋体", Font.PLAIN, 12),     15, 20, 10);ImageIO.write(fixedImage, "PNG", new File("D:/fixed_columns_250420.png"));AI生成项目java运行        生成的成果图片如下:   3、指定列自适应生成        生成指定列的自适应图片生成的核心方法如下: // 自适应列宽模式    public static BufferedImage createAdaptiveColumnsImage(String[] texts, int columns,                                                          Font font, int padding,                                                           int columnSpacing, int rowSpacing) {        BufferedImage tempImg = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);        Graphics2D tempG = tempImg.createGraphics();        tempG.setFont(font);        FontMetrics fm = tempG.getFontMetrics();                final int RECT_SIZE = 10;        final int ENTRY_SPACING = 5; // 图标与文字间距                // 生成颜色序列        Color[] colors = generateDistinctColors(texts.length);        int index = 0;        for (String text : texts) {        texts[index] = Color2String(colors[index]) + " " + text;             //processedTexts.add(truncateText(text, textMaxWidth, fm));        index ++;        }                // 计算列宽        int[] columnWidths = new int[columns];        for (int i = 0; i < texts.length; i++) {            int col = i % columns;            int width = RECT_SIZE + ENTRY_SPACING + fm.stringWidth(texts[i]);            if (width > columnWidths[col]) {                columnWidths[col] = width;            }        }         // 计算总尺寸        int totalWidth = padding * 2;        for (int w : columnWidths) {            totalWidth += w + columnSpacing;        }        totalWidth -= columnSpacing; // 最后一列不加间距         int rows = (int) Math.ceil((double)texts.length / columns);        int totalHeight = padding * 2 + rows * (fm.getHeight() + rowSpacing) - rowSpacing;         // 创建图像        BufferedImage image = new BufferedImage(totalWidth, totalHeight, BufferedImage.TYPE_INT_ARGB);        Graphics2D g = image.createGraphics();        setupGraphics(g, font);         // 绘制背景        g.setColor(Color.WHITE);        g.fillRect(0, 0, totalWidth, totalHeight);         // 绘制条目        int x = padding;        int yBase = padding + fm.getAscent();        int[] columnX = new int[columns];        for (int i = 0; i < columns; i++) {            columnX[i] = x;            x += columnWidths[i] + columnSpacing;        }         g.setColor(Color.RED);        for (int i = 0; i < texts.length; i++) {               g.setColor(colors[i]);                   int col = i % columns;            int row = i / columns;                        int y = yBase + row * (fm.getHeight() + rowSpacing);            int rectY = y - fm.getAscent() + (fm.getHeight() - RECT_SIZE)/2;                        // 绘制矩形            g.fillRect(columnX[col], rectY, RECT_SIZE, RECT_SIZE);                        // 绘制文本            g.drawString(texts[i],                 columnX[col] + RECT_SIZE + ENTRY_SPACING,                 y);        }         g.dispose();        tempG.dispose();        return image;    }AI生成项目java运行         在自适应生成的过程中,最需要处理的逻辑就是动态的计算宽度等值。最终生成的结果图片如下:  三、总结        以上就是本文的主要内容,本实战旨在深入探讨基于 Java 的不固定长度字符集在指定宽度和自适应模型下图片绘制生成的方法与技巧。不固定长度字符集为图片绘制带来了独特的挑战与机遇。一方面,其灵活多变的字符组合方式能够创造出丰富多样、极具个性化的图像效果,为创意表达提供了广阔空间;另一方面,如何在保证图像整体协调性与美观性的前提下,合理安排不同长度字符在指定宽度内的布局,实现自适应模型下的高效绘制,需要深入研究与实践。         通过本次实战,我们期望为读者提供一套完整、实用且具有创新性的基于 Java 的图片绘制解决方案,帮助读者提升在图像生成领域的技术能力,激发他们在数字创作方面的灵感与潜力,从而在各自的应用场景中创造出更具价值与吸引力的图像作品,为推动图像技术的发展与应用贡献一份力量。行文仓促,定有不足之处,欢迎各位朋友在评论区批评指正,不胜感激。———————————————— 原文链接:https://blog.csdn.net/yelangkingwuzuhu/article/details/147401525
  • [统一运维] 园区基线部署有什么要求
    园区基线部署有什么要求,有没有对应材料可以发一下
  • [使用说明] maven构建时希望能参考一下idea的构建
    在idea上构建含有自己依赖从父依赖继承自己依赖的项目是不会报错能正常编译的,希望能参考idea尽量减少开发者处理的错误,这种错误明显不符合常理按正常逻辑重复的依赖应该能自动剔除。
  • [技术干货] java 集合知识 第一篇
    1.概念1.1.集合与数组的区别集合:长度不固定,动态的根据数据添加删除改变长度,并且只能存入引用类型,读取采用迭代器或其他方法数组:长度固定,不可改变,既可以存入基本类型也可以存入引用类型,读取使用索引读(for)长度    存入类型    读取集合    长度不固定,动态的根据数据添加删除改变长度    只能存入引用类型    采用迭代器或其他方法数组    长度固定,不可改变    既可以存入基本类型也可以存入引用类型    使用索引(for)1.2.集合分类分为三类:List类,Set类,Map类List集合:集合里面元素有序,并且允许可重复Set集合:集合里面元素无序,并且不可重复(保证唯一性)Map集合:集合采用键值对方式,key唯一(不允许重复)无序,value没有要求是否有序    是否可重复List    有序    可重复Set    无序    不可重复1.3.Collection和Collections的区别Collection是一个接口,给集合实现的,里面定义了一些操作集合的方法Collections是一个工具类,位于java.util包中,可以直接使用该类操作集合(增删改,排序)1.4.集合遍历的方法有六种方法:for,增强for,迭代器,列表迭代器,foeEach,Stream流for:带索引查询(区分集合是否带索引,才能使用该方法)List<String> list = Arrays.asList("A", "B", "C"); // 通过索引遍历(适合 ArrayList 等支持随机访问的集合)for (int i = 0; i < list.size(); i++) {    System.out.println(list.get(i));}增强for:没有索引,直接遍历查询List<String> list = Arrays.asList("A", "B", "C"); // 直接遍历元素(底层基于迭代器实现)for (String item : list) {    System.out.println(item);}迭代器:在迭代器里面只能删除元素,不能插入元素List<String> list = Arrays.asList("A", "B", "C");Iterator<String> iterator = list.iterator(); // 通过迭代器遍历(适用于所有 Collection)while (iterator.hasNext()) {    String item = iterator.next();    System.out.println(item);    // 可在遍历中安全删除元素:iterator.remove();}List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C"));Iterator<String> iterator = list.iterator(); while (iterator.hasNext()) {    String item = iterator.next();    if ("B".equals(item)) {        iterator.remove(); // 允许删除当前元素        // iterator.add("D"); // 编译错误:Iterator 没有 add() 方法    }}列表迭代器:没有限制,可以进行删除查询插入元素List<String> list = Arrays.asList("A", "B", "C");ListIterator<String> listIterator = list.listIterator(); // 正向遍历(从头到尾)while (listIterator.hasNext()) {    String item = listIterator.next();    System.out.println(item);} // 反向遍历(从尾到头)while (listIterator.hasPrevious()) {    String item = listIterator.previous();    System.out.println(item);}List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C"));ListIterator<String> listIterator = list.listIterator(); // 正向遍历while (listIterator.hasNext()) {    String item = listIterator.next();    if ("B".equals(item)) {        listIterator.remove();  // 删除当前元素        listIterator.add("D");  // 在当前位置插入新元素        listIterator.set("E");  // 替换当前元素(需在 next() 或 previous() 后调用)    }} // 反向遍历while (listIterator.hasPrevious()) {    String item = listIterator.previous();    System.out.println(item);}forEach:因为它基于迭代器实现的,因此也不能在循环中插入元素List<String> list = Arrays.asList("A", "B", "C"); // 使用 Lambda 表达式遍历list.forEach(item -> System.out.println(item)); // 或使用方法引用list.forEach(System.out::println);Stream:没有限制List<String> list = Arrays.asList("A", "B", "C"); // 转换为 Stream 并遍历list.stream().forEach(item -> System.out.println(item)); // 并行流遍历(多线程处理)list.parallelStream().forEach(item -> System.out.println(item));2.List2.1.List的实现实现List的集合有:ArrayList,LinkedList,VectorArrayList:基于动态的数组创建的,查询效率高,增删效率一般,线程不安全LinkedList:基于双向链表创建的,查询效率一般,增删效率高,线程不安全Vector:基于动态数组创建的,与ArrayList类似,不过它是线程安全的数据结构    读操作    写操作    线程安全ArrayList    数组    高    一般    不安全LinkedList    双向链表    一般    高    不安全Vector    数组    高    一般    安全2.2.可以一边遍历一边修改List的方法首先思考有几个遍历方法:六个哪些是不能修改元素的:迭代器,forEach最终得到的方法:for,增强for,列表迭代器,Stream流2.3.List快速删除元素的原理原理是基于集合底层数据结构不同,分为两类:ArrayList,LinkedListArrayList:基于数组对吧,原先数组是通过索引删除数据,那么因此ArrayList也是如此,基于索引来删除数据具体实现:如果你是删除尾部最后一个数据,直接删除即可,时间复杂度为O(1),如果不是,那么它会将索引元素删除后,将后面的元素往前面覆盖,然后计算出集合长度,时间复杂度为O(n),n为元素的个数LinkedList:基于双向链表,简单来说链表由节点组成,每个节点包含自己的数据与前一个节点的引用和后一个节点的引用,实现双向并通具体实现:如果你是删除尾部最后一个数据,直接删除即可,时间复杂度为O(1),如果不是,那么就是从头或尾进行查询删除,时间复杂度O(n)2.4.ArrayList与LinkedList的区别数据结构组成不同:Array List基于数组,LinkedList基于双向链表删除和插入效率不同:ArrayList在尾部的效率高(平均O(1)),在其他的地方效率低,由于需要进行元素覆盖,而LinkedList它基于链表引用,在尾部的效率(O(1)比ArrayList效率低(ArrayList基于数组,内存是连续的,而LinkedList基于链表,内存不连续),在其他地方删除与插入与ArrayList效率差不多(O(n))随机访问速度:由于ArrayList基于数组根据索引查询,时间复杂度O(1),而LinkedList基于链表,它需要从头或尾部访问,因此时间复杂度为O(n)适用场景不同:ArrayList更适合高频的随机访问操作或尾部插入为主,LinkedList更适合高频头尾插入/删除(队列)或需要双向遍历线程安全:都是线程不安全的2.5.线程安全实现线程(List)安全的方法有:实现Collections.synchronizedList,将线程不安全的List集合加个锁,变成安全的直接使用线程安全的List集合:比如Vector,CopyOnWirteArrayList2.6.ArrayList的扩容机制首先如果你没有指定长度,默认长度为10,当你要添加元素并且超过此时容量长度时,就会进行扩容操作实现:1.扩容:创建一个新的数组,新数组的长度为原数组的1.5倍数,然后再检查容量是否足够,不够继续扩容---2.复制:将旧的数组里面的值复制进新的数组中,再进行写操作---3.更改引用:将原先指向旧数组的引用指向新数组---4.扩容完成:可以继续扩容2.7.CopyOnWirteArrayList它实现了读写分离,写操作加了互斥锁ReentrantLock,避免出现线程安全问题,而读操作没有加锁,使用volatile关键字修饰数组,保证当前线程对数组对象重新赋值后,其他线程可以及时感知到(所有线程可见性)。线程读取数据可以直接读取,提高效率写操作:它不会向ArrayList一样直接扩容1.5倍,它是根据你的添加元素个数多少来扩容,如果你只添加一个元素,那么它会创建一个新数组,长度比旧数组长度多一,然后依旧是依次复制元素进新数组中,改变内部引用指向(需要频繁创建新的数组,以时间换空间)读操作:就是说它不会管你的数据是否修改,内部指向是旧数组,那么就读取旧数组的数据,指向是新数组就读取新数据,这样效率会高(数据弱一致性)————————————————原文链接:https://blog.csdn.net/2402_88700528/article/details/148262923
  • [技术干货] Java中==和equals()的区别
    在Java中,==和equals()是两个常用的比较操作符和方法,但它们之间的用法和含义却有着本质的区别。本文将详细解释这两个操作符/方法之间的区别。1、==操作符==操作符 在Java中 主要用于比较两个变量的值是否相等。但是,这个“值”的含义取决于变量的类型:1、于基本数据类型(如int, char, boolean等):== 比较的是两个变量的值是否相等。2、对于引用类型(如对象、数组等):== 比较的是两个引用是否指向内存中的同一个对象(即地址是否相同)。示例:int a = 5;  int b = 5;  System.out.println(a == b); // 输出true,因为a和b的值相等    Integer c = new Integer(5);  Integer d = new Integer(5);  System.out.println(c == d); // 输出false,因为c和d指向的是不同的对象2、equals()方法equals()方法是Java Object 类的一个方法,用于比较两个对象的内容是否相等。需要注意的是,默认的 equals() 方法 实现其实就是 == 操作符对于引用类型的比较,即比较的是两个引用是否指向同一个对象。但是,很多Java类(如String, Integer等)都重写了 equals() 方法,以提供基于内容的比较。示例:String str1 = new String("hello");  String str2 = new String("hello");  System.out.println(str1.equals(str2)); // 输出true,因为str1和str2的内容相等    // Integer类也重写了equals方法  Integer e = new Integer(5);  Integer f = new Integer(5);  System.out.println(e.equals(f)); // 输出true,因为Integer类重写了equals方法,基于值进行比较3、总结1、==操作符:对于基本数据类型,比较的是值是否相等。对于引用类型,比较的是两个引用是否指向同一个对象(即地址是否相同)。2、equals()方法:默认实现是基于 == 操作符的,即比较两个引用是否指向同一个对象。但很多类(如String, Integer等)都重写了 equals() 方法,以提供基于内容的比较。重要提示:1、当比较两个对象的内容是否相等时,应该优先使用 equals() 方法,而不是 == 操作符。2、自定义类如果需要比较内容是否相等,也应该重写 equals() 方法。3、需要注意的是,如果重写了 equals() 方法,通常也需要重写 hashCode() 方法,以保持两者的一致性。这是因为在Java中,很多集合类(如HashSet, HashMap等)在存储和查找元素时,都会同时用到 equals() 和 hashCode() 方法。————————————————原文链接:https://blog.csdn.net/qq_41840843/article/details/139706184
  • [技术干货] 【 java 虚拟机知识 第一篇 】
    1.内存模型1.1.JVM内存模型的介绍内存模型主要分为五个部分:虚拟机栈,本地方法栈,堆,方法区(永久代或元空间),程序计数器,当然还有一部分是直接内存。虚拟机栈:每个线程各有一个,线程独有,当执行方法(除了本地方法native修饰的方法)之前,会创建一个栈帧,栈帧里面包含局部变量表和操作数栈和动态链接和方法出口等信息,而每个栈帧就是存入栈中本地方法栈:每个线程各有一个,线程独有,当执行本地方法时类似于虚拟机栈,一样会创建栈帧,存入对应信息程序计数器:每个线程各有一个,线程独有,它的作用是记录当前线程下一次执行的二进制字节码指令地址,如果执行的是本地方法那么它会记录为定值(null)堆:所有线程共享,堆的回收由垃圾回收机制管理,堆中主要存入对象实例信息,类信息,数组信息,堆是JVM内存中最大的一个永久代:在jdk1.7及以前是方法区的实现,使用的是jvm内存,独立于堆,主要存入类信息,静态变量信息,符号引用等信息元空间:在jdk1.8及以后是方法区的实现,使用的是本地内存,主要存入类信息,静态变量信息,符号引用等信息直接内存:该内存属于操作系统,由NIO引入,操作系统和Java程序都可以进行操作,实现共享----常量池:属于class文件的一部分,主要存储字面量,符号引用----运行时常量池:属于方法区,其实就是将常量池中的符号引用替换成了直接引用,其余一样1.2.堆和栈的区别五个点:用途,生命周期,存储速度,存储空间,可见性用途:栈主要存储方法返回地址,方法参数,临时变量,每次方法执行之前会创建栈帧,而堆存储对象的实例信息,类实例信息,数组信息生命周期:栈的生命周期可见,每次方法执行完栈帧就会移除(弹出),而堆中的数据需要由垃圾回收器回收,回收时间不确定存储速度:栈的速度更快,栈保持"先进后出"的原则,操作简单快,而堆需要对对象进行内存分配和垃圾回收,并且垃圾回收器本身运行也会损耗性能,速度慢存储空间:栈的空间相对于堆的空间小,栈的空间小且固定,由操作系统管理,而堆的空间是jvm中最大的,由jvm管理可见性:栈是每个线程都有的,而堆是所有线程共享的1.3.栈的存储细节如果执行方法时,里面创建了基本类型,那么基本类型的数据会存入栈中,如果创建了引用类型,会将地址存入栈,其实例数据存入堆中1.4.堆的部分堆主要分为两部分:新生代,老年代,它的比例:1:2新生代:新生代分为两个区:伊甸园区和幸存者区,而幸存者区又平均分为S0和S1区,伊甸园区与S0与S1之间的比例:8:1:1,每次新创建的对象实例都会先存入伊甸园区,它们主要使用的垃圾回收算法是复制算法,当伊甸园区的内存使用完时,会使用可达性分析算法,标记不可存活的对象(没有被引用的对象)将存活对象复制移入S0或S1中,这个过程叫Minor GC,如果这次移入的是S0,那么下次就会将伊甸园区和S0中的对象移入S1中,循环反复,每经历一次Minor GC过程就会给对象年龄加一,直到大于等于15时,会认为该对象生命周期长,移入老年代中细节:其实新创建的对象不会直接存入伊甸园区,如果多线程情况下同时进行存入对象(线程竞争压力大)会导致性能的损失,因此会给每个线程从伊甸园区中先申请一块TLAB区域,先将对象存入该区,如果该区内存使用完,会重写申请或直接存入伊甸园区老年代:老年代就是存储生命周期长的对象(不经常回收的对象),主要使用的垃圾回收算法为标记清除算法或标记整理算法,看场景出发,其中老年代还包含一个大对象区大对象区:主要存储的就是新创建的大对象比如说大数组,会直接将该对象存入大对象区中,不在存入新生代可达性分析算法:从GC Root出发找对应引用对象,如果一个对象没有被直接引用或间接引用,那么会被标记,GC Root可以是java的核心库中的类,本地方法使用的类,还未结束的线程使用的类,使用了锁的类标记清除算法:对引用的对象进行标记,然后进行清除(不是真正的清除,而是记录其对象的起始地址和结束地址到一个地址表中,下次要添加新对象时会先从表中找,找到一个适合大小的就会进行覆盖),清除:记录地址,新对象进行覆盖,好处:速度快,缺点:内存碎片化严重(内存不连续了,本来可以存入的对象存入不了)标记整理算法:同理进行标记,然后再对可存活对象进行整理,最后清除,好处:避免了内存碎片化问题,缺点:速度慢复制算法:将内存空间分为两份,一份存对象from,一份为空to,当要回收时,复制可存活对象移入为空的内存空间to中(移入既整理),然后对存对象的空间from整体清除,然后名称from和to换过来为什么会有大对象区:因为伊甸园区的内存空间本身就不大,如果你直接创建一个大于它空间的对象,会出现问题,还有就是即使没有超过伊甸园区的空间,但是其对象依旧很大,频繁的复制移动很影响性能1.5.程序计数器的作用简单来说:线程1执行到某个地方时,线程2抢到了执行权,那么等到线程1执行时是不是需要知道上次执行到哪里了,所以程序计数器就是记录执行到哪里的,并且每次线程都需要有一个来记录1.6.方法区的内容方法区主要包含:类信息,静态变量信息,运行时常量池,即时编译器的缓存数据1.7.字符串池在jdk1.6及以前字符串池属于永久代,jdk1.7字符串池移入堆中但是还是属于永久代的,jdk1.8及以后还是存入堆中,但是不属于元空间了(1.7以前是永久代,1.8以后是元空间)细节:String s1 = "a";它的过程是:先去字符串池中找,看是否能找到该字符,找到了直接复用池中地址,没有找到会先在堆中创建一个String对象,jdk1.6它会将数据复制一份重新创建一个新的对象存入池中,jdk1.7会将其地址复用给池中String s2 = new("b");同理String s3 = "a" + "b";常量进行相加,与new("ab")基本一致String s4 = s1 + s2;变量相加,底层使用的是new StringBuilder.append("a").append("b").toString(),如果池中存在"ab",它也不会复用,而是直接创建,如果池中不存在,而不会将新创建的对象存入池中1.8.引用类型引用类型:强引用,软引用,弱引用,虚引用,(终结器引用)强引用:比如new就是,只要有强引用指向对象,那么该对象永远不会被回收软引用:如果出现内存溢出的情况,再下次GC时会对其回收弱引用:每次进行GC过程都会进行回收虚引用:每次进行GC过程都会进行回收细节:这些都是对象,等级依次递减软引用:创建一个软引用对象时你可以指定引用队列,如果不指定会导致软引用为null一个空壳,比如说出现了GC Root强引用软引用对象,导致软引用对象无法被回收,你想要其对象被回收,可以使用引用队列,简单来说就是出现了这种情况,将软引用对象存入队列中,下次GC会扫描队列进行回收,当然这是特殊情况,总结来说:软引用可以使用引用队列也可以不使用public class SoftRefDemo {    public static void main(String[] args) throws InterruptedException {        // 1. 创建引用队列        ReferenceQueue<Object> queue = new ReferenceQueue<>();                // 2. 创建大对象(确保能被GC回收)        byte[] data = new byte[10 * 1024 * 1024]; // 10MB                // 3. 创建软引用并关联队列        SoftReference<Object> softRef = new SoftReference<>(data, queue);                // 4. 移除强引用(只保留软引用)        data = null;                System.out.println("GC前: ");        System.out.println("  softRef.get() = " + softRef.get());        System.out.println("  queue.poll()  = " + queue.poll());                // 5. 强制GC(模拟内存不足)        System.gc();        Thread.sleep(1000); // 给GC时间                System.out.println("\nGC后: ");        System.out.println("  softRef.get() = " + softRef.get());        System.out.println("  queue.poll()  = " + queue.poll());    }}GC前:   softRef.get() = [B@15db9742  queue.poll()  = null GC后:   softRef.get() = null  queue.poll()  = java.lang.ref.SoftReference@6d06d69c弱引用:与软引用相同WeakHashMap<Key, Value> map = new WeakHashMap<>(); Key key = new Key();map.put(key, new Value()); // 移除强引用key = null; System.gc(); // GC后Entry自动被移除System.out.println(map.size()); // 输出: 0虚引用:最好的例子就是直接内存:它就是使用了虚引用,直接内存就是从操作系统中申请了一块空间来使用,因此GC是不能对其进行回收的,如果当强引用消失只剩下虚引用,那么会将虚引用对象存入引用队列中,等队列来执行本地方法释放直接内存public class PhantomRefDemo {    public static void main(String[] args) {        // 1. 创建引用队列        ReferenceQueue<Object> queue = new ReferenceQueue<>();                // 2. 创建虚引用        Object obj = new Object();        PhantomReference<Object> phantomRef = new PhantomReference<>(obj, queue);                // 3. 模拟直接内存分配        ByteBuffer directBuffer = ByteBuffer.allocateDirect(1024 * 1024); // 1MB                System.out.println("GC前:");        System.out.println("  phantomRef.get() = " + phantomRef.get()); // null        System.out.println("  queue.poll()     = " + queue.poll());     // null                // 4. 移除强引用(触发回收条件)        obj = null;        directBuffer = null; // 释放DirectByteBuffer强引用                // 5. 强制GC(实际应用中会自动触发)        System.gc();        try { Thread.sleep(500); } catch (Exception e) {}                System.out.println("\nGC后:");        System.out.println("  phantomRef.get() = " + phantomRef.get()); // null        System.out.println("  queue.poll()     = " + queue.poll());     // 返回phantomRef对象                // 6. 实际效果:DirectByteBuffer分配的1MB堆外内存已被释放    }}终结器引用:在所有父类Object中有一个终结器方法finalize()方法,如果重写该方法,那么执行GC之前会先执行该方法,当没强引用指向了,而这个对象还重写了finalize()方法,那么会将这个终结器引用对象加入队列中,下次GC时会先由队列来执行finalize()方法,但是指定执行的队列是一个优先级不高的队列,会导致资源释放缓慢public class ResourceHolder {    // 重写finalize方法(不推荐!)    @Override    protected void finalize() throws Throwable {        releaseResources(); // 释放资源        super.finalize();    }}1.9.内存泄漏与内存溢出内存泄漏:就是说没有被引用的对象没有被回收,导致可用内存空间减少比如:静态集合没有释放:一直存在线程未释放:线程应该执行完了,但是没有释放事件监听:事件源都不存在了,还在监听例子:使用对应的文件流,字节流,但是没有释放该流,就会导致内存泄漏解决:释放流内存溢出:就是说内存不足了比如:一直创建新对象持久引用:集合一直添加但是没有被清除递归例子:ThreadLocal,每个线程都有一个ThreadLocal,本质就是每个线程存在一个ThreadLocalMap对象,key(弱引用)存入的是TreadLocal的实例,value(强引用)为自己指定的Object对象,如果没有使用该TreadLocal了,也就是说没有强引用指向TreadLocalMap对象,那么其中的key就会被设置为null,那如果该线程一直不结束,导致key不能被回收,随着key为null的情况增多就会导致内存溢出解决:使用TreadLocal.recome();1.10.会出现内存溢出的结构会出现该问题的内存结构:堆,栈,元空间,直接空间————————————————原文链接:https://blog.csdn.net/2402_88700528/article/details/148516238
总条数:691 到第
上滑加载中