我正打算写写 Python 的生成器,然而查资料时发现,引入生成器的 PEP
没人翻译过,因此就花了点时间翻译出来。如果在阅读时,你有读不懂的地方,不用怀疑,极有可能是我译得不到位。若出现这种情况,我建议你直接阅读原文,最好也能将错误处告知于我,以便做出修改。

在难也要学完《深入分析的Java
Web》的深入理解Session与Cookie,断断续续花了三四天的时间在学习这块内容,好难啊,但还是得逼自己学完,不想一直掉坑里
记录一下学习内容,这样哪天忘记了又能重新拾起来

简书 加薪猫
转载请注明原创出处,谢谢!
这一系列主要介绍HashMap(1.8),记录也是分享,欢迎讨论

先来张超喜欢的硬图~~~让近来忙的团团转的自己开心一哈图片 1图片 2图片 3看到这图莫名的充满了力量,还不禁嘴角上扬图片 4图片 5图片 6(已然忘记了图的出处,若你知道图片来源请告知俺)

简书 加薪猫
转载请注明原创出处,谢谢!
这一系列主要介绍HashMap(1.8),记录也是分享,欢迎讨论

 号:960410445 群里有志同道合的小伙伴,互帮互助, 群里有不错的视频学习教程和PDF!

(一)浅入部分:方法使用介绍
1.Session创建的时间:客户端第一次请求服务器的时候创建的。
通过request.getSession()可以获得对应的session对象(返回类型为HttpSession),不存在将返回null。调用request.getSession(true),如果该客户的session不存在,将先创建session对象,在返回session。
测试代码:SessionTest.java

0.HashMap 结构

图片 7

上回书说到get方法的爱恨情仇,其实主要还是简单介绍了HashMap的一些基本结构,还有HashMap的哈希值是怎么算出来的,这些可以为我们的put方法打下一定的基础,今天就来分享put方法

摘要

public class SessionTest extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        HttpSession session = request.getSession();
        String id = session.getId();
        System.out.println("session_id: " + id);
    }
}
HashMap 的数据存在哪里?数据结构是什么?

1.HashMap所有的key-value,存在一个全局变量Node<K,V>[] table里面。

2.Node<K,V> 这个的结构具体看代码

    static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        V value;
        Node<K,V> next;

        Node(int hash, K key, V value, Node<K,V> next) {
            this.hash = hash;
            this.key = key;
            this.value = value;
            this.next = next;
        }

        public final K getKey()        { return key; }
        public final V getValue()      { return value; }
        public final String toString() { return key + "=" + value; }

        public final int hashCode() {
            return Objects.hashCode(key) ^ Objects.hashCode(value);
        }

        public final V setValue(V newValue) {
            V oldValue = value;
            value = newValue;
            return oldValue;
        }

        public final boolean equals(Object o) {
            if (o == this)
                return true;
            if (o instanceof Map.Entry) {
                Map.Entry<?,?> e = (Map.Entry<?,?>)o;
                if (Objects.equals(key, e.getKey()) &&
                    Objects.equals(value, e.getValue()))
                    return true;
            }
            return false;
        }
    }

hash用来存储这个Node中key经过HashMap的hash(K
key)方法计算后得出的,key、value不说了,next存储一下个Node节点。HashMap是数组+链表的形式存储的,当然这个Node的子类还有TreeNode,这个我们之后再说

言归正传,前段时间分享了一些文本分类和文本特征的文章,其中有一篇关于Word2Vec的文本分类特征提取之Word2Vec,里面上来就提到了SoftMax,后台有小伙伴反馈,SoftMax是怎么回事?有没有这方便的笔记分享?

2.put(K,V)

老样子,先怼源码

public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }

put方法完结撒花,下面我们来讲putVal.(本猫一系列都将以JDK1.8为主讲述,如果有心人看源码时发现自己的源码可能是个假源码的请更换JDK版本,hash()方法可看上节)

putVal(int hash,K key,V value, boolean onlyIfAbsent,boolean evict)

