需求:一条语句从(0,1,2,3,4,5,6,7,8,9,10)中取出(7,5,3)

分析:可以使用Python的切片功能。

完整用法:iteratableObj[startIndex:endIndex:step]

其中iteratableObj可以是list, tuple,其他的我就不清楚了。(没去查官方文档= =)

然后startIndex为包含性的从0开始的元素编号,表示切片的开始位置;endIndex为非包含性的从0开始的元素编号,表示切片的结束位置;step表示步进,即每次前进多少个元素,默认为1。

这些数字如果是负数的话,也是有特定含义的,会被Python解释器认为是反向编址的序号,即常说的倒数第几个。相应地,step处的负号会让整个列表翻转。然后这里就涉及了一个很有意思的问题:同时进行切片和翻转,到底谁先谁后?也就是说,startIndexendIndex到底应该是填顺序的还是反序的?

经过实践,发现顺序如下:

  1. 先检查step的符号,如果为负,翻转列表(也可能是将读取顺序标记为反序)
  2. 按照startIndexstep开始选取元素,以endIndex为界限(不包含)。这里的两个index都还是顺序的!

于是上文的题目解答如下:

arr = (0,1,2,3,4,5,6,7,8,9,10)
print(arr[7:2:-2]) # or print(arr[-4:-9:-2])

千万要记住这里的第二个地址还是不包含的,所以一定要多顺着方向将地址+1!

在初步接触Java Servlet之后,会发现一个HTTPServlet有这么两个附属对象:ServletConfigServletContext,两者长得很像,又有着不少相同的方法(getAttribute(), getInitParameter(), getServletName()/getServletNames()),在网上搜了一下,发现有讲述两者之间的区别的博客,但是我是在理解之后才看懂的。。

在此简述一下使用Tomcat运行Web服务的三大主要层次,我依次将其命名为Server, Application和Servlet。其中Server指的是Tomcat本身,可以理解为一台服务器上一般只会跑一个Tomcat,所有需要借助Tomcat实现的Web服务都会依赖于这一个Tomcat实例。其次就是Application了,这就是上面所说,需要借助Tomcat实现的服务。一个Web服务就是一个Web项目,内部可以有复杂的Java逻辑代码,可以包括与数据库交互,等等。一个Servlet指的是提供单一Web功能的程序单位。

Application举例说明的话就是登陆系统,从登陆到连接后台,查看数据,修改用户信息、密码等,算是一个完整的后台管理系统服务,仅靠单一的URL是很难(但是是可以)实现整个功能的,起码作为一个便于维护和扩展的项目来说,都会需要多个URL来实现不同的功能,因为这样至少光靠URL地址就能有效地组织整合不同的功能了。这是Application。而Servlet实现的是原子操作,比如说后台管理系统中的登陆,或是获取用户信息,或是修改密码这种单一而清晰的任务。

然后就可以说明了:ServletConfig是Servlet层面的,每个Servlet都有自己独立的ServletConfig;而ServletContext是Application层面的,同一服务/项目下的所有Servlet可以通过ServletContext共享数据。那为什么ServletContext不是Server层面呢?ServletContext中可以存放应用的重要数据,出于安全考虑,一是防止数据窃取,二是防止篡改,不论是无意还是有意。

再仔细看看ServletContext的方法的话,会发现有一个方法名为getServletNames(),返回的是一个Enumeration,也就是说具有多个Servlet名称。相对地,ServletConfig的方法名为getServletName(),返回一个String。这下就更加清晰了,显然一个Servlet只应该有一个名字,那么ServletContext肯定就是Servlet层面之上的了。

此外有一个小坑:声明Application级别的初始变量是在web-app建立context-param子节点,使用ServletContextgetInitParameter()方法获取;Servlet级别的初始变量则需要在web-app节点下先新建servlet子节点,再在servlet子节点下新建init-param节点定义,使用ServletConfiggetInitParameter()方法获取。其中两个param的定义结构相同,使用param-name子节点定义变量名,param-value子节点定义变量值,而显然这个值只能定义为String类型。

例如:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" id="WebApp_ID" version="4.0">

  ...

  <context-param>
    <param-name>contextInitParam</param-name>
    <param-value>someValue</param-value>
  </context-param>

  <servlet>
    <servlet-name>demoServlet</servlet-name>
    <servlet-class>tk.esperz.demoServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
    <init-param>
      <param-name>servletInitParam</param-name>
      <param-value>otherServletsCannotSeeMe</param-value>
    </init-param>
  </servlet>
  <servlet-mapping>
     <servlet-name>demoServlet</servlet-name>
     <url-pattern>/demo.do</url-pattern>
  </servlet-mapping>
</web-app>

其中为该Web项目定义了一个名为contextInitParam的上下文初始化变量,值为someValue,所有该项目中的Servlet都可以通过ServletContext.getInitParameter("contextInitParam")获取;为名为demoServlet的Servlet定义了一个名为servletInitParam的初始化变量,值为otherServletsCannotSeeMe,仅在该Servlet内可以通过ServletConfig.getInitParameter("servletInitParam")获取。凡是用错了方法的都会返回null

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

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地址的数据并未被真实访问过,而我们也按照预期取得了偏移量的数值。