Esper 发布的文章

按照常规操作,在定义完变量之后应该对其进行初始化,然后再使用。于是就有了这么一段代码:

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // TODO Auto-generated method stub
        PrintWriter out = response.getWriter();
        response.setCharacterEncoding("UTF-8");
        response.setContentType("text/html; charset=UTF-8");
        out.write("喵喵喵?");
    }

然而在编译并由Tomcat发布后,访问获得的结果却是????。 你是????,我也是????啊??比特流编码和传输给浏览器的MIME信息都已经声明了,为啥还是不对?

直到看了这个 https://blog.csdn.net/tlms_/article/details/78749980

才发现

要先设置好response的属性,然后再获得由这些属性生成的PrintWriter。很是奇怪的逻辑。。 将getWriter()操作放到几个set操作之后,问题顺利解决。

在把Eclipse JEE更新到2019-09之后,打开之前可以运行的,使用Gradle配置的JSP项目运行时,发现所有页面都变成了404。前往Eclipse配置的tomcat临时文件夹查看,发现所写的jsp文件全部没有复制过去。然后在网上搜索发现,是由于JSP搜索路径没有配置好的问题。

https://guides.gradle.org/building-java-web-applications/ 从Gradle的官方教程不难发现,其默认配置的JSP根目录在src/main/webapp下,而不是Eclipse所使用的WebContent

https://stackoverflow.com/questions/18273184/how-to-set-webappdirname-in-gradle 依照这个方法,使用webAppDirName(或者全写为project.webAppDirName)参数指定JSP包的根目录后,方可使其正常搜索WebContent目录下的JSP文件。经过测试,这个参数与是否使用了war插件似乎无关,也就是说,不使用war插件也不能正常识别WebContent目录下的JSP文件,而这时webAppDirName参数是否有效,由于未经测试,则不得而知了。

这个问题与IDE的版本是有关的,在Eclipse JEE 2017-12中可以正常使用,升级到2019-09就不行了。

