我的博客

每天进步一点点


  • 首页

  • 标签

  • 分类

  • 归档

数据结构篇——散列

发表于 2018-12-02 | 分类于 数据结构

散列后的数据可以快速插入或取用,散列使用的数据结构是散列表

适合插入、删除、取用数据;不适合查找

更现实的目标是:让散列函数尽量将键均匀地映射到数组中

两个键映射到同一个值的情况叫做碰撞

首先用一个HashTable的类来表示散列表,我们可以通过这个类来向散列插入新数据、从散列中读取数据、显示散列中数据分布、定义如何去计算散列值

1
2
3
4
5
6
7
function HashTable() {
this.table = new Array(137) // 这里数组的长度推荐是质数
this.simpleHash = simpleHash
this.showDistro = showDistro
this.put = put
this.get = get
}

散列函数是这个过程的重点,它相当于一个信息压缩函数,将键(通常是字符串的形式)压缩成一个值(通常为数值形式)

散列函数定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
function simpleHash(data){
let total = 0
for(let i = 0; i < data.length; ++i){
total += data.charCodeAt(i);
}
return total % this.table.length
}

function put(data){
let pos = this.simpleHash(data)
this.table[pos] = data
}

function showDistro(){
for(let i = 0; i < this.table.length; ++i){
if(this.table[i] != undefined){
console.log(i + ":" + this.table[i])
}
}
}

// 测试代码
let cities = ['shenzhen', 'beijing', 'guangzhou', 'shanghai']
let hTable = new HashTable()
for(let i = 0; i < cities.length; ++i){
hTable.put(cities[i])
}
hTable.showDistro()

以上这种散列函数,会使得如果两个键所产生的散列值是相同的,那么只有一个能存入散列表,也就是发生了碰撞

我们可以通过改善散列函数来避免碰撞

需要从以下几个方面入手改善

  • 数组的长度应该是质数
  • 霍纳算法

使用霍纳算法来改善之前的散列函数

1
2
3
4
5
6
7
8
9
function betterHash(string, arr){
const H = 37
let total = 0
for(let i = 0; i< string.length; ++i){
total += H * total + string.charCodeAt(i) // 区别是每次求和时都要乘以一个质数
}
total = total % arr.length
return parseInt(total)
}

上面我们所散列化的键都是字符串类型,接下来要继续修改这个程序,使之可以散列化整型键

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function getRandomInt(min, max){
return Math.floor(Math.random()*(max - min + 1)) + min
}

function genStuData(arr){
for(let i = 0; i < arr.length; ++i){
let num = ''
for(let j = 1; j <= 9; ++j){
num += Math.floor(Math.random()*10)
}
num += getRandomInt(50, 100)
arr[i] = num
}
}

同理使用betterHash得到散列值,以避免发生碰撞

普遍采用betterHash,所以重写put方法

1
2
3
4
function put(key, data) {
let pos = this.betterHash(key)
this.table[pos] = dataa
}

定义get方法,用来读取存储在散列表中的数据

1
2
3
function get(key) {
return this.table[this.betterHash(key)]
}

通过对键值进行散列,得到一个散列值,再通过这个散列值去散列表中读取对应的数据

接下来重点介绍如何解决碰撞

首先我们知道,当不同的输入通过散列函数后,得到相同的输出,这就是碰撞。解决碰撞一般有两种方式

  • 开链法
  • 线性探测法

开链法的重点是用来存储散列值的数组中每个元素,是一个新的数据结构,这样相同的输出也就能够共存了

相碰撞的两个散列值在第一数组的存储位置是相同的,不同的第二数组的位置,实现代码如下:

1
2
3
4
5
function buildChains() {
for(let i = 0; i < this.table.length; ++i) {
this.table[i] = new Array()
}
}

重新定义put方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function put(key, data){
let pos = this.betterHash(key)
let index = 0
if(this.table[pos][index] == undefined) {
this.table[pos][index + 1] = data
}
++index
else {
while (this.table[pos][index] != undefined){
++index
}
this.table[pos][index+1] = data
}
}

重新定义get方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function get(key){
let index = 0
let hash = this.betterHash(key)
if(this.table[hash][index] == key){
return this.table[hash][index+1]
}
index += 2
else {
while(this.table[pos][index] != key){
index += 2
}
return this.table[hash][index+1]
}
return undefined
}

React组件——下拉刷新、上拉加载

