1、文件

Java编译器在编译的过程中会涉及到对各种文件的搜索和查找,例如在文件夹下搜索.java源在压缩包*.jar内搜索.class文件,同时也会将编译生成的二进制文件写入文件。Java编译器有自己的文件相关类及管理类,以方便对文件进行各种操作。

为什么要用ct.sym要研究:https://blog.csdn.net/blomule/article/details/40866271

1.1 文件相关实现类

包是存放类/集合的目录或者压缩包。包与类的关系类似于目录/压缩包与文件。Java类库大多以压缩包形式存储,如*.jar,实际上,lib目录下的ct.sym也是压缩包。

javac在编译类时,如果要使用JDK中rt.jar提供的一些类库API,那么会使用ct.sym。这样做是为了避免开发人员合适一些内部的API。这样做是避免当java开发人员调整这些接口造成客户端代码无法运行。

Java中处理最多的就是.class与.java结尾的文件,这些文件都以对象来表示并且需要专门进行管理,其文件对象的继承体系如下:

其中的RegularFileObject就是普通我们自己编写的java类对象,而SymbolFileObject就代表了ct.sym压缩包对象,ZipFileObject代表了除rt.jar外的所有jar包。另外还可以看到有ZipFileIndexFileObject,这表示其它我们也可以为其它的ZipFile压缩包建立快速索引,由于篇幅的限制,在这里不做讲解。

1.2 ZipArchive嵌套类

ZipArchive是ZipFileObject类的内部类,对于除rt.jar外的所有jar都是ZipArchive对象,这个类中有两个重要的属性定义:

/**
 * The index for the contents of this archive.
 */
// 相对路径与class文件的对应关系
protected final Map<RelativeDirectory,List<String>> map;
/**
 * The zip file for the archive.
 */
public final ZipFile zfile;

在构造函数初始化时一般会调用initMap()方法从ZipFile中读取信息填充map。这个map保存了RelativeDirectory目录的相对路径到类的映射关系。  

protected void initMap() throws IOException {
    for (Enumeration<? extends ZipEntry> e = zfile.entries(); e.hasMoreElements(); ) {
        ZipEntry entry;
        try {
            entry = e.nextElement();
        } catch (InternalError ex) {
            IOException io = new IOException();
            io.initCause(ex); // convenience constructors added in Mustang :-(
            throw io;
        }
        addZipEntry(entry);
    }
}

public void addZipEntry(ZipEntry entry) {
    String name = entry.getName();
    int i = name.lastIndexOf('/');
    String n = name.substring(0, i+1);
    RelativeDirectory dirname = new RelativeDirectory(n);
    String basename = name.substring(i+1);
    if (basename.length() == 0)
        return;
    List<String> list = map.get(dirname);
    if (list == null)
        list = List.nil();
    list = list.prepend(basename);
    map.put(dirname, list);
}

 

1.3 SymbolArchive嵌套类

SymbolFileObject代表ct.sym文件,继承了ZipArchive类,在构造函数初始化时也会调用initMap()方法,不过覆写了自己的addZipEntry()方法,如下:

@Override
public void addZipEntry(ZipEntry entry) {
    String name = entry.getName();
    if (!name.startsWith(prefix.path)) {
        return;
    }
    name = name.substring(prefix.path.length());
    int i = name.lastIndexOf('/');
    RelativeDirectory dirname = new RelativeDirectory(name.substring(0, i+1));
    String basename = name.substring(i + 1);
    if (basename.length() == 0) {
        return;
    }
    List<String> list = map.get(dirname);
    if (list == null)
        list = List.nil();
    list = list.prepend(basename);
    map.put(dirname, list);
}

主要是屏蔽掉不以META-INF/sym/rt.jar/开头的相对路径。 

2、文件的管理

JavacFileManager类注释如下:

This class provides access to the source, class and other files used by the compiler and related tools.

而文件管理类主要是JavacFileManager类,继承关系如下图。

