JVM----类加载和初始化_jvm 类加载 初始化-程序员宅基地

技术标签: JVM  jvm  java  

JVM-类加载和初始化

在这里插入图片描述
类加载-初始化

  1. loading 把class文件加载到内存
  2. linking
  3. Verification:校验class文件是否符合标准
  4. preparation:给静态变量赋默认值,如给static int i = 8赋值为i=0
  5. resolution:常量池中的用到的那些符号引用要准换成能访问到的内存地址
  6. initializing :这时候才会调用静态代码块给静态变量赋值

类加载器

在这里插入图片描述

loading

jvm中所有的class都是被classloader加载到内存
以上几个类加载器的关系不是继承,是父加载器与自加载器的关系。

双亲委派

  • 父加载器
    父加载器不是“类加载器的加载器”
  • 双亲委派是一个孩子向父亲方向,然后父亲向孩子方向的双亲委派过程

那么问题来了, 为什么要搞双亲委派
java.lang.String类由自定义加载器加载行不行?

回答这个问题, 首先要弄明白class的加载过程。

根据上图所示,一个class类首先要经过CustomClassloader(自定义类加载器),查询其缓存中是否已经将该class加载,如果有,则将其返回,没有,则向上检查,此时到了APP(AppClassLoader,同样检查其缓存是否已加载,没有,则继续向上,Extension加载器同样如此,一直检查到BootStrap加载器,当Bootstrap加载器同样没有加载该calss时,开始自顶向下进行实际查找和加载。首先判断该类是否该由Bootstrap加载,不是,则向下,一直到Custom加载器,如果没有找到,则抛异常(ClassNotFound)。

主要是为了安全
假设自定义了一个Java.lang.String,覆盖sun的String,同时自定义一个String的类加载器,将自定义的这个String加载到内存,接下来将整个自定义部分打包成一个类库,交给客户使用 ,此时客户输入密码将会变得非常不安全。
但是采用双亲委派就不会有这个问题,自低向上检查,一直到Bootstap,发现String类已经被Bootstrap加载,其他加载器便不能再次加载这个类,从而保证了安全。

类加载过程

在这里插入图片描述

类加载器范围

在这里插入图片描述
这些加载范围是由launcher的源码决定
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
查看每个目录下都有哪些jar包

public class T003_ClassLoaderScope {
    
    public static void main(String[] args) {
    
        String pathBoot = System.getProperty("sun.boot.class.path");
        System.out.println(pathBoot.replaceAll(";", System.lineSeparator()));

        System.out.println("--------------------");
        String pathExt = System.getProperty("java.ext.dirs");
        System.out.println(pathExt.replaceAll(";", System.lineSeparator()));

        System.out.println("--------------------");
        String pathApp = System.getProperty("java.class.path");
        System.out.println(pathApp.replaceAll(";", System.lineSeparator()));
    }
}

输出结果

C:\Program Files\Java\jdk1.8.0_51\jre\lib\resources.jar
C:\Program Files\Java\jdk1.8.0_51\jre\lib\rt.jar
C:\Program Files\Java\jdk1.8.0_51\jre\lib\sunrsasign.jar
C:\Program Files\Java\jdk1.8.0_51\jre\lib\jsse.jar
C:\Program Files\Java\jdk1.8.0_51\jre\lib\jce.jar
C:\Program Files\Java\jdk1.8.0_51\jre\lib\charsets.jar
C:\Program Files\Java\jdk1.8.0_51\jre\lib\jfr.jar
C:\Program Files\Java\jdk1.8.0_51\jre\classes
--------------------
C:\Program Files\Java\jdk1.8.0_51\jre\lib\ext
C:\Windows\Sun\Java\lib\ext
--------------------
C:\Program Files\Java\jdk1.8.0_51\jre\lib\charsets.jar
C:\Program Files\Java\jdk1.8.0_51\jre\lib\deploy.jar
C:\Program Files\Java\jdk1.8.0_51\jre\lib\ext\access-bridge-64.jar
C:\Program Files\Java\jdk1.8.0_51\jre\lib\ext\cldrdata.jar
C:\Program Files\Java\jdk1.8.0_51\jre\lib\ext\dnsns.jar
C:\Program Files\Java\jdk1.8.0_51\jre\lib\ext\jaccess.jar
C:\Program Files\Java\jdk1.8.0_51\jre\lib\ext\jfxrt.jar
C:\Program Files\Java\jdk1.8.0_51\jre\lib\ext\localedata.jar
C:\Program Files\Java\jdk1.8.0_51\jre\lib\ext\nashorn.jar
C:\Program Files\Java\jdk1.8.0_51\jre\lib\ext\sunec.jar
C:\Program Files\Java\jdk1.8.0_51\jre\lib\ext\sunjce_provider.jar
C:\Program Files\Java\jdk1.8.0_51\jre\lib\ext\sunmscapi.jar
C:\Program Files\Java\jdk1.8.0_51\jre\lib\ext\sunpkcs11.jar
C:\Program Files\Java\jdk1.8.0_51\jre\lib\ext\zipfs.jar
C:\Program Files\Java\jdk1.8.0_51\jre\lib\javaws.jar
C:\Program Files\Java\jdk1.8.0_51\jre\lib\jce.jar
C:\Program Files\Java\jdk1.8.0_51\jre\lib\jfr.jar
C:\Program Files\Java\jdk1.8.0_51\jre\lib\jfxswt.jar
C:\Program Files\Java\jdk1.8.0_51\jre\lib\jsse.jar
C:\Program Files\Java\jdk1.8.0_51\jre\lib\management-agent.jar
C:\Program Files\Java\jdk1.8.0_51\jre\lib\plugin.jar
C:\Program Files\Java\jdk1.8.0_51\jre\lib\resources.jar
C:\Program Files\Java\jdk1.8.0_51\jre\lib\rt.jar
G:\SoftWare\Java\IntelliJ IDEA 2019.1.3\lib\idea_rt.jar

ClassLoader类加载器

package com.cyc.jvm.c2_classloader;

/**
 * classloader加载器
 */
public class T002_ClassLoaderLevel {
    
    public static void main(String[] args) {
    
        //String是由 bootStrapClassLoader加载的
        System.out.println(String.class.getClassLoader());
        System.out.println(sun.awt.HKSCS.class.getClassLoader());
        System.out.println(sun.net.spi.nameservice.dns.DNSNameService.class.getClassLoader());
        //自定义类的classLoader的类加载器为AppClassLoader
        System.out.println(T002_ClassLoaderLevel.class.getClassLoader());
        //extClassLoader的类加载器是BootStrapClassLoader加载的
        System.out.println(sun.net.spi.nameservice.dns.DNSNameService.class.getClassLoader().getClass().getClassLoader());
        System.out.println(T002_ClassLoaderLevel.class.getClassLoader().getClass().getClassLoader());

        //一个自定义classLoader的默认父classLoader是AppClassLoader
        System.out.println(new T006_CYCClassLoader().getParent());
        //ClassLoader的systemClassLoader也是AppClassLoader
        System.out.println(ClassLoader.getSystemClassLoader());
    }
}

自定义类加载器

准备阶段

  1. 一个需要解析的的class文件
package com.cyc.jvm;

public class Hello {
    
    public void m() {
    
        System.out.println("Hello JVM!");
    }
}

  1. 去项目目录下找到Hello的class文件, 带上根目录复制到D盘的test文件夹下

在这里插入图片描述

  1. 自定义类加载器

public class T006_CYCClassLoader extends ClassLoader {
    

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
    
        File f = new File("D:/test/", name.replace(".", "/").concat(".class"));
        try {
    
            FileInputStream fis = new FileInputStream(f);
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            int b = 0;

            while ((b=fis.read()) !=0) {
    
                baos.write(b);
            }

            byte[] bytes = baos.toByteArray();
            baos.close();
            fis.close();//可以写的更加严谨

            return defineClass(name, bytes, 0, bytes.length);
        } catch (Exception e) {
    
            e.printStackTrace();
        }
        return super.findClass(name); //throws ClassNotFoundException
    }

    public static void main(String[] args) throws Exception {
    
        ClassLoader l = new T006_CYCClassLoader();
        Class clazz = l.loadClass("com.cyc.jvm.Hello");
        Class clazz1 = l.loadClass("com.cyc.jvm.Hello");
        //由于使用的是同一个类加载器,加载出来的是同一个class对象, 所以这里会输出true
        System.out.println(clazz == clazz1);

        Hello h = (Hello)clazz.newInstance();
        h.m();
        //自定义类加载器的类加载器是AppClassLoader
        System.out.println(l.getClass().getClassLoader());
        //他的父加载器同样也是AppClassLoader,但是注意, 他们之间不是继承关系。这些类加载器继承的都是ClassLoader
        System.out.println(l.getParent());
        System.out.println(getSystemClassLoader());
    }
}

