zoukankan      html  css  js  c++  java
  • jdk代理和cglib代理源代码之我见

    以前值是读过一遍jdk和cglib的代理,时间长了,都忘记入口在哪里了,值是记得其中的一些重点了,今天写一篇博客,当作是笔记。和以前一样,关键代码,我会用红色标记出来。

    首先,先列出我的jdk代理对象和测试代码:

    package com.example.gof.proxy;
    
    /**
     * 买车接口
     */
    public interface BuyCard {
        void buycard();
    }
    package com.example.gof.proxy;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    
    public class BuyCardDynamicProxy implements InvocationHandler {
        private Object object;
    
        public BuyCardDynamicProxy(final Object object) {
            this.object = object;
        }
    
        /**
         *  代理回调的方法,反编译后可以看到调用的是这段代码:super.h.invoke(this, m3, null);
         *  m3 = Class.forName("com.example.gof.proxy.BuyCard").getMethod("buycard", new Class[0]);
         * @param proxy 代理
         * @param method  代理调用的而方法
         * @param args 调用的方法的参数
         * @return
         * @throws Throwable
         */
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("动态代理,买车前");
            Object res= method.invoke(object,args);
            System.out.println("动态代理,洗刷刷");
            return res;
        }
    }
    package com.example.gof.proxy;
    
    import com.example.gof.proxy.impl.BuyCardImpl;
    import sun.misc.ProxyGenerator;
    
    import java.io.File;
    import java.io.FileNotFoundException;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.lang.reflect.Proxy;
    
    public class TestBuyCardDynamicProxy {
        public static void main(String[] args) throws IOException {
            BuyCard buyCard = new BuyCardImpl();
            BuyCard buyCardProxy = (BuyCard) Proxy.newProxyInstance(BuyCard.class.getClassLoader(),
                    new Class[]{BuyCard.class}, new BuyCardDynamicProxy(buyCard));
            buyCardProxy.buycard();
    //下面这段是用于生成字节码分析的
    // byte[] classFile= ProxyGenerator.generateProxyClass("$Proxy", new Class<?>[]{BuyCard.class}); // File file=new File("D:\java\GoF\src\main\java\com\example\gof\proxy/$Proxy.class"); // FileOutputStream os=new FileOutputStream(file); // os.write(classFile); // os.flush(); // os.close(); } }

    跟踪进去,查看 Proxy.newProxyInstance(BuyCard.class.getClassLoader() 这个方法,代码如下:

     /**
         * Returns an instance of a proxy class for the specified interfaces
         * that dispatches method invocations to the specified invocation
         * handler.
         * 返回一个代理类实例,用于调用响应的接口方法
         * <p>{@code Proxy.newProxyInstance} throws
         * {@code IllegalArgumentException} for the same reasons that
         * {@code Proxy.getProxyClass} does.
         *
         * @param   loader the class loader to define the proxy class
         * @param   interfaces the list of interfaces for the proxy class
         *          to implement
         * @param   h the invocation handler to dispatch method invocations to
         * @return  a proxy instance with the specified invocation handler of a
         *          proxy class that is defined by the specified class loader
         *          and that implements the specified interfaces
         * @throws  IllegalArgumentException if any of the restrictions on the
         *          parameters that may be passed to {@code getProxyClass}
         *          are violated
         * @throws  SecurityException if a security manager, <em>s</em>, is present
         *          and any of the following conditions is met:
         *          <ul>
         *          <li> the given {@code loader} is {@code null} and
         *               the caller's class loader is not {@code null} and the
         *               invocation of {@link SecurityManager#checkPermission
         *               s.checkPermission} with
         *               {@code RuntimePermission("getClassLoader")} permission
         *               denies access;</li>
         *          <li> for each proxy interface, {@code intf},
         *               the caller's class loader is not the same as or an
         *               ancestor of the class loader for {@code intf} and
         *               invocation of {@link SecurityManager#checkPackageAccess
         *               s.checkPackageAccess()} denies access to {@code intf};</li>
         *          <li> any of the given proxy interfaces is non-public and the
         *               caller class is not in the same {@linkplain Package runtime package}
         *               as the non-public interface and the invocation of
         *               {@link SecurityManager#checkPermission s.checkPermission} with
         *               {@code ReflectPermission("newProxyInPackage.{package name}")}
         *               permission denies access.</li>
         *          </ul>
         * @throws  NullPointerException if the {@code interfaces} array
         *          argument or any of its elements are {@code null}, or
         *          if the invocation handler, {@code h}, is
         *          {@code null}
         */
        @CallerSensitive
        public static Object newProxyInstance(ClassLoader loader,
                                              Class<?>[] interfaces,
                                              InvocationHandler h)
            throws IllegalArgumentException
        {
            Objects.requireNonNull(h);
    
            final Class<?>[] intfs = interfaces.clone();
            final SecurityManager sm = System.getSecurityManager();
            if (sm != null) {
                checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
            }
    
            /*
             * Look up or generate the designated proxy class.
    * 查找或者生成一个代理对象
    */ Class<?> cl = getProxyClass0(loader, intfs); /* * Invoke its constructor with the designated invocation handler. */ try { if (sm != null) { checkNewProxyPermission(Reflection.getCallerClass(), cl); } final Constructor<?> cons = cl.getConstructor(constructorParams); final InvocationHandler ih = h; if (!Modifier.isPublic(cl.getModifiers())) { AccessController.doPrivileged(new PrivilegedAction<Void>() { public Void run() { cons.setAccessible(true); return null; } }); } return cons.newInstance(new Object[]{h}); } catch (IllegalAccessException|InstantiationException e) { throw new InternalError(e.toString(), e); } catch (InvocationTargetException e) { Throwable t = e.getCause(); if (t instanceof RuntimeException) { throw (RuntimeException) t; } else { throw new InternalError(t.toString(), t); } } catch (NoSuchMethodException e) { throw new InternalError(e.toString(), e); } }
        /**
         * a cache of proxy classes
         */
        private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
            proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory()); 
    
        /**
         * Generate a proxy class.  Must call the checkProxyAccess method
         * to perform permission checks before calling this.
         */
        private static Class<?> getProxyClass0(ClassLoader loader,
                                               Class<?>... interfaces) {
            if (interfaces.length > 65535) {
                throw new IllegalArgumentException("interface limit exceeded");
            }
    
            // If the proxy class defined by the given loader implementing
            // the given interfaces exists, this will simply return the cached copy;
            // otherwise, it will create the proxy class via the ProxyClassFactory
            return proxyClassCache.get(loader, interfaces);
        }

    上面这个代码当初看了很久,怎么到proxyClassCache.get(loader, interfaces); 就已经生成了代理对象呢。后来百度下,才知道proxyClassCache9 这个对象调用构造参数时候,就生成了二进制文件,核心代码也在这里。继续往下看

        /**
         * A factory function that generates, defines and returns the proxy class given
         * the ClassLoader and array of interfaces.
    * 一个工厂函数,它生成、定义并返回给定类加载器和接口数组的代理类。
    */ private static final class ProxyClassFactory implements BiFunction<ClassLoader, Class<?>[], Class<?>> { // prefix for all proxy class names private static final String proxyClassNamePrefix = "$Proxy"; // next number to use for generation of unique proxy class names private static final AtomicLong nextUniqueNumber = new AtomicLong(); @Override public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) { Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length); for (Class<?> intf : interfaces) { /* * Verify that the class loader resolves the name of this * interface to the same Class object. */ Class<?> interfaceClass = null; try { interfaceClass = Class.forName(intf.getName(), false, loader); } catch (ClassNotFoundException e) { } if (interfaceClass != intf) { throw new IllegalArgumentException( intf + " is not visible from class loader"); } /* * Verify that the Class object actually represents an * interface. */ if (!interfaceClass.isInterface()) { throw new IllegalArgumentException( interfaceClass.getName() + " is not an interface"); } /* * Verify that this interface is not a duplicate. */ if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) { throw new IllegalArgumentException( "repeated interface: " + interfaceClass.getName()); } } String proxyPkg = null; // package to define proxy class in int accessFlags = Modifier.PUBLIC | Modifier.FINAL; /* * Record the package of a non-public proxy interface so that the * proxy class will be defined in the same package. Verify that * all non-public proxy interfaces are in the same package. */ for (Class<?> intf : interfaces) { int flags = intf.getModifiers(); if (!Modifier.isPublic(flags)) { accessFlags = Modifier.FINAL; String name = intf.getName(); int n = name.lastIndexOf('.'); String pkg = ((n == -1) ? "" : name.substring(0, n + 1)); if (proxyPkg == null) { proxyPkg = pkg; } else if (!pkg.equals(proxyPkg)) { throw new IllegalArgumentException( "non-public interfaces from different packages"); } } } if (proxyPkg == null) { // if no non-public proxy interfaces, use com.sun.proxy package proxyPkg = ReflectUtil.PROXY_PACKAGE + "."; } /* * Choose a name for the proxy class to generate. */ long num = nextUniqueNumber.getAndIncrement(); String proxyName = proxyPkg + proxyClassNamePrefix + num; /* * Generate the specified proxy class.
    * 生成代理对象
    */ byte[] proxyClassFile = ProxyGenerator.generateProxyClass( //生成二进制字节码 proxyName, interfaces, accessFlags); try {
    //加载到jvm里,返回代理对象
    return defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length); } catch (ClassFormatError e) { /* * A ClassFormatError here means that (barring bugs in the * proxy class generation code) there was some other * invalid aspect of the arguments supplied to the proxy * class creation (such as virtual machine limitations * exceeded). */ throw new IllegalArgumentException(e.toString()); } } }

    继续跟进生成二进制字节码的方法:ProxyGenerator.generateProxyClass( proxyName, interfaces, accessFlags)

    public static byte[] generateProxyClass(final String var0, Class<?>[] var1, int var2) {
            ProxyGenerator var3 = new ProxyGenerator(var0, var1, var2);
            final byte[] var4 = var3.generateClassFile();
            if (saveGeneratedFiles) {
                AccessController.doPrivileged(new PrivilegedAction<Void>() {
                    public Void run() {
                        try {
                            int var1 = var0.lastIndexOf(46);
                            Path var2;
                            if (var1 > 0) {
                                Path var3 = Paths.get(var0.substring(0, var1).replace('.', File.separatorChar));
                                Files.createDirectories(var3);
                                var2 = var3.resolve(var0.substring(var1 + 1, var0.length()) + ".class");
                            } else {
                                var2 = Paths.get(var0 + ".class");
                            }
    
                            Files.write(var2, var4, new OpenOption[0]);
                            return null;
                        } catch (IOException var4x) {
                            throw new InternalError("I/O exception saving generated file: " + var4x);
                        }
                    }
                });
            }
    
            return var4;
        }

    由上面可以看见,核心代码,就是生成了二进制字节码文件Files.write(var2, var4, new OpenOption[0]);

    为什么我们看不见呢,那是因为被删除了,可以手动生成这个二进制文件(.class后缀的),也就是上面的那段测试代码的例子。

    下面是二进制字节码的文件内容:

    //
    // Source code recreated from a .class file by IntelliJ IDEA
    // (powered by Fernflower decompiler)
    //
    
    import com.example.gof.proxy.BuyCard;
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    import java.lang.reflect.UndeclaredThrowableException;
    
    public final class $Proxy extends Proxy implements BuyCard {
        private static Method m1;
        private static Method m2;
        private static Method m3;
        private static Method m0;
    
        public $Proxy(InvocationHandler var1) throws  {
            super(var1);
        }
    
        public final boolean equals(Object var1) throws  {
            try {
                return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
            } catch (RuntimeException | Error var3) {
                throw var3;
            } catch (Throwable var4) {
                throw new UndeclaredThrowableException(var4);
            }
        }
    
        public final String toString() throws  {
            try {
                return (String)super.h.invoke(this, m2, (Object[])null);
            } catch (RuntimeException | Error var2) {
                throw var2;
            } catch (Throwable var3) {
                throw new UndeclaredThrowableException(var3);
            }
        }
    
        public final void buycard() throws  {
            try {
                super.h.invoke(this, m3, (Object[])null);
            } catch (RuntimeException | Error var2) {
                throw var2;
            } catch (Throwable var3) {
                throw new UndeclaredThrowableException(var3);
            }
        }
    
        public final int hashCode() throws  {
            try {
                return (Integer)super.h.invoke(this, m0, (Object[])null);
            } catch (RuntimeException | Error var2) {
                throw var2;
            } catch (Throwable var3) {
                throw new UndeclaredThrowableException(var3);
            }
        }
    
        static {
            try {
                m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
                m2 = Class.forName("java.lang.Object").getMethod("toString");
                m3 = Class.forName("com.example.gof.proxy.BuyCard").getMethod("buycard");
                m0 = Class.forName("java.lang.Object").getMethod("hashCode");
            } catch (NoSuchMethodException var2) {
                throw new NoSuchMethodError(var2.getMessage());
            } catch (ClassNotFoundException var3) {
                throw new NoClassDefFoundError(var3.getMessage());
            }
        }
    }

    从上面可以看到,除了自定义的buycard() 方法外,还有toString()、equals()、hashCode() 三个方法。这三个方法是Object 的方法,每个接口、类都会从Object继承这三个方法。当调用buycard()的时候,可以看到调用的是super.h.invoke(xxx) 这样调用的。这里的h,就是我们自定义的,继承了InvocationHandler的类:BuyCardDynamicProxy。

    至于invoke方式是怎么调用的,在另外一篇博客https://www.cnblogs.com/drafire/p/9637349.html 继续解析

     ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

    下面看下cglib的代码解读,依然是先贴出测试代码

    public class BuyCardCglibProxy implements MethodInterceptor {
        private Object target;
    
        public Object getInstance(final Object target) {
            this.target = target;
            Enhancer enhancer=new Enhancer();
            enhancer.setSuperclass(target.getClass());
            enhancer.setCallback(this);
            return enhancer.create();
        }
    
        @Override
        public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
            System.out.println("cglib动态代理前");
            //这里使用的是methodProxy.invokeSuper,而不是method.invoke
            //代理类调用父类的方法
            Object res= methodProxy.invokeSuper(o,objects);
            System.out.println("cglib动态代理后");
            return res;
        }
    }
    package com.example.gof.proxy;
    
    import com.example.gof.proxy.impl.BuyCardImpl;
    import org.springframework.cglib.core.DebuggingClassWriter;
    
    public class TestBuyCardCglibProxy {
        public static void main(String[] args) {
            System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, TestBuyCardCglibProxy.class.getClass().getResource("/").getPath() );
            BuyCard buyCard=new BuyCardImpl();
            BuyCardCglibProxy proxy=new BuyCardCglibProxy();
            BuyCardImpl impl= (BuyCardImpl)proxy.getInstance(buyCard);
            impl.buycard();
        }
    }

    关键代码在:enhancer.create() 这个方法,跟踪进去,代码如下:

    public Object create() {
            this.classOnly = false;
            this.argumentTypes = null;
            return this.createHelper();
        }
     private Object createHelper() {
            this.preValidate();
            Object key = KEY_FACTORY.newInstance(this.superclass != null ? this.superclass.getName() : null, ReflectUtils.getNames(this.interfaces), this.filter == ALL_ZERO ? null : new WeakCacheKey(this.filter), this.callbackTypes, this.useFactory, this.interceptDuringConstruction, this.serialVersionUID);
            this.currentKey = key;
            Object result = super.create(key);
            return result;
        }
    protected Object create(Object key) {
            try {
                ClassLoader loader = this.getClassLoader();   //获取加载器
                Map<ClassLoader, AbstractClassGenerator.ClassLoaderData> cache = CACHE;
    //从缓存中读取 AbstractClassGenerator.ClassLoaderData data
    = (AbstractClassGenerator.ClassLoaderData)cache.get(loader); if (data == null) { //如果缓存没有对应的数据 Class var5 = AbstractClassGenerator.class; synchronized(AbstractClassGenerator.class) { cache = CACHE; data = (AbstractClassGenerator.ClassLoaderData)cache.get(loader); if (data == null) { Map<ClassLoader, AbstractClassGenerator.ClassLoaderData> newCache = new WeakHashMap(cache); data = new AbstractClassGenerator.ClassLoaderData(loader); //生成二进制字节码 newCache.put(loader, data); CACHE = newCache; } } } this.key = key; Object obj = data.get(this, this.getUseCache());
    //生成代理对象
    return obj instanceof Class ? this.firstInstance((Class)obj) : this.nextInstance(obj); } catch (RuntimeException var9) { throw var9; } catch (Error var10) { throw var10; } catch (Exception var11) { throw new CodeGenerationException(var11); } }
     public ClassLoaderData(ClassLoader classLoader) {
                if (classLoader == null) {
                    throw new IllegalArgumentException("classLoader == null is not yet supported");
                } else {
                    this.classLoader = new WeakReference(classLoader);
                    Function<AbstractClassGenerator, Object> load = new Function<AbstractClassGenerator, Object>() {
                        public Object apply(AbstractClassGenerator gen) {
                            Class klass = gen.generate(ClassLoaderData.this);   //核心代码
                            return gen.wrapCachedClass(klass);
                        }
                    };
                    this.generatedClasses = new LoadingCache(GET_KEY, load);
                }
            }
    protected Class generate(AbstractClassGenerator.ClassLoaderData data) {
            Object save = CURRENT.get();
            CURRENT.set(this);
    
            Class var8;
            try {
                ClassLoader classLoader = data.getClassLoader();
                if (classLoader == null) {
                    throw new IllegalStateException("ClassLoader is null while trying to define class " + this.getClassName() + ". It seems that the loader has been expired from a weak reference somehow. Please file an issue at cglib's issue tracker.");
                }
    
                String className;
                synchronized(classLoader) {
                    className = this.generateClassName(data.getUniqueNamePredicate());
                    data.reserveName(className);
                    this.setClassName(className);
                }
    
                Class gen;
                if (this.attemptLoad) {
                    try {
                        gen = classLoader.loadClass(this.getClassName());
                        Class var25 = gen;
                        return var25;
                    } catch (ClassNotFoundException var20) {
                        ;
                    }
                }
    
                byte[] b = this.strategy.generate(this);   //核心代码
                className = ClassNameReader.getClassName(new ClassReader(b));
                ProtectionDomain protectionDomain = this.getProtectionDomain();
                synchronized(classLoader) {
                    if (protectionDomain == null) {
                        gen = ReflectUtils.defineClass(className, b, classLoader);
                    } else {
                        gen = ReflectUtils.defineClass(className, b, classLoader, protectionDomain);
                    }
                }
    
                var8 = gen;
            } catch (RuntimeException var21) {
                throw var21;
            } catch (Error var22) {
                throw var22;
            } catch (Exception var23) {
                throw new CodeGenerationException(var23);
            } finally {
                CURRENT.set(save);
            }
    
            return var8;
        }
    //orgspringframeworkspring-core5.0.8.RELEASEspring-core-5.0.8.RELEASE.jar!orgspringframeworkcglibcoreDefaultGeneratorStrategy.class
    public
    byte[] generate(ClassGenerator cg) throws Exception { DebuggingClassWriter cw = this.getClassVisitor(); //从这里的源代码,可以看出,底层是使用asm包的 this.transform(cg).generateClass(cw); return this.transform(cw.toByteArray()); }
    //orgspringframeworkspring-core5.0.8.RELEASEspring-core-5.0.8.RELEASE.jar!orgspringframeworkcglibcoreKeyFactory.class
    
    public void generateClass(ClassVisitor v) {
                ClassEmitter ce = new ClassEmitter(v);
                Method newInstance = ReflectUtils.findNewInstance(this.keyInterface);
                if (!newInstance.getReturnType().equals(Object.class)) {
                    throw new IllegalArgumentException("newInstance method must return Object");
                } else {
                    Type[] parameterTypes = TypeUtils.getTypes(newInstance.getParameterTypes());
                    ce.begin_class(46, 1, this.getClassName(), KeyFactory.KEY_FACTORY, new Type[]{Type.getType(this.keyInterface)}, "<generated>");
                    EmitUtils.null_constructor(ce);
                    EmitUtils.factory_method(ce, ReflectUtils.getSignature(newInstance));
                    int seed = 0;
                    CodeEmitter e = ce.begin_method(1, TypeUtils.parseConstructor(parameterTypes), (Type[])null);
                    e.load_this();
                    e.super_invoke_constructor();
                    e.load_this();
                    List<FieldTypeCustomizer> fieldTypeCustomizers = this.getCustomizers(FieldTypeCustomizer.class);
    
                    int i;
                    for(i = 0; i < parameterTypes.length; ++i) {
                        Type parameterType = parameterTypes[i];
                        Type fieldType = parameterType;
    
                        Iterator var11;
                        FieldTypeCustomizer customizer;
                        for(var11 = fieldTypeCustomizers.iterator(); var11.hasNext(); fieldType = customizer.getOutType(i, fieldType)) {
                            customizer = (FieldTypeCustomizer)var11.next();
                        }
    
                        seed += fieldType.hashCode();
                        ce.declare_field(18, this.getFieldName(i), fieldType, (Object)null);
                        e.dup();
                        e.load_arg(i);
                        var11 = fieldTypeCustomizers.iterator();
    
                        while(var11.hasNext()) {
                            customizer = (FieldTypeCustomizer)var11.next();
                            customizer.customize(e, i, parameterType);
                        }
    
                        e.putfield(this.getFieldName(i));
                    }
    
                    e.return_value();
                    e.end_method();
                    e = ce.begin_method(1, KeyFactory.HASH_CODE, (Type[])null);
                    i = this.constant != 0 ? this.constant : KeyFactory.PRIMES[Math.abs(seed) % KeyFactory.PRIMES.length];
                    int hm = this.multiplier != 0 ? this.multiplier : KeyFactory.PRIMES[Math.abs(seed * 13) % KeyFactory.PRIMES.length];
                    e.push(i);
    
                    for(int i = 0; i < parameterTypes.length; ++i) {
                        e.load_this();
                        e.getfield(this.getFieldName(i));
                        EmitUtils.hash_code(e, parameterTypes[i], hm, this.customizers);
                    }
    
                    e.return_value();
                    e.end_method();
                    e = ce.begin_method(1, KeyFactory.EQUALS, (Type[])null);
                    Label fail = e.make_label();
                    e.load_arg(0);
                    e.instance_of_this();
                    e.if_jump(153, fail);
    
                    int i;
                    for(i = 0; i < parameterTypes.length; ++i) {
                        e.load_this();
                        e.getfield(this.getFieldName(i));
                        e.load_arg(0);
                        e.checkcast_this();
                        e.getfield(this.getFieldName(i));
                        EmitUtils.not_equals(e, parameterTypes[i], fail, this.customizers);
                    }
    
                    e.push(1);
                    e.return_value();
                    e.mark(fail);
                    e.push(0);
                    e.return_value();
                    e.end_method();
                    e = ce.begin_method(1, KeyFactory.TO_STRING, (Type[])null);
                    e.new_instance(Constants.TYPE_STRING_BUFFER);
                    e.dup();
                    e.invoke_constructor(Constants.TYPE_STRING_BUFFER);
    
                    for(i = 0; i < parameterTypes.length; ++i) {
                        if (i > 0) {
                            e.push(", ");
                            e.invoke_virtual(Constants.TYPE_STRING_BUFFER, KeyFactory.APPEND_STRING);
                        }
    
                        e.load_this();
                        e.getfield(this.getFieldName(i));
                        EmitUtils.append_string(e, parameterTypes[i], EmitUtils.DEFAULT_DELIMITERS, this.customizers);
                    }
    
                    e.invoke_virtual(Constants.TYPE_STRING_BUFFER, KeyFactory.TO_STRING);
                    e.return_value();
                    e.end_method();
                    ce.end_class();
                }
            }
  • 相关阅读:
    [HNOI2002]营业额统计
    HDU 1374
    HDU 3345
    HDU 2089
    Graham扫描法
    Codeforces 1144D Deduction Queries 并查集
    Codeforces 916E Jamie and Tree 线段树
    Codeforces 1167F Scalar Queries 树状数组
    Codeforces 1167E Range Deleting
    Codeforces 749E Inversions After Shuffle 树状数组 + 数学期望
  • 原文地址:https://www.cnblogs.com/drafire/p/10409424.html
Copyright © 2011-2022 走看看