抽象类BaseFileManger,这个类中有一些共有的实现方法,并不会涉及到具体具体文件对象或者类路径的引用,而实现的接口StandardJavaFileManager与JavaFileManager是Java专门针对文件操作而定义的相关接口。

(2)声明只是表示需要一个List类,并没有指明这个List类以什么样的文件格式存在,如.class或者.java,那么该以什么格式进行搜索呢?或者.class与.java同时存在时该选取哪一种格式呢?

2.1 获取JavacFileManager实例

查看Context类的解释及实例

2.2 Location及Path类

其主要的实现类为StandardLocation,这是一个枚举类,定义了几个重要的枚举常量:

public enum StandardLocation implements Location {

    /**
     * Location of new class files.
     */
    CLASS_OUTPUT,

    /**
     * Location of new source files.
     */
    SOURCE_OUTPUT,

    /**
     * Location to search for user class files.
     */
    CLASS_PATH,

    /**
     * Location to search for existing source files.
     */
    SOURCE_PATH,

    /**
     * Location to search for annotation processors.
     */
    ANNOTATION_PROCESSOR_PATH,

    /**
     * Location to search for platform classes.  Sometimes called
     * the boot class path.
     */
    PLATFORM_CLASS_PATH;
    //...
}

Java编译器可能输出源代码或者二进制的class文件,而文件生成到哪里是通过CLASS_OUTPUT与SOURCE_OUTPUT来指定的。

对.java及.class文件的搜索路径进行了归类,主要是4大类:

(1)PLATFORM_CLASS_PATH

(2)SOURCE_PATH

(3)CLASS_PATH

(4)ANNOTATION_PROCESSOR_PATH

优先在PLATFORM_CLASS_PATH类别下搜索.class类型的文件,将会搜索到<java_home>/lib和<java_home>/ext包下的jar文件。

这两个路径只有在指定了-classpath或者-sourcepath时才会有用。

1、当 -sourcepath 没有指定时,在 -classpath 路径里面搜索 .class 和 .java 文件

2、当 -sourcepath 指定时,只搜索 -classpath 路径下的 .class 文件,即使-classpath 路径下有要找的.java文件也会不搜索这个文件

3、 -sourcepath 只搜索 .java 文件,不搜索 .class 文件。因此应该避免用 -sourcepath,而只用 -classpath 来指定搜索 .class 和 .java 文件的路径

在Paths中对定义了前三个类别的搜索路径,如下:

protected void lazy() {
    if (!inited) {
        warn = lint.isEnabled(Lint.LintCategory.PATH);
        pathsForLocation.put(PLATFORM_CLASS_PATH, computeBootClassPath());
        pathsForLocation.put(CLASS_PATH, computeUserClassPath());
        pathsForLocation.put(SOURCE_PATH, computeSourcePath());
        inited = true;
    }
}

  

(1)PLATOFRM_CLASS_PATH代表的搜索路径是通过调用computeBootClassPath()方法得到的,这个方法的实现如下:

private Path computeBootClassPath() {
        defaultBootClassPathRtJar = null;
        Path path = new Path(this);

        String bootclasspathOpt = options.get(BOOTCLASSPATH); // -bootclasspath
        String endorseddirsOpt = options.get(ENDORSEDDIRS); // -endorseddirs
        String extdirsOpt = options.get(EXTDIRS); // -extdirs
        String xbootclasspathPrependOpt = options.get(XBOOTCLASSPATH_PREPEND); // -Xbootclasspath/p:
        String xbootclasspathAppendOpt = options.get(XBOOTCLASSPATH_APPEND); // -Xbootclasspath/a:

        path.addFiles(xbootclasspathPrependOpt);

        if (endorseddirsOpt != null) {
            path.addDirectories(endorseddirsOpt);
        }else {
            path.addDirectories(System.getProperty("java.endorsed.dirs"), false);
        }

        if (bootclasspathOpt != null) {
            path.addFiles(bootclasspathOpt);
        } else {
            // Standard system classes for this compiler's release.
            String files = System.getProperty("sun.boot.class.path");
            path.addFiles(files, false);
            File rt_jar = new File("rt.jar");
            for (File file : getPathEntries(files)) {
                if (new File(file.getName()).equals(rt_jar)) {
                    defaultBootClassPathRtJar = file;
                }
            }
        }

        path.addFiles(xbootclasspathAppendOpt);

        // Strictly speaking, standard extensions are not bootstrap
        // classes, but we treat them identically, so we'll pretend
        // that they are.
        if (extdirsOpt != null) {
            path.addDirectories(extdirsOpt);
        }else {
            path.addDirectories(System.getProperty("java.ext.dirs"), false);
        }

        isDefaultBootClassPath =
                (xbootclasspathPrependOpt == null) &&
                (bootclasspathOpt == null) &&
                (xbootclasspathAppendOpt == null);

        return path;
    }

