基本上都是自己写的工具构建前端工程,压缩/混淆 JavaScript 代码的工具必不可少。我们是 Java 平台的,就是说用 Java 去压缩 JS,这样比较方便。虽然咱们可以外部调用 node 等专门的前端构建工具,但那样不省事,能在 Java 圈子里面搞定就行,我们不搞太复杂的。好~闲话不多说,先看看低配版的。

低配版

这个低配版就几个函数组成,没以前来其他第三方的包,故称为“低配版”。简单实用也可以,我也用了很久。

/*** This file is part of the Echo Web Application Framework (hereinafter "Echo").* Copyright (C) 2002-2009 NextApp, Inc.** Compresses a String containing JavaScript by removing comments and* whitespace.*/
public class JavaScriptSimpleCompressor {private static final char LINE_FEED = '\n';private static final char CARRIAGE_RETURN = '\r';private static final char SPACE = ' ';private static final char TAB = '\t';/*** Compresses a String containing JavaScript by removing comments and* whitespace.* * @param script the String to compress* @return a compressed version*/public static String compress(String script) {JavaScriptSimpleCompressor jsc = new JavaScriptSimpleCompressor(script);return jsc.outputBuffer.toString();}/** Original JavaScript text. */private String script;/*** Compressed output buffer. This buffer may only be modified by invoking the* <code>append()</code> method.*/private StringBuffer outputBuffer;/** Current parser cursor position in original text. */private int pos;/** Character at parser cursor position. */private char ch;/** Last character appended to buffer. */private char lastAppend;/** Flag indicating if end-of-buffer has been reached. */private boolean endReached;/** Flag indicating whether content has been appended after last identifier. */private boolean contentAppendedAfterLastIdentifier = true;/*** Creates a new <code>JavaScriptCompressor</code> instance.* * @param script*/private JavaScriptSimpleCompressor(String script) {this.script = script;outputBuffer = new StringBuffer(script.length());nextChar();while (!endReached) {if (Character.isJavaIdentifierStart(ch)) {renderIdentifier();} else if (ch == ' ') {skipWhiteSpace();} else if (isWhitespace()) {// Compress whitespaceskipWhiteSpace();} else if ((ch == '"') || (ch == '\'')) {// Handle stringsrenderString();} else if (ch == '/') {// Handle commentsnextChar();if (ch == '/') {nextChar();skipLineComment();} else if (ch == '*') {nextChar();skipBlockComment();} else {append('/');}} else {append(ch);nextChar();}}}/*** Append character to output.* * @param ch the character to append*/private void append(char ch) {lastAppend = ch;outputBuffer.append(ch);contentAppendedAfterLastIdentifier = true;}/*** Determines if current character is whitespace.* * @return true if the character is whitespace*/private boolean isWhitespace() {return ch == CARRIAGE_RETURN || ch == SPACE || ch == TAB || ch == LINE_FEED;}/*** Load next character.*/private void nextChar() {if (!endReached) {if (pos < script.length()) {ch = script.charAt(pos++);} else {endReached = true;ch = 0;}}}/*** Adds an identifier to output.*/private void renderIdentifier() {if (!contentAppendedAfterLastIdentifier)append(SPACE);append(ch);nextChar();while (Character.isJavaIdentifierPart(ch)) {append(ch);nextChar();}contentAppendedAfterLastIdentifier = false;}/*** Adds quoted String starting at current character to output.*/private void renderString() {char startCh = ch; // Save quote charappend(ch);nextChar();while (true) {if ((ch == LINE_FEED) || (ch == CARRIAGE_RETURN) || (endReached)) {// JavaScript error: string not terminatedreturn;} else {if (ch == '\\') {append(ch);nextChar();if ((ch == LINE_FEED) || (ch == CARRIAGE_RETURN) || (endReached)) {// JavaScript error: string not terminatedreturn;}append(ch);nextChar();} else {append(ch);if (ch == startCh) {nextChar();return;}nextChar();}}}}/*** Moves cursor past a line comment.*/private void skipLineComment() {while ((ch != CARRIAGE_RETURN) && (ch != LINE_FEED)) {if (endReached) {return;}nextChar();}}/*** Moves cursor past a block comment.*/private void skipBlockComment() {while (true) {if (endReached) {return;}if (ch == '*') {nextChar();if (ch == '/') {nextChar();return;}} elsenextChar();}}/*** Renders a new line character, provided previously rendered character is not a* newline.*/private void renderNewLine() {if (lastAppend != '\n' && lastAppend != '\r') {append('\n');}}/*** Moves cursor past white space (including newlines).*/private void skipWhiteSpace() {if (ch == LINE_FEED || ch == CARRIAGE_RETURN) {renderNewLine();} else {append(ch);}nextChar();while (ch == LINE_FEED || ch == CARRIAGE_RETURN || ch == SPACE || ch == TAB) {if (ch == LINE_FEED || ch == CARRIAGE_RETURN) {renderNewLine();}nextChar();}}
}

压缩的 js 没啥逻辑错误,否则我也不会用那么久。只是有点蛋疼的是,这货居然把 Stirng 里面的空格都处理。因为写 vue 模板的时候,我用了多行字符串,换行符为\,哈哈,有点高级的引用,低配版就搞不定了,也不怪你了,也不是什么大罪,才多少行代码唷。

调用方法如下:

JavaScriptSimpleCompressor.compress(jsCode);

具体压缩过程:

package com.ajaxjs.web;import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Date;
import java.util.Objects;
import java.util.logging.Logger;import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;/*** 打包 js*/
@WebServlet("/JsController")
public class JsController extends HttpServlet {private static final long serialVersionUID = 1L;protected void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {String js = "// build date:" + new Date() + "\n";js += JavaScriptCompressor.compress(read(mappath(request, "js/ajaxjs-base.js"))) + "\n";js += JavaScriptCompressor.compress(read(mappath(request, "js/ajaxjs-list.js"))) + "\n";js += action(mappath(request, "js/widgets/"), true) + "\n";String output = request.getParameter("output"); // 保存位置Objects.requireNonNull(output, "必填参数");save(output + "\\WebContent\\asset\\js\\all.js", js);response.getWriter().append("Pack js Okay.");}static String frontEnd = "C:\\project\\wstsq\\WebContent\\asset\\css";/*** 压缩 CSS 并将其保存到一个地方*/protected void doPost(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {String css = request.getParameter("css"),file = request.getParameter("file") == null ? "main" : request.getParameter("file");String output = "";String saveFolder = request.getParameter("saveFolder") == null ? frontEnd : request.getParameter("saveFolder");Logger.getGlobal().info(request.getParameter("saveFolder"));try {save(saveFolder + "\\" + file, css);output = "{\"isOk\":true}";} catch (Throwable e) {e.printStackTrace();output = "{\"isOk\":false}";}response.getWriter().append(output);}/*** 打包某个目录下所有的 js* * @param _folder* @param isCompress* @return*/public static String action(String _folder, boolean isCompress) {StringBuilder sb = new StringBuilder();File folder = new File(_folder);File[] files = folder.listFiles();if (files != null)for (File file : files) {if (file.isFile()) {String jsCode = null;try {jsCode = read(file.toPath());} catch (IOException e) {e.printStackTrace();}sb.append("\n");sb.append(isCompress ? JavaScriptCompressor.compress(jsCode) : jsCode);}}return sb.toString();}/*** 获取磁盘真實地址* * @param cxt          Web 上下文* @param relativePath 相对地址* @return 绝对地址*/public static String mappath(HttpServletRequest request, String relativePath) {String absolute = request.getServletContext().getRealPath(relativePath);if (absolute != null)absolute = absolute.replace('\\', '/');return absolute;}public static String read(Path path, Charset encode) throws IOException {if (Files.isDirectory(path))throw new IOException("参数 fullpath:" + path.toString() + " 不能是目录,请指定文件");if (!Files.exists(path))throw new IOException(path.toString() + " 不存在");return new String(Files.readAllBytes(path), encode);}public static String read(String fullpath, Charset encode) throws IOException {Path path = Paths.get(fullpath);return read(path, encode);}public static String read(Path path) throws IOException {return read(path, StandardCharsets.UTF_8);}public static String read(String fullpath) throws IOException {return read(fullpath, StandardCharsets.UTF_8);}public static void saveClassic(String fullpath, String content) throws IOException {File file = new File(fullpath);if (file.isDirectory())throw new IOException("参数 fullpath:" + fullpath + " 不能是目录,请指定文件");try (FileOutputStream fop = new FileOutputStream(file)) {if (!file.exists())file.createNewFile();fop.write(content.getBytes());fop.flush();}}public void test() throws IOException {String content = read("c://temp//newfile.txt");save("c://temp//newfile2.txt", content);}public static void save(String fullpath, String content) throws IOException {Path path = Paths.get(fullpath);if (Files.isDirectory(path))throw new IOException("参数 fullpath:" + fullpath + " 不能是目录,请指定文件");if (!Files.exists(path))Files.createFile(path);Logger.getGlobal().info(path.toString());Files.write(path, content.getBytes());}
}

YUI Compressor

于是得用第三方库了。第一时间想到 YUI Compressor,这是我当年学前端就有了(“史前”),不过很遗憾居然不支持 ES5 的箭头函数,直接报错,要是你可以忽略 Error 也行呀,——可是显然对新语法不兼容,无法压缩出来,于是也只能放弃鸟~唉 跟不上形势了, 14年最后更新停留在 2.4.8,不支持新 JS 不能爱呀。

简单用法如下。

private static String yuicompressor(String code) {String result = null;try (StringWriter writer = new StringWriter();InputStream in = new ByteArrayInputStream(code.getBytes());Reader reader = new InputStreamReader(in);) {JavaScriptCompressor compressor = new JavaScriptCompressor(reader, e);compressor.compress(writer, -1, true, false, false, false);result = writer.toString();} catch (EvaluatorException | IOException e) {e.printStackTrace();}return result;
}private static ErrorReporter e = new ErrorReporter() {@Overridepublic void warning(String message, String sourceName, int line, String lineSource, int lineOffset) {if (line < 0)System.err.println("/n[WARNING] " + message);elseSystem.err.println("/n[WARNING] " + line + ':' + lineOffset + ':' + message);}@Overridepublic void error(String message, String sourceName, int line, String lineSource, int lineOffset) {if (line < 0)System.err.println("/n[ERROR] " + message);elseSystem.err.println("/n[ERROR] " + line + ':' + lineOffset + ':' + message);}@Overridepublic EvaluatorException runtimeError(String message, String sourceName, int line, String lineSource,int lineOffset) {error(message, sourceName, line, lineSource, lineOffset);return new EvaluatorException(message);}
};

压缩 CSS 也可以。

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Writer;import org.mozilla.javascript.ErrorReporter;
import org.mozilla.javascript.EvaluatorException;import com.yahoo.platform.yui.compressor.CssCompressor;
import com.yahoo.platform.yui.compressor.JavaScriptCompressor;/*** JS、CSS压缩工具 https://blog.csdn.net/jianggujin/article/details/80202559* * @author jianggujin**/
public class CompressorUtils {public void compressJS(File js, Writer out) throws Exception {compressJS(js, out, -1, true, true, false, false);}public void compressJS(File js, Writer out, int linebreakpos, boolean munge, boolean verbose, boolean preserveAllSemiColons, boolean disableOptimizations) throws IOException {try (InputStreamReader in = new InputStreamReader(new FileInputStream(js), "UTF-8");) {JavaScriptCompressor compressor = new JavaScriptCompressor(in, new ErrorReporter() {@Overridepublic void warning(String message, String sourceName, int line, String lineSource, int lineOffset) {System.err.println("[ERROR] in " + js.getAbsolutePath() + line + ':' + lineOffset + ':' + message);}@Overridepublic void error(String message, String sourceName, int line, String lineSource, int lineOffset) {System.err.println("[ERROR] in " + js.getAbsolutePath() + line + ':' + lineOffset + ':' + message);}@Overridepublic EvaluatorException runtimeError(String message, String sourceName, int line, String lineSource, int lineOffset) {error(message, sourceName, line, lineSource, lineOffset);return new EvaluatorException(message);}});compressor.compress(out, linebreakpos, munge, verbose, preserveAllSemiColons, disableOptimizations);}}public void compressCSS(File css, Writer out) throws Exception {compressCSS(css, out, -1);}public void compressCSS(File css, Writer out, int linebreakpos) throws IOException {try (InputStreamReader in = new InputStreamReader(new FileInputStream(css), "UTF-8");) {CssCompressor compressor = new CssCompressor(in);compressor.compress(out, linebreakpos);}}
}

高配版——Google Closure Compiler

你大爷还是你大爷,谷歌这项目一直更新。实际上 Java 生态 js 压缩工具没啥好选择,只剩大爷这货了。二话不多说,先给出 Maven 坐标。

<!-- https://mvnrepository.com/artifact/com.google.javascript/closure-compiler -->
<dependency><groupId>com.google.javascript</groupId><artifactId>closure-compiler</artifactId><version>v20200504</version>
</dependency>

Google Closure Compiler 的问题是文档不足,很少介绍 Java 里面的用法,有的早就过时了。好在找到这篇博文,可以顺利压缩。另外还有 Closure 专门的电子书。

用法:

/*** 校验js语法、压缩js* * @param code* @return*/
public static String compileJs(String code) {CompilerOptions options = new CompilerOptions();// Simple mode is used here, but additional options could be set, too.CompilationLevel.WHITESPACE_ONLY.setOptionsForCompilationLevel(options);// To get the complete set of externs, the logic in// CompilerRunner.getDefaultExterns() should be used here.SourceFile extern = SourceFile.fromCode("externs.js", "function alert(x) {}");// The dummy input name "input.js" is used here so that any warnings or// errors will cite line numbers in terms of input.js.
//      SourceFile input = SourceFile.fromCode("input.js", code);SourceFile jsFile = SourceFile.fromFile(code);Compiler compiler = new Compiler();compiler.compile(extern, jsFile, options);// The compiler is responsible for generating the compiled code; it is not// accessible via the Result.if (compiler.getErrorCount() > 0) {StringBuilder sb = new StringBuilder();for (JSError jsError : compiler.getErrors()) {sb.append(jsError.toString());}// System.out.println(sb.toString());}return compiler.toSource();
}

相中方法: Result compile(JSSourceFile extern, JSSourceFile input, CompilerOptions options) 。input 和 options 容易理解,extern是什么?其实类的描述里也稍微提了下:

External variables are declared in ‘externs’ files. For instance, the file may include definitions for global javascript/browser objects such as window, document.

很显然可以没有 extern,但不能为 null。

文中提到:

三种压缩模式介绍