先介绍这个方法的几个参数:
hash就是上节我们说过的,经过HashMap处理过的哈希值
key和value不谈,onlyIfAbsent这个参数的作用在于,如果我们传入的key已存在我们是否去替换,true:不替换,false:替换。
参数evict是用在LinkedHashMap的,这里暂且不谈,等之后分享LinkedHashMap的时候我们再来细细分析
我们先看putVal的代码

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i; //①
        if ((tab = table) == null || (n = tab.length) == 0)//②
            n = (tab = resize()).length;
        if ((p = tab[i = (n - 1) & hash]) == null)//③
            tab[i] = newNode(hash, key, value, null);
        else {//④
            Node<K,V> e; K k;//⑤
            if (p.hash == hash &&//⑥
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            else if (p instanceof TreeNode)//⑦
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {//⑧
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {//⑨
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&//⑩
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key ⑪
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;//⑫
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

以上代码很长,加上手机上显示好像没有高亮,看着更是难受。所以读者可以以上面为机构,我会在下面拆分进项讲解。下面的拆分和上面的注解一起看,效果更加哦。

这个 PEP 想在 Python 中引入生成器的概念,以及一个新的表达式,即 yield
表达式。动机

运行结果:

哈希值是否有重复?为什么要用链表存储?

按理说哈希值是不会有重复的,java
Object类中的hashCode方法使用类的地址转int,保证了hash值的唯一性,虽说哈希值不会重复,但是在存储时我们还是会发生冲突的,具体我们可以看下面的介绍,用链表存储就是为了解决冲突问题的,具体可以仔细研究1.1.2节。其次1.8不仅用链表,当链表长度超过默认值(8)时,HashMap还会把这个链表转为红黑树,这也是为了提升查找效率。

正好近来小编也在学习,倒是搜集了一些资料。未必透彻,但可以带你认识Softmax。

 Node<K,V>[] tab; Node<K,V> p; int n, i;

第一次看源码的时候,很容易搞混变量,尤其是看在中间的时候会突然走个神就想着,哎?我是谁,我在哪,我在干什么。。所以最好的方式,还是在看代码时,当你看懂这个变量的意义的时候就加上自己的注释。
tab:HashMap里面存储Node节点的数组
p:当前要put进HashMap的key,经过计算得出的tab下标所对应第一个Node。(啥?我在说啥?
第一个Node的意思是,我们的Node本来就是链表中的一个节点,我们根据Key只能找到这个链表的头)

这个更精确地说是当前处理的节点
n:tab当前的大小
i:key算出来的下标

当一个生产者函数在处理某些艰难的任务时,它可能需要维持住生产完某个值时的状态,大多数编程语言都提供不了既舒服又高效的方案,除了往参数列表中添加回调函数,然后每生产一个值时就去调用一下。

session_id: BE37D76B19597F991999F482A0A6F9BD

正文

HashMap源码中最有用,最值得看的就是resize()扩容方法,直接去看resize()方法肯定是一头雾水。

所以这里是从我们最常用的方法一步步去分享。

关键词

        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;

table是全局变量,先设置tab= table, n =
tab.length,判断如果tab为null,或者tab的大小为0的话,我们就要调用resize()方法进行第一次扩容(resize()方法我们在下面详细说,这里你可以暂时理解为初始化HashMap),从这里我们可以知道的一点是HashMap不是在实例化话的时候就生成指定大小的Node<K,V>[],而是在第一次put时才会进行初始化。

例如,标准库中的tokenize.py采用这种方法:调用者必须传一个 tokeneater
函数给 tokenize() ,当 tokenize() 找到下一个 token 时再调用。这使得
tokenize 能以自然的方式编码,但程序调用 tokenize
会变得极其复杂,因为它需要记住每次回调前最后出现的是哪个
token。tabnanny.py中的 tokeneater
函数是处理得比较好的例子,它在全局变量中维护了一个状态机,用于记录已出现的
token 和预期会出现的 token
。这很难正确地工作,而且也挺难让人理解。不幸的是,它已经是最标准的解决方法了。

说明在客户端第一次请求时就创建了session对象,并且这个过程对我们来说是透明的。
注:获取session时只需调用getSession()方法就可以了,不需要指定是哪个客户端的session。session的机制决定了当前客户只会获得自己的session,而不会获得别人的session。各客户的session互相独立、互不可见

1. get(Object key)

直接上代码

public V get(Object key) {
            Node e;
            return(e = getNode(hash(key),key)) ==null?null: e.value;
}

get方法里面主要就是hash 和 getNode

Softmax回归     Softmax Regression

if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);

这里是我们根据计算的哈希值(具体计算方式可以看上一节)算出下标的方式,(n-1)& hash,(15&3=3,15&18=2,与运算这里就不多说了哈~),如果这算出来的下标i,tab[i]为空的话,我们可以直接tab[i]下塞一个新的Node就好了。

有一个替代方案是一次性生成 Python 程序的全部解析,并存入超大列表中。这样
tokenize
客户端可以用自然的方式,即使用局部变量和局部控制流(例如循环和嵌套的 if
语句),来跟踪其状态。然而这并不实用:程序会变得臃肿,因此不能在实现整个解析所需的内存上放置先验限制;而有些
tokenize 客户端仅仅想要查看某个特定的东西是否曾出现(例如,future
声明,或者像 IDLE
做的那样,只是首个缩进的声明),因此解析整个程序就是严重地浪费时间。

2.session的生命周期
tomcat8中默认session的生命周期为30min

1.1 hash(Object key)

static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

有监督学习       supervised learning

④ 仿佛又是一大块,这也是这个函数重点理解的地方,⑤~⑪可以就看下面的代码,当然我上面也会标注的
else {//④
            Node<K,V> e; K k;//⑤
            if (p.hash == hash &&//⑥
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            else if (p instanceof TreeNode)//⑦
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {//⑧
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {//⑨
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&//⑩
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key ⑪
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }

这一段主要讲的当算出的索引下标已经有值时HashMap的处理方式。

另一个替代方案是把 tokenize 变为一个迭代器,每次调用它的 next()
方法时再传递下一个
token。这对调用者来说很便利,就像前一方案把结果存入大列表一样,同时没有内存与“想要早点退出怎么办”的缺点。然而,这个方案也把
tokenize 的负担转化成记住 next() 的调用状态,读者只要瞄一眼
tokenize.tokenize_loop()
,就会意识到这是一件多么可怕的苦差事。或者想象一下,用递归算法来生成普通树结构的节点:若把它投射成一个迭代器框架实现,就需要手动地移除递归状态并维护遍历的状态。第四种选择是在不同的线程中运行生产者和消费者。这允许两者以自然的方式维护其状态,所以都会很舒服。实际上,Python
源代码发行版中的 Demo/threads/Generator.py
就提供了一个可用的同步通信(synchronized-communication)类,来完成一般的任务。但是,这在没有线程的平台上无法运用,而且就算可用也会很慢(与不用线程取得的成就比)。

  <!-- ==================== Default Session Configuration ================= -->
  <!-- You can set the default session timeout (in minutes) for all newly   -->
  <!-- created sessions by modifying the value below.                       -->

    <session-config>
        <session-timeout>30</session-timeout>
    </session-config>

1.1.1 hashCode

所有对象都会有也是必须有hashCode()方法的,这也就是为什么HashMap的Key要求必须是对象的原因(不可用基本类型,int,long,等等)。当然Value也是要求必须是对象的,为什么就待日后再说。

这是Object类里面hashCode()方法。

 public native int hashCode();

一个不常见的关键词
native,使用native关键字说明这个方法是原生函数,也就是这个方法是用C/C++语言实现的。。。后面的不想说啦,既然是C系列那么就不在在下的关注之中了,我们回到源码Object对这个函数的描写

 * Returns a hash code value for the object. This method is
 * supported for the benefit of hash tables such as those provided by
 * {@link java.util.HashMap}.
 * 返回对象的哈希值,这个方法是为了给哈希表提供帮助的(也就是这次讲的HashMap)
 * <p>
 * The general contract of {@code hashCode} is:
 * 约束是
 * <ul>
 * <li>Whenever it is invoked on the same object more than once during
 *     an execution of a Java application, the {@code hashCode} method
 *     must consistently return the same integer, provided no information
 *     used in {@code equals} comparisons on the object is modified.
 *     This integer need not remain consistent from one execution of an
 *     application to another execution of the same application.
 *     在一个java应用执行中,对同一个对象多次调用 hashCode()方法,必须返回相同的整形,
 *     这个前提是在对象的比较中,没有任何信息被修改.相同程序在多次分别执行时,是不需要相同的
 * <li>If two objects are equal according to the {@code equals(Object)}
 *     method, then calling the {@code hashCode} method on each of
 *     the two objects must produce the same integer result.
 *     如果两个对象调用equals()方法是相等的,那么调用hashCode方法的返回也是相同的
 * <li>It is <em>not</em> required that if two objects are unequal
 *     according to the {@link java.lang.Object#equals(java.lang.Object)}
 *     method, then calling the {@code hashCode} method on each of the
 *     two objects must produce distinct integer results.  However, the
 *     programmer should be aware that producing distinct integer results
 *     for unequal objects may improve the performance of hash tables.
 *     两个不相等的对象,(不)要求hashCode相同,但是程序猿需要知道,给不相等对象提供不同的
 *     hash值有利于hash表的查询
 * </ul>
 * <p>
 * As much as is reasonably practical, the hashCode method defined by
 * class {@code Object} does return distinct integers for distinct
 * objects. (This is typically implemented by converting the internal
 * address of the object into an integer, but this implementation
 * technique is not required by the
 * Java™ programming language.)
 * 呵呵,Object自己的hashCode方法在不同的对象上返回不同的整形
 *(这是依赖内部地址转换为整形来实现,但是我们重写这个方法的时候不要求这样~)
 * 
 * @return  a hash code value for this object.
 * @see     java.lang.Object#equals(java.lang.Object)
 * @see     java.lang.System#identityHashCode

无监督学习       unsupervised learning

Node<K,V> e; K k;

e:如果要put进HashMap的key已存在,则e为这个key的节点,如果不存在则为null。
k:遍历链表时,当前节点的key

最后一个选择是使用 Python 的变种 Stackless
来实现,它支持轻量级的协程。它与前述的线程方案有相同的编程优势,效率还更高。然而,Stackless
在 Python 核心层存在争议,Jython 也可能不会实现相同的语义。这个 PEP
不是讨论这些问题的地方,但完全可以说生成器是 Stackless
相关功能的子集在当前 CPython 中的一种简单实现,而且可以说,其它 Python
实现起来也相对简单。

session的生命周期没有cookie长是有原因的,cookie保存在浏览器中一般是永久的。session就不一样了,为了获得更高的缓存速度,服务器一般把session保存在内存中,但是,如果网站是高并发的,并且session保存的内容很多,就会造成内存溢出,所以需要定期清除那些不常用的session,通过设置一个时间,时间不可以太长,超时清理,缓减内存压力。如果自己需要更改sessiond的生命周期,有两种方式可以修改
1)tomcat web.xml文件中进行修改
2)调用setMaxInactiveInterval(int i)方法进行修改
注意web.xml中时间的单位为小时,setMaxInactiveInterval(int
i)方法中时间的单位为秒

1.1.2 >>>

Object.hashCode()已经返回了这个对象的hash值,那么为什么HashMap里面还有有个hash方法呢?

(h = key.hashCode()) ^ (h >>> 16)

这个操作的高度概括就是让Key的哈希值高位参与运算。

深度学习          deep learning

            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;

总是判断第一个,如果算出的hash值相同并且key相同(这个key相同是指要么是同一个对象,要么是equals返回true),那么e=p然后直接进入⑪的判断

以上分析完了已有的方案。其它一些高级语言也提供了不错的解决方案,特别是
Sather 的迭代器,它受到 CLU 的迭代器启发;Icon
的生成器,一种新颖的语言,其中每个表达式都是生成器。它们虽有差异,但基本的思路是一致的:提供一种函数,它可以返回中间结果给它的调用者,同时还保存了函数的局部状态,以便在停止的位置恢复(译注:resum,下文也译作激活)调用。一个非常简单的例子:

3.session对浏览器的要求
session需要依赖cookie,服务器生成session时会生成一个name为jsessionid的cookie,值为session的id(即HttpSession.getId()的返回值),session通过cookie来识别是否为同一个用户。服务器自动生成的cookie,maxAge属性一般为-1,仅在当前浏览器内的当前窗口有效,关闭浏览器就会失效。如果浏览器禁用cookie或不支持cookie,就需要使用url地址重写

url地址重写原理:将该用户的session的id信息重写到url地址中。服务器能够解析重写后的url获取session的id。这样就可以不需要使用cookie记录session的id了。
测试代码:SessionTest.java

为什么要高位参与运算呢?

我们算出来的哈希值是一个int型,2进制32位带符号的int是-2147483648~2147483648,其实很难会发生碰撞(上面也说到了,Object提供的hashCode方法算出来不同对象的哈希值是不会有重复的),如果我们直接使用哈希值作为数组下标访问的话,内存是吃不消的,所以这个算出来的哈希值之后会和
当前HashMap的大小做取模运算得到的余数 当前HashMap的大小-1
做与运算作为下标

p = tab[index = (n - 1) & hash]

这一段是之后put方法中使用获得下标的语句,这个我们之后再讲,根据上面的代码,我们再看。HashMap的默认大小是2^4=16,换算成32位的样式就是

0000 0000 0000 0000 0000 0000 0001 0000 –>16

0000 0000 0000 0000 0000 0000 0000 1111 –>15

当我们拿着15去做与运算的时候

从0000 0000 0000 0000 0000 0000 0000 0000

到1111 1111 1111 1111 1111 1111 1111 0000

所有低四位为0的对象,在这个HashMap中都会存到下标为0的对象里面,不考虑扩容等问题,这样的HashMap(1.8之前)他的查找效率就跟链表一样,即使1.8将链表大小超过8的链表转为红黑树,这也不是HashMap的设计初衷。

但是我们将算出来的哈希值右移16位后取异或,那么就当前大小16的HashMap来说,参与运算的就不只是0
~ 3位,是0 ~ 3和16 ~ 19位共同的计算结果,这个操作使我们的分布更加均匀。

而我们的HashMap的大小默认值是16也就是2^4,而有符号int标志范围。

顺便一提的是这也是为什么我们HashMap的大小必须2的幂次方,因为这样,大小-1正好是地位掩码。

logistic回归      logistic regression

            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);

如果p已经是TreeNode类了(也就是已经转换成红黑树了)就直接调用红黑树的putTreeVal,这个方法之后我们再具体讲,这里只要知道如果是key不存在这个方法返回null,如果存在返回对应的节点进入⑪的判断

def fib():a, b = 0, 1while 1:yield ba, b = b, a+b当 fib()
首次被调用时,它将 a 设为 0,将 b 设为 1,然后生成 b
给其调用者。调用者得到 1。当 fib 恢复时,从它的角度来看,yield
语句实际上跟 print 语句相同:fib
继续执行,且所有局部状态完好无损。然后,a 和 b 的值变为 1,并且 fib
再次循环到 yield,生成 1 给它的调用者。以此类推。 从 fib
的角度来看,它只是提供一系列结果,就像用了回调一样。但是从调用者的角度来看,fib
的调用就是一个可随时恢复的可迭代对象。跟线程一样,这允许两边以最自然的方式进行编码;但与线程方法不同,这可以在所有平台上高效完成。事实上,恢复生成器应该不比函数调用昂贵。

public class SessionTest extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("进入SessionTest方法体内");
        HttpSession session = request.getSession();
        String id = session.getId();
        System.out.println("session_id: " + id);
        Cookie[] cookies = request.getCookies();

        System.out.println("size: " + cookies.length);

        if (cookies.length != 0) {
            for (Cookie c : cookies) {
                System.out.println("name: " + c.getName() + "  value: " + c.getValue());
            }

        }
//        response.sendRedirect("/cookie_message");
    }

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doPost(request, response);
    }
}

1.2 getNode(int hash,Object key)

以上,我们知道了在HashMap中哈希值的计算方式,下面我们要讨论的是取到hash值之后,我们要怎么去取到具体的Value

final Node<K,V> getNode(int hash, Object key) {
        Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (first = tab[(n - 1) & hash]) != null) {
            if (first.hash == hash && // always check first node
                ((k = first.key) == key || (key != null && key.equals(k))))
                return first;
            if ((e = first.next) != null) {
                if (first instanceof TreeNode)
                    return ((TreeNode<K,V>)first).getTreeNode(hash, key);
                do {
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        return e;
                } while ((e = e.next) != null);
            }
        }
        return null;
    }