通过这个方法可以清楚的看到,在Javac中指定一些路径之间的关系,不过我们一般都不会通过命令来指定这些路径,默认会获取到。

(2)SOURCE_PATH

private Path computeSourcePath() {
        String sourcePathArg = options.get(SOURCEPATH);
        if (sourcePathArg == null) {
            return null;
        }

        return new Path(this).addFiles(sourcePathArg);
    }

(3)CLASS_PATH

 private Path computeUserClassPath() {
        String cp = options.get(CLASSPATH);

        // CLASSPATH environment variable when run from `javac'.
        if (cp == null) {
            cp = System.getProperty("env.class.path");
        }

        // If invoked via a java VM (not the javac launcher), use the
        // platform class path
        if (cp == null &&
                System.getProperty("application.home") == null) {
            cp = System.getProperty("java.class.path");
        }

        // Default to current working directory.
        if (cp == null) {
            cp = ".";
        }

        return new Path(this)
            .expandJarClassPaths(true)        // Only search user jars for Class-Paths
            .emptyPathDefault(new File("."))  // Empty path elt ==> current directory
            .addFiles(cp);
    }

  

JavacFileManager类中的共有接口,如下:

public Iterable<? extends File> getLocation(Location location) {
        nullCheck(location);
        paths.lazy();
        if (location == CLASS_OUTPUT) {
            return (getClassOutDir() == null ? null : List.of(getClassOutDir()));
        } else if (location == SOURCE_OUTPUT) {
            return (getSourceOutDir() == null ? null : List.of(getSourceOutDir()));
        } else {
            return paths.getPathForLocation(location);
        }
    }

  

2.3 JavacFileManager的实现

编译器主要通过JavacFileManager来完成源文件、二进制及其它文件的获取,例如如下实例在编译源代码时,经常通过import关键字声明对依赖类的导入,如下:

package com.test20;

import java.util.List;

class TestHH{
	List<String> l = null;
}

从import声明就可以看出,这个类依赖于java.util包下的公共类List,如果要查找及加载这个依赖类,肯定会去java.util包下找一个名称为List的文件,因为List类与当前类不在同一个包中,肯定是public修饰符修饰的类,而Java规定由public修饰符修饰的类必须与文件同名。

在真正实现时必须要考虑下面2个问题:

(1)java.util只是包名,是查找类的相对路径。而要想加载一个文件必须要确定其绝对路径,该如何得到这个类的绝对路径呢?

JavacFileManager类提供了一个重要的搜索API,实现如下:

public Iterable<JavaFileObject> list(Location location,
                                     String packageName,
                                     Set<JavaFileObject.Kind> kinds,
                                     boolean recurse) throws IOException {
    // validatePackageName(packageName);
    nullCheck(packageName);
    nullCheck(kinds);

    Iterable<? extends File> path = getLocation(location);
    if (path == null) {
        return List.nil();
    }
    RelativeDirectory subdirectory = RelativeDirectory.forPackage(packageName);
    ListBuffer<JavaFileObject> results = new ListBuffer<JavaFileObject>();

    for (File directory : path) {
        listContainer(directory, subdirectory, kinds, recurse, results);
    }
    return results.toList();
}

