浏览器前进与后退的秘密——栈 (栈的理解与实现)


在这里插入图片描述

🐱‍🐉作者简介:大家好,我是黑洞晓威,一名大二学生,希望和大家一起进步。
👿本文收录于 算法,本专栏是针对大学生、初学算法的人准备,解析常见的数据结构与算法,同时备战蓝桥杯。

前言:浏览器与栈的纠缠

浏览器的前进、后退功能,我想你肯定很熟悉吧?

当你依次访问完一串页面a-b-c之后,点击浏览器的后退按钮,就可以查看之前浏览过的页面b和a。当你后退到页面a,点击前进按钮,就可以重新查看页面b和c。但是,如果你后退到页面b后,点击了新的页面d,那就无法再通过前进、后退功能查看页面c了。

假设你是浏览器的开发工程师,你会如何实现这个功能呢?

这就要用到我们今天要讲的“栈”这种数据结构。带着这个问题,我们来学习今天的内容。

如何理解“栈”?

关于“栈”,我有一个非常贴切的例子,就是一摞叠在一起的盘子。我们平时放盘子的时候,都是从下往上一个一个放;取的时候,我们也是从上往下一个一个地依次取,不能从中间任意抽出。 后进者先出,先进者后出,这就是典型的“栈”结构。

在这里插入图片描述

从栈的操作特性上来看, 栈是一种“操作受限”的线性表,只允许在一端插入和删除数据。

我第一次接触这种数据结构的时候,就对它存在的意义产生了很大的疑惑。因为我觉得,相比数组和链表,栈带给我的只有限制,并没有任何优势。那我直接使用数组或者链表不就好了吗?为什么还要用这个“操作受限”的“栈”呢?

事实上,从功能上来说,数组或链表确实可以替代栈,但你要知道,特定的数据结构是对特定场景的抽象,而且,数组或链表暴露了太多的操作接口,操作上的确灵活自由,但使用时就比较不可控,自然也就更容易出错。

当某个数据集合只涉及在一端插入和删除数据,并且满足后进先出、先进后出的特性,这时我们就应该首选“栈”这种数据结构

如何实现一个“栈”?

从刚才栈的定义里,我们可以看出,栈主要包含两个操作,入栈和出栈,也就是在栈顶插入一个数据和从栈顶删除一个数据。理解了栈的定义之后,我们来看一看如何用代码实现一个栈。

实际上,栈既可以用数组来实现,也可以用链表来实现。用数组实现的栈,我们叫作 顺序栈,用链表实现的栈,我们叫作 链式栈

基于数组的顺序栈

我这里实现一个基于数组的顺序栈。

// 基于数组实现的顺序栈
public class ArrayStack {
  private String[] items;  // 数组
  private int count;       // 栈中元素个数
  private int n;           //栈的大小

  // 初始化数组,申请一个大小为n的数组空间
  public ArrayStack(int n) {
    this.items = new String[n];
    this.n = n;
    this.count = 0;
  }

  // 入栈操作
  public boolean push(String item) {
    // 数组空间不够了,直接返回false,入栈失败。
    if (count == n) return false;
    // 将item放到下标为count的位置,并且count加一
    items[count] = item;
    ++count;
    return true;
  }

  // 出栈操作
  public String pop() {
    // 栈为空,则直接返回null
    if (count == 0) return null;
    // 返回下标为count-1的数组元素,并且栈中元素个数count减一
    String tmp = items[count-1];
    --count;
    return tmp;
  }
}

基于链表的链式栈

基于链表实现的链式栈的代码:

/*
 * 用链表作为栈的底层
 */
public class SingleLinkedListStack {
//  用链表作为栈的底层
	public SingleLinkedList<E> list;
	public SingleLinkedListStack() {
	             list = new SingleLinkedList();
}
	@Override
	public void push(E e) {
		// TODO Auto-generated method stub
		list.addFirst(e);
	}

	@Override
	public E pop() {
		// TODO Auto-generated method stub
		return (E) list.removeFirst();
	}

	@Override
	public E peek() {
		// TODO Auto-generated method stub
		return  list.getfirst();
	}

	@Override
	public int getSize() {
		// TODO Auto-generated method stub
		return list.getSize();
	}