我们已经知道了,HashMap,最底层的数据结构是Node<K,V>[],其实这段代码蛮好懂的,就是几个条件,唯一值得注意的地方就是,我们在具体判断的时候,首先判断((k
= e.key) == key),其次我们还要判断(key != null &&
key.equals(k)),==和equals的区别就不赘述了。

从一个get方法我们也可以看出,如果我们想用自己新建的一个类作为HashMap的key,我们一定要正确的重写这个类的hashCode()和equals()方法,不然最终的结果可能并不是我们想要的。

这段代码里面还有与之前JDK版本相比最大的区别就是引入了红黑树,这段代码也可以看到,如果我们这个节点是TreeNode的话,我们会使用TreeNode的getTreeNode(hash,key)的方法获得我们想要的Node节点,如果不是TreeNode的话,我们会遍历链表。

今天先到这里~

我是加薪猫
技术是加薪的前提,生活是加薪的意义
技术等于生活

截距项             intercept term

for (int binCount = 0; ; ++binCount) {
.....
}

这个循环主要是用来记录链表的数量,之后如果超过默认值(8),会将链表转换为红黑树

同样的方法适用于许多生产者/消费者函数。例如,tokenize.py 可以生成下一个
token 而不是用它作为参数调用回调函数,而且 tokenize
客户端可以以自然的方式迭代 tokens:Python
生成器是一种迭代器,但是特别强大。