这个方法中涉及到了几个辅助类,如Location、JavaFileObject.Kind与RelativeDirectory。其中的Location代表了搜索的具体路径,上一章详细介绍过,而RelativeDirectory代表了相对路径,父类为 RelativePath类代表相对路径,主要有两个实现类RelativeFile与RelativeDirectory,继承关系如下图所示。

其中RelativeFile代表文件的相对路径,而RelativeDirectory代表了文件夹的相对路径。

JavaFileObject.Kind枚举类正是指定了搜索文件的格式,如下:

/**
     * Kinds of JavaFileObjects.
     */
    enum Kind {
        /**
         * Source files written in the Java programming language.  For
         * example, regular files ending with {@code .java}.
         */
        SOURCE(".java"),

        /**
         * Class files for the Java Virtual Machine.  For example,
         * regular files ending with {@code .class}.
         */
        CLASS(".class"),

        /**
         * HTML files.  For example, regular files ending with {@code
         * .html}.
         */
        HTML(".html"),

}

不过最常见的还是.java与.class文件。  

这样我们就可以在Location指定的类别路径下通过包的相对路径RelativeDirectory和指定的文件格式JavaFileObject.Kind来搜索文件了,可以看到调用了listContainer()方法,这个方法的实现如下:

/**
 * container is a directory, a zip file, or a non-existant path.
 * Insert all files in subdirectory subdirectory of container which
 * match fileKinds into resultList
 */
private void listContainer(File container,
                           RelativeDirectory subdirectory,
                           Set<JavaFileObject.Kind> fileKinds,
                           boolean recurse,
                           ListBuffer<JavaFileObject> resultList) {
    // 取出来的一定是ct.sym或者jar或者是生成的索引文件,不会存储目录
    Archive archive = archives.get(container);
    if (archive == null) {
        // archives are not created for directories.
        // jar包不是Directory,如resources.jar
        if  (fsInfo.isDirectory(container)) {
            listDirectory(container,subdirectory,fileKinds,recurse,resultList);
            return;
        }

        // Not a directory; either a file or non-existant, create the archive
        try {
            // 因为archive为空,又不是目录,所以可能是archive没有打开
            archive = openArchive(container);
        } catch (IOException ex) {
            log.error("error.reading.file",container, getMessage(ex));
            return;
        }
    }
    listArchive(archive,subdirectory,fileKinds,recurse,resultList);
}

由于ct.sym与jar包在编译器中都是以Archive

下面首先来看listDirectory()方法的实现,如下:

/**
 * Insert all files in subdirectory subdirectory of directory directory
 * which match fileKinds into resultList
 */
private void listDirectory(File directory,
                           RelativeDirectory subdirectory,
                           Set<JavaFileObject.Kind> fileKinds,
                           boolean recurse,
                           ListBuffer<JavaFileObject> resultList) {

    // directory拼接上subdirectory后形成的路径
    File d = subdirectory.getFile(directory);
    if (!caseMapCheck(d, subdirectory)) {
        return;
    }

    File[] files = d.listFiles();
    if (files == null) {
        return;
    }

    for (File f: files) {
        String fname = f.getName();
        if (f.isDirectory()) { // 是目录
            if (recurse && SourceVersion.isIdentifier(fname)) {
                // 递归时directory值不变,而subdirectory值
                RelativeDirectory subDir = new RelativeDirectory(subdirectory, fname);
                // 递归调用
                listDirectory(directory,subDir,fileKinds,recurse,resultList);
            }
        } else { // 是文件
            if (isValidFile(fname, fileKinds)) {
                File file = new File(d, fname);
                JavaFileObject fe = new RegularFileObject(this, fname,file);
                resultList.append(fe);
            }
        }
    }
}

