Categories
不学无术 木有技术

LeetCode 385. Mini Parser

思路

一开始还以为又回到了编译原理,想到了状态转换图啊之类的。后来发现这个题目没有这么变态,要考虑的是个栈的问题。
为什么会想到栈呢?因为看到了括号,因为括号结束的时候我们要把某个部分的内容添到更大的括号里头去。
我的处理方式是:

  • 构造一个工作栈,一开始是空的
  •  碰到数字
    • 生成一个只包含数字的NestedInteger
      • 如果工作栈空,那么字符串就是个纯数字,直接返回
      • 如果工作栈非空,那么这个字符串就要加到当前工作栈顶端的元素后面(考虑情况[35,345]这种)
  • 碰到'[‘
    • 新建一个空的NestedInteger(至于里面填什么,不是这里要考虑的事儿)
    • 把它压入栈
  • 碰到’]’
    • 取出当前栈顶元素formerTop
    • 判断栈是否空了
      • 非空,则formerTop要加到当前栈顶的后面
      • 空,可以返回了
  • 碰到’,’
    • 其实没什么用,往前挪动吧

这个步骤还能再精简一下的,判断栈是否空,是否返回的部分是重复了

代码

/**
 * // This is the interface that allows for creating nested lists.
 * // You should not implement it, or speculate about its implementation
 * class NestedInteger {
 *   public:
 *     // Constructor initializes an empty nested list.
 *     NestedInteger();
 *
 *     // Constructor initializes a single integer.
 *     NestedInteger(int value);
 *
 *     // Return true if this NestedInteger holds a single integer, rather than a nested list.
 *     bool isInteger() const;
 *
 *     // Return the single integer that this NestedInteger holds, if it holds a single integer
 *     // The result is undefined if this NestedInteger holds a nested list
 *     int getInteger() const;
 *
 *     // Set this NestedInteger to hold a single integer.
 *     void setInteger(int value);
 *
 *     // Set this NestedInteger to hold a nested list and adds a nested integer to it.
 *     void add(const NestedInteger &ni);
 *
 *     // Return the nested list that this NestedInteger holds, if it holds a nested list
 *     // The result is undefined if this NestedInteger holds a single integer
 *     const vector<NestedInteger> &getList() const;
 * };
 */
class Solution {
private:
    stack<NestedInteger*> workingStack;
public:
    int readIntVal(const string& s, int& pos)
    {
        // pos will be set to the end index of current integer
        int value = 0;
        bool neg = false;
        if(s[pos] == '-'){
            neg = true;
            pos++;
        }
        while(pos < s.size() &&
                (s[pos] >= '0' && s[pos] <= '9'))
        {
            value *= 10;
            value += (s[pos] - '0');
            pos++;
        }
        pos--;
        return neg? -value : value;
    }
    NestedInteger deserialize(string s) {
        int pos = 0;
        while(pos < s.size())
        {
            char c = s[pos];
            if(c == '-' || (c >= '0' && c <= '9'))
            {
                NestedInteger* digitNI = new NestedInteger(readIntVal(s, pos)); //Integer containing NestedInteger
                if(workingStack.empty())
                {
                    //Should return
                    return *digitNI;
                }
                else
                {
                    //Append to existing NI
                    NestedInteger* currNI = workingStack.top();
                    currNI->add(*digitNI);
                    pos++;
                }
            }
            else if(c == ',')
            {
                pos++;
            }
            else if(c == '[')
            {
                //Create an NI and push to working stack
                NestedInteger* ni = new NestedInteger();
                workingStack.push(ni);
                pos++;
            }
            else if(c == ']')
            {
                //pop workingStack and append former-top item to current top
                NestedInteger* formerTop = workingStack.top();
                workingStack.pop();
                if(workingStack.empty())
                    return *formerTop;
                else
                {
                    //Append
                    NestedInteger* currTop = workingStack.top();
                    currTop->add(*formerTop);
                    pos++;
                }
            }
        }
        //Shouldn't be here!
        return NestedInteger();
    }
};

题目

Given a nested list of integers represented as a string, implement a parser to deserialize it.
Each element is either an integer, or a list — whose elements may also be integers or other lists.
Note: You may assume that the string is well-formed:

  • String is non-empty.
  • String does not contain white spaces.
  • String contains only digits 0-9, [, - ,, ].

Example 1:

Given s = "324",
You should return a NestedInteger object which contains a single integer 324.

Example 2:

