1如既往

1如既往 查看完整档案

北京编辑青岛大学  |  软件工程 编辑北京company  |  web前端 编辑填写个人主网站
编辑

提问题和解决问题,都能让我们彼此进步

个人动态

1如既往 发布了文章 · 11月17日

什么是token

对于初学者来说,对Token和Session的使用难免会限于困境,开发过程中知道有这个东西,但却不知道为什么要用他?更不知道其原理,今天我就带大家一起分析分析这东西。

    一、我们先解释一下他的含义:

1、Token的引入:Token是在客户端频繁向服务端请求数据,服务端频繁的去数据库查询用户名和密码并进行对比,判断用户名和密码正确与否,并作出相应提示,在这样的背景下,Token便应运而生。

2、Token的定义:Token是服务端生成的一串字符串,以作客户端进行请求的一个令牌,当第一次登录后,服务器生成一个Token便将此Token返回给客户端,以后客户端只需带上这个Token前来请求数据即可,无需再次带上用户名和密码。

3、使用Token的目的:Token的目的是为了减轻服务器的压力,减少频繁的查询数据库,使服务器更加健壮。

    了解了Token的意义后,我们就更明确的知道为什么要用他了。

    二、如何使用Token?

    这是本文的重点,在这里我就介绍常用的两种方式。

1、用设备号/设备mac地址作为Token(推荐)

    客户端:客户端在登录的时候获取设备的设备号/mac地址,并将其作为参数传递到服务端。

    服务端:服务端接收到该参数后,便用一个变量来接收同时将其作为Token保存在数据库,并将该Token设置到session中,客户端每次请求的时候都要统一拦截,并将客户端传递的token和服务器端session中的token进行对比,如果相同则放行,不同则拒绝。

分析:此刻客户端和服务器端就统一了一个唯一的标识Token,而且保证了每一个设备拥有了一个唯一的会话。该方法的缺点是客户端需要带设备号/mac地址作为参数传递,而且服务器端还需要保存;优点是客户端不需重新登录,只要登录一次以后一直可以使用,至于超时的问题是有服务器这边来处理,如何处理?若服务器的Token超时后,服务器只需将客户端传递的Token向数据库中查询,同时并赋值给变量Token,如此,Token的超时又重新计时。

2、用session值作为Token

    客户端:客户端只需携带用户名和密码登陆即可。

    客户端:客户端接收到用户名和密码后并判断,如果正确了就将本地获取sessionID作为Token返回给客户端,客户端以后只需带上请求数据即可。

分析:这种方式使用的好处是方便,不用存储数据,但是缺点就是当session过期后,客户端必须重新登录才能进行访问数据。

    三、使用过程中出现的问题以及解决方案?

刚才我们轻松介绍了Token的两种使用方式,但是在使用过程中我们还出现各种问题,Token第一种方法中我们隐藏了一个在网络不好或者并发请求时会导致多次重复提交数据的问题。

    该问题的解决方案:将session和Token套用,如此便可解决,如何套用呢?请看这段解释:

     这就是解决重复提交的方案。

     总结:以上是个人对开发中使用Token和session的一点总结,如有叙述不当之处请指正,我将及时改正并感谢,我知道还有更多更好的使用方式,我在这里只是抛砖引玉,希望大家将您的使用方式提出来,我们一起讨论,学习,一起进步,同时也为像我一样对这方面理解薄弱的朋友提供点帮助,谢谢

作者:9264oo
链接:https://www.jianshu.com/p/248...
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

查看原文

赞 1 收藏 1 评论 0

1如既往 关注了专栏 · 9月4日

前端面试每日3+1

前端面试每日 3+1,以面试题来驱动学习,提倡每日学习与思考,每天进步一点!每天早上5点纯手工发布面试题(死磕自己,愉悦大家)

关注 222

1如既往 关注了用户 · 9月4日

浪子神剑 @haizlin

前端面试每日3+1发起者
前端剑解公众号作者
https://github.com/haizlin/fe...
羽毛球、前端架构师

关注 50

1如既往 发布了文章 · 9月3日

玩转 CSS 变量

如果当年的 CSS 预处理器变量对于初入前端的我来说是开启了新世界的大门,那么 CSS 变量对于我来说无疑就是晴天霹雳。其功能不但可以优雅的处理之前 js 不好处理或不适合的业务需求。更在创造力无穷的前端开发者手中大放异彩。

基础用法

在前端的领域中,标准的实现总是比社区的约定要慢的多,前端框架最喜欢的 $ 被 Sass 变量用掉了。而最常用的 @ 也被 Less 用掉了。官方为了让 CSS 变量也能够在 Sass 及 Less 中使用,无奈只能妥协的使用 --。

<style>
  /* 在 body 选择器中声明了两个变量  */ 
  body {
    --primary-color: red;
    /* 变量名大小写敏感,--primary-color 和 --PRIMARY-COLOR 是两个不同变量 */  
    --PRIMARY-COLOR: initial;  
  }

  /** 同一个 CSS 变量,可以在多个选择器内声明。优先级高的会替换优先级低的 */
  main {
    --primary-color: blue;
  }
    
  /** 使用 CSS 变量 */
  .text-primary {
    /* var() 函数用于读取变量。 */  
    color: var(--primary-color)
  }
<style>
    
<!-- 呈现红色字体,body 选择器的颜色  -->    
<div class="text-primary">red</div> 
    
<!-- 呈现蓝色字体,main 选择器定义的颜色  -->    
<main class="text-primary">blue</main>
    
<!-- 呈现紫色字体,当前内联样式表的定义  --> 
<div style='--primary-color: purple" class="text-primary">purple</main>    

这里我们可以看到针对同一个 CSS 变量,可以在多个选择器内声明。读取的时候,优先级最高的声明生效。这与 CSS 的"层叠"(cascade)规则是一致的。

由于这个原因,全局的变量通常放在根元素:root里面,确保任何选择器都可以读取它们。

:root {
  --primary-color: #06c;
}

同时, CSS 变量提供了 JavaScript 与 CSS 通信的方法。就是利用 js 操作 css 变量。我们可以使用:

<style>
  /* ...和上面 CSS 一致 */  
</style>    

<!-- 呈现黄色字体  -->    
<div class="text-primary">red</div> 
    
<!-- 呈现蓝色字体,main 选择器定义的颜色  -->    
<main id='primary' class="text-primary">blue</main>
    
<!-- 呈现紫色字体,当前内联样式表的定义  --> 
<div id="secondary" style="--primary-color: purple" class="text-primary">purple</main> 
<script>
// 设置变量
document.body.style.setProperty('--primary-color', 'yellow');
                                                           
// 设置变量,js DOM 元素 ID 就是全局变量,所以直接设置 main 即可
// 变为 红色
primary.style.setProperty('--primary-color', 'red');    

// 变为 黄色,因为当前样式被移除了,使用 body 上面样式
secondary.style.removeProperty('--primary-color');

// 通过动态计算获取变量值
getComputedStyle(document.body).getPropertyValue('--primary-color')

</script>

我们可以在业务项目中定义以及替换 CSS 变量,大家可以参考 mvp.css。该库大量使用了 CSS 变量并且让你去根据自己需求修改它。

:root {
  --border-radius: 5px;
  --box-shadow: 2px 2px 10px;
  --color: #118bee;
  --color-accent: #118bee0b;
  --color-bg: #fff;
  --color-bg-secondary: #e9e9e9;
  --color-secondary: #920de9;
  --color-secondary-accent: #920de90b;
  --color-shadow: #f4f4f4;
  --color-text: #000;
  --color-text-secondary: #999;
  --font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
  --hover-brightness: 1.2;
  --justify-important: center;
  --justify-normal: left;
  --line-height: 150%;
  --width-card: 285px;
  --width-card-medium: 460px;
  --width-card-wide: 800px;
  --width-content: 1080px;
}

我们可以看到基于 CSS 变量,可以更友好的和设计师的设计意图结合在一起。也易于修改,在业务项目中合理使用无疑可以事半功倍。

实现默认配置

如果让我来思考,我肯定无法想象出结合 CSS 预处理器 + CSS 变量便可以实现组件样式的默认配置。这里我先介绍两个关于该功能的前置知识点:

事实上,CSS 变量的 var() 函数还可以使用第二个参数,表示变量的默认值。如果该变量此前没有定义或者是无效值,就会使用这个默认值。