接着看listContainer()方法中调用的openArchive()方法的源代码实现,这个方法的实现有些复杂,

在openArchive()方法中首先对对ct.sym做了特殊处理,如下:

if (!ignoreSymbolFile &&  // 不忽略符号文件
            paths.isDefaultBootClassPathRtJar(zipFileName) // zipFileName为rt.jar
     ){
        File file = zipFileName.getParentFile().getParentFile(); // ${java.home}
        // C:\Program Files\Java\jdk1.7.0_79\jre\lib\rt.jar
        if (new File(file.getName()).equals(new File("jre"))) {
            file = file.getParentFile(); // C:\Program Files\Java\jdk1.7.0_79
        }
        // file == ${jdk.home}
        // C:\Program Files\Java\jdk1.7.0_79\lib  =>  C:\Program Files\Java\jdk1.7.0_79\lib\ct.sym
        for (String name : symbolFileLocation) {
            file = new File(file, name);
        }
        // file == ${jdk.home}/lib/ct.sym
        if (file.exists()) {
            zipFileName = file; // 最后拼接后的zipFileName路径为C:\Program Files\Java\jdk1.7.0_79\lib\ct.sym
        }
    }

代码复杂,其实就是通过rt.jar的绝对路径找到ct.sym的绝对路径,如我本机 rt.jar的绝对路径为C:\Program Files\Java\jdk1.7.0_79\jre\lib\rt.jar,则最终zipFileName的路径变为C:\Program Files\Java\jdk1.7.0_79\lib\ct.sym。

然后将ZipFile对象进一步封装为ZipArchive与SymbolArchive,之前讲解过,如果调用ZipArchive与SymbolArchive的构造函数,会初始化其中的map属性并填充值。

下面将File到Archive的对应关系保存到JavacFileManager的全局map中,如下:

/** A directory of zip files already opened.
*/
Map<File, Archive> archives = new HashMap<File,Archive>();

回到listContainer()方法中,完成最后一个方法的调用,如下:

listArchive(archive,subdirectory,fileKinds,recurse,resultList);

listArchive()方法的源代码如下:

/**
 * Insert all files in subdirectory subdirectory of archive archive
 * which match fileKinds into resultList
 */
private void listArchive(Archive archive,
                           RelativeDirectory subdirectory,
                           Set<JavaFileObject.Kind> fileKinds,
                           boolean recurse,
                           ListBuffer<JavaFileObject> resultList) {
    // Get the files directly in the subdir
    // 获取压缩包中的所有文件
    List<String> files = archive.getFiles(subdirectory);
    if (files != null) {
        for (; !files.isEmpty(); files = files.tail) {
            String file = files.head;
            if (isValidFile(file, fileKinds)) {
                JavaFileObject jfo = archive.getFileObject(subdirectory, file);
                resultList.append(jfo);
            }
        }
    }
    if (recurse) {
        // 获取压缩包中所有的目录
        for (RelativeDirectory s: archive.getSubdirectories()) {
            if (subdirectory.contains(s)) {
                // Because the archive map is a flat list of directories,
                // the enclosing loop will pick up all child subdirectories.
                // Therefore, there is no need to recurse deeper.
                listArchive(archive, s, fileKinds, false, resultList); // 递归调用
            }
        }
    }
}

 

3、实例分析

编译器要分析源文件,首先要获取通过路径找到这个源文件,然后获取到字符流,在JavaCompiler中有如下调用:

/**
 * Parse contents of file.
 * @param filename The name of the file to be parsed.
 */
public JCCompilationUnit parse(JavaFileObject filename) {
    JavaFileObject prev = log.useSource(filename);
    try {
        CharSequence content = readSource(filename);
        JCCompilationUnit t = parse(filename, content);
        if (t.endPositions != null) {
            log.setEndPosTable(filename, t.endPositions);
        }
        return t;
    } finally {
        log.useSource(prev);
    }
}