运行结果:

二元分类          binary classification

if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }

p.next==null的意思是指我们遍历链表已经找到最后一个节点了,那就是虽然算出下标下是有链表的,但是并没有相同key,所以直接创建新的节点放在链表的最后。同时,如果这个链表的长度已经大于默认值8就会将当前这个链表转换为红黑树,具体treeifyBin()方法我们之后分享红黑树的时候来分享。

设计规格:yield

进入SessionTest方法体内
session_id: 5748CB766C7130F80F259E72A5637920
进入SessionTest方法体内
session_id: 5748CB766C7130F80F259E72A5637920
size: 1
name: JSESSIONID  value: 5748CB766C7130F80F259E72A5637920

类型标记          class labels

if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;

如果要put的key已经被找到了,则直接进入⑪判断

引入了一种新的表达式:yield_stmt:“yield”expression_listyield
是一个新的关键字,因此需要一个 future
声明来进行引入:在早期版本中,若想使用生成器的模块,必须在接近头部处包含以下行(详见
PEP 236):

先清理一下浏览器缓存,第一次进入方法体时会报500空指针异常
报空指针异常的原因是此时request.getCookies()没有结果,第二次继续调用方法时才成功,从结果中看出确实生成一个name为JSESSIONID
,值为session id的cookie。
思考了一下出错原因:客户端第一次请求服务器时生成一个session,同时生成了一个cookie,但这次生成的cookie要通过服务器的响应以后才能返回给客户端,所以第一次取不到想要的cookie。
禁用cookie后,因为在tomcat配置文件中禁用cookie没有效果,直接禁用了浏览器