lazyloading

严格来讲应该叫lazylnitializing

public class T008_LazyLoading {
     //严格讲应该叫lazy initialzing,因为java虚拟机规范并没有严格规定什么时候必须loading,但严格规定了什么时候initialzing
    public static void main(String[] args) throws Exception {
    
        P p;
        X x = new X();
        System.out.println(P.i);
        System.out.println(P.j);
        Class.forName("com.cyc.jvm.c2_classloader.T008_LazyLoading$P");

    }

    public static class P {
    
        final static int i = 8;
        static int j = 9;
        static {
    
            System.out.println("P");
        }
    }

    public static class X extends P {
    
        static {
    
            System.out.println("X");
        }
    }
}

混合模式

在这里插入图片描述
测试

package com.cyc.jvm.c2_classloader;

public class T009_WayToRun {
    
    public static void main(String[] args) {
    
        for(int i=0; i<10_0000; i++)
            m();

        long start = System.currentTimeMillis();
        for(int i=0; i<10_0000; i++) {
    
            m();
        }
        long end = System.currentTimeMillis();
        System.out.println(end - start);
    }

    public static void m() {
    
        for(long i=0; i<10_0000L; i++) {
    
            long j = i%3;
        }
    }
}

首先是混合模式,这也是默认的运行模式
在这里插入图片描述
混合模式运行时间