/* 没有设置过 --primary-color,颜色默认使用 #7F583F */
color: var(--primary-color, #7F583F);

虽然目前 CSS 变量不是新的属性,但终究不是所有的浏览器都支持 CSS 变量的,这里我们还是要考虑一下优雅降级。

/* 对于不支持 CSS 变量的浏览器,可以采用下面的写法。*/
a {
 /* 颜色默认值 */
  color: #7F583F;
  /* 不支持则不识别该条规则 */  
  color: var(--primary);
}

结合 CSS 处理器 + CSS 变量便可以实现组件样式的默认配置。这里参考了有赞的 Vant Weapp 的做法。有赞代码 theme.less 如下所示:

// 先导入所有 Less 变量
@import (reference) './var.less';

// 利用正则去替换 Less 变量 为 CSS 变量
.theme(@property, @imp) {
  @{property}: e(replace(@imp, '@(\[^() \]+)', '@{$1}', 'ig'));
  @{property}: e(replace(@imp, '@(\[^() \]+)', 'var(--$1, @{$1})', 'ig'));
}

函数效果如下所示:

@import '../common/style/theme.less';

.van-button {
  // ... 其他省略
  .theme(height, '@button-default-height');
    
  .theme(line-height, '@button-line-height');
    
  .theme(font-size, '@button-default-font-size');
}

// => less 编译之后生成

.van-button{
   // ... 其他省略
  height:44px;
  height:var(--button-default-height,44px);
    
  line-height:20px;
  line-height:var(--button-line-height,20px);
    
  font-size:16px;
  font-size:var(--button-default-font-size,16px);
}

我们可以看到每调用一次 Less 函数将会被编译成两个属性。第一个属性的设定对于不支持 CSS 变量的设备可以直接使用,如果当前设备支持 CSS 变量,则会使用 CSS 变量,但是由于当前 CSS 变量未定义,就会使用变量的默认值。虽然 '@button-default-height 虽然也是一个变量,但是该变量仅仅只是 less 变量,最终生成的代码中并没有 --button-default-height 这样的变量。此时我们就可以在使用样式的位置或者 :root 中添加变量 --button-default-height。

这种方式更适合组件开发,因为该方案不声明任何 css 变量,只是预留的 css 变量名称和默认属性。这样的话,无论开发者的选择器优先度有多低,代码都可以很容易的覆盖默认属性。因为我们仅仅使用 css 的默认值。

大家可能有时候会想,这样的话,我们不是有更多的代码了吗?其实未必,事实上我们可以直接直接在页面内部定义变量样式。其他组件直接通过 style 去使用页面内的变量。当然了,事实上书写的代码多少,重点在于想要控制默认样式的粒度大小。粒度越小,则需要在各个组件内部书写的变量越多,粒度大,我们也就不必考虑太多。

Space Toggle 逻辑切换

CSS 没有逻辑切换似乎是一种共识,但是我们可以利用选框(单选与多选)和 CSS 变量来实现判断逻辑。我们先来看看如何使用 CSS 变量。

<style>
  .red-box {
    --toggler: ;
    --red-if-toggler: var(--toggler) red;
    background: var(--red-if-toggler, green); /* will be red! */
  }
  .green-box {
    --toggler: initial;
    --red-if-toggler: var(--toggler) red;
    background: var(--red-if-toggler, green); /* will be green! */
  }

</style>

<!-- 宽度高度为 100px 的 红色盒子 -->
<div 
  style="height: 100px; width: 100px" 
  class="red-box"
></div> 
<!-- 宽度高度为 100px 的 绿色盒子 -->
<div 
  style="height: 100px; width: 100px" 
  class="green-box"
></div>

这里因为一个变量 --toggler 使用空格 或者 initial 而产生了不同的结果,基于这样的结果我们不难想象我们可以触发变量的修改而产生不同的结果。

他不是一个 bug,也不是一个 hack。他的原理完完全全的在 CSS Custom Properties 规范 中。

This value serializes as the empty string, but actually writing an empty value into a custom property, like --foo: ;, is a valid (empty) value, not the guaranteed-invalid value. If, for whatever reason, one wants to manually reset a variable to the guaranteed-invalid value, using the keyword initial will do this.

解释如下,事实上 -foo: ; 这个变量并不是一个无效值,它是一个空值。initial 才是 CSS 变量的无效值。其实这也可以理解,css 没有所谓的空字符串,空白也不代表着无效,只能使用特定值来表示该变量无效。这个时候,我们再回头来看原来的 CSS 代码。

.red-box {
  /* 当前为空值 */
  --toggler: ;

  /* 因为 var(--toggler) 得到了空,所以得到结果 为 --red-if-toggler:  red */
  --red-if-toggler: var(--toggler) red;
  /** 变量是 red, 不会使用 green */
  background: var(--red-if-toggler, green); /* will be red! */
}
.green-box {
  /** 当前为无效值 */
  --toggler: initial;
  /** 仍旧无效数据,因为 var 只会在参数不是 initial 时候进行替换 */
  --red-if-toggler: var(--toggler) red;
  /** 最终无效值没用,得到绿色 */
  background: var(--red-if-toggler, green); /* will be green! */

  /* 根据当前的功能,我们甚至可以做到 and 和 or 的逻辑 
   * --tog1 --tog2 --tog3 同时为 空值时是 红色
   */
  --red-if-togglersalltrue: var(--tog1) var(--tog2) var(--tog3) red;

 /*
  * --tog1 --tog2 --tog3 任意为 空值时是 红色
  */ 
  --red-if-anytogglertrue: var(--tog1, var(--tog2, var(--tog3))) red;
}

新式媒体查询

当我们需要开发响应式网站的时候,我们必须要使用媒体查询 @media。先看一下用传统的方式编写这个基本的响应式 CSS:

.breakpoints-demo > * {
  width: 100%;
  background: red;
}

@media (min-width: 37.5em) and (max-width: 56.249em) {
  .breakpoints-demo > * {
    width: 49%;
  }
}

@media (min-width: 56.25em) and (max-width: 74.99em) {
  .breakpoints-demo > * {
    width: 32%;
  }
}

@media (min-width: 56.25em) {
  .breakpoints-demo > * {
    background: green;
  }
}

@media (min-width: 75em) {
  .breakpoints-demo > * {
    width: 24%;
  }
}

同样,我们可以利用 css 变量来优化代码结构,我们可以写出这样的代码:

/** 移动优先的样式规则 */
.breakpoints-demo > * {
  /** 小于 37.5em, 宽度 100%  */
  --xs-width: var(--media-xs) 100%;

  /** 小于 56.249em, 宽度 49%  */
  --sm-width: var(--media-sm) 49%;

  --md-width: var(--media-md) 32%;
  --lg-width: var(--media-gte-lg) 24%;
  

  width: var(--xs-width, var(--sm-width, var(--md-width, var(--lg-width))));

  --sm-and-down-bg: var(--media-lte-sm) red;
  --md-and-up-bg: var(--media-gte-md) green;
  background: var(--sm-and-down-bg, var(--md-and-up-bg));
}

可以看出,第二种 CSS 代码非常清晰,数据和逻辑保持在一个 CSS 规则中,而不是被 @media 切割到多个区块中。这样,不但更容易编写,也更加容易开发者读。详情可以参考 css-media-vars。该代码库仅仅只有 3kb 大小,但是却是把整个编写代码的风格修改的完全不同。原理如下所示:

/**
 * css-media-vars
 * BSD 2-Clause License
 * Copyright (c) James0x57, PropJockey, 2020
 */

html {
  --media-print: initial;
  --media-screen: initial;
  --media-speech: initial;
  --media-xs: initial;
  --media-sm: initial;
  --media-md: initial;
  --media-lg: initial;
  --media-xl: initial;
  /* ... */
  --media-pointer-fine: initial;
  --media-pointer-none: initial;
}

/* 把当前变量变为空值 */
@media print {
  html { --media-print: ; }
}

@media screen {
  html { --media-screen: ; }
}

@media speech {
  html { --media-speech: ; }
}

/* 把当前变量变为空值 */
@media (max-width: 37.499em) {
  html {
    --media-xs: ;
    --media-lte-sm: ;
    --media-lte-md: ;
    --media-lte-lg: ;
  }
}

其他

CSS 键盘记录器 暴露了 CSS 安全性问题之后,CSS 变量又一次让我看到了玩技术是怎么样的。CSS Space Toggle 技术不但可以应用于上面的功能,甚至还可以编写 UI 库 augmented-ui 以及 扫雷 游戏。这简直让我眼界大开。在我有限的开发生涯中,很难找到类似于 css 这种设计意图和使用方式差异如此之大的技术。

CSS 是很有趣的,而 CSS 的有趣之处就在于最终呈现出来的技能强弱与你自身的思维方式,创造力是密切相关的。上文只是介绍了 CSS 变量的一些玩法,也许有更多有意思的玩法,不过这就需要大家的创造力了。

参考资料

augmented-ui

css-media-vars

css-sweeper

查看原文

赞 0 收藏 0 评论 0

1如既往 发布了文章 · 8月28日

递归算法

目录:
1.简单递归定义
2.递归与循环的区别与联系
3.递归的经典应用

1.简单递归定义

什么叫递归?(先定义一个比较简单的说法,为了理解,不一定对)

递归:无限调用自身这个函数,每次调用总会改动一个关键变量,直到这个关键变量达到边界的时候,不再调用。

比如说我要你先求一个N!的结果

你说我会用循环啊(没错,但是现在是学递归)

int factorial(int x,int ans)
{
    if(x==1)
       return  ans;
    factorial(x-1,ans*x);
}

怎么样,对于C基础如果掌握的还行的话,这段代码应该很好理解。递归,顾名思义就是“递”和“归”。也就是说,写每一个递归函数的时候,都应该在写之前考虑清楚,哪里体现了“递”,哪里体现了“归”。

但是常常递归函数会比较复杂, “递”和“归”看起来并不是那么明显,这就需要我们进一步来理解递归算法的思想。

比如说我现在要你用辗转相除法求出两个数的最大公约数,递归函数如下:

int gcd(int a,int b)
{
    return a%b==0?b:gcd(b,a%b);
}

这是一段很常用的代码,我们知道,在学习过程中不求甚解是最不应该的。因此现在来仔细看一看。这里的“递”和“归”放在同一行。首先进行判断a==b?(我们可以想象成“归”的内容,如果这个条件符合的话)。当然,如果不符合这个判断,那就继续“递”,也就是继续进行gcd(b,a%b);

看到这里,你就会发现,递归不就是循环的另一种方式么?

说对了一半,不过递归是一种思想,现在还暂时不能说透,需要大家先比较一下循环和递归的相同点和不同点(饭一口一口吃,别着急)

2.递归与循环的区别于联系

相同点:
(1)都是通过控制一个变量的边界或者多个),来改变多个变量为了得到所需要的值,而反复而执行的;
(2)都是按照预先设计好的推断实现某一个值求取;(请注意,在这里循环要更注重过程,而递归偏结果一点)

不同点:
(1)递归通常是逆向思维居多,“递”和“归”不一定容易发现(比较难以理解);而循环从开始条件到结束条件,包括中间循环变量,都需要表达出来(比较简洁明了)。

简单的来说就是:用循环能实现的,递归一般可以实现,但是能用递归实现的,循环不一定能。因为有些题目①只注重循环的结束条件和循环过程,而往往这个结束条件不易表达(也就是说用循环并不好写);②只注重循环的次数而不注重循环的开始条件和结束条件(这个循环更加无从下手了)。