Given s = "[123,[456,[789]]]",
Return a NestedInteger object containing a nested list with 2 elements:
1. An integer containing value 123.
2. A nested list containing two elements:
    i.  An integer containing value 456.
    ii. A nested list with one element:
         a. An integer containing value 789.
Categories
不学无术

LeetCode 343. Integer Break

Given a positive integer n, break it into the sum of at least two positive integers and maximize the product of those integers. Return the maximum product you can get.
For example, given n = 2, return 1 (2 = 1 + 1); given n = 10, return 36 (10 = 3 + 3 + 4).
Note: You may assume that n is not less than 2 and not larger than 58.

思路 O(n^2)

注:这题还有O(n)解决的方法,我提交的方法是O(n^2)的。
有点像动态规划的题,上一个数的状态经过乘法可以作为下个数的备选(要与下个数比较)。
假设result[i]存了当前能加到i的最好结果值;
第一遍,从1开始往后加(反正最大的n也就58),1+1=2,则result[2]中就是1*1=1,1+2=3=>result[3]=1*2=2;result[4]=3
第二遍,从2开始往后加,a[3]=2,a[4]=2*2(原始值为3,这里更新为4),…

第五遍,从5开始往后加,但之前算的过程中,result[5]的结果已经被更新成了6,这个数比5大,因此×的时候,被乘数是6而不是5;
以此类推…

思路 O(logn)

需要一点数学思维,详见https://discuss.leetcode.com/topic/42991/o-log-n-time-solution-with-explanation

代码

public class Solution {
    public int max(int x, int y){
        return x>y ? x : y;
    }
    public int integerBreak(int n) {
        int[] result = new int[59]; //0 to 58
        result[1] = 1;  //dummy
        for(int i=1; i<=n-1; i++)
        {
            for(int j=1; j<=n-i; j++)
            {
                int temp = result[i];
                if(result[i] < i)
                    temp = i;
                result[i+j]=max(result[i+j], temp * j);
            }
        }
        return result[n];
    }
}

 

Categories
不学无术

Hibernate Community Documentation 之 Chapter 5. Locking 锁(翻译)

私人翻译,如有错误请大胆指出,我相信不会有人转载:)
原文见https://docs.jboss.org/hibernate/orm/4.0/devguide/en-US/html/ch05.html

所谓“锁”是指用来在关系型数据库中防止在数据读取(read)到数据使用(used)之间其内容发生变化的操作。
锁的实现分为两种,分别是乐观(Optimistic)与悲观(Pessimestic)。

锁策略

乐观锁

乐观锁假设多组事务(transaction)能够互不干涉地并发完成,因此这些事务执行时不需要将他们影响到的数据加锁。在事务提交(commit)前,每个事务都会校验以确保它的数据没有被别人改动过。如果校验的结果是已有修改冲突(conflicting modifications),整个事务就会回滚(rollback)。

悲观锁

悲观锁假设事务之间会相互冲突,因此要求在数据读取时上锁,直至数据使用完毕后才释放锁。
 
Hibernate为以上两种锁策略都提供了支持。

5.1 乐观锁 Optimistic

如果你的应用使用了长事务或是由许多数据库事务组成的会话(conversation),你可以通过存储版本(version)数据使得当一个实体(entity)被两个会话更新时,最后一个提交更改的会话会被告知出现冲突,并且此时不会影响到前一个会话进行的更改。该方法保证了一定的隔离性,扩展性好且特别适用于常读取、少写入(Read-Often Write-Sometimes)的场景。

5.1.1 专用版本号 Dedicated version number

实现乐观锁的版本号机制是通过@Version 注释来实现的

@Entity
public class Flight implements Serializable {
...
    @Version
    @Column(name="OPTLOCK")
    public Integer getVersion() { ... }
}

此处,Version属性被映射到OPTLOCK列,实体管理器(the entity manager)通过这个注释来检测有冲突的更新,借此防止数据更新被最后一个写入提交操作给覆盖(last-commit-wins)。
Version列可以是任意类型的,只要你定义并且实现一个自定义的UserVersionType即可。
Hibernate禁止你的应用手动变更版本号(version number)。如需手动增加版本号,请查看下列相关文档:
LockModeType.OPTIMISTIC_FORCE_INCREMENT 或 LockModeType.PESSIMISTIC_FORCE_INCREMENTcheck。

如果版本号是由数据库通过注入triggert自动生成的,需使用注释