在设置完webAppDirName之后,再次使用Gradle进行编译的时候,又可能会遇到找不到Servlet的问题。原因推测是在指定JSP目录的时候,破坏了对Java源代码目录的设置。可以注意到Gradle编译的输出项,其中compileJava一项显示的是NO-SOURCE`,显然就是没找到写好的Java源代码文件。

https://stackoverflow.com/questions/31077844/add-another-java-source-directory-to-gradle-script 依照这个方法,显示指定Java源文件的目录后,再重新使用Gradle编译即可解决问题。

添加的参考配置如下:

sourceSets {
    main {
        java {
            srcDir 'src'
        }
    }
}

该配置可用于直接在src目录下建立包名,即使用Eclipse传统的Dynamic Web App向导建立的目录结构。

在看操作系统的时候看到的,好骚啊。直接暴力使用结构体的地址进行推断,其间还巧妙地利用了编译器的优化原理。据说该方法广泛用于Linux内核。

使用宏定义,代码分三行,定义三层操作:

注意:由于在#define操作中使用空格符号作为定义与替换内容的分隔符,所以在定义时不要加入空格。替换内容中似乎可以包含空格,因为该宏指令没有第三个参数

第一行:对外提供功能的函数名

#define member2parent(member_ptr,member_name) \
          to_struct(member_ptr, struct Parent, member_name)

其中这里的member_ptr是需要用来寻找结构体首部地址的成员变量地址,member_name不是变量,而是该成员变量在结构体中定义的名称。这里巧妙地利用了宏定义的替换原理,能够传递非变量的元素。在这里将结构体的定义加进去,能够减少使用时的代码量(避免重复输入结构体名)。

第二行:实际上进行由成员地址计算出结构体首地址操作的部分

#define to_struct(member_ptr,type,member_name) \
          ((type*)(char*)(member_ptr) - offset_of(member_name, type))

这里做的事情不但将结构体的首地址计算出来,还将格式转换为了type类型的指针。又调用了一个新的宏offset_of

第三行:获取成员变量相对结构体首部的偏移

#define offset_of(type,member) ((size_t)(&(((type*)0)->member)))

https://blog.csdn.net/changqing1990/article/details/85256717 这里获取偏移的方法就很巧妙了。所使用的思路是,假设一个结构体的实体从0地址开始,那么只要取得成员变量的地址,就相当于取得了该成员变量相对于结构体首部的偏移。但是仔细观察的话,会发现一个问题:((type*)0)->member这么一个操作,所做的事情是取得一个指针对应的成员变量的值,对C语言稍有了解的话,会知道其实NULL和0是一个东西。对空指针取成员变量值,不会出错吗?实际上外面还有一层操作:取地址符&。如同上文链接所述,实际上很容易理解,对一个地址取值再取地址,可以直接简化为地址的变换操作。由于编译器发现这个值在过程中并未使用,于是就进行了这种优化,实际上0地址的数据并未被真实访问过,而我们也按照预期取得了偏移量的数值。

本篇采用倒叙法,将废话放在片尾

原因:JavaScript在浏览器中顺序而异步地执行,导致某些需要有前后顺序要求的操作失效。比如先要通过XHR获得token,再用token访问新网页,异步执行会导致访问新网页的时候用的token可能还是undefined,即此时XHR请求还没完成。

解决方案:Promise对象

参考了廖雪峰老师的博客:https://www.liaoxuefeng.com/wiki/1022910821149312/1023024413276544

Promise的使用,在下将其归类为两种操作,创建和运行。一种创建Promise对象,另一种对Promise对象进行操作:进入下一状态或者得出结果。

Promise对象的创建:两种方法,一个是直接new,另一个利用Promise.resolve()或者Promise.reject()是创建有状态的Promise。后者两者分别对应一个Promise对象在执行成功和出错时的返回结果。从语法上允许直接向上述两个Promise方法传递一个Object,作为一个新Promise的返回结果,但是从使用上一般都是作为管道,即将结果从一个Promise传递到另一个Promise,不会用于一个全新Promise的生成。

将操作写入到Promise对象的函数中,然后就可以“运行”该Promise对象。Promise对象有一个then()方法,该方法接受一个函数作为参数,执行的工作是将传入Promise的函数体执行后,将(如果存在的)resolve的参数值作为then()方法内传入的函数的第一个参数,然后执行。结合代码解释如下:

function addBy1(a) {
  return new Promise((resolv, reject) => {
    resolv(a + 1);
  })
}

var p = addBy1(0);

首先在此创建了一个函数,命名为addBy1,接受一个参数,结合函数体可以大致猜出,该函数的功能是将传入a的值加1。

Promise对象有两种自用的返回值的方法:resolve()reject(),可以分别理解为带成功和失败状态的返回结果。在直接使用new关键字生成一个新的Promise对象时,这两个函数以参数的形式传入函数体,供不同条件下的返回使用。

实际上,可以直接将addBy1的return语句中的部分用于变量p的赋值。但是为何在这里分开呢?下面自有用处。(突然跑题)

显然,若是将addBy1函数就视作一个将传入数字加1的功能的话,应该得到相应的结果。如何得到呢?答案是使用Promise对象的运行功能。(对=>运算符略作解释:该符号用于简化匿名函数的表达,(参数列表)=>{函数体}

p.then(
  (a) => {
    console.log(a);
  }
);

然后就可以获得梦寐以求的1了。但是,显然功能不会就这么简单。Promise的妙处就在于这个then(),简要来说,它提供了在异步执行的JavaScript中执行一系列的同步操作的能力。正是有了这个功能,使得需要依赖前一个操作完成后再执行下一个操作的逻辑流程能够按照正确的时序图执行。直观地感受一下:

var p = addBy1(0);
p.then(addBy1).then(addBy1).then(addBy1).then(addBy1).then(
  (a) => {
    console.log(a);
  }
);

在执行了这一段代码后,会发现输出的结果是5:从创建变量开始,一共执行了5次addBy1函数,而初始值为0。

但是若是再使用上面的一段代码输出p中的返回值,会发现其仍然是1。理解为then()方法不会改变原始Promise对象的状态吧。

then()函数接收的参数若是返回Promise对象,就可以在后面继续接.then(),从而可以定义无限长的.then()序列。而这些then()方法又必须串行执行。这就达到了一开始的目的:串行地执行JavaScript代码。

这么一说,操作都是n+1,怎么看得出来到底是不是1+1,2+1一直到4+1呢?虽然从逻辑上似乎也找不出其他的可行执行方法,但是可以通过在第一段代码的resolv()函数之前加一句console.log(a);来验证,显然输出结果会是01234。因而拓展一下,将简单的1+1操作换为需要较长时间的,比如XHR请求一类的操作的话,就可以方便地实现时序的可控化了。

最后,上文不是有提到为什么要将addBy1操作单独提出作为一个函数吗。原因有二,其一就是上文已经用到了的,为了重复进行Promise的操作,其二就是,这是一个极好的函数式编程的入门范例。函数式编程的思想之一就是使用尽可能少的参数。比如传统的1+1会表示为var result = plus(1, 1),而函数式编程会这么想:

class Number {
  constructor(x) {this.number = x;}
  addBy1() {
    return this.number + 1;
  }
}

即在正常使用时,会这么用:

var a = new Number(1);
var result = a.addBy1();

从而得到2。在使用过程中,除开对象的声明之外,没有用到一个有参数的方法。


最近处于各种原因,开始了前端编程。。然后上手就是微信(诶,好像不对)丁丁(也不对)钉钉(啊对,就是这个了)小程序。不出所料,工作就是生物代码粘合器(2077警告)。这里即兴赋歌一段:

我有个appId 我有个appSecret (双手做插合状,并摆出夸张的面部表情) access_token

我有个access_token 我有个authCode (双手做插合状,并摆出夸张的面部表情) userId

(大雾)

虽然对于很多干这行的人来说,可能认为在下这是对工作本身的不尊重,但是这确实反映了在下在看文档的时候,看到为了获取一个userId而不得不从appIdappSecret开始一路以token换token最终拿到所需要的数据的过程中,哭笑不得的感觉。我当时不选择做前端,大概就有一部分原因在这。虽然知道出于安全角度考虑,这是较为稳妥的一种方式,但是如果深陷其中,并以其为日常的工作,显然就太有些小看程序员的能力了。(咳咳,但是砖总得要有人去搬啊)

https://www.cnblogs.com/xiami-xm/articles/8417526.html

由于GEM5需要从原始的Linux内核可执行文件启动,不能使用压缩过的vmlinuz,所以一开始的想法是从当前发行版的软件包管理器下载压缩过的内核,然后从里面解压出完整版本的来。在Stackoverflow上看到了两种方法:

  1. 二进制暴力解法 https://superuser.com/questions/298826/how-do-i-uncompress-vmlinuz-to-vmlinux 分析vmlinuz的构成原理,可以得知其拆解方式。在此仅引用结论,原理学习还请暂时移步其他博客。 zImagebzImage都是指的使用gzip压缩的内核,有无b的主要区别在于内核文件的大小,实际上b就代表big。作为第一个在裸处理器上运行的程序,内核显然不能指望有其他程序能够替自己解压缩,因而vmlinuz是包含有gzip自解压的代码的。也就是说,vmlinuz文件至少由压缩的内核和预操作(比如解压)代码组成。在承认这些部分的数据是各自连续的情况下,可以通过在其中寻找gzip的文件头,来寻找压缩内核的位置,然后解压即可获得原始内核。

借用原文中的例子:

> od -A d -t x1 vmlinuz | grep '1f 8b 08 00'
0024576 24 26 27 00 ae 21 16 00 1f 8b 08 00 7f 2f 6b 45

其中1f 8b 08 00是gzip文件的文件头,标识一个合法的gzip文件开头。 很幸运,在当前的vmlinuz中似乎还是只含有一个gzip文件,而这个文件正是我们所需要的,经过gzip压缩的vmlinux。

> dd if=vmlinuz bs=1 skip=24584 | zcat > vmlinux
1450414+0 records in
1450414+0 records out
1450414 bytes (1.5 MB) copied, 6.78127 s, 214 kB/s

使用dd命令,从指定位置复制数据出来,bs=1表示每次的数据操作量为1字节,使用这个值能够较为简便地确定开始位置(od命令所提供的偏移量就是以字节为单位的)。比如上文中的0024576表示十六进制数据“24”相对文件首部的偏移量为24576 * 1字节 = 24576字节。将偏移量定位到gzip文件首部的“1f”,相对“24”向后偏移了8字节,故dd中的命令偏移量设置为24576 + 8 = 24584。使用管道将二进制内容直接发送到zcat,在命令行内完成文件的解压,得到预期中的vmlinux文件。

  1. 使用Linux内核包提供的解压工具(天知道原理和1是不是一样的) https://stackoverflow.com/questions/12002315/extract-vmlinux-from-vmlinuz-or-bzimage 简单说就是使用scripts目录下的extract-vmlinux脚本来完成操作。不过这个我没试过。