	@Override
	public boolean isEmpty() {
		// TODO Auto-generated method stub
		return list.isEmpty();
	}
	@Override
	public String toString() {
		
		  StringBuilder sb=new StringBuilder(); sb.append("stack ");
		  sb.append("push:>");
		  for(int i=0;i<list.getSize();i++) {
			  sb.append(list.get(i));
			  sb.append("->");
		  }
		  sb.append("null");
		return sb.toString();
	}
	
}

了解了定义和基本操作,那它的操作的时间、空间复杂度是多少呢?

不管是顺序栈还是链式栈,我们存储数据只需要一个大小为n的数组就够了。在入栈和出栈过程中,只需要一两个临时变量存储空间,所以空间复杂度是O(1)。

注意,这里存储数据需要一个大小为n的数组,并不是说空间复杂度就是O(n)。因为,这n个空间是必须的,无法省掉。所以我们说空间复杂度的时候,是指除了原本的数据存储空间外,算法运行还需要额外的存储空间。

空间复杂度分析是不是很简单?时间复杂度也不难。不管是顺序栈还是链式栈,入栈、出栈只涉及栈顶个别数据的操作,所以时间复杂度都是O(1)。

解答开篇

好了,我想现在你已经完全理解了栈的概念。我们再回来看看开篇的思考题,如何实现浏览器的前进、后退功能?其实,用两个栈就可以非常完美地解决这个问题。

我们使用两个栈,X和Y,我们把首次浏览的页面依次压入栈X,当点击后退按钮时,再依次从栈X中出栈,并将出栈的数据依次放入栈Y。当我们点击前进按钮时,我们依次从栈Y中取出数据,放入栈X中。当栈X中没有数据时,那就说明没有页面可以继续后退浏览了。当栈Y中没有数据,那就说明没有页面可以点击前进按钮浏览了。

比如你顺序查看了a,b,c三个页面,我们就依次把a,b,c压入栈,这个时候,两个栈的数据就是这个样子:

在这里插入图片描述

当你通过浏览器的后退按钮,从页面c后退到页面a之后,我们就依次把c和b从栈X中弹出,并且依次放入到栈Y。这个时候,两个栈的数据就是这个样子:

在这里插入图片描述

这个时候你又想看页面b,于是你又点击前进按钮回到b页面,我们就把b再从栈Y中出栈,放入栈X中。此时两个栈的数据是这个样子:

在这里插入图片描述

这个时候,你通过页面b又跳转到新的页面d了,页面c就无法再通过前进、后退按钮重复查看了,所以需要清空栈Y。此时两个栈的数据这个样子:

在这里插入图片描述

  • 65
    点赞
  • 63
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 87
    评论