在这里插入图片描述
解释模式
在这里插入图片描述
查看运行时间(时间过于漫长)

编译模式
在这里插入图片描述

查看运行时间
在这里插入图片描述

初始化(initializing)

package com.cyc.jvm.c2_classloader;

public class T001_ClassLoadingProcedure {
    
    public static void main(String[] args) {
    
        System.out.println(T.count);
    }
}

class T {
    
    //这个顺序,由于new T(),会进行T的初始化, 给T赋值为null,T中的成员编程int型的count赋值为0,
    //然后调用T的构造方法, 执行count++, 此时count为1,接来下开始执行 public static int count = 2;在这里给count赋值为2
    public static T t = new T(); // null
    public static int count = 2; //0

    private T() {
    
        count ++;
        //System.out.println("--" + count);
    }
}
class T {
    

    //首先归对象T进行初始化, 此时对象为null, 对象内的变量count赋值为默认值0,然后在initial阶段给count赋指定值2
    //接着调用T的构造方法, 执行count++, count变为3
    public static int count = 2; //2->3
    public static T t = new T(); // null->对象

    private T() {
    
        count ++;
        //System.out.println("--" + count);
    }
}

new对象的过程其实也是分为两步, new 出来T , 先给里面的成员变量赋默认值,new出来T,申请完内存之后,开始调用构造方法,才给成员变量赋初始值。

扩展

结合单例模式解析初始化过程

public class Singleton06 {
    

    //volatile关键字禁止指令重排
    private static volatile Singleton06 INSTANCE;


    public void c() {
    
        System.out.println("C");
    }

    /**
     * 构造方法为私有,只能在当前类中new,外部类无法new出来
     */
    private Singleton06() {
    
    }