@org.hibernate.annotations.Generated(GenerationTime.ALWAYS)
<version
        column="version_column"
        name="propertyName"
        type="typename"
        access="field|property|ClassName"
        unsaved-value="null|negative|undefined"
        generated="never|always"
        insert="true|false"
        node="element-name|@attribute-name|element/@attribute|."
/>
column 存储版本号的列名. 可选,默认是属性名.
name 需要持久化的类名
type 版本号存储类型. 可选,默认是 integer.
access Hibernate访问属性时使用的方式. 可选, 默认是 property.
unsaved-value Indicates that an instance is newly instantiated and thus unsaved. This distinguishes it from detached instances that were saved or loaded in a previous session. The default value,undefined, indicates that the identifier property value should be used. Optional.
generated 表明这是由数据库生成的版本号. 可选,默认是never.
insert 是否在SQL insert 语句中加入版本号. 默认是 true, 但如果你的版本号列有默认值0的话,可以设置成false.

5.1.2 时间戳 Timestamp

与版本号相比,时间戳在实现锁时稍显不可靠一些,但如果应用想要借此实现一些其他功能,还是有用的。如果@Version 注释应用在Date或Calendar对象上时,会自动采用时间戳实现。

@Entity
public class Flight implements Serializable {
...
    @Version
    public Date getLastUpdate() { ... }
}

如果你用注释@org.hibernate.annotations.Source 特别指定,Hibernate可以从数据库或者JVM中撷取时间戳的值:可以是org.hibernate.annotations.SourceType.DB或者是org.hibernate.annotations.SourceType.VM,如果不特别指定,默认值是使用数据库的时间戳。
如果指定了@org.hibernate.annotations.Generated(GenerationTime.ALWAYS) 注释,时间戳的值可以借由数据库直接生成,而不是Hibernate。

<timestamp
        column="timestamp_column"
        name="propertyName"
        access="field|property|ClassName"
        unsaved-value="null|undefined"
        source="vm|db"
        generated="never|always"
        node="element-name|@attribute-name|element/@attribute|."
/>
column 存放时间戳的列名。可选,默认值为属性名。
name The name of a JavaBeans style property of Java type Date or Timestamp of the persistent class.
access Hibernate用于访问属性的策略。可选,默认为property。
unsaved-value A version property which indicates than instance is newly instantiated, and unsaved. This distinguishes it from detached instances that were saved or loaded in a previous session. The default value of undefined indicates that Hibernate uses the identifier property value.
source Whether Hibernate retrieves the timestamp from the database or the current JVM. Database-based timestamps incur an overhead because Hibernate needs to query the database each time to determine the incremental next value. However, database-derived timestamps are safer to use in a clustered environment. Not all database dialects are known to support the retrieval of the database’s current timestamp. Others may also be unsafe for locking, because of lack of precision.
generated 是否由数据库内部自动生成时间戳的值。可选,默认值是never。

5.2 悲观锁 Pessimistic

通常情况下,你只需要指明一个JDBC的隔离等级(lsolation level),把锁的问题交给数据库处理就行了。如果你确实需要获得独占锁(exclusive locks)或是在事务开始时重新得到(re-obtain)锁,Hibernate会提供给你需要的方法。

Hibernate总是使用数据库的锁机制,从不在内存中给对象上锁

5.2.1 LockMode类

LockMode类指定了Hibernate能取得的各种锁级别。

LockMode.WRITE 在Hibernate执行update或insert行时自动获取
LockMode.UPGRADE 在用户显式地使用 SELECT ... FOR UPDATE 语句时(仅当数据库支持该语法)
LockMode.UPGRADE_NOWAIT 在用户对Oracle数据库显式地使用SELECT ... FOR UPDATE NOWAIT 语句时.
LockMode.READ 在Hibernate以Repeatable Read或是Serializable隔离级别读取时自动获取,亦可由用户显式地获取
LockMode.NONE 不启用锁。在事物结束后,所有的对象将会切换到这一状态。通过调用update()或saveOrUpdate()方法与会话(session)关联的对象亦以这种锁模式启动。

 
 
附注:有关锁的其他参考文章

Categories
不学无术

Spring AOP + log4j 实现SpringMVC站点日志输出功能(使用maven依赖)

最近真是不停地学各种东西,现在发现难怪那么多人用java,成熟的包一套一套的。
我们目标是,在一些特定的方法执行的时候(比如执行前,执行完毕)通过日志输出一些东西。这正符合了《Spring in Action》里头说到的“这些功能需要用到应用程序的多个地方,但是我们又不想在每个点调用他们”。为了顺应时代的潮流(其实是我没学xml定义),这里全部使用基于注释(annotation)的方式实现。