  • Whitespace only:只是简单的去除空格换行注释。
  • Simple:比Whitespace only更高端一点,在其基础上,还对局部变量的变量名进行缩短。这也是其他压缩工具所使用的压缩方式,如UglifyJS等,也是最为主流的压缩方式。比较安全。
  • Advanced:Advanced级别的压缩改变(破坏)了原有代码结构,直接输出代码最终运行结果,而且这种级别的压缩还会删除未调用的函数代码

注意:Advanced级别的压缩虽然对代码压缩做到了极致,但也改变(破坏)了原有代码结构,直接输出了代码最终运行结果,所以使用起来得异常小心,稍微有不规范可能就会引起压缩报错或者压缩成功后却不能正常运行。

不知为啥选择最简单的 Whitespace only 依然还是有 ‘use strict’;,官方在线的例子又不会。我只好强行 replaceAll 替换掉。

我弄的 js 打包器


import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Date;
import java.util.Objects;
import java.util.logging.Logger;import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;import com.google.javascript.jscomp.CompilationLevel;
import com.google.javascript.jscomp.Compiler;
import com.google.javascript.jscomp.CompilerOptions;
import com.google.javascript.jscomp.JSError;
import com.google.javascript.jscomp.SourceFile;/*** 打包 js*/
@WebServlet("/JsController")
public class JsController extends HttpServlet {private static final long serialVersionUID = 1L;protected void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {String js = "// build date:" + new Date() + "\n";js += compileJs(mappath(request, "js/ajaxjs-base.js")) + "\n";js += compileJs(mappath(request, "js/ajaxjs-list.js")) + "\n";js += action(mappath(request, "js/widgets/")) + "\n";String output = request.getParameter("output"); // 保存位置Objects.requireNonNull(output, "必填参数");save(output + "\\WebContent\\asset\\js\\all.js", js.replaceAll("'use strict';", ""));response.getWriter().append("Pack js Okay.");}/*** 校验js语法、压缩js* * @param code* @return*/public static String compileJs(String code) {CompilerOptions options = new CompilerOptions();// Simple mode is used here, but additional options could be set, too.CompilationLevel.WHITESPACE_ONLY.setOptionsForCompilationLevel(options);// To get the complete set of externs, the logic in// CompilerRunner.getDefaultExterns() should be used here.SourceFile extern = SourceFile.fromCode("externs.js", "function alert(x) {}");// The dummy input name "input.js" is used here so that any warnings or// errors will cite line numbers in terms of input.js.
//      SourceFile input = SourceFile.fromCode("input.js", code);SourceFile jsFile = SourceFile.fromFile(code);Compiler compiler = new Compiler();compiler.compile(extern, jsFile, options);// The compiler is responsible for generating the compiled code; it is not// accessible via the Result.if (compiler.getErrorCount() > 0) {StringBuilder sb = new StringBuilder();for (JSError jsError : compiler.getErrors()) {sb.append(jsError.toString());}// System.out.println(sb.toString());}return compiler.toSource();}static String frontEnd = "C:\\project\\wstsq\\WebContent\\asset\\css";/*** 压缩 CSS 并将其保存到一个地方*/protected void doPost(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {String css = request.getParameter("css"),file = request.getParameter("file") == null ? "main" : request.getParameter("file");String output = "";String saveFolder = request.getParameter("saveFolder") == null ? frontEnd : request.getParameter("saveFolder");Logger.getGlobal().info(request.getParameter("saveFolder"));try {save(saveFolder + "\\" + file, css);output = "{\"isOk\":true}";} catch (Throwable e) {e.printStackTrace();output = "{\"isOk\":false}";}response.getWriter().append(output);}/*** 打包某个目录下所有的 js* * @param _folder* @param isCompress* @return*/public static String action(String _folder) {StringBuilder sb = new StringBuilder();File folder = new File(_folder);File[] files = folder.listFiles();if (files != null)for (File file : files) {if (file.isFile()) {sb.append("\n");sb.append(compileJs(file.toPath().toString()));}}return sb.toString();}/*** 获取磁盘真實地址* * @param cxt          Web 上下文* @param relativePath 相对地址* @return 绝对地址*/public static String mappath(HttpServletRequest request, String relativePath) {String absolute = request.getServletContext().getRealPath(relativePath);if (absolute != null)absolute = absolute.replace('\\', '/');return absolute;}public static void saveClassic(String fullpath, String content) throws IOException {File file = new File(fullpath);if (file.isDirectory())throw new IOException("参数 fullpath:" + fullpath + " 不能是目录,请指定文件");try (FileOutputStream fop = new FileOutputStream(file)) {if (!file.exists())file.createNewFile();fop.write(content.getBytes());fop.flush();}}public static void save(String fullpath, String content) throws IOException {Path path = Paths.get(fullpath);if (Files.isDirectory(path))throw new IOException("参数 fullpath:" + fullpath + " 不能是目录,请指定文件");if (!Files.exists(path))Files.createFile(path);Logger.getGlobal().info(path.toString());Files.write(path, content.getBytes());}
}

参考