    public static Singleton06 getInstance() {
    
        if (INSTANCE == null) {
    
            //双重检查
            synchronized (Singleton06.class) {
    
                if (INSTANCE == null) {
    
                    try {
    
                        //这里让进入此代码块的线程睡一毫秒
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
    
                        e.printStackTrace();
                    }
                    INSTANCE = new Singleton06();
                }
            }
        }
        return INSTANCE;
    }

    public static void main(String[] args) {
    
        for (int i = 0; i < 100; i++) {
    
            new Thread(() ->
                    System.out.println(Singleton06.getInstance().hashCode())
            ).start();
        }
    }

}

为什么要加volatile?

为了防止指令重排。这里涉及到类的加载过程。

首先,第一个线程进来了, 加上锁之后,进入到INSTANCE = new Singleton06();代码,在初始化进行到一半的时候,也就是在preparation阶段,已经给Singleton06申请完内存,里面的成员变量已经赋过默认值,比如0,此时INSTANCE 已经指向这个分配的内存, 已经不再是null,此时另外一个线程进来了,由于此时INSTANCE 已经进行了半初始化状态,所以在if (INSTANCE == null)为false,此时另一个线程会拿到这个INSTANCE中的成员变量进行操作, 这样显然是不满足要求的。

想要解析这个问题, 需要查看其字节码文件

例如下面这个测试类T, 使用idea插件查看其字节码文件

在这里插入图片描述

在0 new #2 <com/cyc/jvm/c0_basic/T>之后,已经申请过内存。

4 invokespecial #3 <com/cyc/jvm/c0_basic/T.> 这个给类中的静态变量赋初始值

在调用完4之后,才会把这块内存赋值给t,但是由于指令可能会重排的原因, 如果先执行的是7 astore_1, 相当于先把这个地址扔到内存中, 然后在进行的T初始化, 这种情况下,在双重检查懒汉式单例中,就会出现有别的线程读取到半初始化的单例。

相关问题

如何打破双亲委派机制

如果只是重写findClass方法, 是无法打破双亲委派机制的, 示例如下

package com.cyc.jvm.c2_classloader;

public class T011_ClassReloading1 {
    
    public static void main(String[] args) throws Exception {
    
        T006_CYCClassLoader cycClassLoader = new T006_CYCClassLoader();
        Class clazz = cycClassLoader.loadClass("com.cyc.jvm.Hello");

        cycClassLoader = null;
        System.out.println(clazz.hashCode());

        cycClassLoader = null;

        cycClassLoader = new T006_CYCClassLoader();
        Class clazz1 = cycClassLoader.loadClass("com.cyc.jvm.Hello");
        System.out.println(clazz1.hashCode());

        System.out.println(clazz == clazz1);
    }
}

输出结果

在这里插入图片描述

可以看到两者class的hashcode值相同, 所以, 依然是同一个class对象。

显然这里需要重写loadclass方法才行

package com.cyc.jvm.c2_classloader;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

public class T012_ClassReloading2 {
    
    private static class MyLoader extends ClassLoader {
    
        //重写loadClass方法, 每次都去加载新的class , 而不是去类加载器缓存池中去找该类是否已加载
        @Override
        public Class<?> loadClass(String name) throws ClassNotFoundException {
    

            File f = new File("D:/test/", name.replace(".", "/").concat(".class"));

            if(!f.exists()) return super.loadClass(name);

            try {
    

                InputStream is = new FileInputStream(f);

                byte[] b = new byte[is.available()];
                is.read(b);
                return defineClass(name, b, 0, b.length);
            } catch (IOException e) {
    
                e.printStackTrace();
            }

            return super.loadClass(name);
        }
    }

    public static void main(String[] args) throws Exception {
    
        MyLoader m = new MyLoader();
        Class clazz = m.loadClass("com.cyc.jvm.Hello");

        m = new MyLoader();
        Class clazzNew = m.loadClass("com.cyc.jvm.Hello");

        System.out.println(clazz == clazzNew);
    }
}

查看输出结果

在这里插入图片描述