Maven依赖

需要引入AOP相关、log4j相关的依赖包
pom.xml

        <!-- AOP -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>4.2.5.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>1.8.9</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.9</version>
        </dependency>
        <!-- log4j-->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>

目录结构

里面提到的相关文件,目录结构大致如下:

CreativeCrowd
|--src/main
      |--...
      |--java/edu/edu/inlab
          |--config
             |--CreCrowdAppInitializer
             |--RootConfig
             |--WebConfig
          |--service
             |--LoggingService.java
      |--resources
          |--log4j.properties
...

如有需要全·套的,可以直接去我的GitHub项目看看:https://github.com/idailylife/CreativeCrowd

Aspect构建

切面的构建可以声明一个类来并加入注释实现。我做的是一个日志服务,有下面的声明
LoggingService.java

package edu.inlab.service;
import edu.inlab.models.json.MTurkIdValidationRequestBody;
import org.apache.log4j.Logger;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import javax.servlet.http.HttpServletRequest;
@Aspect
public class LoggingService {
    final static Logger logger = Logger.getLogger(LoggingService.class);
    /* User */
    @Pointcut("execution(* edu.inlab.web.UserController.userLogIn(..))")
    public void userLoginPost(){}
    @Before("userLoginPost()")
    public void logUserLoginAttempt(){
        logger.debug("user login attempt.");
    }
    /* Task */
    @Pointcut(value = "execution(* edu.inlab.web.TaskController.checkMturkId(..))" +
    "&& args(request, body,..)", argNames = "request, body")
    public void checkMTurkId(HttpServletRequest request, MTurkIdValidationRequestBody body){}
    @Before(value = "checkMTurkId(request, body)", argNames = "request, body")
    public void logCheckMtAttempt(HttpServletRequest request, MTurkIdValidationRequestBody body){
        logger.debug("Check mturk id, Remote IP= " + getRemoteIP(request) +
                ", RequestBody= " + body);
    }
    String getRemoteIP(HttpServletRequest request){
        String ip = request.getHeader("X-FORWARDED-FOR");
        if(ip == null){
            ip = request.getRemoteAddr();
        }
        return ip;
    }
}

里面的重点是要用@Aspect声明这是一个切面,另外@Pointcut是切点的注释,而@Before这种就是对应的前置通知(当然也可以是@After等等啦)
看一下里面的关于TaskController的操作(/*Task*/下面的),这其实是我控制器里头的一个函数,原函数比较长,函数签名是这样:

public AjaxResponseBody checkMturkId(HttpServletRequest request,
                                         @Valid MTurkIdValidationRequestBody body,
                                         BindingResult bindingResult)

可以看到里面有3个参数,而在切面定义的时候,我写日志只需要它两个参数就可以了,于是就有了下图这样的定义:
SpringAOP
上图这种写法用到了命名切入点,其实还有匿名切入点,这个具体可以看下面给的参考文献,介绍的很详细了。
execution(* edu.inlab.web.TaskController.checkMturkId(..))这种里面有个*号,这表示接受任何形式的返回参数,当然如果要指定也是可以的,记得要放类型的全名(比如com.blabla.SomeClass)。

配置

声明了切面,也别忘了在Spring里面开启AOP的东西。对应我的SpringMVC项目,我再rootConfig里面写了个Bean。
什么是RootConfig?就是在extend AbstractAnnotationConfigDispatcherServletInitializer的时候Override的那个方法嘛,比如:
CreCrowdAppInitializer.java

public class CreCrowdAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
    protected Class<?>[] getRootConfigClasses() {
        return new Class<?>[]{RootConfig.class};
    }
    ...
}

RootConfig.java
开启AOP的关键是在配置文件中声明@EnableAspectJAutoProxy,并且给提供个可以注入的Bean

@Configuration
@ComponentScan(basePackages = "edu.inlab",
        excludeFilters = {
        @ComponentScan.Filter(type = FilterType.ANNOTATION, value = EnableWebMvc.class)
})
@EnableAspectJAutoProxy
public class RootConfig {
    @Bean
    public LoggingService loggingService(){
        return new LoggingService();
    }
}

Log4j

log4j的使用很方便,而且发现开了log4j以后Hibernate自己也会输出一大堆东西(这个跟hibernate的配置有关)。Log4j在maven里面添加依赖之后,需要声明一个配置文件log4j.properties,放在src目录下。基本的配置可以照着下面画葫芦:
log4j.properties