3.递归的经典应用

来看看“汉诺塔问题”

如图,汉诺塔问题是指有三根杆子A,B,C。C杆上有若干碟子,把所有碟子从A杆上移到C杆上,每次只能移动一个碟子,大的碟子不能叠在小的碟子上面。求最少要移动多少次?

这里写图片描述

这是一个循环只注重循环次数的常见例子,我们知道,用循环有点无从下手(就目前作者水平来看),但是递归就很好写了。

汉诺塔,什么鬼,我不会啊?

别急,慢慢来。

我们首先需要一点思维:解决n块盘子从A移动到C,那么我只需要先把n-1块盘子从A移到B,然后把最下面的第n块盘子从A移到C,最后把n-1块盘子从B移到C(这就完成了)。

等等,那么如何把n-1块盘子从A移到B?

很好,这说明你已经开始递归入门了。

同样这样去想:解决n-1块盘子从A移动到B,那么我只需要先把n-2块盘子从A移动到C,然后把倒数第二块盘子从A移到B,最后把n-2块盘子从C移到B(这就完成了)。

这就是递归的“递”!

那么“归”呢?n==1的时候?

Bingo

int i;    //记录步数  
//i表示进行到的步数,将编号为n的盘子由from柱移动到to柱(目标柱)  
void move(int n,char from,char to){  
    printf("第%d步:将%d号盘子%c---->%c\n",i++,n,from,to);  
}  
  
//汉诺塔递归函数  
//n表示要将多少个"圆盘"从起始柱子移动至目标柱子  
//start_pos表示起始柱子,tran_pos表示过渡柱子,end_pos表示目标柱子  

void Hanio(int n,char start_pos,char tran_pos,char end_pos)
{  
    if(n==1)      //很明显,当n==1的时候,我们只需要直接将圆盘从起始柱子移至目标柱子即可.  
        move(n,start_pos,end_pos);   
    else
    {  
        Hanio(n-1,start_pos,end_pos,tran_pos);   //递归处理,一开始的时候,先将n-1个盘子移至过渡柱上  
        move(n,start_pos,end_pos);                //然后再将底下的大盘子直接移至目标柱子即可  
        Hanio(n-1,tran_pos,start_pos,end_pos);    //然后重复以上步骤,递归处理放在过渡柱上的n-1个盘子  
                                                  //此时借助原来的起始柱作为过渡柱(因为起始柱已经空了)  
    }  
}  

实际上这里面已经使用到了一点点栈的思想(即最上面的最先考虑变化),但其实递归有的时候就是真的可以理解为栈!

到了这一步,相信大家应该已经有所明白。循环其实就是一个控制变量从开始条件走到结束条件的过程(在循环的过程顺带把其他变量也改变一下),因此需要控制变量,开始条件,结束条件(缺一不可)。但是递归只要告诉你“归”是什么,如何去“递”,不管过程如何,只要计算结果即可。

(2)递归可以是多个“递”,也可以是多个“归”;而循环由始至终都只由一个变量控制(就算有几个变量同时控制)也只有一个出口,每次循环也只是一个“递”。

再看一个例子

用二分思想建立二叉树(通常的是递归实现),比如说线段树

//root   节点序号  
//left   节点维护的左边界  
//right  节点维护的右边界
void build(int root,int left,int right)
{
    if(left==right)
      return ;
    int mid=(left+right)/2;
    build(root*2,left,mid);
    build(root*2+1,mid+1,right);
}

如果你是新手看不太懂也没关系,现在最主要的是明白:在这个程序里面只有一个“归”,但是有两个“递”。

那么如果学过一点但是对这一块还不明白的怎么办呢?别急,听我来解释:

实际上,这两个“递”是按照先后分别进行的,等到第一个“递”执行完(也就是到了“归”的条件之后),才开始执行第二个“递”。也就是说,通常在建树的时候,都不是一层一层同时建的,而是先建一棵子树,等到这棵子树全部建完之后,才开始建立另外一棵子树。

那就会问了,一棵子树建完了之后root值不会变么,root值变了之后还怎么建另外一棵子树呢?

root值不会变!大家请注意,这里root*2是写在递归函数里面的,实际上并没有赋值?为什么要这样写?因为如果不这样写,你直接写在外边的话,一棵子节点到达叶子节点之后,需要一层一层往上回溯(在这里提到了回溯的思想),而回溯就会无故产生很多不必要的时间复杂度,降低了递归效率(实际上递归的时间效率本来就有一点偏低)。

所以到目前为止,我只是介绍一些很常见的简单的递归,但是在接下来,我就需要说一些比较深层一点的知识了。

首先要理解一下什么是回溯(写的不好,大佬勿喷)

回溯:在递归的过程中由于改变的量需要倒退到某一个位置而执行的步骤。

先来看一个简单的素数环问题:

给出1到n的n个连续的正整数(这里n暂时等于6),并且把这n个数填写在如下图的n个圆圈里面(当然是不重复不遗漏了)。要求是每一个位置上面的数跟他相邻的数之和都为一个素数,打印并输出最后满足条件的情况。

这里写图片描述

首先明白,开始条件是1,把1填写在第一个位置,然后在剩下的n-1个数字里找到一个满足与1的和是一个素数的数(当然如果有多个,先靠前的先考虑)。接下来再继续从剩下n-2个数字里找到一个与这个数的和又是一个素数的数(当然如果有多个,同上。)。。。最后的一个数只要满足与最开始的数1之和是一个素数的话,这个情况就满足了(就可以打印输出这样一个例子了)

但事情并没有想象的那么简单。。。(告诉我如果在中途寻找的过程中从剩下的数里找不到与当前数的的和是一个素数的情况出现怎么办?在线等)

这就表明这样一条路终归是一条思路,你要往回走了!这就很符合我们给回溯的定义了,此时这个改变的量需要倒退的前面一步从另外一个方向寻找了。(还是举栗子吧)

比如说:

1->2->3->4 突然发现5和6都不满足要求了

那么就倒退,准备找另外满足要求的数

1->2->3 又发现除了4以外3跟5或者3跟6也不满足要求

那就继续倒退,继续准备找另外满足要求的数

1->2->5->6 接下来发现6跟3或者6跟4不满足要求

…(还想继续下去?你是要玩死我?别这样,我也累啊,看一两个就行了,啊!)
最后发现满足条件的一个是

1->4->3->2->5->6

大家应该已经懂了,上面的倒退,实际上就是回溯。(暂时这样简单的理解吧,错了也不能怪你们)

实际上,递归+回溯就已经是dfs(深度优先搜索)的内容范畴了。

void dfs(int x)
{
    if(x==n+1&&prime(a[x-1]+a[1]))    //如果满足条件就可以打印输出数据了,这里就是“归”
    {
        for(int i=1;i<n;i++)
          cout<<a[i]<<" ";
        cout<<a[n]<<endl;
    }
    else                        //否则就继续“递”
    {
        for(int i=2;i<=n;i++)
        {
            if(!vis[i]&&prime(i+a[x-1]))
            {
                vis[i]=1;            //vis[]是一个标记数组,表示当前的数字已经被使用过了
                a[x]=i;
                dfs(x+1);   //“递”的入口
                vis[i]=0;          //请注意,回溯点在这里
            }
        }
    }
}

大家可能前面都看懂了,比如说“递”和“归”,vis[]标记数组什么的。但是最后一个vis[i]=0是啥意思?难道不多余么?

不多余!前面我已经拿建树给大家讲过递归的“工作原理”,它是先无限递归,然后到达某个条件之后,回溯到上面一个位置,继续向其他方向递归。而这个vis[i]=0就是清楚当前数字的标记,表示从当前节点开始,之后递归过的内容统统清空(也就是回溯)。然后根据循环,进行下面一个方向的继续递归。

这也是dfs的经典思想所在!

因此,讲到这里,不说说dfs似乎也是吊大家胃口了。所以接下来,就聊一聊dfs中的递归。

比如说hdu上面的1312 http://acm.hdu.edu.cn/showproblem.php?pid=1312

我简单说一下意思,如下图,判断一个图内包括@符号在内的所有‘.’和‘@’的个数。有个限制条件,如果‘.’被‘#’封死,则‘.’不可访问。

6 9 (分别表示行和列)

....#.
.....#
......
......
......
......
......
#@...#
.#..#.

45

比如说这一个数据就有三个‘.’被边界和‘#’困死而不可访问,因此只有45个点满足要求。

本题的思路就是从’@'点开始,bfs搜索每一个点,分成上下左右四个方向分别递归搜索。

int cnt,a[4]={-1,0,1,0},b[4]={0,1,0,-1},n,m,vis[22][22];
char s[22][22];      
void dfs(int x,int y)
{
    for(int i=0;i<4;i++)      //四个方向循环搜索
    {
        int p=x+a[i];
        int q=y+b[i];
        if(p>=0&&p<m&&q>=0&&q<n&&!vis[p][q]&&s[p][q]=='.')     //判断递归条件,包括在数组边界之内,该点未被标记 
        {
            vis[p][q]=1;    //标记该点 
              cnt++;      //计数变量加一 
            dfs(p,q);     //递归搜索 
        }
    }
}

请注意:本题中因为可以提前判断下一个要搜索的点是否为‘#’而免于回溯,降低时间复杂度。

