1、数据结构
数据结构主要有以下几种:
- 数组
- 栈
- 队列
- 链表
- 树
- 图
- 字典树(这是一种高效的树形结构,但值得单独说明)
- 散列表(哈希表)
1、数组
数组是最简单、也是使用最广泛的数据结构。栈、队列等其他数据结构均由数组演变而来。
每个数据元素都关联一个正数值,我们称之为索引,它表明数组中每个元素所在的位置。大部分语言将初始索引定义为零。
以下是数组的两种类型:
- 一维数组
- 多维数组(数组的数组)
2、栈
著名的撤销操作几乎遍布任意一个应用。但你有没有思考过它是如何工作的呢?这个问题的解决思路是按照将最后的状态排列在先的顺序,在内存中存储历史工作状态(当然,它会受限于一定的数量)。这没办法用数组实现。但有了栈,这就变得非常方便了。
可以把栈想象成一列垂直堆放的书。为了拿到中间的书,你需要移除放置在这上面的所有书。这就是LIFO(后进先出)的工作原理。
3、队列
与栈相似,队列是另一种顺序存储元素的线性数据结构。栈与队列的最大差别在于栈是LIFO(后进先出),而队列是FIFO,即先进先出。
一个完美的队列现实例子:售票亭排队队伍。如果有新人加入,他需要到队尾去排队,而非队首——排在前面的人会先拿到票,然后离开队伍。
移除先入队的元素、插入新元素。
4、链表
链表是另一个重要的线性数据结构,乍一看可能有点像数组,但在内存分配、内部结构以及数据插入和删除的基本操作方面均有所不同。
链表就像一个节点链,其中每个节点包含着数据和指向后续节点的指针。 链表还包含一个头指针,它指向链表的第一个元素,但当列表为空时,它指向null或无具体内容。
链表一般用于实现文件系统、哈希表和邻接表。
链表包括以下类型:
- 单链表(单向)
- 双向链表(双向)
5、图
图是一组以网络形式相互连接的节点。节点也称为顶点。 一对节点(x,y)称为边(edge),表示顶点x连接到顶点y。边可以包含权重/成本,显示从顶点x到y所需的成本。
图的类型
- 无向图
- 有向图
常见图遍历算法
- 广度优先搜索
- 深度优先搜索
6、树
树形结构是一种层级式的数据结构,由顶点(节点)和连接它们的边组成。 树类似于图,但区分树和图的重要特征是树中不存在环路。
树形结构被广泛应用于人工智能和复杂算法,它可以提供解决问题的有效存储机制。
树数据结构中使用的基本术语:
- Root - 根节点
- Parent - 父节点
- Child - 子节点
- Leaf - 叶子节点
- Sibling - 兄弟节点
以下是树形结构的主要类型:
- N元树
- 平衡树
- 二叉树
- 二叉搜索树
- AVL树
- 红黑树
- 2-3树
其中,二叉树和二叉搜索树是最常用的树。
7、字典树(Trie)
字典树,也称为“前缀树”,是一种特殊的树状数据结构,对于解决字符串相关问题非常有效。它能够提供快速检索,主要用于搜索字典中的单词,在搜索引擎中自动提供建议,甚至被用于IP的路由。
以下是在字典树中存储三个单词“top”,“so”和“their”的例子:
这些单词以顶部到底部的方式存储,其中绿色节点“p”,“s”和“r”分别表示“top”,“thus”和“theirs”的底部。
8、哈希表
哈希法(Hashing)是一个用于唯一标识对象并将每个对象存储在一些预先计算的唯一索引(称为“键(key)”)中的过程。因此,对象以键值对的形式存储,这些键值对的集合被称为“字典”。可以使用键搜索每个对象。基于哈希法有很多不同的数据结构,但最常用的数据结构是哈希表。
哈希表通常使用数组实现。
散列数据结构的性能取决于以下三个因素:
- 哈希函数
- 哈希表的大小
- 碰撞处理方法
下图为如何在数组中映射哈希键值对的说明。该数组的索引是通过哈希函数计算的。
2、进程与线程的区别
- 进程是资源分配的最小单位,线程是程序执行的最小单位(资源调度的最小单位)
- 进程有自己的独立地址空间,每启动一个进程,系统就会为它分配地址空间,建立数据表来维护代码段、堆栈段和数据段,这种操作非常昂贵。
- 而线程是共享进程中的数据的,使用相同的地址空间,因此CPU切换一个线程的花费远比进程要小很多,同时创建一个线程的开销也比进程要小很多。
- 线程之间的通信更方便,同一进程下的线程共享全局变量、静态变量等数据,而进程之间的通信需要以通信的方式(IPC)进行。不过如何处理好同步与互斥是编写多线程程序的难点。
- 但是多进程程序更健壮,多线程程序只要有一个线程死掉,整个进程也死掉了,而一个进程死掉并不会对另外一个进程造成影响,因为进程有自己独立的地址空间。
3、HTTP协议
http是一个简单的请求-响应协议,它通常运行在TCP之上。它指定了客户端可能发送给服务器什么样的消息以及得到什么样的响应。请求和响应消息的头以ASCII码形式给出;
HTTP是基于客户/服务器模式,且面向连接的。典型的HTTP事务处理有如下的过程:
- 客户与服务器建立连接;
- 客户向服务器提出请求;
- 服务器接受请求,并根据请求返回相应的文件作为应答;
- 客户与服务器关闭连接。
客户与服务器之间的HTTP连接是一种一次性连接,它限制每次连接只处理一个请求,当服务器返回本次请求的应答后便立即关闭连接,下次请求再重新建立连接。这种一次性连接主要考虑到WWW服务器面向的是Internet中成干上万个用户,且只能提供有限个连接,故服务器不会让一个连接处于等待状态,及时地释放连接可以大大提高服务器的执行效率。
HTTP是一种无状态协议,即服务器不保留与客户交易时的任何状态。这就大大减轻了服务器记忆负担,从而保持较快的响应速度。
4、从输入url到页面加载完成发生了什么
- 浏览器的地址栏输入URL并按下回车。
- 浏览器查找当前URL是否存在缓存,并比较缓存是否过期。
- DNS解析URL对应的IP。
- 根据IP建立TCP连接(三次握手)。
- HTTP发起请求。
- 服务器处理请求,浏览器接收HTTP响应。
- 渲染页面,构建DOM树。
- 关闭TCP连接(四次挥手)。
1、DNS域名解析
DNS域名解析(域名解析),DNS实际上是一个域名和IP对应的数据库。
IP地址往都难以记住,但机器间互相只认IP地址,于是人们发明了域名,让域名与IP地址之间一一对应,它们之间的转换工作称为域名解析,域名解析需要由专门的域名解析服务器来完成,整个过程是自动进行的。
首先浏览器先检查本地hosts文件是否有这个网址映射关系,如果有就调用这个IP地址映射,完成域名解析。
如果没找到则会查找本地DNS解析器缓存,如果找到则返回。
如果还是没有找到则会查找本地DNS服务器,如果找到则返回。
最后迭代查询,按根域服务器库(.com,.cn,.vip,.top…)->顶级域(.com),然后根据顶级域(.com)->第二层域子域(baidu.com),最后根据baidu.com的域名找到相应的IP,返回给浏览器。
2、TCP连接
在通过上一步的DNS域名解析后,获取到了服务器的IP地址后,便会开始建立一次连接,这是由TCP协议完成的,主要通过三次握手进行连接。
第一次握手: 建立连接时,客户端发送syn包(syn=j)到服务器,并进入等待服务器确认的状态;
第二次握手: 服务器收到syn包,必须确认客户端的syn(ack=j+1),同时自己根据syn生成一个ACK包,此时服务器进入等待状态;
第三次握手: 客户端收到服务器的ACK包,向服务器发送确认,此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手。
3、页面渲染
浏览器按顺序解析html文件,构建DOM树,在解析到外部的css和js文件时,向服务器发起请求下载资源,若是下载css文件,则解析器会在下载的同时继续解析后面的html来构建DOM树;若在下载js文件,解析器会停止对html的解析,这就出现了js阻塞问题。
预加载器
当浏览器被脚本文件阻塞时,预加载器(一个轻量级的解析器)会继续解析后面的html,寻找需要下载的资源。如果发现有需要下载的资源,预加载器在开始接收这些资源。预加载器只能检索HTML标签中的URL,无法检测到使用脚本添加的URL,这些资源要等脚本代码执行时才会获取。
4、关闭TCP连接或继续保持连接
通过四次挥手关闭连接
- 第一次握手是浏览器发完数据,然后发送FIN请求断开连接。
- 第二次握手是服务器向客户端发送ACK,表示同意。
- 第三次握手是服务器可能还有数据向浏览器发送,所以向浏览器发送ACK同时也发送FIN请求。
- 第四次握手是浏览器接受返回的ACK,表示数据传输完成。
5、HTML5 语义化
看了一下网上的解读,我觉得 HTML5 语义化就是根据网页的结构,选择合乎语义的标签,方便开发者理解。
如上图,这个页面结构中摒弃了所有div元素,取而代之的是HTML5语义化标签。
6、vw和vh
vw是可视区宽度单位。1vw等于可视区宽度的百分之一。vw单位跟百分比很相似,不过vw和父元素无关。
vh和vw单位一样,不同的是vh是相对于可视区的高度。
7、new操作符做了什么
new 运算符创建一个用户定义的对象类型的实例或具有构造函数的内置对象的实例。
new 关键字会进行如下的操作:
- 创建一个空的简单JavaScript对象(即{});
- 链接该对象(即设置该对象的构造函数)到另一个对象 ;
- 将步骤1新创建的对象作为this的上下文 ;
8、cookie/session记住登录状态机制原理
Cookie的机制
在网站中,http请求是无状态的,也就是说,即使第一次和服务器连接后并且登录成功后,第二次请求服务器依然不能知道当前请求是哪个用户。cookie的出现就是为了解决这个问题:当浏览器访问网站后,这些网站将一组数据存放在客户端,当该用户发送第二次请求的时候,就会自动的把上次请求存储的cookie数据自动携带给服务器,服务器通过浏览器携带的数据就能识别当前用户。
1)如果对cookie进行有效时间设置,当cookie的有效时间过了之后,这些数据就被自动删除了;此时会话cookie保存在硬盘上。关闭浏览器后再次打开,这些cookie依然有效,直到超过设定的有效时间。存储在硬盘上的cookie可以在不同的浏览器进程间共享,比如两个IE窗口。
2)如果不设置过期时间,则表示这个cookie生命周期为浏览器会话期间,只要关闭浏览器窗口,cookie就消失。这种生命期为浏览会话期的cookie被称为会话cookie。会话cookie一般不保存在硬盘上,而是保存在内存里。
特点:cookie存储在本地浏览器,且存储数据量有限,不同的浏览器有不同的存储大小,但一般不超过4KB。因此使用cookie只能存储一些小量的数据。
Session的机制
Session是存放在服务器端的类似于哈希表结构的一组数据,当浏览器第一次发送请求时,服务器自动生成了一些数据和一个Session ID用来唯一标识这个用户,并将其通过响应发送到浏览器。当浏览器第二次发送请求,会将前一次服务器响应中的Session ID放在请求中一并发送到服务器上,服务器从请求中提取出Session ID,并和保存的所有Session ID进行对比,找到这个用户对应的数据。
一般情况下,服务器会在一定时间内(默认20分钟)保存这个数据,过了时间限制,就会销毁这个数据。在销毁之前,程序员可以将用户的一些数据以Key和Value的形式暂时存放在这个数据中。当然,也有使用数据库将这个数据序列化后保存起来的,这样的好处是没了时间的限制,坏处是随着时间的增加,这个数据库会急速膨胀,特别是访问量增加的时候。一般还是采取前一种方式,以减轻服务器压力。
总而言之,session和cookie的作用是类似的,都是为了存储用户相关的信息。
特点:cookie存储在本地浏览器,而session存储在服务器。存储在服务器的数据会更加的安全,不容易被窃取。但会占用服务器的资源。
Session的客户端实现形式(即Session ID的保存方法)
一般浏览器提供了两种方式来保存,还有一种是程序员使用HTML隐藏域的方式自定义实现:
1) 使用cookie来保存。这是最常见的方法,本文“记住我的登录状态”功能的实现就是基于这种方式的。服务器通过设置cookie的方式将Session ID发送到浏览器。
a.如果我们不设置有效时间,这个cookie将存放在内存中,成为一个和浏览器共存亡的会话cookie。当浏览器关闭的时候,cookie消失,对应的Session ID也就丢失了;
b.如果我们设置这个时间为若干天之后,那么这个Cookie会保存在客户端硬盘中,即使浏览器关闭,这个值仍然存在,下次访问相应网站时,同样会发送到服务器上。
2)使用URL附加信息的方式。也就是像我们经常看到JSP网站会有aaa.jsp?JSESSIONID=*一样的。这种方式和第一种方式里面不设置cookie过期时间是一样的。不过我觉得这样安全性比较低。
3)第三种方式是在页面表单里面增加隐藏域,这种方式实际上和第二种方式一样,只不过前者通过GET方式发送数据,后者使用POST方式发送数据。但是明显后者比较麻烦。
实现“记住我的登录状态”功能
前面我们了解到,如果我们将Session ID通过Cookie发送到客户端的时候设置其有效时间为1年,那么在今后的一年时间内,客户端访问网站的时候都会将这个Session ID值发送到服务器上,服务器根据这个Session ID从内存或者数据库里面恢复存放Key-Value对的数据。
但是,实际上Session并不会一直存在。过了一定的时间之后,服务器上的Session就会被销毁,以减轻服务器的访问压力。当服务器上的数据被销毁后,即使客户端上存放了cookie也没有办法“记住我的登录状态”了。
通用的实现办法是,将用户的用户名和加密之后的密码也通过cookie的方式存放在客户端,当服务器上的Session销毁以后,使用cookie里面存 放的用户名和加密之后的密码重新执行一次登录操作,重建Session,并更新客户端上cookie中存放的的Session ID,而这个操作是发生在用户请求一个需要身份验证的页面资源的背后,对于用户来讲是透明的,于是就达到了“记住我的登录状态”的目的了。
9、Render Tree
CSSOM树和DOM树连接在一起形成一个render tree,渲染树用来计算可见元素的布局并且作为将像素渲染到屏幕上的过程的输入。
- DOM树和CSSOM树连接在一起形成render tree
- render tree只包含了用于渲染页面的节点
- 布局计算了每一个对象的准确的位置以及大小
- 绘画是最后一步,绘画要求利用render tree来将像素显示到屏幕上
首先是结合DOM树和CSSOM树形成“render tree”,渲染树用来描述所有可见的DOM内容,并且将CSSOM样式信息附加到节点上。
为了形成渲染树,浏览器大致做的事情有:
- 从DOM树根节点开始,遍历每一个可见的节点;
一些节点是完全不可见的(比如 script标签,meta标签等),这些节点会被忽略,因为他们不会影响渲染的输出;
一些节点是通过CSS样式隐藏了,这些节点同样被忽略——例如样式是display:none
的 - 对每一个可见的节点,找到合适的匹配的CSSOM规则,并且应用样式
- 显示可见节点(节点包括内容和被计算的样式)
另外需要注意visibility:hidden
和display:none
之间的不同,visibility:hidden
将元素设置为不可见,但是同样在布局上占领一定空间(例如,它会被渲染成为空盒子),但是display:none
的元素是将节点从整个render tree中移除,所以不是布局中的一部分 。
之后输出的是一个render包括了屏幕上可见内容的样式信息和内容信息。
浏览器所做的事情:
- 处理HTML标签建立DOM树
- 处理CSS标签建立CSSOM树
- 连接CSSOM树和DOM树形成一个render树
- 在render树上运行布局来计算每个节点的形状
- 在屏幕上画每一个节点
10、装箱/拆箱
装箱:把基本数据类型转换成对应的引用类型的操作。
拆箱:把引用类型转换成基本数据类型的操作。