# Root logger option
log4j.rootLogger=DEBUG, stdout, file
# Redirect log messages to console
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n
# Redirect log messages to a log file, support file rolling.
log4j.appender.file=org.apache.log4j.RollingFileAppender
log4j.appender.file.File=D:/Code/Java/CreativeCrowd/log/common.log
log4j.appender.file.MaxFileSize=5MB
log4j.appender.file.MaxBackupIndex=10
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n

然后就是使用了。在之前的代码里其实已经写了log4j的一些东西,首先需要拿到log4j的一个静态对象(这个是静态函数给的)

final static Logger logger = Logger.getLogger(LoggingService.class);

里面的参数是你声明的类。
然后就可以直接使用logger啦,比如

logger.debug(...)

然后运行起来,命令行也会看得到输出(真心推荐IntelliJ IDEA,edu邮箱可以有免费优惠!!!)
log4j
 

参考文献:

Categories
不学无术

未填之坑:Java包、类的加载机制(实作)

我是一个坑。

Categories
不学无术

Java包、类的加载机制(理论)

本人Java菜鸡一枚,如果下面的内容有误,请各位拼命留言,我秒秒钟改!

做项目的同时努力补全知识点或许是当务之急?

问题

此面试问题暴露了我对Java内部原理的无知,哈哈,赶紧学。题外话:换做面C++的话,我估计面试官会用一个多继承里头的问题来考。

有a.jar和b.jar,里面都有某个类MyClass且他们的包名完全相同,但是有不同的foo()方法,在一个项目中同时引入这两个jar会出现什么奇妙的事情呢?

这个问题的答案是,要根据类加载器的加载顺序来决定我们构建MyClass对象的时候究竟会先找到哪个jar里头的文件,a.jar/b.jar哪个运气好拔得头筹了,剩下的就被忽视了。
如果我们构建了不同的类加载器对象的话,可以通过自定义的类加载器来同时加载这两个长得看起来一模一样的类,并且可以各自调用其方法属性什么的。
更详尽的回答可以看这里:http://stackoverflow.com/questions/6879652/possible-to-use-two-java-classes-with-same-name-and-same-package

包(Package)、编译单元

C/C++里面用#include来表示引用的类(文件),并且用过C++的盆友肯定知道namespace 这个关键字可以用来隔离名字相同东西,最常见的莫过于using namespace std; 了。以上这些与所谓“访问权限控制”有关(详见《Java编程思想》第6章)。
而Java以及诸如Python这种语言采用包(package)作为库单元,一个包内包含一组类,比如ArrayList在Java标准库中被放置在java.util包(名字空间)下。包的作用有点像地址,在寻找一个类的时候就像快递分拣一样,根据包的名字能找到最终的目标,一个不恰当的例子就是,package 浙江.杭州.西湖 底下可能会有 同仁医院 这个类,而 package 福建.福州.鼓楼(这个例子不恰当,因为按照Java的原意,包名应该是“从后往前”写的,如cn.com.ibm) 底下也可以有 同仁医院 这个类,在Java代码中使用类的内容的时候,Java虚拟机就得根据包的名称来确定你要引用的到底是哪个医院类了。
而所谓编译单元,大多是指Java源代码文件。一个编译单元的后缀就是.java,文件里只能有一个public的类且该类的名称与文件名相同,否则就会导致编译错误。Java解释器的运行过程大致是:找出环境变量CLASSPATH(包含一个或多个目录)用作查找.class文件的根目录。从根目录开始,解释器获取包的名称并将每个句点替换为反斜杠\,比如 浙江.杭州.西湖 就变成了目录 浙江\杭州\西湖(根据操作系统可能略有不同,变为正斜杠/也是有可能的)。随后解释器就在这么个目录中查找对应你给的类名称的.class文件,比如上面的 同仁医院.class

编译执行流程