第一次被加载的类, 在类空间里,当它的classLoader被干掉之后, 由于没有任何引用指向它了, 所以会被gc回收。

  • bootstrap加载器为什么返回的是null?

    因为它是由c++编写的,java中并没有与之对应的class

  • class的加载过程用到了哪些设计模式?

    classloader的load过程用到了设计模式中的模板方法模式,因为所有方法都已经写好了,如果要自定义classloader,自己只需要重写findclass方法就可以了。

Tomcat为什么要重写类加载器?

无法实现隔离性:如果使用默认的类加载器机制,那么是无法加载两个相同类库的不同版本的,默认的类加器是不管你是什么版本的,只在乎你的全限定类名,并且只有一份。一个web容器可能要部署两个或者多个应用程序,不同的应用程序,可能会依赖同一个第三方类库的不同版本,因此要保证每一个应用程序的类库都是独立、相互隔离的。部署在同一个web容器中的相同类库的相同版本可以共享,否则,会有重复的类库被加载进JVM, web容器也有自己的类库,不能和应用程序的类库混淆,需要相互隔离

无法实现热替换:jsp 文件其实也就是class文件,那么如果修改了,但类名还是一样,类加载器会直接取方法区中已经存在的,修改后的jsp是不会重新加载的。

打破双亲委派机制(参照JVM中的内容)OSGI是基于Java语言的动态模块化规范,类加载器之间是网状结构,更加灵活,但是也更复杂,JNDI服务,使用线程上线文类加载器,父类加载器去使用子类加载器