  • 一个 Filter 例子:http://pro.ctlok.com/2011/10/closure-compiler-run-time-compress.html
  • 在线工具 https://closure-compiler.appspot.com/home
  • https://blog.csdn.net/iteye_12911/article/details/82063320

Java 压缩/混淆 JavaScript 代码相关推荐

  1. [转载]用UglifyJS2合并压缩混淆JS代码——javascript系列

    从零开始nodejs系列文章,将介绍如何利Javascript做为服务端脚本,通过Nodejs框架web开发.Nodejs框架是基于V8的引擎,是目前速度最快的Javascript引擎.chrome浏 ...

  2. webpack中的代码压缩混淆机制

    压缩 删除 Javascript 代码中所有注释.跳格符号.换行符号及无用的空格,缩短变量名称从而压缩 JS 文件大小.并且不同作用域的变量名是可以重复的,类似a,b,c可以反复出现. 混淆 经过编码 ...

  3. 几维安全Javascript代码混淆(js加密)在线使用说明

    2019独角兽企业重金招聘Python工程师标准>>> 几维安全Javascript代码混淆是一项在线加密服务,用户只需将JS文件打包成zip包,提交到加密平台,即可完成代码混淆.字 ...

  4. 如何对Javascript代码进行二次压缩(混淆)