发表于 2018-12-01 | 分类于 React

在移动端项目开发中,应用下拉刷新或上拉加载的场景非常多,避免编写重复代码,可以考虑把它封装成一个通用的组件,基于公司的技术栈,我们使用React来编写组件。

直接上代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
import React from 'react'
import _ from 'lodash'

class PullFresh extends React.Component {
constructor (props) {
super(props)
this.state = {
list: Array(50).fill(Math.ceil(Math.random() * 10))
}
this.dropPull = this.dropPull.bind(this)
}

dropPull () {
let loadingText = this.refs.dropPull
let _element = this.refs.refreshContainer
let startPos = 0
let transitionHeight = 0
let startFlag = false
let movingFlag = false

_element.addEventListener('touchstart', e => {
// console.log('初始位置:', e.touches[0].pageY)
if (this.getScrollTop() === 0) { // 只有当滚动条处于顶部时,才触发下拉刷新的的动作
startFlag = true
console.log('start')
startPos = e.touches[0].pageY
_element.style.position = 'relative'
_element.style.transition = 'transform 0s'
}
}, true)

_element.addEventListener('touchmove', e => {
console.log('moving')
// console.log('当前位置:', e.touches[0].pageY)
transitionHeight = e.touches[0].pageY - startPos // 获取下拉的距离
// console.log(transitionHeight)
if (transitionHeight > 0 && transitionHeight < 60) {
_element.style.transform = `translateY(${transitionHeight}px)`
movingFlag = true
}
if (transitionHeight > 55) {
loadingText.innerText = '释放更新'
}
}, true)

_element.addEventListener('touchend', e => {
console.log('end')
if (startFlag && movingFlag) { // 只有当在顶部下拉且移动了一段距离,才触发动作
_element.style.transition = 'transform 0.5s ease'
_element.style.transform = 'translateY(0px)'
loadingText.innerText = '更新中...'
console.log('获取上一页')
startFlag = false
movingFlag = false
}
// console.log(transitionHeight)

this.setState({
list: Array(50).fill(Math.ceil(Math.random() * 10))
})
}, true)
}

getScrollTop () {
let scrollTop = 0
if (document.documentElement && document.documentElement.scrollTop) {
scrollTop = document.documentElement.scrollTop
} else if (document.body) {
scrollTop = document.body.scrollTop
}
return scrollTop
}

getClientHeight () {
let clientHeight = 0
if (document.body.clientHeight && document.documentElement.clientHeight) {
clientHeight = Math.min(document.body.clientHeight, document.documentElement.clientHeight)
} else {
clientHeight = Math.max(document.body.clientHeight, document.documentElement.clientHeight)
}
return clientHeight
}

getScrollHeight () {
return Math.max(document.body.scrollHeight, document.documentElement.scrollHeight)
}

upPull () {
let _text = this.refs.upPull
// let _container = this.refs.refreshContainer
window.addEventListener('scroll', () => {
// console.log(Math.ceil(this.getScrollTop()) + this.getClientHeight() === this.getScrollHeight())
if (Math.ceil(this.getScrollTop()) + this.getClientHeight() === this.getScrollHeight()) {
_text.innerText = '加载中...'
console.log('获取下一页数据')
const newList = this.state.list
newList.push(Math.ceil(Math.random() * 10), Math.ceil(Math.random() * 10), Math.ceil(Math.random() * 10))
setTimeout(() => {
this.setState({
list: newList
})
}, 1000)
}
}, true)
}
componentDidMount () {
this.dropPull()
this.upPull()
}

render () {
return (
<div>
<p ref='dropPull'>下拉刷新</p>
<ul ref='refreshContainer'>
{
_.map(this.state.list, (item, key) => {
return (
<li key={key}>{item}</li>
)
})
}
</ul>
<p ref='upPull'>上拉加载</p>
</div>
)
}
}

export default PullFresh

数据结构篇——散列

发表于 2018-11-28 | 分类于 数据结构

散列表是散列使用的数据结构

性能优化之虚拟列表

发表于 2018-11-27 | 分类于 性能优化

在前端开发中,UI渲染一直都是重头戏。

我们可以回想一下,通常我们将一个DOM节点渲染到页面上,通常采用的方式有以下两种

  • innerHTML
  • appendChild

这两种DOM渲染的方式之间的对比,网络上有很多分析的文章,这里不再赘述