估计大家都知道,Java中的类可以被动态加载到Java虚拟机中执行。下面这张图(推荐https://www.processon.com/,在线画简单流程图的好东西)可以大致说明:
Java Classloader
Java 源程序(.java 文件)在经过 Java 编译器编译之后就被转换成 Java 字节代码(.class 文件)。类加载器负责读取 Java 字节代码,并转换成java.lang.Class 类的一个实例。每个这样的实例用来表示一个 Java 类。通过此实例的 newInstance() 方法就可以创建出该类的一个对象。实际的情况可能更加复杂,比如 Java 字节代码可能是通过工具动态生成的,也可能是通过网络下载的。
基本上所有的类加载器都是 java.lang.ClassLoader 类的一个实例。上图可以看出一点,就是通过不同的类加载器,可以加载出不同的Class类的实例。

java.lang.ClassLoader

之前说过类加载器是干啥使的,除了加载类以外,其实诸如图像文件什么的资源也是它加载的,不过这里暂且不谈。
java.lang.ClassLoader其实是一个抽象类,有几个比较重要的方法:

  • getParent() 用于返回该类加载器的父类加载器;这个方法与经常能看到的“双亲委派”这个关键词有关:)
  • loadClass(String name) 加载名称为name的类,返回java.lang.Class实例。
    该方法有三步:
    1.调用findLoadedClass方法看看是不是已经加载过这个类了;
    2.如果没有,在父类加载器上调用loadClass方法。如果父类加载器是null(啊..到底了…),则用虚拟机的内置类加载器(下面讲到的引导类加载器);
    3.还是不行,调用findClass方法查找类
  • findClass(String name) 查找名称为name的类,返回java.lang.Class实例
  • findLoadedClass(String name) 查找一个名为name的已经加载过的类,返回java.lang.Class实例
  • defineClass(String name, byte[] b, int off, int len) 把字节数组b中的内容转换为Java类,返回java.lang.Class实例,这是个final方法
  • resolveClass(Class<?> c) 链接指定的Java类

注意:里面的参数name都是二进制名称。
系统提供的类加载器主要有三类:

  • Bootstrap class loader: 引导类加载器,用原生代码写的,用途就是bootstrap Java的核心库。
  • Extensions class loader: 扩展类加载器,用来加载扩展库。
  • System class loader: 系统类加载器,根据Java应用类的路径(CLASSPATH)加载Java类,一般我们写的东西就是它来加载的。

除了写好的类加载器外,我们可以自己继承java.lang.ClassLoader来实现自己的类加载器,满足一些奇奇怪怪的需求,比如问题1…

getParent()——找“爹”方法

在参考文献中是这么写的“对于开发人员编写的类加载器来说,其父类加载器是加载此类加载器 Java 类的类加载器”,是不是很拗口?比如我们自己继承了个ClassLoader类的话(假设他叫MyClassLoader),一般会在这个方法里面返回System class loader,为啥呢?因为我们自己写的MyClassLoader也是个类(先有鸡还是先有蛋,哈哈),也需要一个加载器把MyClassLoader加载了,我们才能用它去加载别的东西。
有了这个方法,就可以在加载类的时候有一个向上追溯的找类过程,这个感觉就有点像JavaScript里面的XX链(原型链、作用域链……哦,扯远了)。类加载器之间可以形成树形结构,比如下面这样子(图版权归IBM所有):

类加载器的树状结构

一路都可以getParent()上去,有些JDK可能输出不到最初的那个引导类加载器(AppClassLoader的getParent()返回了null)。

类加载器的代理模式——”我”是”我”?

Java虚拟机怎么判断两个类相同呢?通过包名+类名?不是的,还得看加载这个类的类加载器是不是一样。本文的第一张图可以看出,相同的字节码,被不同的类加载器加载之后,得到的Class类实例是不同的。
不同的类加载器使得包、类名称完全相同的两个类可以并存在Java虚拟机中,且不同加载器加载的类之间是不兼容的。代理模式的设计动机是保证Java核心库的类型安全。我们知道java.lang.Object类是”万物的基础“,如过这个东西能够用自己的类加载器来先加载的话,那就可能存在多个版本的java.lang.Object类并且他们还互不兼容,这就懵逼了。

加载类的过程

首先明确一点,由于类的加载是自顶向下尝试的,真正完成类加载工作的加载器和启动加载过程的加载器有可能不是同一个。
defineClass方法的作用是”真正的“完成类的加载过程(你看它的传入参数,字节数组什么的,多么朴实),执行这个方法的加载器被称作定义加载器;启动类的加载过程是调用loadClass方法实现的(它只要给个名字就行),执行这个方法的加载器叫做初始加载器,它要找不到类的话,就抛出个ClassNotFoundException异常。成功加载一个类后,会把得到的Class类实例缓存起来,下次请求时就不会重复加载了,即对于一个类(全名表示)来说,loadClass方法不会在一个classLoader里头重复调用。
 
额,如果有空的话,下一篇会实作一个自定义的ClassLoader。

参考文献

  • 彻底搞懂Java Classloader: http://weli.iteye.com/blog/1682625
  • 深入探讨Java类加载器:https://www.ibm.com/developerworks/cn/java/j-lo-classloader/
  • 《Java编程思想(第四版)》