并且大家可以看出,上面的代码实际上是稍微复杂一点的递归算法(把从‘@’出发的每一个方向看成一条线段,而这条线段的另外一个终点就是边界或者’#’),因此这就是可以看成循环了四次的递归算法,而每一次递归调用的过程,每一方向又看成从当前点出发的一条线段。如此反复。(中间的cnt用来计数)

请注意,cnt就是就是递归的次数(因为没有回溯,如果有回溯,计数的话不一定等于递归的次数)

到此,基本知识点已经全部讲完,下面给出一点个人关于写递归算法的建议吧:

(1)把递归当成复杂的循环来写,如果不明白过程,多模拟几遍数据;

(2)把递归逆向写的时候当做一个栈来实现(即符合后进先出的思想);

(3)当递归和回溯结合在一起的时候需要明白递归次数和统计次数之间的练习和区别;

(4)但递归有多个“递”和“归”的时候,选择一个重点的“递”和“归”作为匹配,即时题目即时分析,注意随机应变即可。

查看原文

赞 0 收藏 0 评论 0

1如既往 发布了文章 · 8月4日

CSS预编译器

当前环境中,直接手写 css 的越来越少,主要是通过各种预编译器来做,主要原因会通过如下几点进行描述。

先看一下 css 的一些基础点,这里只说关键的,完整的可以自己查看 css2.1规范

css 基础知识点

css 盒模型

css盒模型css盒模型计算结果

页面都是通过盒模型来拼凑的,首先需要知道都有哪些块模型(也就是 display 的值):

  1. inline: 行内元素
  2. inline-block: 行内块元素
  3. block: 块元素
  4. none: 不显示元素,不占位置
  5. table 及其相关: 主要用于设置以表格的形式展示内容
  6. flex 及其相关: 弹性布局
  7. grid 及其相关: 网格布局

一般最常用的 div 就 display: block 的通用块。

css 变量

定义语法 --* ,使用语法 var(--*)

* 表示变量名称,可以是 数字字母下划线短横线 [0-9a-zA-Z_-] 进行组合。

无论变量定义还是使用都只能在声明块 {} 内:

/* 全局变量 */
:root {
  --theme: #f00;
}
/* 局部变量 */
.side {
  --color: #0f0;
}

.content {
  color: var(--theme);
}
.footer {
  color: var(--color);
}
<div class="content">
  <div class="body"></div>
  <div class="side"></div>
  <div class="footer"></div>
</div>

javascript 可以获取和设置 css 中的变量:

// 获取 css 变量值
getComputedStyle(document.documentElement).getPropertyValue('--color')

// 设置 css 变量值
document.documentElement.style.setProperty('--color', '#' + Math.random().toString(16).slice(2,8))

例子:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>css变量</title>
  <style>
    :root {
      --color: #f00;
    }
    html,
    body {
      margin: 0;
      height: 100%;
    }
    body {
      display: flex;
      flex-direction: column;
    }
    .header, .footer {
      height: 30px;
      color: var(--color);
    }
    .body {
      --color: #00f;
      flex: auto;
      color: var(--color);
    }
  </style>
</head>
<body>
  <div class="header">headerheaderheader</div>
  <div class="body" id="body">
    <button id="changeTheme">修改主题色</button>
    <button id="changeBodyTheme">修改body主题色</button>
    <div>bodybodybody</div>
  </div>
  <div class="footer">footerfooterfooter</div>
  <script>
    document.querySelector("#changeTheme").onclick = function(){
      console.log(getComputedStyle(document.documentElement).getPropertyValue('--color').trim())
      document.documentElement.style.setProperty('--color', '#' + Math.random().toString(16).slice(2,8))
    }
    document.querySelector("#changeBodyTheme").onclick = function(){
      console.log(getComputedStyle(document.querySelector("#body")).getPropertyValue('--color').trim())
      document.querySelector("#body").style.setProperty('--color', '#' + Math.random().toString(16).slice(2,8))
    }
  </script>
</body>
</html>

Less

参考链接:Less文档

Less 变量

@width: 10px;
@height: @width + 10px;

#header {
  width: @width;
  height: @height;
}

编译后:

#header {
  width: 10px;
  height: 20px;
}

Less 变量还可以用与以下几种情况:

  1. 选择器

    @my-selector: banner;
    
    .@{my-selector} {
      font-weight: bold;
      line-height: 40px;
      margin: 0 auto;
    }

    编译后:

    .banner {
      font-weight: bold;
      line-height: 40px;
      margin: 0 auto;
    }
  2. URLs

    @images: "../img";
    
    body {
      color: #444;
      background: url("@{images}/white-sand.png");
    }
  3. Import Statements

    @themes: "../../src/themes";
    
    @import "@{themes}/tidal-wave.less";
  4. Properties

    @property: color;
    
    .widget {
      @{property}: #0ee;
      background-@{property}: #999;
    }

    编译后:

    .widget {
      color: #0ee;
      background-color: #999;
    }
  5. Variable Variables

    @primary:  green;
    @secondary: blue;
    
    .section {
      @color: primary;
    
      .element {
        color: @@color;
      }
    }

    编译后:

    .section .element {
      color: green;
    }

Less 混合

Less 混合 简单使用

.bordered {
  border-top: dotted 1px black;
  border-bottom: solid 2px black;
}

#menu a {
  color: #111;
  .bordered();
}

.post a {
  color: red;
  .bordered();
}

编译后:

#menu a {
  color: #111;
  border-top: dotted 1px black;
  border-bottom: solid 2px black;
}

.post a {
  color: red;
  border-top: dotted 1px black;
  border-bottom: solid 2px black;
}

Less 混合 带参数混合

.border-radius(@radius) {
  -webkit-border-radius: @radius;
     -moz-border-radius: @radius;
          border-radius: @radius;
}

.button {
  .border-radius(6px);
}

Less 混合 参数默认值混合

.border-radius(@radius: 5px) {
  -webkit-border-radius: @radius;
     -moz-border-radius: @radius;
          border-radius: @radius;
}

.button {
  .border-radius();
}
#header {
  .border-radius(4px);
}

Less 嵌套

#header {
  color: black;
  .navigation {
    font-size: 12px;
  }
  .logo {
    width: 300px;
  }
}

编译后:

#header {
  color: black;
}
#header .navigation {
  font-size: 12px;
}
#header .logo {
  width: 300px;
}

注意:请勿过多的嵌套层。

在嵌套内可以使用 & 来指向父选择器

a {
  color: blue;
  &:hover {
    color: green;
  }
}

编译后:

a {
  color: blue;
}

a:hover {
  color: green;
}

Less 导入

@import "library.less";

Sass

参考链接:Sass文档

Sass 变量

$nav-color: #F90;
nav {
  $width: 100px;
  width: $width;
  color: $nav-color;
}

编译后:

nav {
  width: 100px;
  color: #F90;
}

默认变量值 !default ,如果这个变量被声明赋值了,那就用它声明的值,否则就用这个默认值。

$fancybox-width: 400px !default;

.fancybox {
  width: $fancybox-width;
}

在上例中,如果成员在导入你的 sass 局部文件之前声明了一个 $fancybox-width 变量,那么你的局部文件中对 $fancybox-width 赋值 400px 的操作就无效。如果成员没有做这样的声明,则 $fancybox-width 将默认为 400px

Element-ui 的 var.scss 文件中就使用的默认变量值。

Sass 变量值可以借助插值 #{} 可以应用到如下模式:

  1. 选择器

    $name: foo;
    $attr: border;
    p.#{$name} {
      #{$attr}-color: blue;
    }

    编译后:

    p.foo {
      border-color: blue;
    }
  2. 属性值,不过通常直接使用变量更方便

    p {
      $font-size: 12px;
      $line-height: 30px;
      font: #{$font-size}/#{$line-height};
    }

    编译后:

    p {
      font: 12px/30px;
    }

Sass 混合

Sass 混合 简单使用

混合器使用 @mixin 标识符定义。

@mixin rounded-corners {
  -moz-border-radius: 5px;
  -webkit-border-radius: 5px;
  border-radius: 5px;
}

通过 @include 使用混合器。

.notice {
  background-color: green;
  border: 2px solid #00aa00;
  @include rounded-corners;
}

//sass最终生成:
.notice {
  background-color: green;
  border: 2px solid #00aa00;
  -moz-border-radius: 5px;
  -webkit-border-radius: 5px;
  border-radius: 5px;
}

Sass 混合器中的CSS规则

@mixin no-bullets {
  list-style: none;
  li {
    list-style-image: none;
    list-style-type: none;
    margin-left: 0px;
  }
}

ul.plain {
  color: #444;
  @include no-bullets;
}

编译后:

ul.plain {
  color: #444;
  list-style: none;
}
ul.plain li {
  list-style-image: none;
  list-style-type: none;
  margin-left: 0px;
}

Sass 混合器传参

@mixin link-colors($normal, $hover, $visited) {
  color: $normal;
  &:hover { color: $hover; }
  &:visited { color: $visited; }
}

a {
  @include link-colors(blue, red, green);
}

编译后:

a { color: blue; }
a:hover { color: red; }
a:visited { color: green; }

传参的时候还可以通过 key:value ,这种方式不必在意参数顺序:

a {
  @include link-colors(
    $normal: blue,
    $visited: green,
    $hover: red
  );
}

Sass 默认参数值

@mixin link-colors(
    $normal,
    $hover: $normal,
    $visited: $normal
  )
{
  color: $normal;
  &:hover { color: $hover; }
  &:visited { color: $visited; }
}

a {
  @include link-colors(blue); // 相当于 @include link-colors(blue, blue, blue)
}

Sass 嵌套

