-
一. BCryptPasswordEncoder介绍 Spring Security 中提供了 BCryptPasswordEncoder用于用户密码的加密和验证,这里讲解一下该 PasswordEncoder 的实现逻辑. 首先 BCryptPasswordEncoder 使用了 BCrypt 算法来对密码实现加密和验证。由于 BCrypt本身是一种 单向Hash算法,因此它和我们日常用的 MD5一样,通常情况下是无法逆向解密的。 在 BSD系统中 BCrypt 算法主要用来替代 md5 加密算法,它使用了一种可变版本的Blowfish流密码算法。通过多次加盐和随机数,大大提高了通过彩虹表撞库的形式破解密码的难度.因此这套加密算法被广泛用于许多系统的密码加密当中。目前来看该加密算法没有已知的漏洞。在新应用中BCrypt加密算法是推荐的四种密码散列算法之一. 流密码加密 相较于 分组加密方案和消息填充算法,流密码加密算法方案本身就可以加密任意长度的数据,无需密码分组和消息填充。 加密参数 在 BCryptPasswordEncoder 加密过程中,会提供主要参数来辅助加密: salt:可选的加盐字符串,如果未指定,则会自动生成一个加盐字符.取值范围为 [./0-9A-Za-z] rounds 一个可选的随机数,默认为 12 ,取值范围为 4 到 31 之间,左闭右闭. version 用于指定一个 BCrypt 算法版本,如果未指定,则默认为 2b,可选版本有: 2,BCrypt 的第一个版本,由于存在一个小的安全缺陷,目前已不再使用 2a在一些实现中存在罕见的安全缺陷,被 2b版本取代 2y使用 crypt_blowfish BCrypt算法实现格式化,除了名称外与 2b无明显区别 官方最新的 BCrypt 算法版本,目前为默认版本 hash算法是一种单向密码体制,即它是一个从明文到密文的不可逆的映射,只有加密过程,没有解密过程。同时,哈希函数可以将任意长度的输入经过变化以后得到固定长度的输出。 二.BCryptPasswordEncoder中的方法: encode-加密:在BCryptPasswordEncoder中使用encode方法对密码进行加密,因为是通过hash算法进行加密,同样的密码输出的密文是不同的 matches-解密/匹配: 虽然通过encode方法加密的密码是不能解密的,但是在BCryptPasswordEncoder中提供了一个matches方法来匹配密码,它的原理是把需要配对的密码经过同一个hash函数计算,把计算得到的hash值到数据库中匹配,相同的hash值则说明是同一个密码。 ———————————————— 原文链接:https://blog.csdn.net/superxmh/article/details/118497296
-
单例模式简介单例模式是 Java 中最简单,也是最基础,最常用的设计模式之一。在运行期间,保证某个类只创建一个实例,保证一个类仅有一个实例,并提供一个访问它的全局访问点。下面就来讲讲Java中的N种实现单例模式的写法。饿汉式public class Singleton { private static Singleton instance = new Singleton(); private Singleton() { } public static Singleton getInstance() { return instance; } }这是实现一个安全的单例模式的最简单粗暴的写法,这种实现方式我们称之为饿汉式。之所以称之为饿汉式,是因为肚子很饿了,想马上吃到东西,不想等待生产时间。这种写法,在类被加载的时候就把Singleton实例给创建出来了。饿汉式的缺点就是,可能在还不需要此实例的时候就已经把实例创建出来了,没起到lazy loading的效果。优点就是实现简单,而且安全可靠。懒汉式public class Singleton { private static Singleton instance; private Singleton() { } public static Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }相比饿汉式,懒汉式显得没那么“饿”,在真正需要的时候再去创建实例。在getInstance方法中,先判断实例是否为空再决定是否去创建实例,看起来似乎很完美,但是存在线程安全问题。在并发获取实例的时候,可能会存在构建了多个实例的情况。所以,需要对此代码进行下改进。public class SingletonSafe { private static volatile SingletonSafe singleton; private SingletonSafe() { } public static SingletonSafe getSingleton() { if (singleton == null) { synchronized (SingletonSafe.class) { if (singleton == null) { singleton = new SingletonSafe(); } } } return singleton; } }这里采用了双重校验的方式,对懒汉式单例模式做了线程安全处理。通过加锁,可以保证同时只有一个线程走到第二个判空代码中去,这样保证了只创建 一个实例。这里还用到了volatile关键字来修饰singleton,其最关键的作用是防止指令重排。什么是指令重排?private Singleton(){ 1 int x = 10; 2 int y =3 0; 3 Object o = new Object(); }上面的构造函数 Singleton(),我们编写的顺序是1、2、3,JVM 会对它进行指令重排序,所以执行顺序可能是3、1、2,也可能是2、3、1,不管是那种执行顺序,JVM 最后都会保证所以实例都完成实例化。如果构造函数中操作比较多时,为了提升效率,JVM 会在构造函数里面的属性未全部完成实例化时,就返回对象。双重检测锁出现空指针问题的原因就是出现在这里,当某个线程获取锁进行实例化时,其他线程就直接获取实例使用,由于JVM指令重排序的原因,其他线程获取的对象也许不是一个完整的对象,所以在使用实例的时候就会出现空指针异常问题。静态内部类public class Singleton { private static class SingletonHolder { private static Singleton instance = new Singleton(); } private Singleton() { } public static Singleton getInstance() { return SingletonHolder.instance; } }通过静态内部类的方式实现单例模式是线程安全的,同时静态内部类不会在Singleton类加载时就加载,而是在调用getInstance()方法时才进行加载,达到了懒加载的效果。似乎静态内部类看起来已经是最完美的方法了,其实不是,可能还存在反射攻击或者反序列化攻击。且看如下代码:public static void main(String[] args) throws Exception { Singleton singleton = Singleton.getInstance(); Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor(); constructor.setAccessible(true); Singleton newSingleton = constructor.newInstance(); System.out.println(singleton == newSingleton); }除了反射攻击之外,还可能存在反序列化攻击的情况。如下:这个依赖提供了序列化和反序列化工具类:<dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.8.1</version> </dependency>Singleton类实现java.io.Serializable接口:public class Singleton implements Serializable { private static class SingletonHolder { private static Singleton instance = new Singleton(); } private Singleton() { } public static Singleton getInstance() { return SingletonHolder.instance; } public static void main(String[] args) { Singleton instance = Singleton.getInstance(); byte[] serialize = SerializationUtils.serialize(instance); Singleton newInstance = SerializationUtils.deserialize(serialize); System.out.println(instance == newInstance); } }通过枚举实现单例模式在effective java(这本书真的很棒)中说道,最佳的单例实现模式就是枚举模式。利用枚举的特性,让JVM来帮我们保证线程安全和单一实例的问题。除此之外,写法还特别简单。public enum Singleton { INSTANCE; public void doSomething() { System.out.println("doSomething"); } }直接通过Singleton.INSTANCE.doSomething()的方式调用即可。方便、简洁又安全。public class Main { public static void main(String[] args) { Singleton.INSTANCE.doSomething(); } }转自:https://mp.weixin.qq.com/s/hi0vJgaHBLhu1KFK0xUrDw
-
前言代理模式是一种结构型设计模式,它允许通过创建一个代理对象来控制对另一个对象的访问。代理对象充当了客户端和实际对象之间的中介,客户端通过代理对象访问实际对象,从而隐藏了实际对象的具体实现细节。代理模式通常涉及三个角色:抽象主题(Subject):定义了代理对象和实际对象之间的共同接口,客户端通过该接口访问实际对象。实际主题(Real Subject):实现了抽象主题接口,是被代理的真实对象,即客户端最终要访问的对象。代理(Proxy):实现了抽象主题接口,并持有一个实际主题的引用。代理对象在执行具体操作之前或之后,可以添加额外的逻辑。代理模式的主要目的是在不改变实际对象的情况下,通过代理对象提供额外的功能或控制访问。它可以用于实现延迟加载、访问控制、远程访问、事务管理等。代理模式还可以实现AOP(面向切面编程)的核心机制,通过代理对象在方法执行前后添加横切逻辑。优点:代理对象和实际对象之间的解耦,客户端只需要通过代理对象进行访问,无需直接与实际对象交互。可以在代理对象中添加额外的逻辑,如访问控制、缓存、日志记录等。可以实现延迟加载,只有在需要时才创建实际对象。缺点:增加了系统的复杂性,引入了额外的代理类。可能会降低系统的性能,因为代理对象需要执行额外的操作。演示JDK动态代理实现方式:JDK动态代理是基于接口的代理,使用java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口实现。通过Proxy.newProxyInstance方法创建代理对象,代理对象会实现指定接口,并将方法调用转发给InvocationHandler处理。限制:JDK动态代理要求目标对象必须实现接口。如果目标对象没有实现接口,就无法使用JDK动态代理。性能:JDK动态代理的性能相对较低,因为它需要使用反射来调用目标对象的方法。import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; interface UserService { void addUser(String username); } class UserServiceImpl implements UserService { public void addUser(String username) { System.out.println("Adding user: " + username); } } class UserServiceProxy implements InvocationHandler { private Object target; public Object bind(Object target) { this.target = target; return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this); } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("Before method call"); Object result = method.invoke(target, args); System.out.println("After method call"); return result; } } public class Main { public static void main(String[] args) { UserService userService = new UserServiceImpl(); UserServiceProxy userServiceProxy = new UserServiceProxy(); UserService proxy = (UserService) userServiceProxy.bind(userService); proxy.addUser("一安未来"); } }在上面的示例中,我们定义了一个UserService接口和一个实现类UserServiceImpl。然后,我们创建了一个UserServiceProxy类,实现了InvocationHandler接口,并在invoke方法中添加了额外的逻辑。最后用Proxy.newProxyInstance方法创建了代理对象,并将其绑定到实际对象上。CGLIB动态代理实现方式:CGLIB动态代理是基于类的代理,使用net.sf.cglib.proxy.Enhancer类和net.sf.cglib.proxy.MethodInterceptor接口实现。通过Enhancer创建代理对象,代理对象会继承目标对象,并重写方法,在方法调用时通过MethodInterceptor处理。限制:CGLIB动态代理可以代理没有实现接口的类,因为它是基于类的代理。性能:CGLIB动态代理的性能相对较高,因为它直接操作字节码,无需使用反射。import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; class UserService { public void addUser(String username) { System.out.println("Adding user: " + username); } } class UserServiceInterceptor implements MethodInterceptor { public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("Before method call"); Object result = proxy.invokeSuper(obj, args); System.out.println("After method call"); return result; } } public class Main { public static void main(String[] args) { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(UserService.class); enhancer.setCallback(new UserServiceInterceptor()); UserService proxy = (UserService) enhancer.create(); proxy.addUser("一安未来"); } }在上面的示例中,我们定义了一个UserService类,没有实现任何接口。然后,我们创建了一个UserServiceInterceptor类,实现了MethodInterceptor接口,并在intercept方法中添加了额外的逻辑。最后使用Enhancer类创建了代理对象,并设置了回调对象为UserServiceInterceptor。总结实现方式:JDK动态代理是基于接口的代理,而CGLIB动态代理是基于类的代理。接口要求:JDK动态代理要求目标对象必须实现接口,而CGLIB动态代理可以代理没有实现接口的类。性能:CGLIB动态代理的性能通常比JDK动态代理更高,因为它直接操作字节码,无需反射。但在某些情况下,JDK动态代理可能更适合,特别是当目标对象实现了多个接口时。选择使用JDK动态代理还是CGLIB动态代理取决于具体的使用场景。如果目标对象实现了接口,且对性能要求不高,可以选择JDK动态代理。如果目标对象没有实现接口,或对性能要求较高,可以选择CGLIB动态代理。这里顺带提一下动态代理与反射:动态代理目的:动态代理是一种机制,用于在运行时创建代理对象,以在代理对象上执行额外的逻辑或操作。使用方式:使用动态代理时,我们需要定义一个接口或类,并创建一个代理对象,将其绑定到实际对象上。代理对象会拦截对实际对象的方法调用,并可以在调用前后执行额外的逻辑。实现方式:Java提供了两种动态代理的实现方式,即基于接口的动态代理(JDK动态代理)和基于类的动态代理(CGLIB动态代理)。反射目的:反射是一种机制,用于在运行时检查、获取和操作类的成员(字段、方法、构造函数等)。使用方式:使用反射时,我们可以通过Class对象获取类的信息,如字段、方法、构造函数等。然后,可以使用反射机制调用这些成员,甚至可以在运行时创建类的实例。实现方式:Java的反射机制主要通过Class、Field、Method等类来实现。1.通过类的全限定名字符串获取Class对象 Class<?> clazz = Class.forName("com.example.MyClass"); 2.通过类名的.class语法获取Class对象 Class<?> clazz = MyClass.class; 3.通过类加载器的loadClass方法获取Class对象 ClassLoader classLoader = MyClass.class.getClassLoader(); Class<?> clazz = classLoader.loadClass("com.example.MyClass"); 4.通过对象的getClass方法获取Class对象 MyClass obj = new MyClass(); Class<?> clazz = obj.getClass(); class MyClass { public void sayHello(String name) { System.out.println("Hello, " + name); } } public class ReflectionExample { public static void main(String[] args) { try { Class<?> clazz = Class.forName("MyClass"); Object obj = clazz.newInstance(); Method method = clazz.getMethod("sayHello", String.class); // Method method = clazz.getDeclaredMethod("sayHello", String.class); method.invoke(obj, "一安未来"); } catch (Exception e) { e.printStackTrace(); } } }在上面的示例中,我们使用Class.forName方法通过类名获取Class对象。然后,使用newInstance方法创建类的实例。接下来,获取sayHello方法的Method对象,并使用invoke方法调用该方法,传递参数"一安未来"。getDeclaredMethod和getMethod是Java反射中用于获取方法的两个方法。它们在获取方法时有以下几个区别:访问级别:getDeclaredMethod:可以获取类中声明的所有方法,包括私有方法、受保护方法和公共方法。不考虑方法的访问修饰符。getMethod:只能获取公共方法(public修饰符),无法获取私有方法和受保护方法。继承关系:getDeclaredMethod:可以获取类中声明的方法,包括私有方法、受保护方法和公共方法,不考虑继承关系。getMethod:除了能获取类中声明的公共方法,还可以获取从父类继承的公共方法。参数类型:getDeclaredMethod和getMethod都可以通过参数类型来定位方法。但是,对于方法重载的情况,getDeclaredMethod和getMethod的参数类型要求略有不同。getDeclaredMethod:参数类型必须与目标方法的参数类型完全匹配。getMethod:参数类型可以是目标方法参数类型的子类。动态代理和反射都是在运行时处理类和对象的机制。它们都允许我们在程序运行时动态地获取和操作类的信息。动态代理和反射都提供了一种动态性的机制,允许我们在运行时进行灵活的操作。它们在某些场景下可以结合使用,例如使用反射获取类的信息,然后使用动态代理来创建代理对象并执行额外的逻辑。总结来说,动态代理和反射都是Java中非常有用的机制,可以在运行时动态地操作类和对象。动态代理主要用于创建代理对象并执行额外的逻辑,而反射主要用于在运行时检查和操作类的成员。转自:https://mp.weixin.qq.com/s/JG8snawxJyVI08KobyXBQg
-
1. 指令重排序程序在运行时内存实际的访问顺序和程序代码编写的访问顺序不一定一致,这就是内存乱序访问。内存乱序访问行为出现的理由是为了提升程序运行时的性能。这种内存乱序问题主要是由两种原因引起的:编译器在编译时进行了编译优化,导致指令重排;在多cpu环境下,为了尽可能地避免处理器访问主内存的时间开销,处理器大多会利用缓存(cache)以提高性能。在这种模型下会存在一个现象,即缓存中的数据与主内存的数据并不是实时同步的,各CPU(或CPU核心)间缓存的数据也不是实时同步的。这导致在同一个时间点,各CPU所看到同一内存地址的数据的值可能是不一致的。从程序的视角来看,就是在同一个时间点,各个线程所看到的共享变量的值可能是不一致的。2. java 内存模型中的happen before原则JSR-1337制定了Java内存模型(Java Memory Model, JMM)中规定的hb原则大致有以下几点:程序次序法则:线程中的每个动作A都happens-before于该线程中的每一个动作B,其中,在程序中,所有的动作B都能出现在A之后。监视器锁法则:对一个监视器锁的解锁 happens-before于每一个后续对同一监视器锁的加锁。volatile变量法则:对volatile域的写入操作happens-before于每一个后续对同一个域的读写操作。线程启动法则:在一个线程里,对Thread.start的调用会happens-before于每个启动线程的动作。线程终结法则:线程中的任何动作都happens-before于其他线程检测到这个线程已经终结、或者从Thread.join调用中成功返回,或Thread.isAlive返回false。中断法则:一个线程调用另一个线程的interrupt happens-before于被中断的线程发现中断。终结法则:一个对象的构造函数的结束happens-before于这个对象finalizer的开始。传递性:如果A happens-before于B,且B happens-before于C,则A happens-before于Cjmm 对java语义的比较重要的两个扩展是:对volatile语义的扩展保证了volatile变量在一些情况下不会重排序,volatile的64位变量double和long的读取和赋值操作都是原子的。对final语义的扩展保证一个对象的构建方法结束前,所有final成员变量都必须完成初始化(前提是没有this引用溢出)。3. 内存屏障(Memory Barrier)Memory barrier 能够让 CPU 或编译器在内存访问上有序。一个 Memory barrier 之前的内存访问操作必定先于其之后的完成。Memory barrier是一种CPU指令,用于控制特定条件下的重排序和内存可见性问题。Java编译器也会根据内存屏障的规则禁止重排序。有的处理器的重排序规则较严,无需内存屏障也能很好的工作,Java编译器会在这种情况下不放置内存屏障。Memory Barrier可以被分为以下几种类型:LoadLoad屏障:对于这样的语句Load1; LoadLoad; Load2,在Load2及后续读取操作要读取的数据被访问前,保证Load1要读取的数据被读取完毕。StoreStore屏障:对于这样的语句Store1; StoreStore; Store2,在Store2及后续写入操作执行前,保证Store1的写入操作对其它处理器可见。LoadStore屏障:对于这样的语句Load1; LoadStore; Store2,在Store2及后续写入操作被刷出前,保证Load1要读取的数据被读取完毕。StoreLoad屏障:对于这样的语句Store1; StoreLoad; Load2,在Load2及后续所有读取操作执行前,保证Store1的写入对所有处理器可见。它的开销是四种屏障中最大的。在大多数处理器的实现中,这个屏障是个万能屏障,兼具其它三种内存屏障的功能参考https://www.cnblogs.com/chenyangyao/p/5269622.html可以得知:Oracle的JDK中提供了Unsafe. putOrderedObject,Unsafe. putOrderedInt,Unsafe. putOrderedLong这三个方法,JDK会在执行这三个方法时插入StoreStore内存屏障,避免发生写操作重排序。而在Intel 64/IA-32架构下,StoreStore屏障并不需要,Java编译器会将StoreStore屏障去除。比起写入volatile变量之后执行StoreLoad屏障的巨大开销,采用这种方法除了避免重排序而带来的性能损失以外,不会带来其它的性能开销。具体用法可以看下disruptor的Sequence的set方法。Intel 64/IA-32架构下写操作之间不会发生重排序,也就是说在处理器上操作的顺序是可以保证的,这时候使用volatile来避免重排序是多此一举的。但是,Java编译器却可能生成重排序后的指令。采用putOrderedObject可以解决这个问题。即使在其它会发生写写重排序的处理器中,由于StoreStore屏障的性能损耗小于StoreLoad屏障,采用这一方法也是一种可行的方案。但值得再次注意的是,这一方案不是对volatile语义的等价替换,而是在特定场景下做的特殊优化,它仅避免了写写重排序,但不保证内存可见性。4. volatile语义中的内存屏障在每个volatile写操作前插入StoreStore屏障,在写操作后插入StoreLoad屏障;在每个volatile读操作前插入LoadLoad屏障,在读操作后插入LoadStore屏障;volatile的内存屏障策略非常严格保守,保证了线程可见性。5. final语义中的内存屏障新建对象过程中,构造体中对final域的初始化写入(StoreStore屏障)和这个对象赋值给其他引用变量,这两个操作不能重排序;初次读包含final域的对象引用和读取这个final域(LoadLoad屏障),这两个操作不能重排序;Intel 64/IA-32架构下写操作之间不会发生重排序StoreStore会被省略,这种架构下也不会对逻辑上有先后依赖关系的操作进行重排序,所以LoadLoad也会变省略。转自:https://cloud.tencent.com/developer/article/1453298
-
一、导读 我们继续总结学习Java基础知识,温故知新。 二、概览 volatile 是一个Java关键字,可以用来修饰变量,volatile也被称为轻量级的synchronized,运行时开销比 synchronized更小。 2.1 作用 1、确保共享变量在线程之间的同步,实现可见性。 2、禁止处理器重排序。 2.2 多线程共享变量的访问流程 线程执行时,先拷贝主存数据到本线程本地,操作完成后再把结果从线程本地刷到主存。 我们看下面的图,多线程时,共享变量操作完的值是在红色的区域。 2.3 多线程为什么会出现可见性问题 可见性是由于CPU缓存引起,CPU 增加了缓存,以均衡与内存的速度差异,导致 可见性问题。 在多线程环境下,多个线程同时访问共享变量,由于不同线程的执行顺序和时间不确定,可能会导致一个线程对共享变量的修改在其他线程中不可见。可参考上面共享变量的访问流程。 2.4 volatile如何实现可见性 volatile不允许线程内进行缓存和重排序,直接修改内存,所以对其他线程是可见的。 被volatile修饰的变量读写时,都会直接刷到主存,从而使得变量可见。 2.5 如何实现禁止指令重排序 volatile是通过编译器在生成字节码时,在指令序列中添加“内存屏障”来禁止指令重排序的。 当一个变量被修饰时,表示变量是“易变的”(volatile)或者“不稳定的”(unstable),它意味着该变量的值可能会被其他线程(或进程)修改。 注意: volatile 关键字只能保证线程之间的同步,不能保证线程安全。 要保证线程安全,需要使用其他同步机制,比如 synchronized 关键字或者 Lock 接口。 volatile具有可见性、有序性,但不具有原子性,所以是线程不安全的。 2.6 举例 不使用volatile关键字 禁止线程缓存变量结果。 可见性问题主要指一个线程修改了共享变量值,而另一个线程却看不到。 引起可见性问题的主要原因是每个线程拥有自己的一个高速缓存区——线程工作内存。 举例: // Thread-A new Thread("Thread A") { @Override public void run() { while (!stop) { } System.out.println(Thread.currentThread() + " stopped"); } }.start(); 一个线程内使用了停止的开关,假如这个stop没有被volatile修饰,我们在线程b中修改, 线程a并不知道开关的值被修改了。 使用volatile 防重排序 从一个最经典的例子来分析重排序问题。大家应该都很熟悉单例模式的实现 public class Singleton { public static volatile Singleton singleton; /** * 构造函数私有,禁止外部实例化 */ private Singleton() {}; public static Singleton getInstance() { if (singleton == null) { synchronized (singleton.class) { if (singleton == null) { singleton = new Singleton(); } } } return singleton; } } 实例化一个对象其实可以分为三个步骤: * 分配内存空间。 * 初始化对象。 * 将内存空间的地址赋值给对应的引用。 但是由于操作系统可以对指令进行重排序,所以上面的过程也可能会变成如下过程: * 分配内存空间。 * 将内存空间的地址赋值给对应的引用。 * 初始化对象 如果是这个流程,多线程环境下就可能将一个未初始化的对象引用暴露出来,从而导致不可预料的结果。 因此,为了防止这个过程的重排序,我们需要将变量设置为volatile类型的变量。 使用volatile 保证原子性:单次读/写 对volatile变量的单次读/写操作可以保证原子性的,如long和double类型变量, 但是并不能保证i++这种操作的原子性,因为本质上i++是读、写两次操作,要保证多步的原子性, 可以通过AtomicInteger或者Synchronized来实现,本质上就是cas操作。 2.7 来答题 问题1: i++为什么不能保证原子性? i++其实是一个复合操作,包括三步骤: * 读取i的值。 * 对i加1。 * 将i的值写回内存。 volatile是无法保证这三个操作是具有原子性的, 我们可以通过AtomicInteger或者Synchronized来保证+1操作的原子性。 问题2: 共享的long和double变量的为什么要用volatile? 因为long和double两种数据类型的操作可分为高32位和低32位两部分, 因此普通的long或double类型读/写可能不是原子的。 因此,鼓励大家将共享的long和double变量设置为volatile类型, 这样能保证任何情况下对long和double的单次读/写操作都具有原子性 三、原理 在JVM底层volatile是采用“内存屏障”来实现的,加入volatile关键字时,会多出一个lock前缀指令 内存屏障,又称内存栅栏,是一个 CPU 指令。 1、用javac命令进行编译生成.class文件, 2、再用javap命令反编译查看.class文件的信息,就可以看到字节码信息中多了一些指令。 为了保证各个处理器的缓存是一致的,实现了缓存一致性协议(MESI),每个处理器通过嗅探在总线上传播的数据来检查自己缓存的值是不是过期了,当处理器发现自己缓存行对应的内存地址被修改,就会将当前处理器的缓存行设置成无效状态,当处理器对这个数据进行修改操作的时候,会重新从系统内存中把数据读到处理器缓存里。 volatile 变量通过这样的机制就使得每个线程都能获得该变量的最新值。 3.2 使用场景: 解决对变量可见性有要求,但是对读取顺序没要求的需求。 volatile特性: (1)volatile仅能使用在变量级别 (2)volatile仅能实现变量的修改可见性,不能保证原子性,volatile + cas 就实现了原子性,如atomic包下面的类。 (3)volatile不会造成线程的阻塞 (4)volatile标记的变量不会被编译器优化 ———————————————— 版权声明:本文为CSDN博主「Android西红柿」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/fumeidonga/article/details/130353475
-
大家好本次整理的是codeArts6月份干货合集,希望可以帮到大家。 1.Go语言五大主流web框架-转载 https://bbs.huaweicloud.com/forum/thread-0213121661901680008-1-1.html 2.Vue中实现自定义excel下载-转载 https://bbs.huaweicloud.com/forum/thread-0278121662059656002-1-1.html 3.如何查看自己的操作系统是否支持 64 位架构 https://bbs.huaweicloud.com/forum/thread-0257121682060865007-1-1.html 4.Solr和Elasticsearch对比 https://bbs.huaweicloud.com/forum/thread-0253122347814807015-1-1.html 5.linux关于sed流编辑器那些事 https://bbs.huaweicloud.com/forum/thread-02116122519740471010-1-1.html 6.JS中的事件监听-转载 https://bbs.huaweicloud.com/forum/thread-0253122779497415032-1-1.html 7.浅谈DNS域名解析的过程-转载 https://bbs.huaweicloud.com/forum/thread-0253122779866795033-1-1.html 8.【服务器】利用树莓派搭建 web 服务器-无需公网IP-转载 https://bbs.huaweicloud.com/forum/thread-02106122779940418023-1-1.html 9.【vue3】使用canvas-转载 https://bbs.huaweicloud.com/forum/thread-0234122780116390011-1-1.html 10.前端面试八股文(超详细)-转载 https://bbs.huaweicloud.com/forum/thread-02116122780328089022-1-1.html 11.python数组和矩阵的区别 https://bbs.huaweicloud.com/forum/thread-0219123321311972030-1-1.html 12.Python NLP开发之实现聊天机器人【转】 https://bbs.huaweicloud.com/forum/thread-0253123321507470064-1-1.html 13.使用vbs脚本来监控windows服务器上的应用程序(不存在就启动)【转】 https://bbs.huaweicloud.com/forum/thread-02117123384453066001-1-1.html 14.vbs ShellExecute运行外部程序时如何判断外部程序执行成功与否【转】 https://bbs.huaweicloud.com/forum/thread-0212123384557753002-1-1.html 15.cmd批量ping命令的实现【转】 https://bbs.huaweicloud.com/forum/thread-0226123384655183003-1-1.html 16.批处理文件bat启动redis及任何软件【转】 https://bbs.huaweicloud.com/forum/thread-0212123384741335003-1-1.html 17.cmd实现清理系统垃圾的示例代码【转】 https://bbs.huaweicloud.com/forum/thread-0284123384865623001-1-1.html 18.cmd常用命令大全【转】 https://bbs.huaweicloud.com/forum/thread-0226123385078380004-1-1.html 19.window使用bat批处理文件执行cmd命令【转】 https://bbs.huaweicloud.com/forum/thread-0215123385522817002-1-1.html 20.DOS批处理字符串截取【转】 https://bbs.huaweicloud.com/forum/thread-02117123385670322002-1-1.html 21.批处理删除指定文件或文件夹的实现【转】 https://bbs.huaweicloud.com/forum/thread-02100123387214334003-1-1.html 22.批处理字符串截取的实现【转】 https://bbs.huaweicloud.com/forum/thread-0248123387845375003-1-1.html 23.Python中选择排序的实现与优化【转】 https://bbs.huaweicloud.com/forum/thread-02105123388234406002-1-1.html 24.flask+pymysql实现Web端操作数据库的项目实践【转】 https://bbs.huaweicloud.com/forum/thread-0248123388970328005-1-1.html 25.深入理解Python中的Contextlib库【转】 https://bbs.huaweicloud.com/forum/thread-02117123389149341007-1-1.html
-
使用HashSet判断主键是否存在 HashSet 实现 Set 接口,由哈希表(实际上是 HashMap )实现,但不保证 set 的迭代顺序,并允许使用 null 元素。HashSet 的时间复杂度跟 HashMap 一致,如果没有哈希冲突则时间复杂度为 O(1) ,如果存在哈希冲突则时间复杂度不超过 O(n) 。所以,在日常编码中,可以使用 HashSet 判断主键是否存在。 案例:给定一个字符串(不一定全为字母),请返回第一个重复出现的字符。 /** 查找第一个重复字符 */ public static char findFirstRepeatedChar(String string) { // 检查空字符串 if (Objects.isNull(string) || string.isEmpty()) { return null; } // 查找重复字符 char[] charArray = string.toCharArray(); Set charSet = new HashSet<>(charArray.length); for (char ch : charArray) { if (charSet.contains(ch)) { return ch; } charSet.add(ch); } // 默认返回为空 return null; } 复制 其中,由于 Set 的 add 函数有个特性——如果添加的元素已经再集合中存在,则会返回 false 。可以简化代码为: if (!charSet.add(ch)) { return ch; } 复制 使用HashMap存取键值映射关系 简单来说,HashMap 由数组和链表组成的,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的。如果定位到的数组位置不含链表,那么查找、添加等操作很快,仅需一次寻址即可,其时间复杂度为 O(1) ;如果定位到的数组包含链表,对于添加操作,其时间复杂度为 O(n) ——首先遍历链表,存在即覆盖,不存在则新增;对于查找操作来讲,仍需要遍历链表,然后通过key对象的 equals 方法逐一对比查找。从性能上考虑, HashMap 中的链表出现越少,即哈希冲突越少,性能也就越好。所以,在日常编码中,可以使用 HashMap 存取键值映射关系。 案例:给定菜单记录列表,每条菜单记录中包含父菜单标识(根菜单的父菜单标识为 null ),构建出整个菜单树。 /** 菜单DO类 */ @Setter @Getter @ToString public static class MenuDO { /** 菜单标识 */ private Long id; /** 菜单父标识 */ private Long parentId; /** 菜单名称 */ private String name; /** 菜单链接 */ private String url; } /** 菜单VO类 */ @Setter @Getter @ToString public static class MenuVO { /** 菜单标识 */ private Long id; /** 菜单名称 */ private String name; /** 菜单链接 */ private String url; /** 子菜单列表 */ private List<MenuVO> childList; } /** 构建菜单树函数 */ public static List<MenuVO> buildMenuTree(List<MenuDO> menuList) { // 检查列表为空 if (CollectionUtils.isEmpty(menuList)) { return Collections.emptyList(); } // 依次处理菜单 int menuSize = menuList.size(); List<MenuVO> rootList = new ArrayList<>(menuSize); Map<Long, MenuVO> menuMap = new HashMap<>(menuSize); for (MenuDO menuDO : menuList) { // 赋值菜单对象 Long menuId = menuDO.getId(); MenuVO menu = menuMap.get(menuId); if (Objects.isNull(menu)) { menu = new MenuVO(); menu.setChildList(new ArrayList<>()); menuMap.put(menuId, menu); } menu.setId(menuDO.getId()); menu.setName(menuDO.getName()); menu.setUrl(menuDO.getUrl()); // 根据父标识处理 Long parentId = menuDO.getParentId(); if (Objects.nonNull(parentId)) { // 构建父菜单对象 MenuVO parentMenu = menuMap.get(parentId); if (Objects.isNull(parentMenu)) { parentMenu = new MenuVO(); parentMenu.setId(parentId); parentMenu.setChildList(new ArrayList<>()); menuMap.put(parentId, parentMenu); } // 添加子菜单对象 parentMenu.getChildList().add(menu); } else { // 添加根菜单对象 rootList.add(menu); } } // 返回根菜单列表 return rootList; } 复制 使用 ThreadLocal 存储线程专有对象 ThreadLocal 提供了线程专有对象,可以在整个线程生命周期中随时取用,极大地方便了一些逻辑的实现。 常见的 ThreadLocal 用法主要有两种: 1、保存线程上下文对象,避免多层级参数传递; 2、保存非线程安全对象,避免多线程并发调用。 保存线程上下文对象,避免多层级参数传递 这里,以 PageHelper 插件的源代码中的分页参数设置与使用为例说明。 设置分页参数代码: /** 分页方法类 */ public abstract class PageMethod { /** 本地分页 */ protected static final ThreadLocal<Page> LOCAL_PAGE = new ThreadLocal<Page>(); /** 设置分页参数 */ protected static void setLocalPage(Page page) { LOCAL_PAGE.set(page); } /** 获取分页参数 */ public static <T> Page<T> getLocalPage() { return LOCAL_PAGE.get(); } /** 开始分页 */ public static <E> Page<E> startPage(int pageNum, int pageSize, boolean count, Boolean reasonable, Boolean pageSizeZero) { Page<E> page = new Page<E>(pageNum, pageSize, count); page.setReasonable(reasonable); page.setPageSizeZero(pageSizeZero); Page<E> oldPage = getLocalPage(); if (oldPage != null && oldPage.isOrderByOnly()) { page.setOrderBy(oldPage.getOrderBy()); } setLocalPage(page); return page; } } 复制 使用分页参数代码: /** 虚辅助方言类 */ public abstract class AbstractHelperDialect extends AbstractDialect implements Constant { /** 获取本地分页 */ public <T> Page<T> getLocalPage() { return PageHelper.getLocalPage(); } /** 获取分页SQL */ @Override public String getPageSql(MappedStatement ms, BoundSql boundSql, Object parameterObject, RowBounds rowBounds, CacheKey pageKey) { String sql = boundSql.getSql(); Page page = getLocalPage(); String orderBy = page.getOrderBy(); if (StringUtil.isNotEmpty(orderBy)) { pageKey.update(orderBy); sql = OrderByParser.converToOrderBySql(sql, orderBy); } if (page.isOrderByOnly()) { return sql; } return getPageSql(sql, page, pageKey); } ... } 复制 使用分页插件代码: /** 查询用户函数 */ public PageInfo<UserDO> queryUser(UserQuery userQuery, int pageNum, int pageSize) { PageHelper.startPage(pageNum, pageSize); List<UserDO> userList = userDAO.queryUser(userQuery); PageInfo<UserDO> pageInfo = new PageInfo<>(userList); return pageInfo; } 复制 如果要把分页参数通过函数参数逐级传给查询语句,除非修改 MyBatis 相关接口函数,否则是不可能实现的。 保存非线程安全对象,避免多线程并发调用 在写日期格式化工具函数时,首先想到的写法如下: /** 日期模式 */ private static final String DATE_PATTERN = "yyyy-MM-dd"; /** 格式化日期函数 */ public static String formatDate(Date date) { return new SimpleDateFormat(DATE_PATTERN).format(date); } 复制 其中,每次调用都要初始化 DateFormat 导致性能较低,把 DateFormat 定义成常量后的写法如下: /** 日期格式 */ private static final DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd"); /** 格式化日期函数 */ public static String formatDate(Date date) { return DATE_FORMAT.format(date); } 复制 由于 SimpleDateFormat 是非线程安全的,当多线程同时调用 formatDate 函数时,会导致返回结果与预期不一致。如果采用 ThreadLocal 定义线程专有对象,优化后的代码如下: /** 本地日期格式 */ private static final ThreadLocal<DateFormat> LOCAL_DATE_FORMAT = new ThreadLocal<DateFormat>() { @Override protected DateFormat initialValue() { return new SimpleDateFormat("yyyy-MM-dd"); } }; /** 格式化日期函数 */ public static String formatDate(Date date) { return LOCAL_DATE_FORMAT.get().format(date); } 复制 这是在没有线程安全的日期格式化工具类之前的实现方法。在 JDK8 以后,建议使用 DateTimeFormatter 代替 SimpleDateFormat ,因为 SimpleDateFormat 是线程不安全的,而 DateTimeFormatter 是线程安全的。当然,也可以采用第三方提供的线程安全日期格式化函数,比如 apache 的 DateFormatUtils 工具类。 注意:ThreadLocal 有一定的内存泄露的风险,尽量在业务代码结束前调用 remove 函数进行数据清除。 使用 Pair 实现成对结果的返回 在 C/C++ 语言中, Pair (对)是将两个数据类型组成一个数据类型的容器,比如 std::pair 。 Pair 主要有两种用途: 1、把 key 和 value 放在一起成对处理,主要用于 Map 中返回名值对,比如 Map 中的 Entry 类; 2、当一个函数需要返回两个结果时,可以使用 Pair 来避免定义过多的数据模型类。 第一种用途比较常见,这里主要说明第二种用途。 定义模型类实现成对结果的返回 函数实现代码: /** 点和距离类 */ @Setter @Getter @ToString @AllArgsConstructor public static class PointAndDistance { /** 点 */ private Point point; /** 距离 */ private Double distance; } /** 获取最近点和距离 */ public static PointAndDistance getNearestPointAndDistance(Point point, Point[] points) { // 检查点数组为空 if (ArrayUtils.isEmpty(points)) { return null; } // 获取最近点和距离 Point nearestPoint = points[0]; double nearestDistance = getDistance(point, points[0]); for (int i = 1; i < points.length; i++) { double distance = getDistance(point, point[i]); if (distance < nearestDistance) { nearestDistance = distance; nearestPoint = point[i]; } } // 返回最近点和距离 return new PointAndDistance(nearestPoint, nearestDistance); } 复制 函数使用案例: Point point = ...; Point[] points = ...; PointAndDistance pointAndDistance = getNearestPointAndDistance(point, points); if (Objects.nonNull(pointAndDistance)) { Point point = pointAndDistance.getPoint(); Double distance = pointAndDistance.getDistance(); ... } 复制 使用 Pair 类实现成对结果的返回 在 JDK 中,没有提供原生的 Pair 数据结构,也可以使用 Map::Entry 代替。不过, Apache 的 commons-lang3 包中的 Pair 类更为好用,下面便以 Pair 类进行举例说明。 函数实现代码: /** 获取最近点和距离 */ public static Pair<Point, Double> getNearestPointAndDistance(Point point, Point[] points) { // 检查点数组为空 if (ArrayUtils.isEmpty(points)) { return null; } // 获取最近点和距离 Point nearestPoint = points[0]; double nearestDistance = getDistance(point, points[0]); for (int i = 1; i < points.length; i++) { double distance = getDistance(point, point[i]); if (distance < nearestDistance) { nearestDistance = distance; nearestPoint = point[i]; } } // 返回最近点和距离 return Pair.of(nearestPoint, nearestDistance); } 复制 函数使用案例: Point point = ...; Point[] points = ...; Pair<Point, Double> pair = getNearestPointAndDistance(point, points); if (Objects.nonNull(pair)) { Point point = pair.getLeft(); Double distance = pair.getRight(); ... } 复制 定义 Enum 类实现取值和描述 在 C++、Java 等计算机编程语言中,枚举类型(Enum)是一种特殊数据类型,能够为一个变量定义一组预定义的常量。在使用枚举类型的时候,枚举类型变量取值必须为其预定义的取值之一。 用 class 关键字实现的枚举类型 在 JDK5 之前, Java 语言不支持枚举类型,只能用类(class)来模拟实现枚举类型。 /** 订单状态枚举 */ public final class OrderStatus { /** 属性相关 */ /** 状态取值 */ private final int value; /** 状态描述 */ private final String description; /** 常量相关 */ /** 已创建(1) */ public static final OrderStatus CREATED = new OrderStatus(1, "已创建"); /** 进行中(2) */ public static final OrderStatus PROCESSING = new OrderStatus(2, "进行中"); /** 已完成(3) */ public static final OrderStatus FINISHED = new OrderStatus(3, "已完成"); /** 构造函数 */ private OrderStatus(int value, String description) { this.value = value; this.description = description; } /** 获取状态取值 */ public int getValue() { return value; } /** 获取状态描述 */ public String getDescription() { return description; } } 复制 用 enum 关键字实现的枚举类型 JDK5 提供了一种新的类型—— Java 的枚举类型,关键字 enum 可以将一组具名的值的有限集合创建为一种新的类型,而这些具名的值可以作为常量使用,这是一种非常有用的功能。 /** 订单状态枚举 */ public enum OrderStatus { /** 常量相关 */ /** 已创建(1) */ CREATED(1, "已创建"), /** 进行中(2) */ PROCESSING(2, "进行中"), /** 已完成(3) */ FINISHED(3, "已完成"); /** 属性相关 */ /** 状态取值 */ private final int value; /** 状态描述 */ private final String description; /** 构造函数 */ private OrderStatus(int value, String description) { this.value = value; this.description = description; } /** 获取状态取值 */ public int getValue() { return value; } /** 获取状态描述 */ public String getDescription() { return description; } } 复制 其实,Enum 类型就是一个语法糖,编译器帮我们做了语法的解析和编译。通过反编译,可以看到 Java 枚举编译后实际上是生成了一个类,该类继承了 java.lang.Enum<E> ,并添加了 values()、valueOf() 等枚举类型通用方法。 定义 Holder 类实现参数的输出 在很多语言中,函数的参数都有输入(in)、输出(out)和输入输出(inout)之分。在 C/C++ 语言中,可以用对象的引用(&)来实现函数参数的输出(out)和输入输出(inout)。但在 Java 语言中,虽然没有提供对象引用类似的功能,但是可以通过修改参数的字段值来实现函数参数的输出(out)和输入输出(inout)。这里,我们叫这种输出参数对应的数据结构为Holder(支撑)类。 Holder 类实现代码: /** 长整型支撑类 */ @Getter @Setter @ToString public class LongHolder { /** 长整型取值 */ private long value; /** 构造函数 */ public LongHolder() {} /** 构造函数 */ public LongHolder(long value) { this.value = value; } } 复制 Holder 类使用案例: /** 静态常量 */ /** 页面数量 */ private static final int PAGE_COUNT = 100; /** 最大数量 */ private static final int MAX_COUNT = 1000; /** 处理过期订单 */ public void handleExpiredOrder() { LongHolder minIdHolder = new LongHolder(0L); for (int pageIndex = 0; pageIndex < PAGE_COUNT; pageIndex++) { if (!handleExpiredOrder(pageIndex, minIdHolder)) { break; } } } /** 处理过期订单 */ private boolean handleExpiredOrder(int pageIndex, LongHolder minIdHolder) { // 获取最小标识 Long minId = minIdHolder.getValue(); // 查询过期订单(按id从小到大排序) List<OrderDO> orderList = orderDAO.queryExpired(minId, MAX_COUNT); if (CollectionUtils.isEmpty(taskTagList)) { return false; } // 设置最小标识 int orderSize = orderList.size(); minId = orderList.get(orderSize - 1).getId(); minIdHolder.setValue(minId); // 依次处理订单 for (OrderDO order : orderList) { ... } // 判断还有订单 return orderSize >= PAGE_SIZE; } 复制 其实,可以实现一个泛型支撑类,适用于更多的数据类型。 定义 Union 类实现数据体的共存 在 C/C++ 语言中,联合体(union),又称共用体,类似结构体(struct)的一种数据结构。联合体(union)和结构体(struct)一样,可以包含很多种数据类型和变量,两者区别如下: 1、结构体(struct)中所有变量是“共存”的,同时所有变量都生效,各个变量占据不同的内存空间; 2、联合体(union)中是各变量是“互斥”的,同时只有一个变量生效,所有变量占据同一块内存空间。 当多个数据需要共享内存或者多个数据每次只取其一时,可以采用联合体(union)。 在Java语言中,没有联合体(union)和结构体(struct)概念,只有类(class)的概念。众所众知,结构体(struct)可以用类(class)来实现。其实,联合体(union)也可以用类(class)来实现。但是,这个类不具备“多个数据需要共享内存”的功能,只具备“多个数据每次只取其一”的功能。 这里,以微信协议的客户消息为例说明。根据我多年来的接口协议封装经验,主要有以下两种实现方式。 使用函数方式实现 Union Union 类实现: /** 客户消息类 */ @ToString public class CustomerMessage { /** 属性相关 */ /** 消息类型 */ private String msgType; /** 目标用户 */ private String toUser; /** 共用体相关 */ /** 新闻内容 */ private News news; ... /** 常量相关 */ /** 新闻消息 */ public static final String MSG_TYPE_NEWS = "news"; ... /** 构造函数 */ public CustomerMessage() {} /** 构造函数 */ public CustomerMessage(String toUser) { this.toUser = toUser; } /** 构造函数 */ public CustomerMessage(String toUser, News news) { this.toUser = toUser; this.msgType = MSG_TYPE_NEWS; this.news = news; } /** 清除消息内容 */ private void removeMsgContent() { // 检查消息类型 if (Objects.isNull(msgType)) { return; } // 清除消息内容 if (MSG_TYPE_NEWS.equals(msgType)) { news = null; } else if (...) { ... } msgType = null; } /** 检查消息类型 */ private void checkMsgType(String msgType) { // 检查消息类型 if (Objects.isNull(msgType)) { throw new IllegalArgumentException("消息类型为空"); } // 比较消息类型 if (!Objects.equals(msgType, this.msgType)) { throw new IllegalArgumentException("消息类型不匹配"); } } /** 设置消息类型函数 */ public void setMsgType(String msgType) { // 清除消息内容 removeMsgContent(); // 检查消息类型 if (Objects.isNull(msgType)) { throw new IllegalArgumentException("消息类型为空"); } // 赋值消息内容 this.msgType = msgType; if (MSG_TYPE_NEWS.equals(msgType)) { news = new News(); } else if (...) { ... } else { throw new IllegalArgumentException("消息类型不支持"); } } /** 获取消息类型 */ public String getMsgType() { // 检查消息类型 if (Objects.isNull(msgType)) { throw new IllegalArgumentException("消息类型无效"); } // 返回消息类型 return this.msgType; } /** 设置新闻 */ public void setNews(News news) { // 清除消息内容 removeMsgContent(); // 赋值消息内容 this.msgType = MSG_TYPE_NEWS; this.news = news; } /** 获取新闻 */ public News getNews() { // 检查消息类型 checkMsgType(MSG_TYPE_NEWS); // 返回消息内容 return this.news; } ... } 复制 Union 类使用: String accessToken = ...; String toUser = ...; List<Article> articleList = ...; News news = new News(articleList); CustomerMessage customerMessage = new CustomerMessage(toUser, news); wechatApi.sendCustomerMessage(accessToken, customerMessage); 复制 主要优缺点: 优点:更贴近 C/C++ 语言的联合体(union); 缺点:实现逻辑较为复杂,参数类型验证较多。 使用继承方式实现 Union Union 类实现: /** 客户消息类 */ @Getter @Setter @ToString public abstract class CustomerMessage { /** 属性相关 */ /** 消息类型 */ private String msgType; /** 目标用户 */ private String toUser; /** 常量相关 */ /** 新闻消息 */ public static final String MSG_TYPE_NEWS = "news"; ... /** 构造函数 */ public CustomerMessage(String msgType) { this.msgType = msgType; } /** 构造函数 */ public CustomerMessage(String msgType, String toUser) { this.msgType = msgType; this.toUser = toUser; } } /** 新闻客户消息类 */ @Getter @Setter @ToString(callSuper = true) public class NewsCustomerMessage extends CustomerMessage { /** 属性相关 */ /** 新闻内容 */ private News news; /** 构造函数 */ public NewsCustomerMessage() { super(MSG_TYPE_NEWS); } /** 构造函数 */ public NewsCustomerMessage(String toUser, News news) { super(MSG_TYPE_NEWS, toUser); this.news = news; } } 复制 Union 类使用: String accessToken = ...; String toUser = ...; List<Article> articleList = ...; News news = new News(articleList); CustomerMessage customerMessage = new NewsCustomerMessage(toUser, news); wechatApi.sendCustomerMessage(accessToken, customerMessage); 复制 主要优缺点: 优点:使用虚基类和子类进行拆分,各个子类对象的概念明确; 缺点:与 C/C++ 语言的联合体(union)差别大,但是功能上大体一致。 在 C/C++ 语言中,联合体并不包括联合体当前的数据类型。但在上面实现的 Java 联合体中,已经包含了联合体对应的数据类型。所以,从严格意义上说, Java 联合体并不是真正的联合体,只是一个具备“多个数据每次只取其一”功能的类。 使用泛型屏蔽类型的差异性 在 C++ 语言中,有个很好用的模板(template)功能,可以编写带有参数化类型的通用版本,让编译器自动生成针对不同类型的具体版本。而在 Java 语言中,也有一个类似的功能叫泛型(generic)。在编写类和方法的时候,一般使用的是具体的类型,而用泛型可以使类型参数化,这样就可以编写更通用的代码。 许多人都认为, C++ 模板(template)和 Java 泛型(generic)两个概念是等价的,其实实现机制是完全不同的。 C++ 模板是一套宏指令集,编译器会针对每一种类型创建一份模板代码副本; Java 泛型的实现基于"类型擦除"概念,本质上是一种进行类型限制的语法糖。 泛型类 以支撑类为例,定义泛型的通用支撑类: /** 通用支撑类 */ @Getter @Setter @ToString public class GenericHolder<T> { /** 通用取值 */ private T value; /** 构造函数 */ public GenericHolder() {} /** 构造函数 */ public GenericHolder(T value) { this.value = value; } } 复制 泛型接口 定义泛型的数据提供者接口: /** 数据提供者接口 */ public interface DataProvider<T> { /** 获取数据函数 */ public T getData(); } 复制 泛型方法 定义泛型的浅拷贝函数: /** 浅拷贝函数 */ public static <T> T shallowCopy(Object source, Class<T> clazz) throws BeansException { // 判断源对象 if (Objects.isNull(source)) { return null; } // 新建目标对象 T target; try { target = clazz.newInstance(); } catch (Exception e) { throw new BeansException("新建类实例异常", e); } // 拷贝对象属性 BeanUtils.copyProperties(source, target); // 返回目标对象 return target; } 复制 泛型通配符 泛型通配符一般是使用"?"代替具体的类型实参,可以把"?"看成所有类型的父类。当具体类型不确定的时候,可以使用泛型通配符 "?";当不需要使用类型的具体功能,只使用Object类中的功能时,可以使用泛型通配符 "?"。 /** 打印取值函数 */ public static void printValue(GenericHolder<?> holder) { System.out.println(holder.getValue()); } /** 主函数 */ public static void main(String[] args) { printValue(new GenericHolder<>(12345)); printValue(new GenericHolder<>("abcde")); } 复制 在 Java 规范中,不建议使用泛型通配符"?",上面函数可以改为: /** 打印取值函数 */ public static <T> void printValue(GenericHolder<T> holder) { System.out.println(holder.getValue()); } 复制 泛型上下界 在使用泛型的时候,我们还可以为传入的泛型类型实参进行上下界的限制,如:类型实参只准传入某种类型的父类或某种类型的子类。泛型上下界的声明,必须与泛型的声明放在一起 。 上界通配符(extends): 上界通配符为 ”extends ”,可以接受其指定类型或其子类作为泛参。其还有一种特殊的形式,可以指定其不仅要是指定类型的子类,而且还要实现某些接口。例如: List<? extends A> 表明这是 A 某个具体子类的 List ,保存的对象必须是A或A的子类。对于 List<? extends A> 列表,不能添加 A 或 A 的子类对象,只能获取A的对象。 下界通配符(super): 下界通配符为”super”,可以接受其指定类型或其父类作为泛参。例如:List<? super A> 表明这是 A 某个具体父类的 List ,保存的对象必须是 A 或 A 的超类。对于 List<? super A> 列表,能够添加 A 或 A 的子类对象,但只能获取 Object 的对象。 PECS(Producer Extends Consumer Super)原则:作为生产者提供数据(往外读取)时,适合用上界通配符(extends);作为消费者消费数据(往里写入)时,适合用下界通配符(super)。 在日常编码中,比较常用的是上界通配符(extends),用于限定泛型类型的父类。例子代码如下: /** 数字支撑类 */ @Getter @Setter @ToString public class NumberHolder<T extends Number> { /** 通用取值 */ private T value; /** 构造函数 */ public NumberHolder() {} /** 构造函数 */ public NumberHolder(T value) { this.value = value; } } /** 打印取值函数 */ public static <T extends Number> void printValue(GenericHolder<T> holder) { System.out.println(holder.getValue()); } ———————————————— 版权声明:本文为CSDN博主「栾还是恋」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/2301_78008478/article/details/131367225
-
@RequestParam 和 @param 都是用于在代码中提供参数的注解,但在不同的编程语言和框架中的具体使用方式和含义可能会有所不同。@RequestParam: @RequestParam 是 Spring 框架中的一个注解,用于映射 HTTP 请求中的参数到方法的参数上。通常在处理 HTTP 请求时,我们需要从请求的 URL 或请求体中获取参数值,然后将其传递给方法进行处理。使用 @RequestParam 注解可以方便地将请求中的参数绑定到方法的参数上。使用 @RequestParam 注解时,通常需要指定参数的名称,可以通过参数名进行匹配和绑定。示例(Java Spring):@GetMapping("/hello") public String sayHello(@RequestParam("name") String name) { return "Hello, " + name + "!"; }在上述示例中,@RequestParam("name") 表示从请求中获取名为 "name" 的参数,并将其绑定到方法的 name 参数上。@param: @param 是一种用于方法注释的标记,用于说明方法参数的含义和用途。通常在编写代码时,我们会为方法添加注释,来提供方法的说明和参数的作用。使用 @param 注解可以清晰地说明方法参数的含义,以便开发者更好地理解和使用方法。示例(Java):/** * 计算两个数的和 * * @param a 第一个数 * @param b 第二个数 * @return 两个数的和 */ public int sum(int a, int b) { return a + b; }在上述示例中,@param a 第一个数 和 @param b 第二个数 表示方法 sum 的两个参数的含义和作用。需要注意的是,@param 注解是一种注释方式,它不会被直接编译和执行,而是用于提供开发文档的信息和提示。
-
@Data 是一个注解,它通常用于简化 Java 类的编写。它是 Lombok 提供的一个注解,通过在类上使用 @Data 注解,可以自动为类生成一些常用的方法,如 getter、setter、toString、equals 和 hashCode 等。使用 @Data 注解可以避免手动编写这些常用方法,减少了代码的冗余,提高了开发效率。以下是 @Data 注解提供的功能:Getter 和 Setter 方法:使用 @Data 注解后,会自动为类的每个字段生成对应的 getter 和 setter 方法。toString 方法:@Data 注解会为类生成一个默认的 toString 方法,用于返回类的字符串表示。该方法会包含类的所有字段和它们的值。equals 和 hashCode 方法:@Data 注解会生成 equals 和 hashCode 方法,用于比较对象的相等性。这些方法会考虑类的所有字段,并根据它们的值计算结果。默认构造方法:若类没有显式定义构造方法,@Data 注解会为类生成一个默认的无参构造方法。使用示例:import lombok.Data; @Data public class MyClass { private String name; private int age; // 可以添加其他自定义方法 }使用 @Data 注解后,编译器会自动为 MyClass 类生成 getter、setter、toString、equals、hashCode 和默认构造方法。需要注意的是,当使用 @Data 注解时,需要事先在项目中引入 Lombok 的依赖,以便让编译器在编译时自动处理注解并生成相应的代码。
-
Dubbo和Feign都是Java中的服务调用框架,但它们之间有一些关键的区别。本文将介绍Dubbo和Feign的主要特点、适用场景以及优缺点。 1. 功能定位 Dubbo是一个高性能、轻量级的RPC(远程过程调用)框架,主要用于微服务架构中的服务治理。它提供了负载均衡、容错、服务注册与发现等功能,帮助开发者快速构建分布式系统。 Feign是一个声明式的Web服务客户端,它使得编写HTTP客户端变得更简单。Feign主要关注于简化HTTP请求的发送,提供了丰富的注解来描述服务接口和请求参数,而不需要关心底层的网络通信细节。 2. 适用场景 Dubbo适用于微服务架构中的服务治理,例如在分布式系统中实现服务的调用、负载均衡、容错等。Dubbo可以帮助开发者更好地管理微服务之间的依赖关系,提高系统的可扩展性和可靠性。 Feign适用于RESTful风格的微服务架构,特别是在Spring Cloud环境中。Feign可以作为Spring Boot应用程序的一部分,通过简单的注解配置即可实现对其他服务的调用。Feign特别适合于构建基于HTTP的客户端,例如调用其他服务的API接口。 3. 优缺点 Dubbo的优点: - 性能较高:Dubbo采用了多种优化策略,如线程池、序列化优化等,提高了服务的响应速度和吞吐量。 - 功能丰富:Dubbo提供了丰富的功能模块,如负载均衡、容错、服务注册与发现等,方便开发者进行微服务治理。 - 社区活跃:Dubbo拥有庞大的用户群体和活跃的社区支持,问题解决较快。 Dubbo的缺点: - 学习成本较高:Dubbo的概念和技术较为复杂,需要一定的学习成本才能掌握。 - 配置繁琐:Dubbo的配置文件较多,需要花费一定时间进行配置。 Feign的优点: - 简单易用:Feign的注解配置方式使得编写HTTP客户端变得非常简单,无需关心底层的网络通信细节。 - 集成方便:Feign可以很好地与Spring Cloud等微服务框架集成,方便开发者进行微服务调用。 - 社区活跃:Feign拥有活跃的社区支持,问题解决较快。 Feign的缺点: - 不适用于所有场景:Feign主要适用于RESTful风格的微服务架构,对于非RESTful的服务调用可能不太适用。 - 功能有限:虽然Feign提供了丰富的注解,但其功能相对于Dubbo来说还是有限的,无法满足所有的需求。 总结:Dubbo和Feign都是Java中优秀的服务调用框架,它们各自有不同的特点和适用场景。在实际项目中,可以根据具体需求选择合适的框架进行使用。
-
1. 相关概念本地缓存:程序运行时,为了提高运行的速度,CPU可以不直接跟内存进行通信,而是先将内存中的数据读到内部缓存,然后再进行操作。这样会提高效率,但是我们不知道本地缓存中的修改何时会回写到共享内存中;内存可见性:可见性的意思是当一个线程修改了一个共享变量时,其他的线程能够读到这个修改的值。(这句话我当初理解的有问题,导致我在修改的代码的时候掉进一个很大的坑里) ;共享内存:JVM在执行Java程序时会 把它管理的内存划分成几个不同的数据区域。这些区域都有着各自的用途,以及创建和销毁的时间。具体其中有部分数据区域是所有线程共享的,这部分数据区域称之为共享内存,共享内存中的变量叫做共享变量。 其中堆是最大的一块共享内存,也是垃圾收集器重点管理的区域。基本上我们所有的实例对象都是在堆上创建的。而方法区主要是用来存储加载过的类信息、常量和静态变量等。内存可见性就是针对于共享内存而言的。 缓存一致性协议:简单来说,就是在多核CPU中一个共享变量被其中一个线程修改且回写到内存中之后,其他的CPU中缓存的这个共享变量就会被置为invalid。在下次读的时候就会更新这个缓存。 原子操作:不可被中断的一个或一系列操作。2. 内存可见性的相关实现2.1 volatilevolatile相较于synchronized而言,使用的代价和成本更低,因为它不会因为线程上下文的切换和调度。 volatile的实现原理当我们使用volatile关键字修饰一个变量之后,在程序运行时,它会导致以下两件事:1. 本地缓存的数据会立马回写到共享内存中; 2. 这个回写会导致其他CPU缓存中这个内存地址对应的数据无效,在下次读的时候就需要更新本地缓存。 volatile的内存语义使用volatile 修饰变量,实际是就对变量的单个读/单个写做了同步。相信很多人都只到volatile++不是原子操作。下面我们通过一些等效代码来分析原因。 单个读写首先我们来理解一下单个读写是什么意思。 public class AtomicVariable { volatile int v; //单次读 public int getA() { return v; } //单次写 public void setA(int v) { this.v = v; }}这里的代码等效于: public class AtomicVariable { volatile int v; //单次读 public synchronized int getA() { return v; } //单次写 public synchronized void setA(int v) { this.v = v; }}通过以上的代码我们可以看出来,针对于volatile修饰的变量,单次的读写,其原子性是可以得到保证的。也就是说,在对于单个线程而言,当读volatile变量时,这个变量肯定是最近修改的值。在写这个变量时,它可以保证下次线程读的这个值是最新的。那对于volatile++的操作为什么不行呢? 首先我们得明白volatile++是一个复合操作,它可以转换成volatile = volatile + 1。即先读了volatile,然后进行加1操作,在写给volatile。这里只有第一步和第三步可以保证原子性,而第二步做不到。我们可以通过代码展示一下。 public class AtomicVariableTest { public volatile int a = 0; @Test public void atomicOperation() throws InterruptedException { List<Thread> threads = new ArrayList<Thread>(); for (int i = 0; i < 4; i++) { threads.add(new Thread(() -> { for (int j = 0; j < 100000; j++) { int tmp = a + 1; a = tmp; } })); } for(Thread t: threads){ t.start(); } for(Thread t: threads){ t.join(); } }}399748399749399750399751399752399753399754399755399756399757从结果来看,这里的读写操作并没有得到同步。 2.2 Synchronizedsynchronized主要是通过锁来实现同步的,而在java中每一个多对象都可以作为锁。具体的表现有以下几种形式:* 对于普通同步方法,锁是当前实例对象;public synchronized void set(int v) { ... }对于静态同步方法,锁是当前类的Class对象;public synchronized static void staticMethod(){ ... }对于同步方法块, 所以Synchronized括号里配置的对象。``` Object ob = new Object(); synchronized(ob){ .... }当一个线程试图运行同步代码块时,它必须先获得锁。在同步代码块执行完之后或抛出异常之后,它必须释放锁。 synchronized用的锁是存储在对象头中的,而锁的状态又分为以下几种: 1. 重量级锁状态2. 轻量级锁状态3. 偏向锁状态4. 无锁状态 锁的级别依次递减,锁可以升级但是不可以降级。关于这几种状态,与本文主题没有太大关联,感兴趣的小伙伴可以再去了解。 synchronized可以保证同步方法或同步代码块一次只能由一个线程访问执行,同时能够保证对共享变量操作的可见性。更改上面的代码:public class AtomicVariableTest { public int a = 0;@Test public void atomicOperation() throws InterruptedException {List<Thread> threads = new ArrayList<Thread>();for (int i = 0; i < 4; i++) { threads.add(new Thread(() -> { for (int j = 0; j < 100000; j++) { increment(); System.out.println(Thread.currentThread().getName() + " " + a); } }));}for(Thread t: threads){ t.start();}for(Thread t: threads){ t.join();}}public synchronized void increment(){ a++; } }这里的代码便得到了同步,结果为400000. **小结**:上面大概说了一下volatile与sychronized,现在做一下总结。首先,相同点大致有两点1. 二者都可以保证可见性,即当前线程的修改,对其他线程下次的读写可见;2. 二者对指令的重排序都有一定的限制。 而不同点则有以下几个方面: 1. sychronized 可以做到同步(操作的原子性),而仅靠volatile做不到;2. sychronized 可以修饰代码块或者方法,而volatile只能修饰单个变量;3. volatile不会造成线程阻塞,而synchronzied会。## 3. 原子操作的实现原理 CPU实现原子操作主要有两种方式: **1. 总线加锁****2. 缓存加锁**### 3.1 使用总线锁保证原子性  我们通过上面的图理解一下总线锁是如何实现原子性的。假设现在共享内存中有一个值a = 1,CPU1到CPU3都要执行`int tmp =a + 1; a = tmp; `这样的操作。那么共享内存中的a就会被多个处理器同时进行操作。这样的多改写操作就不是原子性的,操作完之后的值可能不是我们想要的。原因很简单,多个CPU都从自己的本地缓存中读取了变量a,然后进行改写操作,而本地缓存中的值可能还是旧值。改写完之后再分别回写,那么内存中值就是最后一次写的。 而总线锁就是保证了一个CPU在对共享内存中的变量进行读改写操作时,其他的CPU不能操作缓存了该共享变量内存地址的缓存。上列中,就是一个CPU对共享变量a进行了读改写操作,其他的CPU就不能操作本地缓存中的a。 总线锁就是在CPU1对变量a进行读改写操作时,会在总线输出一个LOCK #信号,之后共享内存就会被CPU1**独占**,其他处理器的总线请求都会被阻塞。这样子就可以保证对变量的读改写操作是原子性的了。但是使用这种方式会导致对于部分变量的读改写,会让其他CPU无法读写共享内存中的其他变量,开销过大。 ### 3.2 使用缓存锁来保证原子性 其实对于一些频繁操作的内存,各个CPU会将其缓存在本地缓存中。那么对于这些内存或变量,我们只要本证他的读改写在各个CPU的缓存之间是原子性的即可,并不需要声明总线锁,带来过大的开销。 **缓存锁**是通过内存一致性协议来实现的,即当某个CPU修改了共享内存中个某个变量并回写之后,其他CPU会让缓存了这块内存数据的缓存失效,在下次读的时候就是最新的值了。 ## 4. Java实现原子操作 所谓原子操作就是指一个线程在执行一系列操作的时候,需要保证其使用的共享变量不会给其他的线程读改写。Java实现原子操作的方式有两种,一个是使用**锁**,另一个是使用**循环CAS**。 ### 4.1 使用循环CAS来实现原子操作Java的中的CAS(Compare And Swap)操作可以保证操作的原子性。CAS操作就是用期望值跟旧值进行比较,如果相同则会将旧值更新成新值,且保证这一过程的原子性。根据这一特性,Java可以实现操作的原子性。且看先下面的Demo: ```java public class CommonTest { private AtomicInteger a = new AtomicInteger(0); @Test public void test() throws InterruptedException { List<Thread> threads = new ArrayList<Thread>(); for (int i = 0; i < 3; i++) { threads.add(new Thread(new CounterRunnable())); } for (Thread t : threads) { t.start(); } for (Thread t : threads) { t.join(); } } class CounterRunnable implements Runnable { @Override public void run() { for (int i = 0; i < 100000; i++) { increment(); } } private void increment() { while (true) { int i = a.get(); boolean changed = a.compareAndSet(i, ++i); if (changed) { System.out.println(a.get()); break; } } } }}输出结果为300000相较于锁,CAS可以高效地实现原子操作,但是在使用它的时候也得注意一些可能遇到的问题。 CAS 实现原子操作可能遇到的三大问题ABA问题:ABA问题就是当状态连续改变之后,CPU可能感知不到,例如变量从A到B,然后再从B变成A。这个时候,实际上变量是改变过了,但是CAS进行检查的时候会发现他的值并没有改变。循环开销过大:在上面的代码样例中,如果一直比较失败的话,线程会一直在那里执行,资源也不会释放,这样的开销是比较大的;只能保证一个变量的原子操作:由CAS的使用方式,我们可以看出来CAS只能对单个变量进行原子性的操作。 4.2 通过锁实现原子操作Java锁机制保证了只有获得了锁的线程才能执行锁定的内存区域,同时能保证操作的原子性。但是相比较与CAS而言,一般情况下他的开销更大。使用时需要慎重。 参考:《Java 并发编程的艺术》 方腾飞,魏鹏,程晓明 《深入理解Java虚拟机》 陶志华Java多线程之原子操作转自 https://zhuanlan.zhihu.com/p/96244543
-
XML文件生成:import java.io.File;import java.io.FileOutputStream;import org.jdom2.Document;import org.jdom2.Element;import org.jdom2.output.Format;import org.jdom2.output.XMLOutputter;public class JDOMCreat {public static void main(String[] args) {Element bookstore = new Element("bookstore");Element book = new Element("book");book.setAttribute("id","1");book.addContent(new Element("name").setText("哈利波特"));book.addContent(new Element("author").setText("罗琳"));book.addContent(new Element("year").setText("2018"));bookstore.addContent(book);//新建文档Document doc = new Document(bookstore);//读取格式Format format = Format.getCompactFormat();format.setEncoding("UTF-8");//设置xml文件缩进4个空格format.setIndent(" ");//建一个xml输出工厂XMLOutputter xmlout = new XMLOutputter(format);try {//将其写好的文本给工厂xmlout.output(doc, new FileOutputStream("D:/book.xml"));} catch (Exception e) {// TODO: handle exception}}}XML文件读取 import java.io.File;import java.util.List;import org.jdom2.Document;import org.jdom2.Element;import org.jdom2.input.SAXBuilder;public class JdomSax {public static void main(String[] args) {//新建构造器解析xmlSAXBuilder sax = new SAXBuilder();Document doc;try {//获取xml文档doc = sax.build(new File("D:/book.xml"));//获得根节点Element bookstore = doc.getRootElement();//获得根节点下的节点数据List<Element> books = bookstore.getChildren();for(Element book: books) {//获得属性值book.getAttributeValue("id");List<Element> bookitems = book.getChildren();for(Element item:bookitems) {System.out.println(item.getName()); System.out.println(item.getText());}}} catch (Exception e) {// TODO: handle exception}}}原文链接:https://blog.csdn.net/HappySSweet/article/details/81668140
-
一、解析xml内容xml文件内容:<?xml version="1.0" encoding="utf-8"?> <RETVAL success='true'> <Shop><sid>1</sid><name>北京鑫和易通贸易有限公司</name></Shop> <Shop><sid>2</sid><name>张家口市金九福汽车服务有限公司</name></Shop> </RETVAL>解析的java代码import org.jdom2.Document; import org.jdom2.Element; import org.jdom2.input.SAXBuilder; import org.xml.sax.InputSource; public Class Test{ public static List test() throws Exception{ List result = new ArrayList(); SAXBuilder builder = new SAXBuilder(); StringReader read = new StringReader(xmlDoc); InputSource source = new InputSource(read); Document document = builder.build(source); Element root = document.getRootElement();// 获得根节点 String ifSuccess = root.getAttributeValue("success"); if (!"true".equals(ifSuccess)) {// 不成功直接返回 throw new Exception("从接口获取全部经销商数据时不成功!"); } List<Element> childrenList = root.getChildren(); for (Element e : childrenList) { Map elementMap = new HashMap<String, String>(); elementMap.put("sid", e.getChildText("sid")); // 经销商id elementMap.put("name", e.getChildText("name")); // 经销商名称 result.add(elementMap); } return result; } }二、解析和操作xml文件xml文件内容<?xml version="1.0" encoding="UTF-8"?> <root> <person id="1"> <username>张三</username> <password>123123</password> </person> <person id="2"> <username>1111111112</username> <password>password2</password> </person> </root>JDOM构建document对象的方法Document build(java.io.File file) //This builds a document from the supplied filename. Document build(org.xml.sax.InputSource in) //This builds a document from the supplied input source. Document build(java.io.InputStream in) //This builds a document from the supplied input stream. Document build(java.io.InputStream in, java.lang.String systemId) //This builds a document from the supplied input stream. Document build(java.io.Reader characterStream) //This builds a document from the supplied Reader. Document build(java.io.Reader characterStream, java.lang.String systemId) //This builds a document from the supplied Reader. Document build(java.lang.String systemId) //This builds a document from the supplied URI. Document build(java.net.URL url) //This builds a document from the supplied URL.解析、新增、修改、删除xml节点属性的javapackage test; import java.io.FileWriter; import java.util.List; import org.jdom2.Document; import org.jdom2.Element; import org.jdom2.input.SAXBuilder; import org.jdom2.output.Format; import org.jdom2.output.XMLOutputter; public class Test { /** * @param args * @throws Exception */ public static void main(String[] args) throws Exception { SAXBuilder builder = new SAXBuilder(); //Document doc = builder.build(new File("src/test.xml")); //Document doc = builder.build(new FileInputStream("src/test.xml")); //Document doc = builder.build(new FileReader("src/test.xml")); //Document doc = builder.build(new URL("http://localhost:8080/jdomTest/test.xml")); Document doc = builder.build("src/test.xml"); //readXmlFile(doc); //addXmlElement(doc); //updateXmlElement(doc); deleteXmlElement(doc); } /** * 解析xml文件 * @throws Exception */ public static void readXmlFile(Document doc) throws Exception { Element root = doc.getRootElement(); //获取根元素 System.out.println("---获取第一个子节点和子节点下面的节点信息------"); Element e = root.getChild("person"); //获取第一个子元素 System.out.println("person的属性id的值:"+e.getAttributeValue("id")); //获取person的属性值 for(Element el: e.getChildren()){ System.out.println(el.getText());//第一次输入张三 第二次输出123123 System.out.println(el.getChildText("username"));//这里这么写会是null System.out.println(el.getChildText("password"));//这里这么写会是null } System.out.println("---直接在根节点下就遍历所有的子节点---"); for(Element el: root.getChildren()){ System.out.println(el.getText());//这里输出4行空格 System.out.println(el.getChildText("username"));//输出张三 & 1111111112 System.out.println(el.getChildText("password"));//输出123123 & password2 } } /** * 新增节点 * @throws Exception */ public static void addXmlElement(Document doc) throws Exception { Element root = doc.getRootElement(); //获取根元素 Element newEle = new Element("person");//设置新增的person的信息 newEle.setAttribute("id","88888"); Element chiledEle = new Element("username"); //设置username的信息 chiledEle.setText("addUser"); newEle.addContent(chiledEle); Element chiledEle2 = new Element("password"); //设置password的信息 chiledEle2.setText("addPassword"); newEle.addContent(chiledEle2); root.addContent(newEle); XMLOutputter out = new XMLOutputter(); out.setFormat(Format.getCompactFormat().setEncoding("GBK"));//设置UTF-8编码,理论上来说应该不会有乱码,但是出现了乱码,故设置为GBK out.output(doc, new FileWriter("src/test.xml")); //写文件 } /** * 更新节点 * @param doc * @throws Exception */ public static void updateXmlElement(Document doc) throws Exception { Element root = doc.getRootElement(); //获取根元素 //循环person元素并修改其id属性的值 for(Element el : root.getChildren("person")){ el.setAttribute("id","haha"); } //循环设置username和password的文本值和添加属性 for(Element el: root.getChildren()){ el.getChild("username").setAttribute("nameVal", "add_val").setText("update_text"); el.getChild("password").setAttribute("passVal", "add_val").setText("update_text"); } XMLOutputter out = new XMLOutputter(); out.setFormat(Format.getCompactFormat().setEncoding("GBK"));//设置UTF-8编码,理论上来说应该不会有乱码,但是出现了乱码,故设置为GBK out.output(doc, new FileWriter("src/test.xml")); //写文件 } /** * 删除节点和属性 * @param doc * @throws Exception */ public static void deleteXmlElement(Document doc) throws Exception { Element root = doc.getRootElement(); //获取根元素 List<Element> personList = root.getChildren("person"); //循环person元素,删除person的id为1的id属性以及username子节点 for(Element el : personList){ if(null!=el.getAttribute("id") && "1".equals(el.getAttribute("id").getValue())){ el.removeAttribute("id"); el.removeChild("username"); } } //循环person元素,删除person的id为2的节点 for(int i=0; i<personList.size(); i++){ Element el = personList.get(i); if(null!=el.getAttribute("id") && "2".equals(el.getAttribute("id").getValue())){ root.removeContent(el);//从root节点上删除该节点 //警告:此处删除了节点可能会使personList的长度发生变化而发生越界错误,故不能写成for(int i=0,len=personList.size(); i<len; i++) } } XMLOutputter out = new XMLOutputter(); out.setFormat(Format.getCompactFormat().setEncoding("GBK"));//设置UTF-8编码,理论上来说应该不会有乱码,但是出现了乱码,故设置为GBK out.output(doc, new FileWriter("src/test.xml")); //写文件 } } 原文链接:https://www.cnblogs.com/yangzhilong/p/2955087.html
-
使用io流完成request传参,获取<xml>,具体代码如下:public Object getParameter(HttpServletRequest request, HttpServletResponse response){request.setCharacterEncoding("UTF-8");//设置字符格式response.setCharacterEncoding("UTF-8");response.setContentType("text/html;charset=UTF-8");response.setHeader("Access-Control-Allow-Origin", "*");// achieve input streamInputStream in = request.getInputStream();ByteArrayOutputStream out = new ByteArrayOutputStream();// length of stringbyte[] buffer = new byte[1024];int len = 0;// achieve data from inwhile ((len = in.read(buffer)) != -1) {out.write(buffer, 0, len);}in.close();out.close();//获取流对象内容String content = new String(out.toByteArray(), "utf-8");//获取json对象JSONObject jsonObject = JSONObject.parseObject(XmltoJsonUtil.xml2JSON(content));//获取xml对象JSONObject result_xml = jsonObject.getJSONObject("xml");// 获取<xml>中某个对象具体的值JSONArray return_code = result_xml.getJSONArray("return_code");String code = return_code.get(0).toString();System.out.println("code:"+code);}//<xml>转为json格式public class XmltoJsonUtil {public static String xml2JSON(String xml) {JSONObject obj = new JSONObject();try {InputStream is = new ByteArrayInputStream(xml.getBytes("utf-8"));SAXBuilder sb = new SAXBuilder();Document doc = sb.build(is);Element root = doc.getRootElement();obj.put(root.getName(), iterateElement(root));return obj.toString();} catch (Exception e) {e.printStackTrace();return null;}}eg:输入的<xml>对象为:<xml><return_code>SUCCESS</return_code><appid><![CDATA[wx2421b1c4370ec43b]]></appid><mch_id><![CDATA[10000100]]></mch_id><nonce_str><![CDATA[TeqClE3i0mvn3DrK]]></nonce_str></xml>输出结果为:code:SUCCESS原文链接:https://www.cnblogs.com/qqzhulu/p/10371696.html
-
环境准备 xml文件 <?xml version="1.0" encoding="UTF-8"?> <root> <category id="1000" name="Laptops" discount="20130701:0.99"> <saleDate date="20130630"> <product id="100012" cost="2900" saleCount="40" salePrice="4099"> ThinkPadE430_1 </product> <product id="100013" cost="2900" saleCount="55" salePrice="3399"> LenovoG480A_1 </product> <product id="100014" cost="2900" saleCount="63" salePrice="2900"> HaseeA488-Isd3_1 </product> </saleDate> <saleDate date="20130701"> <product id="100012" cost="2900" saleCount="48" salePrice="4099"> ThinkPadE430_1_1 </product> <product id="100013" cost="2900" saleCount="55" salePrice="3399"> LenovoG480A_1_1 </product> <product id="100014" cost="2900" saleCount="63" salePrice="2900"> HaseeA488-Isd3_1_1 </product> </saleDate> </category> <category id="2000" name="CPU" discount="none"> <saleDate date="20130630"> <product id="100012" cost="2900" saleCount="40" salePrice="4099"> ThinkPadE430_2 </product> <product id="100013" cost="2900" saleCount="55" salePrice="3399"> LenovoG480A </product> <product id="100014" cost="2900" saleCount="63" salePrice="2900"> HaseeA488-Isd3 </product> </saleDate> <saleDate date="20130701"> <product id="100012" cost="2900" saleCount="48" salePrice="4099"> ThinkPadE430 </product> <product id="100013" cost="2900" saleCount="55" salePrice="3399"> LenovoG480A </product> <product id="100014" cost="2900" saleCount="63" salePrice="2900"> HaseeA488-Isd3 </product> </saleDate> </category> </root> 实体类 public class Category { private String id; private String name; private String discount; public Category() { } public Category(String id, String name, String discount) { this.id = id; this.name = name; this.discount = discount; } public String getId() { return id; } public String getName() { return name; } public String getDiscount() { return discount; } public void setId(String id) { this.id = id; } public void setName(String name) { this.name = name; } public void setDiscount(String discount) { this.discount = discount; } @Override public String toString() { return "Category{" + "id='" + id + '\'' + ", name='" + name + '\'' + ", discount='" + discount + '\'' + '}'; } } public class Product { private String id; private String cost; private String saleCount; private String salePrice; private Category category; private String date; public Product() { } public Product(String id, String cost, String saleCount, String salePrice, Category category, String date) { this.id = id; this.cost = cost; this.saleCount = saleCount; this.salePrice = salePrice; this.category = category; this.date = date; } public String getId() { return id; } public void setId(String id) { this.id = id; } public String getCost() { return cost; } public void setCost(String cost) { this.cost = cost; } public String getSaleCount() { return saleCount; } public void setSaleCount(String saleCount) { this.saleCount = saleCount; } public String getSalePrice() { return salePrice; } public void setSalePrice(String salePrice) { this.salePrice = salePrice; } public Category getCategory() { return category; } public void setCategory(Category category) { this.category = category; } public String getDate() { return date; } public void setDate(String date) { this.date = date; } @Override public String toString() { return "Product{" + "id='" + id + '\'' + ", cost='" + cost + '\'' + ", saleCount='" + saleCount + '\'' + ", salePrice='" + salePrice + '\'' + ", category=" + category + ", date='" + date + '\'' + '}'; } } 业务方法 public List<Product> getProducts() { List<Product> list = new ArrayList<>(); try { Reader resourceAsReader = Resources.getResourceAsReader("SalesRecords.xml"); SAXReader saxReader = new SAXReader(); Document document = saxReader.read(resourceAsReader); Element rootElement = document.getRootElement(); Iterator<Element> elementIterator = rootElement.elementIterator(); int count = 0; for (Iterator iterator = elementIterator; iterator.hasNext(); ) { Element element = (Element) iterator.next(); Iterator<Element> elementIterator1 = element.elementIterator(); for (Iterator iterator1 = elementIterator1; iterator1.hasNext(); ) { Element element1 = (Element) iterator1.next(); List<Element> elements = element1.elements(); Attribute attribute = element1.attribute(0); for (Element element2 : elements) { List<Attribute> list_Attributes = element2.attributes(); count++; Product product = new Product(); for (int i = 0; i < list_Attributes.size(); i++) { if (i == 0) product.setId(list_Attributes.get(i).getValue()); if (i == 1) product.setCost(list_Attributes.get(i).getValue()); if (i == 2) product.setSaleCount(list_Attributes.get(i).getValue()); if (i == 3) product.setSalePrice(list_Attributes.get(i).getValue()); if (attribute.getName().equals("date"))product.setDate(attribute.getValue()); if (count <=6){ product.setCategory(getCategorys().get(0)); } else { product.setCategory(getCategorys().get(1)); } } list.add(product); } } } } catch (IOException | DocumentException ioException) { throw new RuntimeException(ioException); } return list; } public List<Category> getCategorys() { List<Category> list = new ArrayList<Category>(); try { Reader resourceAsReader = Resources.getResourceAsReader("SalesRecords.xml"); SAXReader saxReader = new SAXReader(); Document document = saxReader.read(resourceAsReader); Element rootElement = document.getRootElement(); List<Element> elements = rootElement.elements(); list = elements.stream().map(element -> { Category category = new Category(); for (int i = 0; i < element.attributeCount(); i++) { Attribute attribute = element.attribute(i); String value = attribute.getValue(); if (i == 0) { category.setId(value); } else if (i == 1) { category.setName(value); } else if (i == 2) { category.setDiscount(value); } } return category; }).collect(Collectors.toList()); } catch (IOException | DocumentException ioException) { throw new RuntimeException(ioException); } return list; } 计算每天的利润,并且转成json格式 import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import com.baizhi.entity.Product; import com.baizhi.service.SalesRecordsService; import com.baizhi.service.TotalService; import com.google.gson.JsonObject; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.math.BigDecimal; import java.math.RoundingMode; import java.util.List; @Service public class TotalServiceImpl implements TotalService { @Autowired SalesRecordsService salesRecordsService; @Override public JSONObject totalArraySale() { List<Product> products = salesRecordsService.getProducts(); double total_20130630 = 0.0; double total_20130701 = 0.0; double total_20130630_None = 0.0; double total_20130701_None = 0.0; JSONObject jsonObject = new JSONObject(); for (Product product : products) { String discount = product.getCategory().getDiscount(); // count 销售数量 String saleCount = product.getSaleCount(); int count = Integer.parseInt(saleCount); // cost 销售成本价 String costf = product.getCost(); int cost = Integer.parseInt(costf); // salePrice 销售价格 String salePricef = product.getSalePrice(); int salePrice = Integer.parseInt(salePricef); if (product.getDate().equals("20130630")){ if (!discount.equals("none")){ // discountDigital 打折数字 String discountDigital = discount.substring(discount.indexOf(":")+1, discount.length()); total_20130630+=(salePrice-cost)*(1-Double.parseDouble(discountDigital))*count; }else{ total_20130630_None+=(salePrice-cost)*count; } }else if (product.getDate().equals("20130701")){ if (!discount.equals("none")){ // discountDigital 打折数字 String discountDigital = discount.substring(discount.indexOf(":")+1, discount.length()); total_20130701+=(salePrice-cost)*(1-Double.parseDouble(discountDigital))*count; }else{ total_20130701_None+=(salePrice-cost)*count; } } } BigDecimal bigDecimal = new BigDecimal(total_20130630 + total_20130630_None); double sale_7 = new BigDecimal(total_20130701 + total_20130701_None).setScale(2, RoundingMode.HALF_EVEN).doubleValue(); BigDecimal bigDecimal1 = bigDecimal.setScale(2, RoundingMode.HALF_EVEN); double sale = bigDecimal1.doubleValue(); jsonObject.put("20130630",sale); jsonObject.put("20130701",sale_7); return jsonObject; } @Override public JSON totalAllSale() { JSONObject object = new JSONObject(); JSONObject jsonObject = totalArraySale(); Double o1 = (Double)jsonObject.get("20130630"); Double o2 = (Double)jsonObject.get("20130701"); long o3 = Math.round(o1 + o2); object.put("fit",o3); return object; } } ———————————————— 原文链接:https://blog.csdn.net/hacker_world/article/details/119064628
上滑加载中
推荐直播
-
华为云码道Agent集成与鸿蒙实战2026/08/11 周二 19:00-21:00
王一男-华为云码道产品规划专家;李炎-华为云码道产品专家;彭江敏-华为云鸿蒙端云一体化开发专家
本次直播带你解读华为云码道7月份产品新特性、新功能。更有专家演示码道Agent Space × 钉钉机器集成实战,从0到1打通消息通道;码道鸿蒙端云一体化实战,快速搭建员工签到系统。
回顾中 -
华为云开发者AI素养直播课·第五期2026/09/04 周五 16:00-18:00
林华鼎-华为云AI开发者运营负责人;蒋春阳-华为云AI开发者案例开发专家
本期直播内容: AI工具体验营 · 第5-8课连讲。Agent-Team 多智能体协作完成毕业设计实践
回顾中 -
华为云开发者AI素养ClassRoom·第六期2026/09/08 周二 19:00-20:00
樊渊-2026华为软件挑战赛冠军
高手来了:看软挑高手解析二维排样问题—从工业难题到算法突破
回顾中
热门标签