Categories
不学无术 木有技术

在SpringMVC 4 中用注释(Annotation)方式使用 Google Kaptcha (Captcha)

原创内容,转载请注明来自http://boweihe.me/?p=2026

近期在努力学习SpringMVC,因为之前对JSP也是一知半解的,干脆拿了本《Spring in Action》(4th edition)啃,发现使用注释的方式比用xml来的有意思一些。网站中要用到验证码,目前能找到的文档都是用xml配置的,感觉有点不爽,决定学我党“摸着石头过河”一次,希望不要naive了..

Maven库

Google自家的库估计是没了,大概是时间太久远了吧。我用的Maven库是这个

<dependency>
            <groupId>com.github.penggle</groupId>
            <artifactId>kaptcha</artifactId>
            <version>2.3.2</version>
</dependency>

Bean配置文件

需要把原有的applicationContext.xml用注释的方式实现,其实就是让Spring找到个可以用的Bean并加载相关配置。
 
我构造了一个类来搞定这个事情,里面的配置参数会从application.properties文件中读取,我仅仅实现了我需要的几个参数,反正如果要加的话就在kaptchaProperties方法里面写就可以,然后Autowire一个Environment用来读取文件配置参数。主要是@Configuration 注释,这个是告诉Spring我是个配置类,这个还是从Hibernate配置中学过来的,哈哈。
声明出来的这个@Bean(name = “captchaProducer”) 就是后面Controller里面要用的了

package edu.inlab.config;
import com.google.code.kaptcha.impl.DefaultKaptcha;
import com.google.code.kaptcha.util.Config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;
import java.util.Properties;
/**
 * Created by inlab-dell on 2016/5/17.
 */
@Configuration
@PropertySource(value = {"classpath:application.properties"})
public class KaptchaConfig {
    @Autowired
    private Environment environment;
    private DefaultKaptcha kaptcha;
    @Bean(name = "captchaProducer")
    public DefaultKaptcha getKaptchaProducer(){
        if(null == kaptcha) {
            kaptcha = new DefaultKaptcha();
            kaptcha.setConfig(getKaptchaConfig());
        }
        return kaptcha;
    }
    @Bean
    public Config getKaptchaConfig(){
        return new Config(kaptchaProperties());
    }
    private Properties kaptchaProperties(){
        Properties properties = new Properties();
        properties.put("kaptcha.image.width",
                environment.getRequiredProperty("kaptcha.image.width"));
        properties.put("kaptcha.image.height",
                environment.getRequiredProperty("kaptcha.image.height"));
        properties.put("kaptcha.textproducer.char.string",
                environment.getRequiredProperty("kaptcha.textproducer.char.string"));
        properties.put("kaptcha.textproducer.char.length",
                environment.getRequiredProperty("kaptcha.textproducer.char.length"));
        return properties;
    }
}

对应的application.properties,具体含义可以看参考文献里,解释的很清楚了。

kaptcha.image.width = 200
kaptcha.image.height = 50
kaptcha.textproducer.char.string = ABCDEFGHKLMNPQRSTWXY3456789
kaptcha.textproducer.char.length = 6

CaptchaController 控制器

项目在实习前怕是赶不完来了,我还是少花点时间写博客吧。
控制器的实现Wiki上都有,主要的是要@Autowire之前我们做好的那个Bean,大概是这样。其实就是个基于set方法的注入嘛~

@Controller
@RequestMapping("/captcha")
public class CaptchaController {
    private Producer captchaProducer;
    @Autowired
    public void setCaptchaProducer(Producer captchaProducer) {
        this.captchaProducer = captchaProducer;
    }
    @RequestMapping(method = RequestMethod.GET)
    public ModelAndView handleRequest(HttpServletRequest request,
                  HttpServletResponse response) throws Exception{
        // ...
    }
}

 

参考文献:

  1. 在springmvc项目中使用kaptcha生成验证码
  2. 简单Maven的Web项目之验证码(Kaptcha篇)
  3. Spring mvc框架下使用kaptcha生成验证码
  4. https://code.google.com/archive/p/kaptcha/wikis

 

Categories
不学无术

LeetCode 119. Pascal's Triangle II

题目

Given an index k, return the kth row of the Pascal’s triangle.
For example, given k = 3,
Return [1,3,3,1].
Note:
Could you optimize your algorithm to use only O(k) extra space?

思路

交替使用两个数组即可。

代码