估值函数/估计值 hypothesis

if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }

一般我们的put的方法都是会直接覆盖旧值的,其实一开始就想说的就是put方法是有返回值的!如果有旧值返回旧值,没有旧值就返回null。afterNodeAccess这个方法是LinkedHashMap用的,这里先不涉及

from future import generators没有引入 future 模块就使用 yield
关键字,将会告警。 在后续的版本中,yield 将是一个语言关键字,不再需要
future 语句。

图片 8

代价函数         cost function

  ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;

首先代码能走到这里,是③为true或者是⑪为false,这两个是什么意思呢,意思是之前没有相同的key,也就是说HashMap的大小会+1,++modCount暂且不提,我们先看后面的,如果此时size+1后比threshold大(这个变量的值我们在讲resize的时候会重新介绍这里你可以简单以为是
容量*0.75),则会扩容。
afterNodeInsertion()方法也是LinkedHashMap的方法,这里暂不做介绍,至此Put方法完成啦~~

本猫的期望就是能够让大家更加容易的学习源码理解源码,诸君如果有建议在下面留言哦~下期会讲resize()方法

我是加薪猫
技术是加薪的前提,生活是加薪的意义
技术等于生活

yield 语句只能在函数内部使用。包含 yield
语句的函数被称为生成器函数。从各方面来看,生成器函数都只是个普通函数,但在它的代码对象的
co_flags 中设置了新的“CO_GENERATOR”标志。

禁用cookie

多元分类         multi-class classification

当调用生成器函数时,实际参数还是绑定到函数的局部变量空间,但不会执行代码。得到的是一个
generator-iterator 对象;这符合迭代器协议,因此可用于 for
循环。注意,在上下文无歧义的情况下,非限定名称 “generator”
既可以指生成器函数,又可以指生成器-迭代器(generator-iterator)。

原来的程序运行结果:

权重衰减         weight decay

每次调用 generator-iterator 的 next() 方法时,才会执行
generator-function 体中的代码,直至遇到 yield 或 return
语句,或者直接迭代到尽头。

进入SessionTest方法体内
session_id: 39385CEDA231B7DDBC1A42C9BBB6EC92
进入SessionTest方法体内
session_id: 31E06944B9086767087BCF32E9A5D273
进入SessionTest方法体内
session_id: A796BD79458635C4C117198B09C9269C
进入SessionTest方法体内
session_id: 31D60D8791F88E7D28E567A085D3BFCC

介绍

如果执行到 yield 语句,则函数的状态会被冻结,并将 expression_list
的值返回给 next()
的调用者。“冻结”是指挂起所有本地状态,包括局部变量、指令指针和内部堆栈:保存足够信息,以便在下次调用
next() 时,函数可以继续执行,仿佛 yield 语句只是一次普通的外部调用。

一直报500空指针的错误,也就是说明没有cookie
那就使用url重写

本篇文章,我们介绍Softmax回归模型,该模型是logistic回归模型在多分类问题上的推广,在多分类问题中,类标签
y 可以取两个以上的值。 Softmax回归模型对于诸如MNIST(MNIST
是一个手写数字识别库,由NYU 的Yann LeCun
等人维护。

限制:yield 语句不能用于 try-finally 结构的 try
子句中。困难的是不能保证生成器会被再次激活,因此无法保证 finally
语句块会被执行;这就太违背 finally 的用处了。

修改一下上面的代码:

首先回归一下之前的logistics回归,在logistics回归中,训练数据集由
m 个已标记的样本构成,即:{(x[^1], y[^1]),(x[^2],
y[^2]),…,(x[^m], y[^m])},其中输入特征
x[^i]—–>R[^(n+1)]。由于logistics针对的是二分类问题,因此标签y[^i]的取值只有{0,
1}。假设函数如下所示:

限制:生成器在活跃状态时无法被再次激活:

public class SessionTest extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("进入SessionTest方法体内");
        HttpSession session = request.getSession();
        String id = session.getId();
        System.out.println("session_id: " + id);
        response.sendRedirect(response.encodeRedirectURL("/cookie_message"));
    }

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doPost(request, response);
    }
}

图片 9

def g():… i = me.next()… yield ime = g()me.next()Traceback
(most recent call last):…File “<string>”, line 2, in
gValueError: generator already executing设计规格:return

url地址栏变为了:
http://localhost:8080/cookie\_message;jsessionid=D4BCA30FE7A2E44D615DBFE04A4016A4
运行结果:

为了求取权值参数,我们需要优化如下的代价损失函数:

生成器函数可以包含以下形式的return语句:return注意,生成器主体中的
return 语句不允许使用 expression_list
(然而当然,它们可以嵌套地使用在生成器里的非生成器函数中)。

进入SessionTest方法体内
session_id: D4BCA30FE7A2E44D615DBFE04A4016A4

图片 10

当执行到 return 语句时,程序会正常 return,继续执行恰当的 finally
子句。然后引发一个 StopIteration
异常,表明迭代器已经耗尽。如果程序没有显式 return
而执行到生成器的末尾,也会引发 StopIteration 异常。

说明url重定向成功了,这样服务器可以通过url来获得session id了