    如何对Javascript代码进行二次压缩(混淆) 对Javascript代码进行压缩(混淆),可以有效减少传输和加载时间.但是,不是所有的变量(方法)都能被混淆的,一般来说,只有非属性的变量(方法) ...

  5. java 代码压缩javascript_通过Java压缩JavaScript代码实例分享

    通过移除空行和注释来压缩 javascript 代码 /** * this file is part of the echo web application framework (hereinafte ...

  6. java 代码压缩javascript_利用Java来压缩 JavaScript 代码详解

    通过移除空行和注释来压缩 JavaScript 代码 /** * This file is part of the Echo Web Application Framework (hereinafte ...

  7. [实现]Javascript代码的另一种压缩与加密方法——代码图片转换

    代码=图片 图片=代码 JS代码对于喜欢F12的同志来说,连个遮羞布都没有... 虽然把代码变成图片也仅仅只是增加一层纱布而已...但这方法还是挺好玩的,而且代码也被压缩了一点. 第一次看到[图片=代 ...

  8. 前端JavaScript代码混淆加密原理介绍

    因为JavaScript大都是运行在浏览器端,这就导致任何人都可以直接对网站的代码进行查看,如果代码没有进行任何处理就会导致直接暴露源码,他人便可轻而易举的复制你的劳动成果,但是由于没有纯粹的加密方案 ...

  9. javascript代码混淆与加解密

    开发一个python的程序,功能很简单,对某个网页发送post请求,把response的结果解析后存入数据库,供后续分析. 抓包 首先是抓包,使用burp suite,发现该网页原始的post请求如下 ...

最新文章

  1. linux vps 自动拒绝弱口令ssh扫描
  2. windows ssh secure shell设置初始窗口大小
  3. ​​​​​​​2016最新CocoaPods安装与使用
  4. HDU1151 Air Raid
  5. Windows下使用Dev-C++开发基于pthread.h的多线程程序
  6. Windows Phone开发(37):动画之ColorAnimation
  7. 2022,前端工具链十年盘点
  8. 【小程序】微信小程序开发实践
  9. mac android 证书生成工具,MAC系统下,生成安卓证书的命令
  10. QBoxLayout中setSpacing(int)和addSpacing(int)的区别
  11. 程序员面临 35 岁危机?网友:我 70 了,依然在写程序
  12. 【转】Oracle查询用户所有表
  13. SqlServer常用对象查询
  14. Oracle临时表GLOBAL TEMPORARY TABLE
  15. c语言编程贪吃蛇的不同功能,贪吃蛇C语言代码实现(难度可选)
  16. ios12完美深色模式插件_那些好玩的插件 iOS 12(十七)
  17. protobuf3 oneof
  18. 大学计算机基础教程第12章软件技术基础
  19. zmud之汉字转换为数字
  20. 大数据营销方案的分析处理

热门文章

  1. 组件封装 - 骨架屏组件
  2. 【C初阶】C初阶考试题
  3. 大学计算机AI学习初步规划 202204
  4. 基于android的即时通讯APP 聊天APP
  5. Mysql5.6 Performance_schema 深入浅出
  6. 北京理工大学 计算机考研真题,北京理工大学考研真题汇总
  7. python在Scikit-learn中用决策树和随机森林预测NBA获胜者
  8. nginx的下载与安装
  9. ARS408-21毫米波雷达笔记
  10. JOL工具及其分析对象在JVM的大小和分布