#content {
  article {
    h1 { color: #333 }
    p { margin-bottom: 1.4em }
  }
  aside { background-color: #EEE }
}

编译后:

#content article h1 { color: #333 }
#content article p { margin-bottom: 1.4em }
#content aside { background-color: #EEE }

注意:请勿过多的嵌套层。

父选择器的标识符 &

article a {
  color: blue;
  &:hover { color: red }
}

编译后:

article a { color: blue }
article a:hover { color: red }

Sass 导入

@import "library.scss";

Sass 部分文件

定义:当通过 @importsass 样式分散到多个文件时,你通常只想生成少数几个 css 文件。那些专门为 @import 命令而编写的 sass 文件,并不需要生成对应的独立 css 文件,这样的 sass 文件称为局部文件。

sass 局部文件的文件名以下划线开头。

使用可以参照 https://github.com/ElemeFE/element/blob/dev/packages/theme-chalk/src/mixins/_button.scss

Sass 嵌套导入

嵌套导入生成对应的 css 文件时,局部文件会被直接插入到 css 规则内导入它的地方。

例如有一个名为 _blue-theme.scss 的局部文件,内容如下:

aside {
  background: blue;
  color: white;
}
.blue-theme {@import "blue-theme"}

生成的结果跟你直接在 .blue-theme 选择器内写 _blue-theme.scss 文件的内容完全一样。

.blue-theme {
  aside {
    background: blue;
    color: #fff;
  }
}

Sass 原生CSS的导入

通常在 sass 中使用 @import 时,sass 会尝试找到对应的 sass 文件并导入进来,但在下列三种情况下会生成原生的 CSS@import,尽管这会造成浏览器解析 css 时的额外下载:

  1. 被导入文件的名字以 .css 结尾;
  2. 被导入文件的名字是一个URL地址(比如 http://www.sass.hk/css/css.css ),由此可用谷歌字体API提供的相应服务;
  3. 被导入文件的名字是 CSS 的url()值。

这就是说,你不能用 sass@import 直接导入一个原始的 css 文件,因为 sass 会认为你想用 css 原生的 @import。但是,因为 sass 的语法完全兼容 css,所以你可以把原始的 css 文件改名为 .scss 后缀,即可直接导入了。

Sass 静默注释

语法:

// 注释内容直到行末

静默注释的内容不会出现在生成的 css 文件中。

Sass 选择器继承

//通过选择器继承继承样式
.error {
  border: 1px red;
  background-color: #fdd;
}
.seriousError {
  @extend .error;
  border-width: 3px;
}

.seriousError 将会继承样式表中任何位置处为 .error 定义的所有样式。以 class="seriousError" 修饰的 html 元素最终的展示效果就好像是 class="seriousError error"

.seriousError 不仅会继承 .error 自身的所有样式,任何跟 .error 有关的组合选择器样式也会被 .seriousError 以组合选择器的形式继承,如下代码:

//.seriousError从.error继承样式
.error a{  //应用到.seriousError a
  color: red;
  font-weight: 100;
}
h1.error { //应用到hl.seriousError
  font-size: 1.2rem;
}

关于 @extend 有两个要点:

  1. 跟混合器相比,继承生成的 css 代码相对更少。因为继承仅仅是重复选择器,而不会重复属性,所以使用继承往往比混合器生成的 css 体积更小。如果你非常关心你站点的速度,请牢记这一点。
  2. 继承遵从 css 层叠的规则。当两个不同的 css 规则应用到同一个 html 元素上时,并且这两个不同的 css 规则对同一属性的修饰存在不同的值,css 层叠规则会决定应用哪个样式。相当直观:通常权重更高的选择器胜出,如果权重相同,定义在后边的规则胜出。

Sass 函数指令

$grid-width: 40px;
$gutter-width: 10px;

@function grid-width($n) {
  @return $n * $grid-width + ($n - 1) * $gutter-width;
}

#sidebar { width: grid-width(5); }

编译后:

#sidebar {
  width: 240px;
}

@function 用于定义函数。
@return 用于设置函数的返回值。

其他指令说明

@at-root

@at-root 指令导致一个或多个规则被限定输出在文档的根层级上,而不是被嵌套在其父选择器下。它可以被用于单一或内联选择器:

.parent {
  ...
  @at-root .child { ... }
}

编译后:

.parent { ... }
.child { ... }

@if

@if 指令需要一个 SassScript 表达和嵌套在它下面要使用的样式,如果表达式返回值不为 false 或者 null ,那么后面花括号中的内容就会返回:

p {
  @if 1 + 1 == 2 { border: 1px solid; }
  @if 5 < 3      { border: 2px dotted; }
  @if null       { border: 3px double; }
}

编译后:

p {
  border: 1px solid;
}

@if 语句后面可以跟多个 @else if 语句和一个 @else 语句。

$type: monster;
p {
  @if $type == ocean {
    color: blue;
  } @else if $type == matador {
    color: red;
  } @else if $type == monster {
    color: green;
  } @else {
    color: black;
  }
}

编译后:

p {
  color: green;
}

@for

@for 指令重复输出一组样式。对于每次重复,计数器变量用于调整输出结果。该指令有两种形式:@for $var from <start> through <end>@for $var from <start> to <end>。注意关键字 throughto 的区别。$var 可以是任何变量名,比如 $i ; <start><end> 是应该返回整数的 SassScript 表达式。当 <start><end> 大的时候,计数器将递减,而不是增量。

@for 语句将设置 $var 为指定的范围内每个连续的数值,并且每一次输出的嵌套样式中使用 $var 的值。对于 from ... through 的形式,范围包括 <start><end> 的值,但 from ... to 的形式从 <start> 开始运行,但不包括 <end> 的值。

@for $i from 1 through 3 {
  .item-#{$i} { width: 2em * $i; }
}

@for $i from 4 to 6 {
  .item-#{$i} { width: 2em * $i; }
}

编译后:

.item-1 {
  width: 2em;
}
.item-2 {
  width: 4em;
}
.item-3 {
  width: 6em;
}

.item-4 {
  width: 8em;
}
.item-5 {
  width: 10em;
}

快速实现栅栏布局:https://github.com/jdf2e/nutui/blob/master/src/packages/col/col.scss

@each

@each 指令通常格式是 @each $var in <list or map>$var 可以是任何变量名,像 $length 或者 $name,和<list or map>是一个返回列表(list)或 map 的 SassScript 表达式。

@each 规则将 $var 设置为列表(list)或 map 中的每个项目,输出样式中包含使用 $var 的值。

@each $animal in puma, sea-slug, egret, salamander {
  .#{$animal}-icon {
    background-image: url('/images/#{$animal}.png');
  }
}

编译后:

.puma-icon {
  background-image: url('/images/puma.png');
}
.sea-slug-icon {
  background-image: url('/images/sea-slug.png');
}
.egret-icon {
  background-image: url('/images/egret.png');
}
.salamander-icon {
  background-image: url('/images/salamander.png');
}

多重赋值:

  1. list

    @each $animal, $color, $cursor in (puma, black, default),
                                      (sea-slug, blue, pointer),
                                      (egret, white, move) {
      .#{$animal}-icon {
        background-image: url('/images/#{$animal}.png');
        border: 2px solid $color;
        cursor: $cursor;
      }
    }

    编译后:

    .puma-icon {
      background-image: url('/images/puma.png');
      border: 2px solid black;
      cursor: default;
    }
    .sea-slug-icon {
      background-image: url('/images/sea-slug.png');
      border: 2px solid blue;
      cursor: pointer;
    }
    .egret-icon {
      background-image: url('/images/egret.png');
      border: 2px solid white;
      cursor: move;
    }
  2. maps

    @each $header, $size in (h1: 2em, h2: 1.5em, h3: 1.2em) {
      #{$header} {
        font-size: $size;
      }
    }

    编译后:

    h1 {
      font-size: 2em;
    }
    h2 {
      font-size: 1.5em;
    }
    h3 {
      font-size: 1.2em;
    }

@while

@while 指令重复输出嵌套样式,直到SassScript表达式返回结果为 false 。这可用于实现比 @for 语句更复杂的循环。

$i: 6;
@while $i > 0 {
  .item-#{$i} {
    width: 2em * $i;
  }
  $i: $i - 2;
}

编译后:

.item-6 {
  width: 12em;
}

.item-4 {
  width: 8em;
}

.item-2 {
  width: 4em;
}

Sass 举例

// 清楚浮动
@mixin utils-clearfix {
  $selector: &;

  @at-root {
    #{$selector}::before,
    #{$selector}::after {
      display: table;
      content: "";
    }
    #{$selector}::after {
      clear: both
    }
  }
}
// 垂直居中
@mixin utils-vertical-center {
  $selector: &;

  @at-root {
    #{$selector}::after {
      display: inline-block;
      content: "";
      height: 100%;
      vertical-align: middle
    }
  }
}
// 超过...
@mixin utils-ellipsis {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

Stylus

参考链接:Stylus文档

stylus 可以写成下面的样子:

body
  color white

Stylus 变量

Stylus 变量采用 key=value 来定义。

可以直接字符串定义:

font-size = 14px

body {
  font: font-size Arial, sans-serif;
}

建议添加标识符 $

$font-size = 14px

body {
  font: $font-size Arial, sans-serif;
}

Stylus 属性查找

#logo {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 150px;
  height: 80px;
  margin-left: -(@width / 2);
  margin-top: -(@height / 2);
}

Stylus 插值

Stylus支持通过使用 {} 字符包围表达式来插入值,其会变成标识符的一部分。

vendor(prop, args)
  -webkit-{prop} args
  -moz-{prop} args
  {prop} args

border-radius()
  vendor('border-radius', arguments)

box-shadow()
  vendor('box-shadow', arguments)

button
  border-radius 1px 2px / 3px 4px

编译后:

button {
  -webkit-border-radius: 1px 2px / 3px 4px;
  -moz-border-radius: 1px 2px / 3px 4px;
  border-radius: 1px 2px / 3px 4px;
}

选择器插值

table
  for row in 1 2 3 4 5
    tr:nth-child({row})
      height: 10px * row

编译后:

table tr:nth-child(1) {
  height: 10px;
}
table tr:nth-child(2) {
  height: 20px;
}
table tr:nth-child(3) {
  height: 30px;
}
table tr:nth-child(4) {
  height: 40px;
}
table tr:nth-child(5) {
  height: 50px;
}

Stylus 混合

Stylus 混入

border-radius(n)
  -webkit-border-radius n
  -moz-border-radius n
  border-radius n

form input[type=button]
  border-radius(5px)

编译后:

form input[type=button] {
  -webkit-border-radius: 5px;
  -moz-border-radius: 5px;
  border-radius: 5px;
}