softmax回归中,我们解决的是多分类问题,类标
y 可以取 k 个不同的值(而不是 2 个)。因此,对于训练集{(x[^1],
y[^1]),(x[^2], y[^2]),…,(x[^m], y[^m])},类别标签y[^i]取值为{1,2,3,….,k} 。例如,在
MNIST 数字识别任务中,我们有 k=10 个不同的类别。

请注意,对于生成器函数和非生成器函数,return
意味着“我已经完成,并且没有任何有趣的东西可以返回”。

(二)深出部分:session底层是如何工作的
4.session工作机制
当客户端和服务器建立连接时会创建一个session,创建session对象的过程大致是这样的:
1)解析url,url中是否有session
id(一般可以解析的url是重写后的url,像这样,http://localhost:8080/cookie\_message;jsessionid=D4BCA30FE7A2E44D615DBFE04A4016A4,id则为jsessionid)
2)如果浏览器是支持cookie的,则从cookie中拿到session
id,并覆盖之前的session id(即url重写中的session id)
所以客户端支持cookie,tomcat仍然会解析cookie中的session
id,然后覆盖url中的session id
3)根据session
id在manager类的session容器中查找是否存在这个id的session对象
4)如果步骤3)中查找不到这个对象,则创建这个id的session对象
5)将这个session对象添加到session容器中
6)根据这个id新增一个cookie,并将这个cookie设置到http协议头中
7)通过调用request.getSession()可以获得该对象。它的底层是通过requestedSessionId从StandardManager(Manager的实现类)的sessions集合中(就是之前说的容器)获取对应客户的HttpSession对象

对于给定的测试输入
x ,我们想用假设函数针对每一个类别 j 估算出概率值 p(y=j|x)
。也就是说,我们想估计
的每一种分类结果出现的概率。因此,我们的假设函数将要输出一个
k 维的向量来表示这
k 个估计的概率值。 具体地说,我们的假设函数形式如下:图片 11

注意,return 并不一定会引发 StopIteration :关键在如何处理封闭的
try-except 结构。 如:

5)管理session对象的StandardManager类介绍
StandardManager类负责Servlet容器中所有session对象生命周期的管理。当tomcat重启或关闭时,StandardManager通过序列化(调用unload方法)将没有过期的session对象持久化到SESSIONS.ser文件中,当tomcat重启时,即StandardManager初始化时,会重新读取这个文件,解析出所有的session对象保存到StandardManager的session集合中。注意:在重启读取SESSIONS.ser文件时,会在一次进行检查session是否过期,过期就不在加载到sessions集合中了
。不正常的关闭tomcat服务器也有可能不会对session进行持久化,因为调用不到unload方法
SESSIONS.ser这个文件有点难找,最后选择以搜索的方式查找到了这类文件

为了方便起见,我们同样使用符号 θ 来表示全部的模型参数。在实现Softmax回归时,将
θ 用一个
k *(n+1) 的矩阵来表示,该矩阵是将θ1, θ2,….,θk 按行排列,如下所示:

def f1():… try:… return… except:… yield 1print
list[]因为,就像在任何函数中一样,return 只是退出,但是:def
f2():… try:… raise StopIteration… except:… yield 42print
list[42]因为 StopIteration 被一个简单的 except
捕获,就像任意异常一样。

图片 12

图片 13

设计规格:生成器和异常传播

皮皮甜电脑上的SESSIONS.ser文件查找结果

 

如果一个未捕获的异常——包括但不限于
StopIteration——由生成器函数引发或传递,则异常会以通常的方式传递给调用者,若试图重新激活生成器函数的话,则会引发
StopIteration 。 换句话说,未捕获的异常终结了生成器的使用寿命。

6)session过期清理
检查session是否过期是tomcat中的一个后台线程完成的(backgroundProcess()方法),如果过期了,则将设置Session.expire(true),否则保留session。过期了的session对象不是立刻就被清理的,tomcat使用一个1分钟的定时任务定时清除当前超时session对象,所以清除超时的session对象有一个小于1分钟的延时,在处理过期问题上,redis清理过期键的机制和它类似。如果此时expire=true,但是定时任务时间没有到,客户端发送request.getSession()请求,则这个session对象将会被立刻清理掉,不会等定时任务来清理,并创建一个新的session对象来响应客户端的请求

代价函数

示例(不合语言习惯,仅作举例):

2017年最后一篇文章
继续沉淀自己。酒越久越醇厚

现在我们来看看softmax回归算法(在下面的公式中:1{.}表示示性函数)。定义代广义价函数如下:

def f():… return 1/0def g():… yield f() # the zero division
exception propagates… yield 42 # and we’ll never get herek =
gTraceback (most recent call last):File “<stdin>”, line 1,
in ?File “<stdin>”, line 2, in gFile “<stdin>”, line
2, in fZeroDivisionError: integer division or modulo by
zerok.next() # and the generator cannot be resumedTraceback (most
recent call last):File “<stdin>”, line 1, in ?StopIteration

图片 14

设计规格:Try/Exception/Finally

logistics回归代价函数为:

前面提过,yield 语句不能用于 try-finally 结构的 try
子句中。这带来的结果是生成器要非常谨慎地分配关键的资源。但是在其它地方,yield
语句并无限制,例如 finally 子句、except 子句、或者 try-except 结构的 try
子句:

图片 15可以看到,Softmax
代价函数与 logistic
代价函数在形式上非常类似,只是在Softmax损失函数中对类标记的
k 个可能值进行了累加。在Softmax 回归中将  x 分类为类别 j
的概率为:

def f():… try:… yield 1… try:… yield 2… 1/0… yield 3
# never get here… except ZeroDivisionError:… yield 4… yield
5… raise… except:… yield 6