通过调用readSource()方法来获取字符流,然后供下一阶段的词法分析使用,普通的java源文件一般会封装为RegularFileObject对象,然后在readSource()方法中调用了文件对象的getCharContent()方法,源代码如下:

@Override
public CharBuffer getCharContent(boolean ignoreEncodingErrors) throws IOException {
    CharBuffer cb = fileManager.getCachedContent(this);
    if (cb == null) {
        InputStream in = new FileInputStream(file);
        try {
            ByteBuffer bb = fileManager.makeByteBuffer(in);
            JavaFileObject prev = fileManager.log.useSource(this);
            try {
                cb = fileManager.decode(bb, ignoreEncodingErrors);
            } finally {
                fileManager.log.useSource(prev);
            }
            fileManager.recycleByteBuffer(bb);
            if (!ignoreEncodingErrors) {
                fileManager.cache(this, cb);
            }
         } finally {
            in.close();
        }
    }
    return cb;
}

首先从JavacFileManager的缓存中获取当前文件对象的字符流,其实就是通过全局的map来保存从文件对象到字符缓冲的映射,如下:

protected final Map<JavaFileObject, ContentCacheEntry> contentCache = new HashMap<JavaFileObject, ContentCacheEntry>();

这个ContentCacheEntry类是BaseFileManager类中定义的一个私有静态内部类,这个ContentCacheEntry类内部是通过软引用来保持对缓冲的引用的。这样我们就知道当首次加载或者内存不足时,通过fileManager.getCachedContent(this)取出来的都可能为空。

如果为空会进入if语句,而首次获取时一般都为空,根据File文件获取InputStream输入流对象后,调用fileManager对象的makeBytebuffer()对象,将文件中的内容读取到缓存中。

/**
 * Make a byte buffer from an input stream.
 */
public ByteBuffer makeByteBuffer(InputStream in) throws IOException {
    int limit = in.available();
    if (limit < 1024) {
        limit = 1024;
    }
    ByteBuffer result = byteBufferCache.get(limit); // 获取出来的result类型为java.nio.HeapByteBuffer
    int position = 0;
    while (in.available() != 0) {
        if (position >= limit) {
            // expand buffer  扩容
            result = ByteBuffer.allocate(limit <<= 1).put((ByteBuffer) result.flip());
        }
        int count = in.read(result.array(),position,limit - position);
        if (count < 0) {
            break;
        }
        result.position(position += count);
    }
    return (ByteBuffer)result.flip();
}

实现如下:

/**
 * A single-element cache of direct byte buffers.
 */
private static class ByteBufferCache {
    private ByteBuffer cached;
    ByteBuffer get(int capacity) {
        if (capacity < 20480) {
            capacity = 20480;
        }
        ByteBuffer result;
        if (cached != null && cached.capacity() >= capacity){
            result = (ByteBuffer)cached.clear();
        }else{
            result = ByteBuffer.allocate(capacity + capacity>>1);
        }
        cached = null;
        return result;
    }
    void put(ByteBuffer x) {
        cached = x;
    }
}

定义了一个字节缓冲类,其中的cached即为具体的缓冲,由get()方法可以看到分析的

  

  

  