上面的例子也可以书写为:

border-radius(n)
  -webkit-border-radius n
  -moz-border-radius n
  border-radius n

form input[type=button]
  border-radius 5px

这个时候是把 border-radius 当做私有属性来对待。

还可以使用 arguments 局部变量,传递包含多值的表达式:

border-radius()
  -webkit-border-radius arguments
  -moz-border-radius arguments
  border-radius arguments

form input[type=button]
  border-radius 1px 2px / 3px 4px

编译后:

form input[type=button] {
  -webkit-border-radius: 1px 2px/3px 4px;
  -moz-border-radius: 1px 2px/3px 4px;
  border-radius: 1px 2px/3px 4px;
}

Stylus 父级引用

父级引用字符 &

stripe(even = #fff, odd = #eee)
  tr
    background-color odd
    &.even
    &:nth-child(even)
      background-color even

table
  stripe()
  td
    padding 4px 10px

table#users
  stripe #303030 #494848
  td
    color white

编译后:

table tr {
  background-color: #eee;
}
table tr.even,
table tr:nth-child(even) {
  background-color: #fff;
}
table td {
  padding: 4px 10px;
}
table#users tr {
  background-color: #494848;
}
table#users tr.even,
table#users tr:nth-child(even) {
  background-color: #303030;
}
table#users td {
  color: #fff;
}

Stylus 注释

Stylus支持三种注释,单行注释,多行注释,以及多行缓冲注释。

  1. 单行注释,双斜杠,CSS中不输出。

    // 我是注释!
    body
      padding 5px
  2. 多行注释

    /*
*/

add(a, b)
  a + b
```
  1. 多行缓冲注释

    /*!
*/

add(a, b)
  a + b
```

Stylus 导入

@import "library.styl";

Stylus 继承

Stylus 的 @extend 指令受 SASS 实现的启发,基本一致,除了些轻微差异。

查看原文

赞 0 收藏 0 评论 0

1如既往 发布了文章 · 7月29日

localStorage存对象取出来不是对象怎么办

存入之后值[object Object]的
取出来又不能用obj.attr来获取值,解决方法如下:
1.先转换为json字符串
localStorage.setItem(‘logo’,JSON.stringify(res.logo))

2.取出来再转为json对象
JSON.parse(localStorage.getItem(‘logo’))

查看原文

赞 0 收藏 0 评论 0

1如既往 发布了文章 · 7月3日

Selector详解

Selector简述

A multiplexor of {@link SelectableChannel} objects.

参照Java doc中Selector描述的第一句话,Selector的作用是Java NIO中管理一组多路复用的SelectableChannel对象,并能够识别通道是否为诸如读写事件做好准备的组件

image.png

Selector的创建过程如下:

// 1.创建Selector
Selector selector = Selector.open();

// 2.将Channel注册到选择器中
// ....... new channel的过程 ....

//Notes:channel要注册到Selector上就必须是非阻塞的,所以FileChannel是不可以使用Selector的,因为FileChannel是阻塞的
channel.configureBlocking(false);

// 第二个参数指定了我们对 Channel 的什么类型的事件感兴趣
SelectionKey key = channel.register(selector , SelectionKey.OP_READ);

// 也可以使用或运算|来组合多个事件,例如
SelectionKey key = channel.register(selector , SelectionKey.OP_READ | SelectionKey.OP_WRITE);

// 不过值得注意的是,一个 Channel 仅仅可以被注册到一个 Selector 一次, 如果将 Channel 注册到 Selector 多次, 那么其实就是相当于更新 SelectionKey 的 interest set.

一个Channel在Selector注册其代表的是一个SelectionKey事件,SelectionKey的类型包括:

  • OP_READ:可读事件;值为:1<<0
  • OP_WRITE:可写事件;值为:1<<2
  • OP_CONNECT:客户端连接服务端的事件(tcp连接),一般为创建SocketChannel客户端channel;值为:1<<3
  • OP_ACCEPT:服务端接收客户端连接的事件,一般为创建ServerSocketChannel服务端channel;值为:1<<4

一个Selector内部维护了三组keys:

  1. key set:当前channel注册在Selector上所有的key;可调用keys()获取
  2. selected-key set:当前channel就绪的事件;可调用selectedKeys()获取
  3. cancelled-key:主动触发SelectionKey#cancel()方法会放在该集合,前提条件是该channel没有被取消注册;不可通过外部方法调用

Selector类中总共包含以下10个方法:

  • open():创建一个Selector对象
  • isOpen():是否是open状态,如果调用了close()方法则会返回false
  • provider():获取当前Selector的Provider
  • keys():如上文所述,获取当前channel注册在Selector上所有的key
  • selectedKeys():获取当前channel就绪的事件列表
  • selectNow():获取当前是否有事件就绪,该方法立即返回结果,不会阻塞;如果返回值>0,则代表存在一个或多个
  • select(long timeout):selectNow的阻塞超时方法,超时时间内,有事件就绪时才会返回;否则超过时间也会返回
  • select():selectNow的阻塞方法,直到有事件就绪时才会返回
  • wakeup():调用该方法会时,阻塞在select()处的线程会立马返回;(ps:下面一句划重点)即使当前不存在线程阻塞在select()处,那么下一个执行select()方法的线程也会立即返回结果,相当于执行了一次selectNow()方法
  • close(): 用完Selector后调用其close()方法会关闭该Selector,且使注册到该Selector上的所有SelectionKey实例无效。channel本身并不会关闭。

关于SelectionKey

谈到Selector就不得不提SelectionKey,两者是紧密关联,配合使用的;如上文所示,往Channel注册Selector会返回一个SelectionKey对象,
这个对象包含了如下内容:

  • interest set,当前Channel感兴趣的事件集,即在调用register方法设置的interes set
  • ready set
  • channel
  • selector
  • attached object,可选的附加对象

interest set
可以通过SelectionKey类中的方法来获取和设置interes set

// 返回当前感兴趣的事件列表
int interestSet = key.interestOps();

// 也可通过interestSet判断其中包含的事件
boolean isInterestedInAccept  = interestSet & SelectionKey.OP_ACCEPT;
boolean isInterestedInConnect = interestSet & SelectionKey.OP_CONNECT;
boolean isInterestedInRead    = interestSet & SelectionKey.OP_READ;
boolean isInterestedInWrite   = interestSet & SelectionKey.OP_WRITE;    

// 可以通过interestOps(int ops)方法修改事件列表
key.interestOps(interestSet | SelectionKey.OP_WRITE);

ready set
当前Channel就绪的事件列表

int readySet = key.readyOps();

// 也可通过四个方法来分别判断不同事件是否就绪
key.isReadable();    //读事件是否就绪
key.isWritable();    //写事件是否就绪
key.isConnectable(); //客户端连接事件是否就绪
key.isAcceptable();  //服务端连接事件是否就绪

channel和selector
我们可以通过SelectionKey来获取当前的channel和selector

// 返回当前事件关联的通道,可转换的选项包括:`ServerSocketChannel`和`SocketChannel`
Channel channel = key.channel();

//返回当前事件所关联的Selector对象
Selector selector = key.selector();

attached object
我们可以在selectionKey中附加一个对象:

key.attach(theObject);
Object attachedObj = key.attachment();

或者在注册时直接附加:

SelectionKey key = channel.register(selector, SelectionKey.OP_READ, theObject);

一个Selector完整的例子

一个Selector的基本使用流程包括(读者不放试着按照这个流程自己实现一波):

  1. 创建一个Selector
  2. 将Channel注册到Selector中,并设置监听的interest set
  3. loop

    • 执行select()方法
    • 调用selector.selectedKeys()获取当前就绪的key
    • 迭代selectedKeys

      • 从key中获取对应的Channel和附加信息(if exist)
      • 判断是哪些 IO 事件已经就绪了, 然后处理它们. 如果是 OP_ACCEPT 事件, 则调用 "SocketChannel clientChannel = ((ServerSocketChannel) key.channel()).accept()" 获取 SocketChannel, 并将它设置为 非阻塞的, 然后将这个 Channel 注册到 Selector 中.
      • 根据需要更改 selected key 的监听事件.
      • 将已经处理过的 key 从 selected keys 集合中删除.
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.util.Iterator;
import java.util.Set;

/**
 * Created by locoder on 2019/2/28.
 */
public class SelectorDemo {