print list[]因为,就像在任何函数中一样,return
只是退出,但是:>>> def f2():… try:… raise
StopIteration… except:… yield 42>>> print
list[42]因为 StopIteration 被一个简单的 except
捕获,就像任意异常一样。设计规格:生成器和异常传播如果一个未捕获的异常——包括但不限于
StopIteration——由生成器函数引发或传递,则异常会以通常的方式传递给调用者,若试图重新激活生成器函数的话,则会引发
StopIteration 。
换句话说,未捕获的异常终结了生成器的使用寿命。示例(不合语言习惯,仅作举例):>>>
def f():… return 1/0>>> def g():… yield f() # the
zero division exception propagates… yield 42 # and we’ll
never get here>>> k = g()>>> k.next()Traceback
(most recent call last):File “<stdin>”, line 1, in ?File
“<stdin>”, line 2, in gFile “<stdin>”, line 2, in
fZeroDivisionError: integer division or modulo by
zero>>> k.next() # and the generator cannot be
resumedTraceback (most recent call last):File “<stdin>”,
line 1, in
?StopIteration>>>设计规格:Try/Exception/Finally前面提过,yield
语句不能用于 try-finally 结构的 try
子句中。这带来的结果是生成器要非常谨慎地分配关键的资源。但是在其它地方,yield
语句并无限制,例如 finally 子句、except 子句、或者 try-except
结构的 try 子句:>>> def f():… try:… yield 1…
try:… yield 2… 1/0… yield 3 # never get here… except
ZeroDivisionError:… yield 4… yield 5… raise… except:…
yield 6… yield 7 # the “raise” above stops thisdef f2():…
try:… raise StopIteration… except:… yield 42>>>
print list[42]因为 StopIteration 被一个简单的 except
捕获,就像任意异常一样。设计规格:生成器和异常传播如果一个未捕获的异常——包括但不限于
StopIteration——由生成器函数引发或传递,则异常会以通常的方式传递给调用者,若试图重新激活生成器函数的话,则会引发
StopIteration 。
换句话说,未捕获的异常终结了生成器的使用寿命。示例(不合语言习惯,仅作举例):>>>
def f():… return 1/0>>> def g():… yield f() # the
zero division exception propagates… yield 42 # and we’ll
never get here>>> k = g()>>> k.next()Traceback
(most recent call last):File “<stdin>”, line 1, in ?File
“<stdin>”, line 2, in gFile “<stdin>”, line 2, in
fZeroDivisionError: integer division or modulo by
zero>>> k.next() # and the generator cannot be
resumedTraceback (most recent call last):File “<stdin>”,
line 1, in
?StopIteration>>>设计规格:Try/Exception/Finally前面提过,yield
语句不能用于 try-finally 结构的 try
子句中。这带来的结果是生成器要非常谨慎地分配关键的资源。但是在其它地方,yield
语句并无限制,例如 finally 子句、except 子句、或者 try-except
结构的 try 子句:>>> def f():… try:… yield 1…
try:… yield 2… 1/0… yield 3 # never get here… except
ZeroDivisionError:… yield 4… yield 5… raise… except:…
yield 6… yield 7 # the “raise” above stops this… except:…
yield 8… yield 9… try:… x = 12… finally:… yield 10…
yield 11print list[1, 2, 4, 5, 8, 9, 10, 11]

图片 16

示例

对于
J(θ)
的最小化问题,目前还没有闭式解法。因此,我们使用迭代的优化算法(例如梯度下降法,或
L-BFGS)。经过求导,我们得到梯度公式如下:

class Tree:

图片 17

def __init__(self, label, left=None, right=None): self.label = label self.left = left self.right = rightdef __repr__(self, level=0, indent=" "): s = level*indent + `self.label` if self.left: s = s + "\n" + self.left.__repr__(level+1, indent) if self.right: s = s + "\n" + self.right.__repr__(level+1, indent) return sdef __iter__: return inorder

有了上面的偏导数公式以后,我们就可以将它代入到梯度下降法等算法中,来最小化
J(θ) 。

def tree:n = lenif n == 0:return []i = n / 2return Tree(list[i],
tree, tree(list[i+1:]))

 

def inorder:if t:for x in inorder:yield xyield t.labelfor x in
inorder:yield x

softmax回归模型参数化的特点

t = tree(“ABCDEFGHIJKLMNOPQRSTUVWXYZ”)

Softmax
回归有一个不寻常的特点:它有一个“冗余”的参数集。为了便于解释,假设从参数向量θ[j] 中减去了向量
φ ,这时,每一个 θ[j] 都变成了 θ[j]- φ (j =
1,2,3….,k)。此时的假设函数如下所示:

for x in t:print x,print

图片 18

def inorder:stack = []while node:while node.left:stack.appendnode =
node.leftyield node.labelwhile not node.right:try:node =
stack.pop()except IndexError:returnyield node.labelnode = node.right

换句话说,从 θ[j]
中减去 φ 完全不影响假设函数的预测结果!这表明前面的 softmax
回归模型中存在冗余的参数。更正式一点来说,
Softmax
模型被过度参数化了。对于任意一个用于拟合数据的假设函数,可以求出多组参数值,这些参数得到的是完全相同的假设函数
h[θ]。进一步而言,如果参数(θ[1], θ[2],…,θ[k])是代价函数 J(θ)
的极小值点,那么(θ[1]-φ ,θ[2]-φ ,…,θ[k]-φ
) 同样也是它的极小值点,其中 φ 可以为任意向量(由于
J(θ) 仍然是一个凸函数,因此梯度下降时不会遇到局部最优解的问题。但是
Hessian
矩阵是奇异的/不可逆的,这会直接导致采用牛顿法优化就遇到数值计算的问题)。

for x in t:print x,printBoth output blocks display:

在实际应用中,为了使算法实现更简单清楚,往往保留所有参数
(θ[1], θ[2],…,θ[n]),而不任意地将某一参数设置为
0。但此时我们需要对代价函数做一个改动:加入权重衰减。权重衰减可以解决
softmax 回归的参数冗余所带来的数值问题。