javac文件系统的更多相关文章

  1. 【从零开始学习Hadoop】--2.HDFS分布式文件系统

    1. 文件系统从头说2. Hadoop的文件系统3. 如何将文件复制到HDFS3.1 目录和文件结构3.2 FileCopy.java文件的源代码3.3 编译3.4打包3.5 运行3.6 检查结果 1 ...

  2. package、import、java及javac的相关介绍(转)

    Package: package中所存放的文件 所有文件,不过一般分一下就分这三种 1.java程序源文件,扩展名为.java: 2.编译好的java类文件,扩展名为.class: 3.其他文件,也称 ...

  3. Hadoop权威指南:HDFS-目录,查询文件系统,删除文件

    Hadoop权威指南:HDFS-目录,查询文件系统,删除文件 [TOC] 目录 FileSystem实例提供了创建目录的方法 public boolean mkdirs(Path f) throws ...

  4. 第一章-Javac编译器介绍

    1.Javac概述 编译器可以将编程语言的代码转换为其他形式,如Javac,将Java语言转换为虚拟机能够识别的.class文件形式.而这种将java源代码(以.java做为文件存储格式)转换为cla ...

  5. .NET Core的文件系统[5]:扩展文件系统构建一个简易版“云盘”

    FileProvider构建了一个抽象文件系统,作为它的两个具体实现,PhysicalFileProvider和EmbeddedFileProvider则分别为我们构建了一个物理文件系统和程序集内嵌文 ...

  6. Linux学习之探索文件系统

    Linux,一起学习进步-    ls With it, we can see directory contents and determine a variety of important file ...

  7. Linux之搭建自己的根文件系统

    Hi!大家好,我是CrazyCatJack.又和大家见面了.今天给大家带来的是构建Linux下的根文件系统.希望大家看过之后都能构建出符合自己需求的根文件系统^_^ 1.内容概述 1.构造过程 今天给 ...

  8. 【架构设计】分布式文件系统 FastDFS的原理和安装使用

    本文地址 分享提纲: 1.概述 2. 原理 3. 安装 4. 使用 5. 参考文档 1. 概述 1.1)[常见文件系统] Google了一下,流行的开源分布式文件系统有很多,介绍如下:   -- mo ...

  9. .NET Core的文件系统[1]:读取并监控文件的变化

    ASP.NET Core 具有很多针对文件读取的应用.比如我们倾向于采用JSON文件来定义配置,所以应用就会涉及针对配置文件读取.如果用户发送一个针对物理文件的HTTP请求,应用会根据指定的路径读取目 ...

随机推荐

  1. B-Spline 样条学习笔记

    (1) 对于clamped样条曲线,节点区间的数目等于曲线段的数目. eg: B-样条曲线有11个控制点(即,n = 10), 3次P样条 (即, p=3)  ,由 m=n+p+1 则有15 个节点  ...

  2. 轉:Jquery绑定img的click事件

    用JQUERY给IMG element绑定click事件的时候,直接用img.click(function(){...})不起作用,如下面代码$("img.ms-rteImage-Light ...

  3. Solr 从文件创建索引

    http://blog.csdn.net/clj198606061111/article/details/21492457 http://wiki.apache.org/solr/Extracting ...

  4. C# WebService调用方法

    public class WebServiceHelper    {        /// < summary>         /// 动态调用web服务         /// < ...

  5. 开源应用框架BitAdminCore:更新日志20180903

    索引 NET Core应用框架之BitAdminCore框架应用篇系列 框架演示:https://www.bitadmincore.com 框架源码:https://github.com/chenyi ...

  6. Cesium Language (CZML) 入门1 — CZML Structure(CZML的结构)

    原文:https://github.com/AnalyticalGraphicsInc/cesium/wiki/CZML-Structure CZML是一种用来描述动态场景的JSON架构的语言,主要用 ...

  7. C#中Cookies的读取

    C#中Cookies的读取   链接: 一 .写入Cookie 1. Name 和 Value 属性由程序设定,默认值都是空引用. 2. Domain属性的默认值为当前URL的域名部分,不管发出这个c ...

  8. python开发工具之分析

    开发工具篇之工具分析 任务:开发python程序环境:编辑器+解释器 [原始开发python] 环境:安装python (提供python解释器,命令行shell窗口,简易python编译器,第三方库 ...

  9. FFMPEG 的学习

    https://blog.csdn.net/leixiaohua1020/article/details/15811977/

  10. robot framework学习笔记之一 资源文件(Resource)和外部资源(External Resources)

    一.资源文件(Resource) 测试套件主要是存放测试案例,资源文件主要是用来存放用户关键字. 添加资源    在目录型的Project/Test Suite下单击鼠标右键,选择『New Resou ...