    public static void main(String[] args) throws IOException {
        // create a Selector
        Selector selector = Selector.open();

        // new Server Channel
        ServerSocketChannel ssc = ServerSocketChannel.open();
        // config async
        ssc.configureBlocking(false);

        ssc.socket().bind(new InetSocketAddress(8080));

        // register to selector
        // Notes:这里只能注册OP_ACCEPT事件,否则将会抛出IllegalArgumentException,详见AbstractSelectableChannel#register方法
        ssc.register(selector, SelectionKey.OP_ACCEPT);

        // loop
        for (; ; ) {
            int nKeys = selector.select();

            if (nKeys > 0) {
                Set<SelectionKey> keys = selector.selectedKeys();

                for (Iterator<SelectionKey> it = keys.iterator(); it.hasNext(); ) {
                    SelectionKey key = it.next();

                    // 处理客户端连接事件
                    if (key.isAcceptable()) {
                        ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();

                        SocketChannel clientChannel = serverSocketChannel.accept();

                        clientChannel.configureBlocking(false);

                        clientChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024 * 1024));

                    } else if (key.isReadable()) {
                        SocketChannel socketChannel = (SocketChannel) key.channel();

                        ByteBuffer buf = (ByteBuffer) key.attachment();

                        int readBytes = 0;
                        int ret = 0;

                        try {
                            while ((ret = socketChannel.read(buf)) > 0) {
                                readBytes += ret;
                            }

                            if (readBytes > 0) {
                                String message = decode(buf);
                                System.out.println(message);

                                // 这里注册写事件,因为写事件基本都处于就绪状态;
                                // 从处理逻辑来看,一般接收到客户端读事件时也会伴随着写,类似HttpServletRequest和HttpServletResponse
                                key.interestOps(key.interestOps() | SelectionKey.OP_WRITE);

                            }
                        } finally {
                            // 将缓冲区切换为待读取状态
                            buf.flip();
                        }

                    } else if (key.isValid() && key.isWritable()) {
                        SocketChannel socketChannel = (SocketChannel) key.channel();

                        ByteBuffer buf = (ByteBuffer) key.attachment();

                        if (buf.hasRemaining()) {
                            socketChannel.write(buf);
                        } else {
                            // 取消写事件,否则写事件内的代码会不断执行
                            // 因为写事件就绪的条件是判断缓冲区是否有空闲空间,绝大多时候缓存区都是有空闲空间的
                            key.interestOps(key.interestOps() & (~SelectionKey.OP_WRITE));
                        }

                        // 丢弃本次内容
                        buf.compact();
                    }
                    // 注意, 在每次迭代时, 我们都调用 "it.remove()" 将这个 key 从迭代器中删除,
                    // 因为 select() 方法仅仅是简单地将就绪的 IO 操作放到 selectedKeys 集合中,
                    // 因此如果我们从 selectedKeys 获取到一个 key, 但是没有将它删除, 那么下一次 select 时, 这个 key 所对应的 IO 事件还在 selectedKeys 中.
                    it.remove();
                }
            }

        }
    }

    /**
     * 将ByteBuffer转换为String
     *
     * @param in
     * @return
     * @throws UnsupportedEncodingException
     */
    private static String decode(ByteBuffer in) throws UnsupportedEncodingException {
        String receiveText = new String(in.array(), 0, in.capacity(), Charset.defaultCharset());
        int index = -1;
        if ((index = receiveText.lastIndexOf("\r\n")) != -1) {
            receiveText = receiveText.substring(0, index);
        }
        return receiveText;
    }

}

深入Selector源码

下面我们继续按照Selector的编码过程来学习Selector源码

(1)创建过程

image.png

从上图上可以比较清晰得看到,openjdk中Selector的实现是SelectorImpl,
然后SelectorImpl又将职责委托给了具体的平台,比如图中框出的linux2.6以后才有的EpollSelectorImpl, Windows平台则是WindowsSelectorImpl, MacOSX平台是KQueueSelectorImpl.
public static Selector open() throws IOException {
    return SelectorProvider.provider().openSelector();
}
// 创建是依赖SelectorProvider.provider()系统级提供
// 我们来看SelectorProvider.provider()方法
// 从系统配置java.nio.channels.spi.SelectorProvider获取
if (loadProviderFromProperty()) return provider;
// 从ServiceLoader#load
if (loadProviderAsService()) return provider;
// 如果还不存在则使用默认provider,即KQueueSelectorProvider
provider = sun.nio.ch.DefaultSelectorProvider.create();

(2)注册过程

// AbstractSelectableChannel#register方法
SelectionKey register(Selector sel, int ops,Object att){
        synchronized (regLock) {
            // 判断当前Channel是否关闭
            if (!isOpen())
                throw new ClosedChannelException();
            // 判断参数ops是否只包含OP_ACCEPT
            if ((ops & ~validOps()) != 0)
                throw new IllegalArgumentException();
            // 使用Selector则Channel必须是非阻塞的
            if (blocking)
                throw new IllegalBlockingModeException();
            // 根据Selector找到SelectionKey,它是可复用的,一个Selector只能有一个SelectionKey,如果存在则直接覆盖ops和attachedObject
            SelectionKey k = findKey(sel);
            if(key != null) {
                ....
            }
            // 如果不存在则直接实例化一个SelectionKeyImpl对象,并为ops和attachedObject赋值;实际调用AbstractSelector的register方法
            // 将Selector和SelectionKey绑定
            if (k == null) {
                // New registration
                synchronized (keyLock) {
                    if (!isOpen())
                        throw new ClosedChannelException();
                    k = ((AbstractSelector)sel).register(this, ops, att);
                    addKey(k);
                }
            }
            return k;
        }
    }

(3)select过程
select是Selector模型中最关键的一步,下面让我们来研究一下其过程

// 首先来看select的调用链
// SelectorImpl#select -> SelectorImpl#lockAndDoSelect -> 具体provider提供的Selector中的doSelect方法
// 值得注意的是:在lockAndDoSelect方法中执行了`synchronized(this)`操作,故select操作是阻塞的

// open过程中我们知道,Selector有好几种实现,但基本都包含以下操作;感兴趣的同学可以具体看看这位大神写的博客:https://juejin.im/entry/5b51546df265da0f70070b93;这里就不深入写这部分了,篇幅有点长

int doSelect(long timeout) {
    // close判断,如果closed,则抛出ClosedSelectorException
    
    // 处理掉被cancel掉的SelectionKey,即`cancelled-key`
    this.processDeregisterQueue();
    
    try {
        // 设置中断器,实际调用的是AbstractSelector.this.wakeup();方法
    // 调用的是方法AbstractInterruptibleChannel.blockedOn(Interruptible);
        this.begin();
        // 从具体的模型中(kqueue、poll、epoll)选择
        this.pollWrapper.poll(...);
    }finally {
        // 关闭中断器
        this.end();
    }
    
    // 重新处理被cancel的key
    this.processDeregisterQueue();
    // 更新各个事件的状态
    int selectedKeys = this.updateSelectedKeys();
    
    // 可能还有一些操作
    .....
    
    return selectedKeys;
    
    
    
    
}

疑惑点

Q:各事件分别在什么条件下就绪?

  • OP_ACCEPT:客户端向服务端发起TCP连接建立【服务端的代码监听】
  • OP_CONNECT:客户端与服务端的连接建立成功或失败【客户端代码监听】
  • OP_READ:客户端向服务端发请求或服务端向客户端写入数据时
  • OP_WRITE:判断缓冲区是否有空闲空间

FYI

查看原文

赞 0 收藏 0 评论 0

1如既往 发布了文章 · 7月3日

Buffer详解

Buffer简介

在Java NIO中,主要有三大基本的组件:Buffer、Channel和Selector,上一篇文章我们具体介绍了Selector,现在让我们深入理解下在实际编程中使用的最多的Buffer。

定义

首先先让我们来认识一下Buffer:

A container for data of a specific primitive type.
A buffer is a linear, finite sequence of elements of a specific primitive type

在Buffer类的描述中,Buffer被定义为用于特定基本类型数据的容器,且是特定基本类型的线性优先元素序列。

Buffer提供了一个字节缓冲区,它可以从channels中读取数据到Buffer,也可以将Buffer中的数据写入到channels,所以NIO被定义为面向缓冲区编程,而IO则是被定义为面向流的编程。

底层实现

通过阅读源码让我们慢慢揭开Buffer的神秘面纱

public abstract class Buffer {

    /************ 状态变量 ***********/
    // Invariants: mark <= position <= limit <= capacity
    private int mark = -1;
    private int position = 0;
    private int limit;
    private int capacity;
    
    /************ 访问方法 ***********/
    public final Buffer limit(int newLimit);
    public final Buffer mark();
    public final Buffer reset();
    public final Buffer clear();
    public final Buffer flip();
    public final Buffer rewind();
    // 剩余可读元素,limit - position
    public final int remaining();
    // 是否是只可读缓冲区
    public abstract boolean isReadOnly();
    // 是否是堆外内存
    public abstract boolean isDirect();

Buffer类一共有四个变量,被称之为状态变量

  • capacity:容量,必须初始化的值(因为底层是数组)
  • limit:上界,缓冲区的临界区,即最多可读到哪个位置
  • position:下标,当前读取到的位置(例如当前读出第5个元素,则读完后,position为6)
  • mark:标记,备忘位置

四个状态大小关系

0 <= mark <= position <= limit <= capacity

通过访问方法我们可以控制变量的指向,来达到读取到我们想要的数据的目的,下面让我们结合源码和图来学习下各方法对缓冲区的操作

ByteBuffer buffer = ByteBuffer.allocate(n);  (1)

buffer.limit(5);    (2)

// 执行get操作会将position加1
buffer.get();
buffer.get();

buffer.mark();      (3)

buffer.get();
buffer.get();

buffer.reset();     (4)

buffer.flip();      (5)

buffer.rewind();    (6)

buffer.reset();     (7)

(1)初始化Buffer
一个初始化的Buffer各变量位置指向如图(这里capacity大小为n):

image.png

(2)limit

    public final Buffer limit(int newLimit) {
        if ((newLimit > capacity) || (newLimit < 0))
            throw new IllegalArgumentException();
        // 设置limit的新位置
        limit = newLimit;
        // 如果当前读取到位置大于新设置的可读上限,则将position重置为limit,意思是缓存区无法再往下读取了
        if (position > limit) position = limit;
        // 如果mark的位置大于新设置的可读上限,则需要重置mark为-1;代表mark的位置失效了
        if (mark > limit) mark = -1;
        return this;
    }

所以如果我们执行limit(5)操作,则上图变化为

image.png

(3)mark

    public final Buffer mark() {
        // 记录当前读到的位置
        mark = position;
        return this;
    }

mark操作之前Buffer已经被读取到第二个位置(此时position=2),准备要读第三个元素之前,我们执行mark操作后,则图变化为

image.png

(4)reset

    // mark位置,将postion置为mark
    // 恢复到上次备忘的位置
    public final Buffer reset() {
        int m = mark;
        if (m < 0)
            throw new InvalidMarkException();
        position = m;
        return this;
    }

reset操作之前,我们执行了两次get操作,所以此时的position=4,状态变化为下图

image.png

执行reset后我们的状态又变为(3)操作

image.png

(5)flip
如果我们想重新读取上一次读取的内容,则可以执行flip操作