KODExplorer是款开源的Web在线文件管理、代码编辑器。它提供了类windows经典用户界面,一整套在线文件管理、文件预览、编辑、上传下载、在线解压缩、音乐播放功能。让你直接在浏览器实现web开发、源码文件预览、网站部署的同时拥有与本地操作一样方便、快捷、安全的体验。 美取代落后的FTP工具:可用于服务器文件管理,支持图片、音乐、视频预览,在线解压缩,文件夹拖拽上传……。 在线编程:支持几乎所有编程语言的在线编辑、代码自动补全(高亮,多光标编辑.堪比本地的sublime) 极佳的操作体验:极其便捷的快捷键支持,让你拥有本地化的体验 中文等多语言支持:中文编码全面兼容,文件编辑自动适配。 超快的速度:全面采用Ajax+Json进行数据通信,毫秒级的响应速度; 全平台兼容性:Win Linux Mac (Apache、Nginx、IIS) 使用场景: 取代古老的FTP,服务端、客户端软件等复杂的安装配置。kod可以一键安装随处使用. 你可以用它来管理你的服务器(备份,在线解压缩,版本发布....) 你可以把他当做管理linux的一个操作系统界面 可以用来作为私有云存储系统,存储你的文件... 当然你也可以用来分享文件 更多场景等你来挖掘!…… 设计理念 传承经典,追求创新,为用户提供方便快捷、安全易用的在线云管理系统。 面向用户 目前KODExplorer系统管理主要定位在个人云主机、中小企业云资源管理、网盘管理、中小型网站管理等。Web开发者&站长(老鸟):在线编辑、压缩备份、部署,经典windows界面操作,上手容易,远离了主机的SSH、ftp复杂枯燥的命令操作。 个人私有云(菜鸟):管理网盘资源,同样经典windows界面操作,可以就地浏览网盘音乐、视屏文件,上传下载快捷方便。 特色 像使用操作系统一样使用体验,右键操作,拖拽,快捷键…… 框中选择,拖拽移动,拖拽上传,在线编辑器,影音播放器,解压缩。全面ajax保证性能和体验! 各个功能直接无缝连接;以对话框形式存在,多任务管理等功能 备的中文支持,各种情况下乱码解决; 文件管理 文件选择:单选,鼠标框选,shift连选,ctrl随意选择,键盘上下左右、home、end选择。 文件操作:选择文件后,可以进行复制,剪切,删除,属性查看,压缩,重命名,打开预览等操作…… 文件上传:多文件批量上传;html5拖拽上传(拖拽到窗口实现无缝上传) 右键功能:文件右键,文件夹右键,多选后右键操作,桌面右键,树目录右键操作,右键菜单绑定快捷键 (全选——复制——剪切——粘贴——删除——重命名,设置……) 文件浏览:列表模式,图标模式;双击进入子文件夹;地址栏操作;打开文件夹记录逆势操作记录(前进后退) 支持拖拽操作:选中后拖拽,实现剪切到指定文件夹功能 快捷键操作:delete删除,ctrl+A全选,ctrl+C复制,ctrl+X剪切,up/down/left/right/home/end选择文件 在线预览 文件预览:文本文件内容查看编辑保存;html,swf文件预览, 图片预览:自动生成缩略图,图片幻灯片播放; 音频播放:在线播放音乐,视频文件;支持mp3,wma,mid,aac,wav;mp4, 视频播放:在线视频文件播放,支持格式:flv,f4v,3gp 在线编辑 支持60多种代码(数据文件)高亮 支持多标签:同时编辑多份文件,拖动标签可以切换顺序;支持最大化模式 主题切换:选择你喜欢的编程风格 zendcodeing支持,从此爱上在线编程 查找、替换;撤销反撤销,维持历史记录 自动补全[],{},"",';自动换行,自定义字体,代码折叠等诸多实用功能 文件管理器:可以像使用本地我的电脑那样使用它 文件编辑器:支持几乎所有编程语言高亮,支持文档多标签。
KODExplorer是款开源的Web在线文件管理、代码编辑器。它提供了类windows经典用户界面,一整套在线文件管理、文件预览、编辑、上传下载、在线解压缩、音乐播放功能。让你直接在浏览器实现web开发、源码文件预览、网站部署的同时拥有与本地操作一样方便、快捷、安全的体验。 美取代落后的FTP工具:可用于服务器文件管理,支持图片、音乐、视频预览,在线解压缩,文件夹拖拽上传……。 在线编程:支持几乎所有编程语言的在线编辑、代码自动补全(高亮,多光标编辑.堪比本地的sublime) 极佳的操作体验:极其便捷的快捷键支持,让你拥有本地化的体验 中文等多语言支持:中文编码全面兼容,文件编辑自动适配。 超快的速度:全面采用Ajax+Json进行数据通信,毫秒级的响应速度; 全平台兼容性:Win Linux Mac (Apache、Nginx、IIS) 使用场景: 取代古老的FTP,服务端、客户端软件等复杂的安装配置。kod可以一键安装随处使用. 你可以用它来管理你的服务器(备份,在线解压缩,版本发布....) 你可以把他当做管理linux的一个操作系统界面 可以用来作为私有云存储系统,存储你的文件... 当然你也可以用来分享文件 更多场景等你来挖掘!…… 设计理念 传承经典,追求创新,为用户提供方便快捷、安全易用的在线云管理系统。 面向用户 目前KODExplorer系统管理主要定位在个人云主机、中小企业云资源管理、网盘管理、中小型网站管理等。Web开发者&站长(老鸟):在线编辑、压缩备份、部署,经典windows界面操作,上手容易,远离了主机的SSH、ftp复杂枯燥的命令操作。 个人私有云(菜鸟):管理网盘资源,同样经典windows界面操作,可以就地浏览网盘音乐、视屏文件,上传下载快捷方便。 特色 像使用操作系统一样使用体验,右键操作,拖拽,快捷键…… 框中选择,拖拽移动,拖拽上传,在线编辑器,影音播放器,解压缩。全面ajax保证性能和体验! 各个功能直接无缝连接;以对话框形式存在,多任务管理等功能 备的中文支持,各种情况下乱码解决; 文件管理 文件选择:单选,鼠标框选,shift连选,ctrl随意选择,键盘上下左右、home、end选择。 文件操作:选择文件后,可以进行复制,剪切,删除,属性查看,压缩,重命名,打开预览等操作…… 文件上传:多文件批量上传;html5拖拽上传(拖拽到窗口实现无缝上传) 右键功能:文件右键,文件夹右键,多选后右键操作,桌面右键,树目录右键操作,右键菜单绑定快捷键 (全选——复制——剪切——粘贴——删除——重命名,设置……) 文件浏览:列表模式,图标模式;双击进入子文件夹;地址栏操作;打开文件夹记录逆势操作记录(前进后退) 支持拖拽操作:选中后拖拽,实现剪切到指定文件夹功能 快捷键操作:delete删除,ctrl+A全选,ctrl+C复制,ctrl+X剪切,up/down/left/right/home/end选择文件 在线预览 文件预览:文本文件内容查看编辑保存;html,swf文件预览, 图片预览:自动生成缩略图,图片幻灯片播放; 音频播放:在线播放音乐,视频文件;支持mp3,wma,mid,aac,wav;mp4, 视频播放:在线视频文件播放,支持格式:flv,f4v,3gp 在线编辑 支持60多种代码(数据文件)高亮 支持多标签:同时编辑多份文件,拖动标签可以切换顺序;支持最大化模式 主题切换:选择你喜欢的编程风格 zendcodeing支持,从此爱上在线编程 查找、替换;撤销反撤销,维持历史记录 自动补全[],{},"",';自动换行,自定义字体,代码折叠等诸多实用功能 文件管理器:可以像使用本地我的电脑那样使用它 文件编辑器:支持几乎所有编程语言高亮,支持文档多标签。
# 单页面和多页面开发及应用 # 单页面: - 什么是单页面:单页面是指只有一个主页面的应用,浏览器一开始要加载所有必须的 html, js, css。所有的页面内容都包含在这个所谓的主页面中。但在写的时候,还是会分开写(页面片段),然后在交互的时候由路由程序动态载入。 - 跳转仅加载一次,用于PC和公务网站 - 页眉 -> 第一页,第二页, 第三页 -> 脚本 # 单页面技术: -AngularJS是一个全面的客户端侧框架。其模板基于双向UI数据绑定。数据绑定是一种自动方法,在模型改变时更新视图,以及在视图改变时更新模型。其HTML模板在浏览器中编译。编译步骤创建纯HTML,浏览器将其重新渲染到实时视图。该步骤会在随后的页面浏览中重复。在传统的服务器端HTML编程中,控制器和模型等概念在服务器进程中进行交互以产生新的HTML视图。在AngularJS框架中,控制器和模型状态在客户端的浏览器中维护,从而使生成新页面不依赖与服务器的交互。 -Ember.js是基于模型-视图-控制器(MVC)软件架构模型的客户端侧JavaScript Web应用程序框架。它允许开发人员在一个框架中通过常用的习惯用语和最佳实践来创建可伸缩的单页面应用程序。该框架提供丰富的对象模型、声明性双向数据绑定、计算属性,Handlebars.js提供的自动更新模板,以及一个路由器管理应用程序状态。 -Meteor.js是一个专门为单页应用设计的全(客户端-服务器)JavaScript框架。它具有比Angular、Ember或ReactJS更简单的数据绑定特性且使用--Distributed Data Protocol和一个发布/订阅来自动将数据更改传播到客户端,无需开发人员编写任何同步代码。全反应确保从数据库到模板的所有层都可以在必要时自动更新。诸如服务器端渲染等生态系统包则解决搜索引擎优化(SEO)等问题。 -Aurelia是一个适用于移动设备、桌面和网页的JavaScript客户端框架。它类似AngularJS,但更新、更符合标准,并采用模块化举措。Aurelia使用下一代ECMAScript编写。 -Vue.js(通常称为Vue)是一个用于构建用户界面的开源渐进式JavaScript框架。 -React(通常写为React.js或ReactJS)是一个构建用户界面的JavaScript函式庫。它由Facebook、Instagram和个人开发者以及企业社区维护。React最大的优势是易于使用——基本上任何熟悉HTML的开发人员都可以创建React应用程序。另一个所称的优势是可能使用相同的技术堆来同时创建Web与移动应用程序。有多家公司使用React和Redux库来让开发人员创建复杂但可扩展的Web应用程序。 -Fulcro是一个全库,它采用Netflix的Falcor,Facebook的Relay和Om Next对反应性,功能性,数据驱动软件进行改编的数据驱动原则。 -单页面做seo(搜索引擎优化): -根据部分进行优化为每个关键字创建一个<div>或<section>或每个关键字并为其分配一个适当的ID,该页面上的内部链接将链接至ID。 -优化页面速度对于单页网站SEO来说至关重要。 - 做了seo 以后,单页面还是单页面 # 单页面优点: -减小服务器压力。 如果不用单页面(spa),如果每次切换页面的时候,都向服务器发送一个请求,服务器返回一个html文件;但是如果使用了单页面,在切换时,不需要请求服务器,只要通过本地的js来切换即可。并且服务器端就不需要配置路由,全做到了前后端分离 -增强用户体验,增加app的使用流畅性。 使用spa之后,页面在切换的时候非常流畅,全没有那种不断刷新的感觉,而是非常快的就有了响应,因为js运行速度很快,所以js在做本地路由的时候,就会非常快。 -单页应用没有页面之间的切换,就不会出现“白屏现象”,也不会出现假死并有“闪烁”现象。 -单页应用相对服务器压力小,服务器只用出数据就可以,不用管展示逻辑和页面合成,吞吐能力会提高几倍。 -良好的前后端分离。后端不再负责模板渲染、输出页面工作,后端API通用化,即同一套后端程序代码,不用修改就可以用于Web界面、手机、平板等多种客户端。 -单页面缺点: -首次加载耗时比较多。 解决办法:可以采用基于HTTP Chunk 的首屏数据渐进式预加载方案,该方案总体减少了单页应用1.2s的首屏呈现时间。首屏数据渐进式预加载的优化思路也得到了体现: 优化首屏数据加载节点的速度。 预先加载首屏数据,使得多个串行节点并行化。 -SEO问题,不利于百度,360等搜索引擎收录。 解决办法:可以采用prerender服务,它拿到请求够,直接在服务端的一个js engine里,运行这个HTML(就像浏览器做的那样),直到内容动态填之后,再广播一个事件告诉phantomjs“内容已经好了,可以返回给爬虫了”,这样爬虫就拿到了一份整的HTML,就和在浏览器里渲染的一样。目前流行的框架都有对应的服务器渲染框架 -如有造成Css命名冲突。 解决办法:我们可以使用Sass、LESS和Stylus等CSS预处理器,在一定程度上可以解决该问题。 -前进后退、地址栏、书签等,都需要程序进行管理,页面的复杂度很高,需要一定的技能水平和开发成本高。 # 多页面: -什么是多页面:每一次页面跳转的时候,后台服务器都会给返回一个新的html文档,这种类型的网站也就是多页网站,也叫做多页应用 - 跳转需要刷新所有资源,用于app 或 客户端 - 页眉 -> 第一页 -> 脚本,页眉 -> 第二页 -> 脚本,页眉 -> 第三页 -> 脚本 -多页面优点:给用户提供一个美的视觉方向,重点是没有很多的菜单,简洁明了的SEO管理。由于可以针对每页一个关键字优化应用程序 -多页面缺点:后端和移动客户端不能同时使用,前端和后端开发紧密结合。开发变得相当复杂。开发人员需要为客户端和服务器端使用框架。这导致应用程序开发时间更长。 # 区别: -组成:单页面由一个外壳页面和多个页面组成, 多页面由多个页面组成 -css(资源公用):单页面为共用,只需要外壳部分加载,多页面则不共用,每个页面单独加载 -刷新方式:单页面页面局部更改或刷新,多页面整页刷新 -url模式:单页面 a.com/#/pageone 多页面 a.com/pageone.html -用户体验:单页面页面切换快,用户体验良好,多页面加载缓慢,流畅度不够,用户体验较差 -转场动画:单页面容易实现,多页面无法实现 -数据传递:单页面容易,多页面需要依赖url传参,或cookie,localSrorage等 -搜索引擎优化:单页面需要单独方案,实现较为困难,不利于SEO搜索,可利用与SSR优化,多页面实现方法容易 -适用范围:单页面高要求的体验度,追求页面的流畅,多页面追求高度支持搜索引擎的应用 -开发成本:单页面较高,需要专业的框架,多页面较低,但页面重复代码较多 -维护成本:单页面相对容易,双页面相对复杂

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 87
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

黑洞晓威

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值