总的来说,两者对性能的影响差异不大,可以忽略不计

既然对性能影响不大,那我们还讨论什么呢?

我们在开发的时候,通常遇到这个场景,后端返回json格式的数据,前端拿到数据自然是渲染,当然在渲染前可能会作一些处理

如果数据量小的话,那还好办直接对数据进行循环遍历就是了;问题是当返回的数据量很大时,我们该怎么办?

特别是在移动端,对于性能的要求比较高,如果将大量的数据一次性渲染出来,很可能会出现卡死、白屏、甚至浏览器会崩溃

我们常见的做法是延迟加载,或叫无限滚动,原理也就是根据滚动条滚动的距离,来判断用户浏览当前网页的位置,监听滚动事件,从而当滚动条到达合适的距离,触发回调向后端发起请求,后端返回数据后再渲染出来

这种解决问题的思路是从数据入手,也就是对数据进行几次分割,每次只是请求部分数据,这样渲染的压力就减小很多

传统PC端的分页也是相似的原理,区别只在于分页会导致页面全部刷新,而无限滚动只是局部刷新,后者用户体验更好

我们今天的主角:虚拟列表。它所要解决的问题也是前面提到的大量DOM渲染开销大的问题,区别于上面的解决手段,它的思路是从UI入手

它的原理是这样的:当我们在浏览网页时,其实看到的只是浏览器视窗范围的内容,超出视窗的内容我们是看不到的

那既然这样,我们在渲染DOM时,就只需要渲染用户当前看的部分,其他的都可以不用渲染了

说到这里,我们就知道要实现虚拟列表,很关键的一点就是高度问题。这里的高度包括浏览器视窗的高度,列表每一项的高度。根据这些高度,我们才可以判断究竟要渲染列表中的哪一项到哪一项。

当时要用到公司的业务中,肯定需要借助市面上成熟的类库,因为技术栈是React,所以这里以react-virtualized为例,来分析其具体技术实现

不要写重复代码

发表于 2018-11-25

在项目开发的过程中,我们会遇到很多编写重复代码的场景

那应该如何解决呢?

首先想到的是循环,可以将要渲染的“数据”提取出来,用数组这种数据结构来存储(只是为了更好地遍历),然后使用map等方法进行渲染

多处用到循环,每个循环的结构好像都差不多,那我们就可以考虑将循环用函数封装起来

能提取就提取,能封装就封装,别到处都是重复的代码

git 拉取远程分支到本地

发表于 2018-11-22 | 分类于 Git

git pull origin 远程分支名:本地分支名

git rebase常见坑点

发表于 2018-11-22 | 分类于 Git

在公司开发项目,经常会出现自己的开发分支处于灰度状态,灰度确定没有问题后,准备全量时,因为有可能别人在你灰度的这段时间中,提交了一些代码,所以需要先更新一下master的代码,也就是执行git rebase -i master。这个命令用得很频繁,但是每次使用都好像不是那么顺利。其中比较麻烦的也就是代码冲突的解决。

解决冲突,首先要看什么是master上的代码,什么是开发分支上的代码

然后比较两者的差异,确定要留下哪部分代码

总之就是解决冲突时要谨慎、仔细

为什么会出现^m?

发表于 2018-11-15 | 分类于 vim

这个问题还得从编程环境说起

公司是在mac系统下编码,我自己的电脑是windows系统

当我想着把公司的项目带回学校自己做时,并没有发现任何问题

但是当第二天到了公司,准备将代码提交上去时,惊奇地发现所有在学校改过的文件,git diff都显示修改过,而事实上我只是改动了一两行而已,git diff显示的是整个文件都被改动过了

这样就会导致一个问题,git diff变得没有意义了;同时以后同事要提交代码,都会出现冲突,这个问题不可小视

通过查找资料,最终得知是换行符的不同导致的,简单讲就是windows系统的换行符和mac系统的换行符不一样

通过vim编辑器打开,我们也可以看到每一行末尾都有^m这个标记

所以解决问题的方法就是去掉文件每一行末尾的^m标记

方式也有很多,常见的有dos2unix、用vim命令来匹配字符然后去掉

一行命令解决的事,绝不用两行

find js/ -name “*.js” | xargs dos2unix

123…7
陈泳仰

陈泳仰

53 日志
16 分类
17 标签
GitHub E-Mail
© 2017 — 2019 陈泳仰