A B C D E F G H I J K L M N O P Q R S T U V W X Y Z问答

 

为什么重用 def 而不用新的关键字?

权重衰减

请参阅下面的 BDFL 声明部分。

通过添加一个权值衰减项来惩罚过大的参数值,其代价函数如下所示:

为什么用新的关键字yield而非内置函数?

图片 19

Python 中通过关键字能更好地表达控制流,即 yield 是一个控制结构。而且为了
Jython
的高效实现,编译器需要在编译时就确定潜在的挂起点,新的关键字会使这一点变得简单。CPython
的实现也大量利用它来检测哪些函数是生成器函数(尽管一个新的关键字替代 def
就能解决 CPython
的问题,但人们问“为什么要新的关键字”问题时,并不想要新的关键字)。

有了这个权重衰减项以后 (
λ>0 ),代价函数就变成了严格的凸函数,这样就可以保证得到唯一的解。 此时的
Hessian矩阵变为可逆矩阵,并且因为是凸函数,梯度下降法和
LBFGS等算法可以保证收敛到全局最优解。为了使用优化算法,我们需要求得这个新函数
J(θ) 的导数,如下:

为什么不是其它不带新关键字的特殊语法?

图片 20

例如,为何不用下面用法而用 yield 3:return 3 and continuereturn and
continue 3return generating 3continue return 3return >> , 3from
generator return 3return >> 3return << 3

 

3<< 3

softmax回归与logistics回归的关系

  • 3我没有错过一个“眼色”吧?在数百条消息中,我算了每种替代方案有三条建议,然后总结出上面这些。不需要用新的关键字会很好,但使用
    yield 会更好——我个人认为,在一堆无意义的关键字或运算符序列中,yield
    更具表现力。尽管如此,如果这引起足够的兴趣,支持者应该发起一个提案,交给
    Guido 裁断。

当类别数 k=2 时,softmax 回归退化为 logistic 回归。这表明 softmax 回归是
logistic回归的一般形式。具体地说,当
k=2 时,softmax 回归的假设函数为:

为什么允许用return,而不强制用StopIteration?

图片 21

“StopIteration”的机制是底层细节,就像 Python 2.1
中的“IndexError”的机制一样:实现时需要做一些预先定义好的东西,而 Python
为高级用户开放了这些机制。尽管不强制要求每个人都在这个层级工作。
“return”在任何一种函数中都意味着“我已经完成”,这很容易解读和使用。注意,return
并不总是等同于 try-except 结构中的 raise
StopIteration(参见“设计规格:Return”部分)。

利用softmax回归参数冗余的特点,我们令
θ[1] = φ ,并且从两个参数向量中都减去向量 θ[1],得到:

那为什么不允许return一个表达式?

图片 22

也许有一天会允许。 在 Icon 中,return expr
意味着“我已经完成”和“但我还有最后一个有用的值可以返回,这就是它”。
在初始阶段,不强制使用return expr的情况下,使用 yield
仅仅传递值,这很简单明了。

图片 19

BDFL声明

有了这个权重衰减项以后
(
λ>0 ),代价函数就变成了严格的凸函数,这样就可以保证得到唯一的解。
此时的 Hessian矩阵变为可逆矩阵,并且因为是凸函数,梯度下降法和
LBFGS等算法可以保证收敛到全局最优解。为了使用优化算法,我们需要求得这个新函数
J(θ) 的导数,如下:

Issue

图片 20图片 25

引入另一个新的关键字(比如,gen 或 generator )来代替 def
,或以其它方式改变语法,以区分生成器函数和非生成器函数。

Con

文献: 

实际上,生成器是函数,但它们具有可恢复性。使它们建立起来的机制是一个相对较小的技术问题,引入新的关键字无助于强调生成器是如何启动的机制(生成器生命中至关重要却很小的部分)。

Pro

机器学习与Python学习

实际上,生成器函数实际上是工厂函数,它们就像施了魔法一样地生产生成器-迭代器。
在这方面,它们与非生成器函数完全不同,更像是构造函数而不是函数,因此重用
def 无疑是令人困惑的。藏在内部的 yield
语句不足以警示它们的语义是如此不同。

 近期热文

BDFL

从最大方差来看主成分分析PCA

def
留了下来。任何一方都没有任何争论是完全令人信服的,所以我咨询了我的语言设计师的直觉。它告诉我
PEP
中提出的语法是完全正确的——不是太热,也不是太冷。但是,就像希腊神话中的
Delphi(译注:特尔斐,希腊古都)
的甲骨文一样,它并没有告诉我原因,所以我没有对反对此 PEP
语法的论点进行反驳。
我能想出的最好的(除了已经同意做出的反驳)是“FUD”(译注:缩写自
fear、uncertainty 和 doubt)。
如果这从第一天开始就是语言的一部分,我非常怀疑这早已让安德鲁·库奇林(Andrew
Kuchling)的“Python Warts”页面成为可能。(译注:wart
是疣,一种难看的皮肤病。这是一个 wiki 页面,列举了对 Python
吹毛求疵的建议)。

常见文本相似度量方法总结

参考实现

文本特征工程之N-Gram

当前的实现,处于初步状态(没有文档,但经过充分测试,可靠),是Python 的
CVS 开发树的一部分。 使用它需要您从源代码中构建 Python。这是衍生自 Neil
Schemenauer的早期补丁。

文本分类特征提取之Word2Vec

干货|免费文本语料训练数据集

机器学习之决策树模型组合理解

机器学习之理解支持向量机SVM

机器学习之朴素贝叶斯分类器

机器学习之决策树分类和预测算法原理

… …

一次痛苦的经历抵得上千百次告诫~~

图片 26

更多干货内容请关注微信公众号“AI
深入浅出”

长按二维码关注

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图