    // 将缓冲区的内容切换为重新读取状态
    public final Buffer flip() {
        limit = position;
        position = 0;
        mark = -1;
        return this;
    }

状态变化为

image.png

(6)rewind
重读缓冲区操作

    public final Buffer rewind() {
        position = 0;
        mark = -1;
        return this;
    }

此时的操作结果与执行flip操作后一致;
但如果我们执行rewind之前不执行flip操作,则在(4)操作后,我们直接执行rewind,则状态变化为

image.png

(7)clear
恢复缓冲区至初始状态

    public final Buffer clear() {
        position = 0;
        limit = capacity;
        mark = -1;
        return this;
    }

image.png

通过上面的实例及图讲解相信大家一定对Buffer加深了了解,所以我们再次简单的定义一下Buffer;Buffer缓冲区其实就是一个线性数组,通过mark、position、limit来控制读取和写入数组,补充一下Buffer的特性:

  • Buffer底层是线性数组,是有限的基本类型元素的组合
  • Buffer可以提供一个固定大小的容器来读取和写入数据
  • 每个Buffer都是可读的,但只有选中的buffer才可写
  • 默认情况下,Buffer不是线程安全的,所以在多线程环境下操作同一个Buffer,一定要使用锁机制保证同步

Buffer的实现类

上文我们对Buffer已经有了一定的了解,下面让我们来看看它有哪些实现吧;首先我们看看Buffer类结构图

image.png

一般的,我们将Buffer分为两类

  • ByteBuffer:字节缓冲区,NIO编程中最常用的缓冲区。主要包含两种,HeapByteBuffer和MappedByteBuffer
  • 基本类型Buffer:除了boolean类型之外的其他基本数据类型的缓冲区

在实际编程中,我们最常用的就是ByteBuffer,因为在实际IO中也都是通过字节流在交互,所以下面我们将重点讲ByteBuffer,废话不多说,先上源码看看

public abstract class ByteBuffer
    extends Buffer implements Comparable<ByteBuffer>
{
    final byte[] hb;
    // 数组的起始位置
    final int offset;
    
    /**********创建ByteBuffer**********/
    
    // 创建基于堆外内存的ByteBuffer
    public static ByteBuffer allocateDirect(int capacity) {
        return new DirectByteBuffer(capacity);
    }
    
     // 创建基于堆内存的ByteBuffer
    public static ByteBuffer allocate(int capacity) {
        if (capacity < 0)
            throw new IllegalArgumentException();
        return new HeapByteBuffer(capacity, capacity);
    }
    
    
    /**********ByteBuffer的方法(不全,重要的几个)**********/
    
    // 字节序
    // BIG_ENDIAN:最低地址存放最高有效字节
    // LITTLE_ENDIAN:最低地址存放最低有效字节
    // java字节序:JAVA虚拟机中多字节类型数据的存放顺序,JAVA字节序也是BIG-ENDIAN。
    public final ByteOrder order() {
        return bigEndian ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN;
    }
    
    // 读取指定位置的元素
    public abstract byte get(int index);
    
    // 往指定位置写入元素
    public abstract ByteBuffer put(int index, byte b);
    
    // 基于当前状态new一个新的ByteBuffer
    public abstract ByteBuffer duplicate();
    
    // 是否是直接操作内存的Buffer;若是,则此Buffer直接操作JVM堆外内存 ,使用Unsafe实现;否则操作JVM堆内存
    public abstract boolean isDirect();
    
    // 丢弃已经读取的数据,保留未读取的数据,并使缓存中处于待填充状态
    public abstract ByteBuffer compact();
    
    // 从当前buffer中生成一个该buffer尚未使用部分的新的缓冲区,例如当前buffer的position为3,limit为5,则新的缓冲区limit和capacity都为2,offset的3,数据区域两者共享;
    public abstract ByteBuffer slice();

从ByteBuffer的源码来看,相比较于Buffer,其新增了几个重要的方法,下面让我们继续以前文的方式来学习下这些方法的作用

ByteBuffer buffer = ByteBuffer.allocate(8); 

// 执行get操作会将position加1
buffer.get();
buffer.get();

buffer.compact();      (1)

buffer.slice();     (2)

执行(1)操作前,状态图如下:

image.png

(1)compact


    protected int ix(int i) {
        return i + offset;
    }
    
    public ByteBuffer compact() {
        // 把已经读取的内容使用后面的内容覆盖
        System.arraycopy(hb, ix(position()), hb, ix(0), remaining());
        // 修改状态
        // 将position置为limit - position
        position(remaining());
        // limit为capacity
        limit(capacity());
        // mark置为-1
        discardMark();
        return this;
    }

状态转换如下,这里我们可以看到,position已经被置为6,但是其实我们是想position置为0,这样才符合我们的初衷;所以,从这点分析来看,compact方法最好是等缓冲区全部被读完后使用,达到复用ByteBuffer的目的,否则就会造成空间浪费

image.png

(2) slice

    // 实际是共享当前ByteBuffer数组的空间
    public ByteBuffer slice() {
        return new HeapByteBuffer(hb,-1,0,this.remaining(),this.remaining(),this.position() + offset);
    }

最终状态如图,其中带1的部分是为新缓冲区的状态:

image.png

HeapByteBuffer和MappedByteBuffer

说了这么多Buffer和ByteBuffer的方法,我们再回过头来看看类结构图中ByteBuffer的两个子类,HeapByteBuffer和MappedByteBuffer,通过对比的方式了解下这两者的区别

HeapByteBuffer

MappedByteBuffer

实现

byte[]数组

Unsafe实现

内存分配

Java堆内存

堆外内存

适用场景

复用

经常释放和新建

这里就不具体介绍这两个类实现的源码了,有兴趣了解Unsafe的同学可以看下我团技术团队推的一篇文章【基本功】Java魔法类:Unsafe应用解析

(1)什么是Java堆内存和堆外内存?

  • Java堆内存:Java内存结构主要有三大块:堆内存、方法区和栈;堆内存(如下图中的Heap区)是JVM中最大的一块由年轻代和老年代组成,是Java中GC的主要操作空间,是Java进程内的单位

image.png

  • 堆外内存:不属于Java直接管理的空间,是不能被GC,所以只能手动申请和释放;属于操作系统直接管理的内存空间,是Java进程外的单位

(2)创建和释放的效率对比?

因为JVM堆中分配和释放内存肯定比系统分配和创建内存高效,所以创建和释放MappedByteBuffer的代价比HeapByteBuffer得要高

(3)IO效率对比?
在回答这个问题之前,首先让我们来看一张图

image.png

在unix和linux的体系架构中,一般会分为用户态和内核态

  • 用户态:上层应用程序的活动空间,应用程序的执行必须依托于内核提供的资源。
  • 内核态:用于控制计算机的硬件资源,并提供上层应用程序运行的环境。
  • 系统调用:为了使上层应用能够访问到这些资源,内核为上层应用提供访问的接口。

而三者之间的关系为:

image.png

从上述的了解中,我们可以得出内核态的效率会大于用户态的效率

而在平时的read/write操作中,应用程序在与I/O设备进行交互时是需要经历一个“内核缓冲区”的。而MappedByteBuffer就好比是“内核缓冲区”上的缓存,而HeapByteBuffer则是应用程序Java中的内存空间,所以结论显而易见,把一个MappedByteBuffer写入一个Channel的速度要比把一个HeapByteBuffer写入一个Channel的速度要快。

这里顺便附上一张性能对比图,详见NIO Buffer performance

image.png

FYI

查看原文

赞 0 收藏 0 评论 0

1如既往 发布了文章 · 7月3日

lodashjs

链接:lodashjs

简单说明:一款功能强大的js工具库,提供很多常用的对数组、对象、字符串等进行操作的处理函数,降低了对array、number、objects、string等的使用难度,包含以下模块化方法:遍历数组对象字符串、对值进行操作和检测,创建符合功能的函数,且所有操作都不会改变原数据。

使用方法:引用lodash 

<script data-original="https://cdn.bootcss.com/lodash.js/4.17.12-pre/lodash.min.js"></script>
然后直接在按js或jQuery语法使用相关方法,与jQuery类似,使用“_”作为元素选择器;

常用方法:

1 数组:

    _.forEach()遍历数组,最后返回结果可直接传给变量

     eg:  var arr1 = _.forEach(arr1_o,function(n,key) {

        arr1_o[key].name= arr1_o[key].id+'-'+arr1_o[key].name;

    });

    _.uniq()  数组去重   _.contract()数组连接     _.drop(array,n)删除array的前n个值;

_.nth(array, n); 返回数组array中的第n个值

_.join(arrary,'string') 将array中的值用string连接;

2 对象

    _.findKey() 根据value找对应key值,匹配到第一个值后结束,匹配不到返回 undefined

    _.forIn() 遍历 

        eg :_.forIn(users, function(value, key) {

              console.log(key+':'+JSON.stringify(value));

        });

3 时间

_.now()获取当前时间

_.isDate()判断是否为date格式

4各种判断方法

_.isNull(val),当value为null时才为真;
_.isUndefined(val) 判断val是否为undefined;
_.isNaN(val) 判断是否为数字 是则返回false
_.isNumber(val) 判断是否为数字 是则返回true
_.isEmpty(val)判断是否为空;
_.isObject(val)判断是否为对象;
_.isArray(val) 判断是否为数组;
_.isString(val) 判断是否为字符串;
_.isElement(val)判断是否为html元素 eg_.isElement(document.body); =>true,_.isElement('<body>'); =>false
_.lt(num1,num2) 比较大小,若num1<num2 返回true;
_.lte(num1,num2) 比较大小,若num1<=num2 返回true
_.isEqual(obj1,obj2) 比较是否相等,比较内容可以是对象,数字,字符串等。

查看原文

赞 0 收藏 0 评论 0

认证与成就

  • 获得 13 次点赞
  • 获得 43 枚徽章 获得 1 枚金徽章, 获得 8 枚银徽章, 获得 34 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2018-03-23
个人主页被 826 人浏览