在这里插入图片描述

  1. tomcat自己定义的类加载器:

    CommonClassLoader:tomcat最基本的类加载器,加载路径中的class可以被tomcat和各个webapp访问

    CatalinaClassLoader:tomcat私有的类加载器,webapp不能访问其加载路径下的class,即对webapp不可见

    SharedClassLoader:各个webapp共享的类加载器,对tomcat不可见

    WebappClassLoader:webapp私有的类加载器,只对当前webapp可见

  2. 每一个web应用程序对应一个WebappClassLoader,每一个jsp文件对应一个JspClassLoader,所以这两个类加载器有多个实例

  3. 工作原理:

    a. CommonClassLoader能加载的类都可以被Catalina ClassLoader和SharedClassLoader使用,从而实现了公有类库的共用

    b. CatalinaClassLoader和SharedClassLoader自己能加载的类则与对方相互隔离

    c. WebAppClassLoader可以使用SharedClassLoader加载到的类,但各个WebAppClassLoader实例之间相互隔离,多个WebAppClassLoader是同级关系

    d. 而JasperLoader的加载范围仅仅是这个JSP文件所编译出来的那一个.Class文件,它出现的目的就是为了被丢弃:当Web容器检测到JSP文件被修改时,会替换掉目前的JasperLoader的实例,并通过再建立一个新的Jsp类加载器来实现JSP文件的HotSwap功能

  4. tomcat目录结构,与上面的类加载器对应

    /common/*

    /server/*

    /shared/*

    /WEB-INF/*

  5. 默认情况下,conf目录下的catalina.properties文件,没有指定server.loader以及shared.loader,所以tomcat没有建立CatalinaClassLoader和SharedClassLoader的实例,这两个都会使用CommonClassLoader来代替。Tomcat6之后,把common、shared、server目录合成了一个lib目录。所以在我们的服务器里看不到common、shared、server目录。

总结

  1. 加载过程
    1. Loading

      1. 双亲委派,主要出于安全来考虑

      2. LazyLoading 五种情况

        1. –new getstatic putstatic invokestatic指令,访问final变量除外

          –java.lang.reflect对类进行反射调用时

          –初始化子类的时候,父类首先初始化

          –虚拟机启动时,被执行的主类必须初始化

          –动态语言支持java.lang.invoke.MethodHandle解析的结果为REF_getstatic REF_putstatic REF_invokestatic的方法句柄时,该类必须初始化

      3. ClassLoader的源码

        1. findInCache -> parent.loadClass -> findClass()
      4. 自定义类加载器

        1. extends ClassLoader
        2. overwrite findClass() -> defineClass(byte[] -> Class clazz)
        3. 加密
        4. 如何打破双亲委派
          1. 用super(parent)指定
          2. 双亲委派的打破
            1. 如何打破:重写loadClass()
            2. 何时打破过?
              1. JDK1.2之前,自定义ClassLoader都必须重写loadClass()

              2. ThreadContextClassLoader可以实现基础类调用实现类代码,通过thread.setContextClassLoader指定

              3. 热启动,热部署(会把整个classLoader都干掉,把class重新load一遍)

                1. osgi tomcat 都有自己的模块指定classloader(可以加载同一类库的不同版本)

                  每一个webApplication,都有自己的一个classLoader, 每个classLoader中可以有自己的类。

      5. 混合执行 编译执行 解释执行

        1. 检测热点代码:-XX:CompileThreshold = 10000
    2. Linking

      1. Verification
        1. 验证文件是否符合JVM规定
      2. Preparation
        1. 静态成员变量赋默认值
      3. Resolution
        1. 将类、方法、属性等符号引用解析为直接引用
          常量池中的各种符号引用解析为指针、偏移量等内存地址的直接引用
    3. Initializing

      1. 调用类初始化代码 ,给静态成员变量赋初始值
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/A_Java_Dog/article/details/117676139

智能推荐

使用nginx解决浏览器跨域问题_nginx不停的xhr-程序员宅基地

文章浏览阅读1k次。通过使用ajax方法跨域请求是浏览器所不允许的,浏览器出于安全考虑是禁止的。警告信息如下:不过jQuery对跨域问题也有解决方案,使用jsonp的方式解决,方法如下:$.ajax({ async:false, url: 'http://www.mysite.com/demo.do', // 跨域URL ty..._nginx不停的xhr

在 Oracle 中配置 extproc 以访问 ST_Geometry-程序员宅基地

文章浏览阅读2k次。关于在 Oracle 中配置 extproc 以访问 ST_Geometry,也就是我们所说的 使用空间SQL 的方法,官方文档链接如下。http://desktop.arcgis.com/zh-cn/arcmap/latest/manage-data/gdbs-in-oracle/configure-oracle-extproc.htm其实简单总结一下,主要就分为以下几个步骤。..._extproc

Linux C++ gbk转为utf-8_linux c++ gbk->utf8-程序员宅基地

文章浏览阅读1.5w次。linux下没有上面的两个函数,需要使用函数 mbstowcs和wcstombsmbstowcs将多字节编码转换为宽字节编码wcstombs将宽字节编码转换为多字节编码这两个函数,转换过程中受到系统编码类型的影响,需要通过设置来设定转换前和转换后的编码类型。通过函数setlocale进行系统编码的设置。linux下输入命名locale -a查看系统支持的编码_linux c++ gbk->utf8

IMP-00009: 导出文件异常结束-程序员宅基地

文章浏览阅读750次。今天准备从生产库向测试库进行数据导入,结果在imp导入的时候遇到“ IMP-00009:导出文件异常结束” 错误,google一下,发现可能有如下原因导致imp的数据太大,没有写buffer和commit两个数据库字符集不同从低版本exp的dmp文件,向高版本imp导出的dmp文件出错传输dmp文件时,文件损坏解决办法:imp时指定..._imp-00009导出文件异常结束

python程序员需要深入掌握的技能_Python用数据说明程序员需要掌握的技能-程序员宅基地

文章浏览阅读143次。当下是一个大数据的时代,各个行业都离不开数据的支持。因此,网络爬虫就应运而生。网络爬虫当下最为火热的是Python,Python开发爬虫相对简单,而且功能库相当完善,力压众多开发语言。本次教程我们爬取前程无忧的招聘信息来分析Python程序员需要掌握那些编程技术。首先在谷歌浏览器打开前程无忧的首页,按F12打开浏览器的开发者工具。浏览器开发者工具是用于捕捉网站的请求信息,通过分析请求信息可以了解请..._初级python程序员能力要求

Spring @Service生成bean名称的规则(当类的名字是以两个或以上的大写字母开头的话,bean的名字会与类名保持一致)_@service beanname-程序员宅基地

文章浏览阅读7.6k次,点赞2次,收藏6次。@Service标注的bean,类名:ABDemoService查看源码后发现,原来是经过一个特殊处理:当类的名字是以两个或以上的大写字母开头的话,bean的名字会与类名保持一致public class AnnotationBeanNameGenerator implements BeanNameGenerator { private static final String C..._@service beanname

随便推点

二叉树的各种创建方法_二叉树的建立-程序员宅基地

文章浏览阅读6.9w次,点赞73次,收藏463次。1.前序创建#include&lt;stdio.h&gt;#include&lt;string.h&gt;#include&lt;stdlib.h&gt;#include&lt;malloc.h&gt;#include&lt;iostream&gt;#include&lt;stack&gt;#include&lt;queue&gt;using namespace std;typed_二叉树的建立

解决asp.net导出excel时中文文件名乱码_asp.net utf8 导出中文字符乱码-程序员宅基地

文章浏览阅读7.1k次。在Asp.net上使用Excel导出功能,如果文件名出现中文,便会以乱码视之。 解决方法: fileName = HttpUtility.UrlEncode(fileName, System.Text.Encoding.UTF8);_asp.net utf8 导出中文字符乱码

笔记-编译原理-实验一-词法分析器设计_对pl/0作以下修改扩充。增加单词-程序员宅基地

文章浏览阅读2.1k次,点赞4次,收藏23次。第一次实验 词法分析实验报告设计思想词法分析的主要任务是根据文法的词汇表以及对应约定的编码进行一定的识别,找出文件中所有的合法的单词,并给出一定的信息作为最后的结果,用于后续语法分析程序的使用;本实验针对 PL/0 语言 的文法、词汇表编写一个词法分析程序,对于每个单词根据词汇表输出: (单词种类, 单词的值) 二元对。词汇表:种别编码单词符号助记符0beginb..._对pl/0作以下修改扩充。增加单词

android adb shell 权限,android adb shell权限被拒绝-程序员宅基地

文章浏览阅读773次。我在使用adb.exe时遇到了麻烦.我想使用与bash相同的adb.exe shell提示符,所以我决定更改默认的bash二进制文件(当然二进制文件是交叉编译的,一切都很完美)更改bash二进制文件遵循以下顺序> adb remount> adb push bash / system / bin /> adb shell> cd / system / bin> chm..._adb shell mv 权限

投影仪-相机标定_相机-投影仪标定-程序员宅基地

文章浏览阅读6.8k次,点赞12次,收藏125次。1. 单目相机标定引言相机标定已经研究多年,标定的算法可以分为基于摄影测量的标定和自标定。其中,应用最为广泛的还是张正友标定法。这是一种简单灵活、高鲁棒性、低成本的相机标定算法。仅需要一台相机和一块平面标定板构建相机标定系统,在标定过程中,相机拍摄多个角度下(至少两个角度,推荐10~20个角度)的标定板图像(相机和标定板都可以移动),即可对相机的内外参数进行标定。下面介绍张氏标定法(以下也这么称呼)的原理。原理相机模型和单应矩阵相机标定,就是对相机的内外参数进行计算的过程,从而得到物体到图像的投影_相机-投影仪标定

Wayland架构、渲染、硬件支持-程序员宅基地

文章浏览阅读2.2k次。文章目录Wayland 架构Wayland 渲染Wayland的 硬件支持简 述: 翻译一篇关于和 wayland 有关的技术文章, 其英文标题为Wayland Architecture .Wayland 架构若是想要更好的理解 Wayland 架构及其与 X (X11 or X Window System) 结构;一种很好的方法是将事件从输入设备就开始跟踪, 查看期间所有的屏幕上出现的变化。这就是我们现在对 X 的理解。 内核是从一个输入设备中获取一个事件,并通过 evdev 输入_wayland

推荐文章

热门文章

相关标签