class Solution {
public:
    vector<int> getRow(int rowIndex) {
        rowIndex++;
        if(rowIndex < 1){
            return vector<int>();
        }
        vector<int> temp0(rowIndex, 0);
        vector<int> temp1(rowIndex, 0);
        vector<int>* pCurrRow = &temp0;
        vector<int>* pLastRow = &temp1;
        for(int i=0; i<rowIndex; i++){
            (*pCurrRow)[0] = 1;
            for(int col=1; col<=i; col++){
                (*pCurrRow)[col] = (*pLastRow)[col-1] + (*pLastRow)[col];
            }
            vector<int>* pTemp = pLastRow;
            pLastRow = pCurrRow;
            pCurrRow = pTemp;
        }
        return (*pLastRow);
    }
};

 

Categories
不学无术

LeetCode 38. Count and Say

题目

The count-and-say sequence is the sequence of integers beginning as follows:
1, 11, 21, 1211, 111221, ...
1 is read off as "one 1" or 11.
11 is read off as "two 1s" or 21.
21 is read off as "one 2, then one 1" or 1211.
Given an integer n, generate the nth sequence.
Note: The sequence of integers will be represented as a string.

思路

好几题没刷leetcode手痒痒,又没太多时间,就选了个Easy的题啦…这个题目确实挺简单的,统计一下前面是不是重复就行了。在句末加一个特殊符号可以避免多写几行代码的样子~

代码

class Solution {
public:
    string countAndSay(int n) {
        string nStr = "1$";
        string nextStr = "";
        while(n > 1){
            char lastChr = '$';
            int lastCount = 0;
            for(int i=0; i<nStr.size(); i++){
                if(nStr[i] == lastChr){
                    lastCount++;
                } else {
                    if(lastCount > 0){
                        string tempStr;
                        while(lastCount > 0){
                            tempStr += (lastCount%10 +'0');
                            lastCount /= 10;
                        }
                        reverse(tempStr.begin(), tempStr.end());
                        nextStr += tempStr;
                        nextStr += lastChr;
                    }
                    lastCount = 1;
                    lastChr = nStr[i];
                }
            }
            nStr = nextStr + "$";
            nextStr = "";
            n--;
        }
        return nStr.substr(0, nStr.size()-1);
    }
};

 

Categories
不学无术

LeetCode347. Top K Frequent Elements

好久不刷题了,今晚来一道。

题目

Given a non-empty array of integers, return the k most frequent elements.
For example,
Given [1,1,1,2,2,3] and k = 2, return [1,2].
Note:

  • You may assume k is always valid, 1 ≤ k ≤ number of unique elements.
  • Your algorithm’s time complexity must be better than O(n log n), where n is the array’s size.

思路

题目中明确提示要O(nlogn)复杂度了…
我的做法是先统计结果到一个map<value, count>里面,然后用一个大小为k的最小堆处理,每次从map中读取元素时根据count值判断是否要替换掉堆顶。由于STL的map是用红黑树实现的,所以时间复杂度就保证了,要再快的话就直接用hash函数吧,可以到O(n)。维护k大小的堆,算最差情况,建堆复杂度是O(k),维护复杂度是O(logk), 要维护M-k次,其中0<M<=N是map中的元素个数.
代码

struct CountVal{
    int val;
    int count;
    CountVal(int _val, int _count){
        val = _val;
        count = _count;
    }
};
bool compareObj(const CountVal& x, const CountVal& y){
    return x.count > y.count;
}
class Solution {
public:
    vector<int> topKFrequent(vector<int>& nums, int k) {
        map<int, int> countMap;
        vector<CountVal> counts;
        for(int i=0; i<nums.size(); i++){
            if(countMap.count(nums[i]) == 0)
                countMap[nums[i]] = 1;
            else
                countMap[nums[i]]++;
        }
        int count = 0;
        for(auto it=countMap.begin(); it!=countMap.end(); it++){
            CountVal item(it->first, it->second);
            if(count < k){
                counts.push_back(item);
                if(count == k-1){
                    make_heap(counts.begin(), counts.end(), compareObj);
                }
            } else {
                if (it->second > counts.front().count) {
                    pop_heap(counts.begin(), counts.end(), compareObj);
                    counts.pop_back();
                    counts.push_back(item);
                    push_heap(counts.begin(), counts.end(), compareObj);
                }
            }
            count++;
        }
        vector<int> results;
        for(auto it=counts.begin(); it!=counts.end(); it++){
            results.push_back(it->val);
        }
        return results;
    }
};