从 0 到 1 开发微信小程序(一):初步认识微信小程序的结构

从 0 到 1 开发微信小程序(一):初步认识微信小程序的结构

这是「从 0 到 1 开发微信小程序」系列的第一篇,我是字节莫。

鉴于是光速入门指南,这里就不涉及微信小程序的运行原理等高深的东西。经过两年的发展,微信小程序的框架已经非常地完善,如果不是一些很底层的需求(如性能优化等),是根本不需要深入到这种程度的。

微信小程序的结构

一个微信小程序是由一个一个的页面(page)组成的,就如同 Windows 程序是由一个一个的窗口组成的一样。开发微信小程序其实就是设计和实现各个页面,然后再实现各个页面间的跳转和交互逻辑。除此之外,还有几个全局文件来控制小程序的某些全局属性(如标题颜色、窗口背景颜色、下方导航栏样式等)。

小程序的全局文件

小程序主要有 app.jsonapp.wxssapp.jsproject.config.json 这四个全局文件。

app.json

app.json 是小程序的全局配置文件,它主要控制的是小程序有哪些页面、页面路径、顶部导航栏(navigationBar)是怎样的、底部的选项栏(tabBar)是怎样的等等。

下方是来自官方文档的一份配置示例:

{
  "pages": ["pages/index/index", "pages/logs/index"],
  "window": {
    "navigationBarTitleText": "Demo"
  },
  "tabBar": {
    "list": [
      {
        "pagePath": "pages/index/index",
        "text": "首页"
      },
      {
        "pagePath": "pages/logs/logs",
        "text": "日志"
      }
    ]
  },
  "networkTimeout": {
    "request": 10000,
    "downloadFile": 10000
  },
  "debug": true,
  "navigateToMiniProgramAppIdList": ["wxe5f52902cf4de896"]
}

上方的示例添加了indexlogs两个小程序页面;配置了上方导航栏中显示的文字为“Demo”;为下方的选项栏添加了“首页”、“日志”两个选项卡以及它们跳转的页面路径;配置了小程序网络连接的超时时间;启用了 debug 模式;配置了可以跳转到的第三方小程序名单。

我们常用到的配置项会在本系列的第二篇「(二):小程序的基本配置 」中详细说到,因而在此暂且按下不表。

如果实在有兴趣,可以到官方文档中查看关于 app.json 详细的可配置选项。

app.wxss

WXSS(WeiXin Style Sheets)是一套样式语言,用于描述 WXML 的组件样式。

WXSS 用于控制小程序页面组件的样式,可以认为 WXSS 是 CSS 的魔改版。对于一些多个页面都使用到的样式,将其抽离出来作为公共样式,就产生了 app.wxss 全局样式表文件。

app.js

小程序中除了页面的组件和样式,还需要有用户交互逻辑控制,如按下某个按钮跳转至某个页面、长按某个列表项弹出操作菜单等,都属于此。在微信小程序中,逻辑交互控制部分是由 JavaScript 代码来控制的,也就是全局和页面中都包含的 .js 文件。

在全局逻辑代码文件 app.js 中,主要进行小程序注册工作(类似于初始化一个对象),以及获取一些小程序要用到的全局数据等,如获取设备状态栏高度、设备屏幕宽高度、系统信息等。

关于数据绑定与更新、小程序生命周期等这部分的详细内容在后面本系列的第五篇「(五):认识 JavaScript 与第一个小程序页面的逻辑交互 」中会讲到,在此就先不赘述。

project.config.json

project.config.json 是小程序项目的配置文件,里面配置了小程序的名称、AppID、小程序依赖的最低基础库版本等信息。

除此之外,里面还配置了该项目微信开发者工具的外观以及代码编译上传选项等信息,当重新安装了开发工具或者在另一台设备中打开同一个项目时,这些选项就会自动恢复成你的自定义配置。这样做的目的是保证同一个项目在不同的设备中有同样的开发体验。

小程序页面的文件组成

假设微信小程序中有一个页面demo

demo.wxml

从事过网页编程的人知道,网页编程采用的是 HTML + CSS + JS 这样的组合,其中 HTML 是用来描述当前这个页面的结构,CSS 用来描述页面的样子,JS 通常是用来处理这个页面和用户的交互。

同样道理,在小程序中也有同样的角色,其中 WXML 充当的就是类似 HTML 的角色。

每一个页面就像一个网页,自然就需要一个文件来描述页面的元素和组成。而demo.wxml就是用来描述小程序页面构成的文件。与 wxss 和 css 之间的关系相似,wxml 也可以认为是 html 的魔改版,其大部分特性都和 html 相似,但也有许多不同的地方。关于 wxml 详细的内容我们会在在本系列的第三篇「(三):认识 wxml 与常用小程序组件」中讲到,在此就不再赘述,只需了解其在构成微信小程序页面中起到的作用即可。

demo.wxss

上文中已经讲到了 wxss 的作用,有区别的是,app.wxss 对小程序所有页面起作用,而单个页面的 wxss 只对该页面起作用,就像编程中全局变量和局部变量的关系一样。另外,页面 wxss 能够覆盖全局 wxss 中的样式。关于 wxss 的详细内容将在本系列的第四篇「(四):认识 wxss 与第一个小程序页面 」中详细讲述。

demo.js

对于一个小程序而言,只有一个静态的页面是无法提供服务的。因此,还需要具备逻辑交互能力来响应用户对页面的操作,如用户点击按钮、用户滑动滑块条等等。而 JavaScript 脚本正是用勒解决这一问题的。相关内容在前文中已有简略叙述,具体的内容将在本系列的第五篇「(五):认识 JavaScript 与第一个小程序页面的逻辑交互 」中详细讲述。

demo.json

上文中讲到小程序全局配置文件app.json中配置了小程序的一些全局选项,如小程序页面路径等。那么同理,对于页面的 JSON 配置文件而言,自然就是用于配置这一页面的一些属性了。

页面 JSON 配置文件用于配置页面的一些属性,如顶部导航栏颜色、顶部导航栏文字、该页面是否允许下拉刷新、上拉加载等等。页面配置可以覆盖全局配置。

关于页面配置的详细内容将在本系列的第二篇「(二):小程序的基本配置 」中详细讲述。

总结

通过上文的讲述,我们知道:一个小程序有四个全局配置文件: app.jsonapp.wxssapp.jsproject.config.json ;小程序由多个页面组成,每个页面由四个文件来描述:page.wxmlpage.wxsspage.jspage.json

至此,我们已经对一个典型小程序的文件组成,以及它们各自的作用有了一定的了解。综上,一个常见小程序项目的文件结构如下:

Miniprogram Project/                    小程序项目目录
├── cloudfunctions                      云函数目录
│   └── login
│       ├── index.js
│       └── package.json
├── Miniprogram                         小程序目录
│   ├── app.js                          小程序全局逻辑代码
│   ├── app.json                        小程序全局配置文件
│   ├── app.wxss                        小程序全局样式表文件
│   ├── components                      自定义组件目录
│   │   └── navigationBar               自定义组件
│   │       ├── navigationBar.js
│   │       ├── navigationBar.json
│   │       ├── navigationBar.wxml
│   │       └── navigationBar.wxss
│   ├── images                          图片文件目录
│   │   ├── img1.png
│   │   ├── img2.png
│   │   └── img3.png
│   └── pages                           小程序页面目录
│       ├── about                       页面
│       │   ├── about.js                页面逻辑代码
│       │   ├── about.json              页面配置文件
│       │   ├── about.wxml              页面结构文件
│       │   └── about.wxss              页面样式表文件
│       └── index
│           ├── index.js
│           ├── index.json
│           ├── index.wxml
│           ├── index.wxss
│           └── user-unlogin.png
└── project.config.json                项目与环境配置(AppID、小程序名称、基础库版本等配置)

最后

第一次写一个系列,同时我也只是业余的小程序开发,文章难免会存在错漏或是不足之处,请务必要指正,不吝感激!

下一篇:「 从 0 到 1 开发微信小程序(二):小程序的基本配置 」

点击量:22

从 0 到 1 开发微信小程序(零):序章

这是「从 0 到 1 开发微信小程序」系列的第零篇,我是字节莫。

前言

背景

最近我在研究微信小程序,作为从未接触过前端开发的小白,几天时间,从 html 都不懂到能写出令自己满意的小程序(水准不算太低,自定义组件、动画、云开发什么的都用上了),一路上坎坎坷坷走了不少弯路,也积累了一些经验。在这里分享出来,希望能帮助微信小程序初学者少走一些弯路、少踩一些坑。

目标人群

这是一份写给从未接触过前端开发的朋友的微信小程序光速入门指南

对于拥有前端开发基础的朋友,大可不必看这份指南。如果实在有兴趣,可以掠过讲前端知识的部分,直接看有关微信小程序独有部分的讲解(一般在各篇文章的后半部分)。微信小程序其实与 H5 异曲同工,或者可以说是魔改版的 H5 。

所谓 “光速入门” ,我写这个系列的初衷是为了帮助没有前端基础的小程序初学者快速入门小程序开发,让他们避免一些小程序的坑,少走一些弯路;所谓 “从 0 到 1” ,即在这个过程中只有选择性地学习小程序开发过程中常用到的一些前端知识而不完整地学完网页开发三剑客,至于如果所学尚不足以实现需求、想要完成从 1 到 100 的过程,就要靠自己系统性的学习前端知识和不懈的努力了,因为后面的路是专业前端的路,已经和微信小程序本身没有太大关系了。

微信小程序的前景

以我愚见,我认为即使是在如今微信和苹果在小程序 / 小游戏问题上无法达成有效共识、造成小程序的能力限制比较大(比如 iOS 端无法调用虚拟支付等)的情况下,微信小程序仍然是比较理想的跨平台方案。尤其是对个人开发者而言。

对微信生态玩家而言,当公众号有限的用户交互和服务形态在触及微信生态的天花板后,微信小程序显然能成为扩展边界的力量,这是其一;对于个人开发者而言,开发简单、轻量级、跨平台、无需下载安装、无需更新的 “App / 游戏” ,这在开发个 App 还要提交一堆的应用商店的五六年前来看,简直就是不敢想象的事情,这是其二;对用户而言,频次不高、轻量化的需求不必在手机里保留一个 app ,微信小程序点开即用、用完即走,无疑是非常理想的存在形态,这是其三。

以上就是我看好小程序的三大主要原因。

写作计划

本系列大致按照如下部分进行讲述,写作计划可能会在写作的过程中进行调整。下方的目录随时更新,已有超链接的部分代表对应的文章已经发布,可以点击阅读。

  • (一):初步认识微信小程序的结构
  • (二):小程序的基本配置
  • (三):认识 wxml 与常用小程序组件
  • (四):认识 wxss 与第一个小程序页面
  • (五):认识 JavaScript 与第一个小程序页面的逻辑交互
  • (六):数据绑定、条件渲染与列表渲染
  • (七):实现小程序动画效果
  • (八):自定义组件开发样例:自定义顶部导航栏
  • (九):使用「小程序 · 云开发」实现小程序的后端
  • (十):小程序性能优化探索

上述的内容非常基础,但也已经涉及了开发一款小程序的绝大部分常见情形。如果把前九篇学完,就已经能够满足很多小程序的 开发需求了。

建议学习方式

微信小程序并不难,看别人写更是会觉得简单,但是当真正上手的时候还是会遇到不少坑。因此,建议大家在学完某项内容之后一定要多动手实践,注册个微信小程序并不是什么难事,或者也可以直接使用官方提供的测试账号(微信开发者工具中新建项目时可以生成临时性的测试账号)。

最重要的一点是:注重查阅官方文档注重查阅官方文档注重查阅官方文档!随着两年的发展,小程序已经拥有了较为完备的体系,所有的东西都能在官方文档中找到。同时,官方文档的更新永远是最及时的,所有新开放的小程序新能力都会第一时间出现在官方文档中。每当我想实现一个功能时候,都是到官方文档中去查阅这个功能能不能实现、实现方式、API 数据格式等等。

微信小程序官方开发文档传送门

最后

第一次写一个系列,同时我也只是业余的小程序开发,文章难免会存在错漏或是不足之处,请务必要指正,不吝感激!

下一篇:「 从 0 到 1 开发微信小程序(一):初步认识微信小程序的结构 」

点击量:4

微信 7.0 更新带来的新机遇

微信更新了,除了UI之外,很重要的一个变化就是「时刻视频」和公众号文章点赞(好看)后会被推荐到「看一看」列表。

先说第一个,时刻视频。这是微信在小视频领域的反击和尝试,在朋友圈、群、通讯录列表等都为时刻视频提供了入口,对很多微商而言或许是利好消息。当前所呈现的短期数据无法看出来什么,还需要持续的观察。

再说第二个,点赞变成「好看」。经过这样的更新之后,看一看中能够看到自己关注的公众号的文章,和好友点了赞的文章。看起来和公众号的信息流页面存在一定程度的重合,但是换一个角度来想的话,又可以理解为公众号的一个新的流量入口

现在都说微信公众号的红利期已经过去,但这次的更改似乎又带来了新的机会。看一看列表中展示了朋友点击了好看的文章,点击好看这个操作是轻而易举的事情,但是却带来了以往转发朋友圈的效果,将从前转发朋友圈所需的 3 步操作缩减为一次点击,大大提高了文章在横向社交关系链中的传播效率,有望成为公众号新的一波流量增长点。虽然不至于达到逆天改命的程度,但依然能够给微信公众号从业者带来一丝丝希望。

换一方面看,从前,用户给文章点赞是匿名的,而现在用户知道自己的好友能看到自己给哪些文章点过好看,匿名行为变成公开行为了,那么会在一定程度上遏制用户点赞。但是此举带来等正面效应是,用户在点好看之前,会考量文章的质量。因此,此举也在某种意义上改善了传播的内容质量,一些标题党、博眼球的文章将很难通过这种方式传播,在某种程度上也是在通过优胜劣汰的方式改善内容质量。

内容行业的红利期已经过去,接下来就是内容为王的时代。复制粘贴再也无法带来成功,需要的是内容创作者持之以恒地输出原创、优质的内容。这一次的「看一看」更新,就能够看出微信在鼓励原创、提升内容质量上的决心,持续产出原创、优质的内容才是王道。

经过互联网十年的野蛮生长,当今巨头林立的局面暂时不会再有太大变数。大的局面短时间内不会再有变化,但小的变化仍每时每刻都在发生,就比如本文中说的「时刻视频」和「看一看」更新等。变化虽小,但其中蕴含的机遇和生机却足以让自身能量渺小的个体在一棵棵参天大树的缝隙里,触得那一缕透过枝叶的阳光。

如果你看得见变化,那么变化就存在。


欢迎关注公众号「字节莫」,探讨更多技术、人文的思考,一起进步!

点击量:9

小米笔记本Air 13.3 指纹版安装黑苹果 macOS High Sierra 10.13 教程

注意:本教程只适用于「小米笔记本Air 13.3 指纹版 i5-7200U」,别的型号哪怕是小米笔记本Air 13.3 非指纹版或者小米笔记本Pro都会存在问题。且安装新系统这件事本身存在风险,有可能造成数据丢失,请谨慎尝试!本教程不对读者造成的任何问题负责!

如果是只有 CPU 不一样但是集成显卡一样的同系列产品,如小米Air指纹版 i7-7500U,因为 i5-7200U 和 i7-7500U 的集成显卡都是 HD620 ,理论上是可以适用我提供的 EFI 文件的。但是不一定能成功(因为可能有别的差异)。

本教程默认读者有一定的计算机基础,一些步骤就不详细放出图片,如果有问题请在下方评论。

1. 准备安装工具

  • 下载黑苹果镜像(此处感谢 @黑果小兵 大神制作的黑苹果镜像):点击下载
  • 下载写盘工具(若有其他代替也可):Etcher
  • 下载磁盘管理工具(若有其他代替也可):DiskGenius
  • 下载我制作好的小米笔记本Air13.3 指纹版专用EFI文件:点击下载

2. 安装前准备

2.1 将镜像写到U盘

打开Etcher,点击「Select image」,选择刚刚下载好的镜像,插入U盘,点击「Flash」,等待结束即可。

2.2 将硬盘分出一个区来安装MacOS

建议分出一个60G以上的区,才不会日后捉襟见肘,如果要安装很多大型软件的话就要更大。这一步的时候,留意硬盘里的ESP分区有没有超过200MB,如果没有,请自行参照网上的方法将ESP分区扩大到200MB以上,否则无法安装MacOS!ESP分区扩容的办法此处就不再赘述。如果无法解决这个问题,请在下方评论,我会单独和你讨论。

2.3 用下载好的EFI镜像替换U盘中的EFI镜像

打开「DiskGenius」,点开U盘的ESP分区,删除「EFI」文件夹,然后将下载好的「EFI.zip」解压出来的「EFI」文件夹替换进去,放到和原来的「EFI」文件夹同样的位置。

至此,一切准备就绪,可以开始了。

3. 开始安装MacOS

3.1 修改启动顺序

  • 进入BIOS
  • (如果你还没有设置管理员密码),进入「Security」,选择「Set Supervisor Password」,输入两次你要设置的密码,点「YES」,完成超级管理员密码的设置
  • (如果你还没有禁用安全启动),选择「Secure Boot Mode」,设置为「Disabled」,即可禁用安全启动
  • 修改UEFI启动顺序,将U盘启动项「EFI USB Device」调到到第一位
  • 重启

如果没有问题的话,将顺利进入引导选择页面。

3.2 准备安装

在引导选择页面,没有意外的话将能够看到连个选项,一个Windows、一个苹果。选择苹果,敲下回车进入。

耐心等待加载结束,进入图形界面,语言选择为简体中文。

进入「磁盘工具」,找到刚才的分出来的用于安装MacOS的分区,将其抹掉,格式为「Mac OS扩展(日志型)」

格式化结束之后,退出磁盘管理工具,选择「安装macOS」

然后按照指示一步一步操作,进行到选择磁盘页面时,选择刚刚抹掉的那个磁盘。

然后开始安装。安装的过程中会多次重启,这些都不是问题,只需要耐心等待就好。完了之后将进入设置向导。

接下来按照指示一步步操作,一些设置或者选项需要自己权衡,我也帮不了你。直到进入系统。

4. 收尾工作

别看时收尾工作,其实这收尾工作的重要性并不比前面几步差,如果这一步没有做好,那么前面所有辛辛苦苦的功夫都将浪费。

这一步我们需要做的是将引导写进本地硬盘,实现本地引导开机。(刚刚是在U盘里,也就是开机必须依赖U盘)。

在Windows下操作,和将EFI文件放进U盘一样的步骤,只是放的对象为本地硬盘里的ESP分区而已,参见前面的步骤。

完善Mac系统:

以下是一些安装好后能可能用到的工具(使用方法想必不用我多说了吧):

后记

小米Air 13.3 指纹版装黑苹果的教程网上不多,要么链接失效,要么没有效果。经过整整两天的折腾,终于装上了黑苹果。再经过一天半的折腾,期间尝试了无数的方法、整合了整整3份驱动,才一一解决了不能外接显示器、没声音、不能调整屏幕亮度等等问题。自己的努力收到了成效的那种感觉,无比开心。

我摸着石头过了河,后来者就可以直接享受前人摸索好的道路了。如果还碰到问题,请在下面评论,我们一起探讨解决~

谢谢各位~

由于安装过程无法复现,本教程部分图片来源于

https://blog.daliansky.net/MacOS-installation-tutorial-XiaoMi-Pro-installation-process-records.html


欢迎关注公众号「字节莫」,探讨更多技术、人文的思考,一起进步!

点击量:1588

基于哈夫曼编码的压缩算法的Python实现

1.背景

离散数学老师布置了一份大作业,作业题目就是用自己喜欢的编程语言来实现课上所学的哈夫曼编码算法(Huffman Coding)。哈夫曼编码是一种采用变长编码表来表示数据的编码方式。其详细介绍详见下方引自维基百科的引文。

在计算机数据处理中,霍夫曼编码使用变长编码表对源符号(如文件中的一个字母)进行编码,其中变长编码表是通过一种评估来源符号出现机率的方法得到的,出现机率高的字母使用较短的编码,反之出现机率低的则使用较长的编码,这便使编码之后的字符串的平均长度、期望值降低,从而达到无损压缩数据的目的。

也就是说,通过采用不等长的编码方式,将出现频率高的符号用相对短的比特串表示、出现频率低的符合以相对长的比特串表示,能够缩短表示完整源数据所需要的总比特长度,从而达到无损压缩数据的效果。

2.哈夫曼树及哈夫曼编码

2.1哈夫曼树

哈夫曼编码基于哈夫曼树(Huffman Tree)来实现,哈夫曼树是将符号出现的频率作为叶子的权值所构建一棵二叉树。我们以一个例子来详细解释哈夫曼树。

有这么一句话:“This is a test str”

字符 T h i s a e t r
字符频率 1 1 2 4 1 1 3 1

 

上表就是这句话中各个字符出现的频率统计(由于空格的表示效果不好,因此此示例中空格忽略不计)。

哈夫曼编码的过程主要如下:

2.1 对权值排序,取最小的两个生成节点

权值的顺序如下:

1(T)、1(h)、1(a)、1(e)、1(r)、2(i)、3(t)、4(s)

取T、h,生成一个小二叉树:

两个子节点生成的父节点的权值为两个子节点之和

2.2 重新对所有根节点的权值进行排序并生成父节点

没有父节点的节点为根节点。例如上一步骤中的“T”“h”节点有父节点,那么它们就不是根节点,不参与排序。重新对所有根节点进行排序如下:

1(a)、1(e)、1(r)、2(T、h)、2(i)、3(t)、4(s)

生成的二叉树如下:

本步骤生成的为两棵孤立的二叉树,但是最终回合并为一棵完整的二叉树

2.3 重复进行排序、生成父节点,直到只剩下一个根节点

反复进行排序和生成父节点直到只剩下一个根节点,我们可以构建出这样一棵二叉树:

基于字符频率权值构建的二叉树(哈夫曼树)

我们定义每个结点的左子节点编号为0,右子节点编号为1,那么可以得到如下的编码表:

字符 编码
T 0010
h 1010
i 111
s 01
a 0011
e 1011
t 00
r 110

 

那压缩的效果如何?我们来对比一下压缩前后表示这个字符串所需要的比特串长度:

压缩前(ASCII编码):

一个字符编码长度(8比特位)× 字符数量(14)= 112比特

压缩后:

T(4)+ h(4)+ a(4)+ e(4)+ r(3)+ i(3×2)+ t(2×3)+ s(2×4)= 39比特

压缩的效果显而易见。
(基于哈夫曼编码的压缩算法并非对所有源数据都能起到同样的压缩效果,这一点我们会在后面讲到)

3.基于哈夫曼编码的压缩算法的Python实现

基于以上的哈夫曼编码算法,我们可以实现自己的压缩算法。

3.1 定义哈夫曼树的节点类

为了使思路更加清晰、有助于算法实现,我们可以将单个节点定义为一个类,从而大大简化了二叉树的维护机制。

class node(object):

    def __init__(self,value = None,left = None,right = None,father = None):
        self.value = value
        self.left = left
        self.right = right
        self.father = father

    def build_father(left,right):
    	n = node(value = left.value + right.value,left = left,right = right)
    	left.father = right.father = n
    	return n

    def encode(n):
    	if n.father == None:
    		return b''
    	if n.father.left == n:
    		return node.encode(n.father) + b'0'		#左节点编号'0'
    	else:
    		return node.encode(n.father) + b'1'		#右节点编号'1'

3.2 构建哈夫曼树

由于哈夫曼编码的过程中有许多步骤重复执行,因此在节点类的基础上,借助递归的思想来完成哈夫曼树的构建。

def build_tree(l):

	if len(l) == 1:
		return l
	sorts = sorted(l,key = lambda x:x.value,reverse = False)
	n = node.build_father(sorts[0],sorts[1])
	sorts.pop(0)
	sorts.pop(0)
	sorts.append(n)
	return build_tree(sorts)

3.3 利用构建好的哈夫曼树进行编码

在上一步中构建好的哈夫曼树的基础上进行编码:

def encode(echo):

	for x in node_dict.keys():
		ec_dict[x] = node.encode(node_dict[x])
		if echo == True:						#输出编码表(用于调试)
			print(x)
			print(ec_dict[x])

3.4 实现文件压缩、解压函数

既然我们实现的是压缩算法,那么就必须能够实现文件的压缩、解压操作才有意义。如果只能实现字符串的编码或者压缩解压,是没有很大的实用价值的。

文件压缩:

def encodefile(file):

	print("Starting encode...")
	f = open(file,"rb")
	bytes_width = 1						#每次读取的字节宽度
	i = 0

	f.seek(0,2)
	count = f.tell() / bytes_width
	print(count)
	nodes = []							#结点列表,用于构建哈夫曼树
	buff = [b''] * int(count)
	f.seek(0)

	#计算字符频率,并将单个字符构建成单一节点
	while i < count:
		buff[i] = f.read(bytes_width)
		if count_dict.get(buff[i], -1) == -1:
			count_dict[buff[i]] = 0
		count_dict[buff[i]] = count_dict[buff[i]] + 1
		i = i + 1
	print("Read OK")
	print(count_dict)
	for x in count_dict.keys():
		node_dict[x] = node(count_dict[x])
		nodes.append(node_dict[x])
	
	f.close()
	tree = build_tree(nodes)		#哈夫曼树构建
	encode(False)					#构建编码表
	print("Encode OK")

	head = sorted(count_dict.items(),key = lambda x:x[1] ,reverse = True)
	bit_width = 1
	print("head:",head[0][1])					#动态调整编码表的字节长度,优化文件头大小
	if head[0][1] > 255:
		bit_width = 2
		if head[0][1] > 65535:
			bit_width = 3
			if head[0][1] > 16777215:
				bit_width = 4
	print("bit_width:",bit_width)
	i = 0
	raw = 0b1
	last = 0
	name = file.split('.')
	o = open(name[0]+".ys" , 'wb')
	o.write(int.to_bytes(len(ec_dict) ,2 ,byteorder = 'big'))		#写出结点数量
	o.write(int.to_bytes(bit_width ,1 ,byteorder = 'big'))			#写出编码表字节宽度
	for x in ec_dict.keys():										#编码文件头
		o.write(x)
		o.write(int.to_bytes(count_dict[x] ,bit_width ,byteorder = 'big'))

	print('head OK')
	while i < count:												#开始压缩数据
		for x in ec_dict[buff[i]]:
			raw = raw << 1
			if x == 49:
				raw = raw | 1
			if raw.bit_length() == 9:
				raw = raw & (~(1 << 8))
				o.write(int.to_bytes(raw ,1 , byteorder = 'big'))
				o.flush()
				raw = 0b1
				tem = int(i  /len(buff) * 100)
				if tem > last:
					print("encode:", tem ,'%')						#输出压缩进度
					last = tem
		i = i + 1

	if raw.bit_length() > 1:										#处理文件尾部不足一个字节的数据
		raw = raw << (8 - (raw.bit_length() - 1))
		raw = raw & (~(1 << raw.bit_length() - 1))
		o.write(int.to_bytes(raw ,1 , byteorder = 'big'))
	o.close()
	print("File encode successful.")

解压文件:

def decodefile(inputfile, outputfile):

	print("Starting decode...")
	count = 0
	raw = 0
	last = 0
	f = open(inputfile ,'rb')
	o = open(outputfile ,'wb')
	f.seek(0,2)
	eof = f.tell()
	f.seek(0)
	count = int.from_bytes(f.read(2), byteorder = 'big')			#取出结点数量
	bit_width = int.from_bytes(f.read(1), byteorder = 'big')		#取出编码表字宽
	i = 0
	de_dict = {}
	while i < count:												#解析文件头
		key = f.read(1)
		value = int.from_bytes(f.read(bit_width), byteorder = 'big')
		de_dict[key] = value
		i = i + 1
	for x in de_dict.keys():
		node_dict[x] = node(de_dict[x])
		nodes.append(node_dict[x])
	tree = build_tree(nodes)					#重建哈夫曼树
	encode(False)								#建立编码表
	for x in ec_dict.keys():					#反向字典构建
		inverse_dict[ec_dict[x]] = x
	i = f.tell()
	data = b''
	while i < eof:								#开始解压数据
		raw = int.from_bytes(f.read(1), byteorder = 'big')
		# print("raw:",raw)
		i = i + 1
		j = 8
		while j > 0:
			if (raw >> (j - 1)) & 1 == 1:
				data = data + b'1'
				raw = raw & (~(1 << (j - 1)))
			else:
				data = data + b'0'
				raw = raw & (~(1 << (j - 1)))
			if inverse_dict.get(data, 0) != 0:
				o.write(inverse_dict[data])
				o.flush()
				#print("decode",data,":",inverse_dict[data])
				data = b''
			j = j - 1
		tem = int(i / eof * 100)
		if tem > last:							
			print("decode:", tem,'%')			#输出解压进度
			last = tem
		raw = 0

	f.close()
	o.close()
	print("File decode successful.")

3.4 测试效果

完成后,我使用各种类型的文件进行测试,测试效果如下:

Psd文件:

压缩率:59.7%

Bmp位图文件:

压缩率:14.93%

Docx文档:

压缩率:99.9%

Mp4视频文件:

压缩率:99.9%

同样内容的Avi视频文件:

压缩率:63.7%

以上用于测试的文件全部复原成功,能够原样打开,包括文件属性的字节数等都没有差别。

4.总结

综上,基于纯哈夫曼算法的压缩程序能够对未经压缩的文件格式起到压缩作用,特别是对字节种类不多、重复次数多的文件格式如bmp位图、avi视频等能够起到非常好的压缩效果,但对于本身已经经过压缩的文件格式如docx、mp4等基本无效。

以上仅为核心代码,完整代码请参见Github

如有问题欢迎留言讨论。


欢迎关注公众号「字节莫」,探讨更多技术、人文的思考,一起进步!

点击量:119

QT通过QProcess执行终端命令并实时输出回显

  • 引子

在QT程序中我们经常需要与其他的程序间进行交互,而与终端进行交互就是一个例子。在不需要获取返回信息的情况下我们可以直接使用”system()”函数执行,但是当需要获取执行的结果或者需要实时地将结果输出到窗口上时,就比较难办了,需要涉及进程管道等等。而QT提供的QProcess类则大大简化了这一过程。

QProcess是QT提供的与外部程序进行交互的一个类,主要使用到以下的函数:

start();     //启动一个进程
kill();         //关闭启动的外部进程
write();     //向外部进程写入数据
readAllStandardOutput();     //读取外部进程的标准输出
readAllStandardError();        //读取外部进程的错误信息
  • 创建QProcess对象并连接信号与槽
cmd = new QProcess(this);
connect(cmd , SIGNAL(readyReadStandardOutput()) , this , SLOT(on_readoutput()));
connect(cmd , SIGNAL(readyReadStandardError()) , this , SLOT(on_readerror()));
  • 实现槽函数

接收到标准输出:

void MainWindow::on_readoutput()
{
    ui->textEdit->append(cmd->readAllStandardOutput().data());   //将输出信息读取到编辑框
}

接收到错误信息:

void MainWindow::on_readerror()
{
    QMessageBox::information(0, "Error", cmd->readAllStandardError().data());    //弹出信息框提示错误信息
}
  • 启动外部程序并写入数据
cmd->start("bash");             //启动终端(Windows下改为cmd)
cmd->waitForStarted();        //等待启动完成
cmd->write("ls\n");               //向终端写入“ls”命令,注意尾部的“\n”不可省略

没有意外的话在编辑框中将看见输出了当前目录下的文件:

同理,可以执行绝大部分在终端中执行的命令,不过有些程序所需要的实现可能不太一样,比如更新软件包,则需执行如下命令:

sudo -S apt-get update

如果不带参数”-S”,则会返回错误信息:“sudo:没有终端存在,且未指定askpass程序”

因此此种方法启动程序的时候如果需要root权限,则需要在”sudo”后加上选项”-S”。

还有的情况就是因为终端启动的时候其实已经帮我们读取了很多环境变量,但是我们在QT程序中直接启动bash的时候这些环境变量尚未被读取,导致一些程序无法启动,报错“未找到命令”。这时需要我们主动去source一遍对应的环境。

  • 善后

QProcess启动的外部程序并不会随着QT程序的关闭而关闭,所以在窗口的销毁函数中加入以下代码,使得窗口销毁时连带终结外部进程。

if(cmd)
{
      cmd->close();
      cmd->waitForFinished();
}
  • 写在最后

代码只是关键部分的代码,还有头文件中的声明、槽函数的声明等等一些琐碎细节的地方没有写出在文中,还请自行添加。同时为了方便你们参照,我也将工程上传了一份到GitHub中,如有不懂之处,可参照我的GitHub上的代码:QProcess_Examples-字节莫的GitHub

如有问题欢迎留言讨论。

欢迎关注公众号「字节莫」,探讨更多技术、人文的思考,一起进步!

点击量:395

Nginx泛域名解析配置

由于日后还有建设主站的想法,所以决定将博客的地址设置到blog的二级域名上,这里涉及到Nginx的泛域名解析的问题,尝试了很多种方法、折腾了半天,好多次配置错误导致Nginx启动失败,最后终于弄好了。过程如下:

将”blog”二级域名引导到博客,将”www”二级域名引导到主站,需要在Nginx的html文件夹下分别创建”www”和”blog”两个文件夹。然后在Nginx的配置文件(”/etc/nginx/sites-available/default”)中设置”server_name”配置项。

将配置文件中这两行内容(顺序可能会不一致,两句也不是连在一起的,根据你的情况来,找到了修改即可)

root /var/www/html;
server_name _;

改为(记得把”yourdomain”修改为你的域名)

if ($subdomain = ''){           #判断二级域名是非为空
        set $subdomain www;     #将空的二级域名设置为默认的“www”
}                               #如果不设置,Nginx将会返回404
root /var/www/html/$subdomain;  #设置网站根目录为以二级域名为名字的目录
server_name ~^(?<subdomain>.+).yourdomain.com$;

因为我不太确定直接在代码中写中文注释复制进配置文件中会发生什么事情,所以我建议复制进去之前先删除中文注释。

然后在html文件夹下创建这两个目录,然后将程序文件放进去。以后还想增加解析别的二级域名也直接创建对应的目录即可,无需再次配置。


欢迎关注公众号「字节莫」,探讨更多技术、人文的思考,一起进步!

点击量:23

解决WordPress修改固定链接后Nginx报404问题

针对这个问题官方是有解决方案的,在Nginx的配置文件中location段添加以下代码即可

if (-f $request_filename/index.html){  
               rewrite (.*) $1/index.html break;  
        }  
        if (-f $request_filename/index.php){  
               rewrite (.*) $1/index.php;  
         }  
        if (!-f $request_filename){  
               rewrite (.*) /index.php;  
         }

 


欢迎关注公众号「字节莫」,探讨更多技术、人文的思考,一起进步!

点击量:3

基于LNMP搭建WordPress博客

折腾了两天部署好了两台服务器(部署好第一台之后发现不适合又买了另一台),分别搭建了LAMP和LNMP,对WEB环境的部署有了一定的心得,于是趁热记录下来,给各位想自己搭建WEB环境的小伙伴一个指引和参考。由于个人站长用的服务器配置都不会太高(壕请无视),Nginx相比Apache更小巧高性能、更节省资源,所以本文只讲LNMP环境的搭建。

我的服务器上运行的操作系统是Ubuntu,部署WEB环境需要有一定的Linux知识,还好我之前学过,上手不难。

  • 首先更新软件源
sudo apt-get update
sudo apt-get upgrade
  • 安装Nginx
sudo apt-get install nginx

此时在浏览器中输入你的服务器IP,已经可以刷出Nginx的欢迎页了。

  • 安装MySQL和PHP的MySQL插件
apt-get install mysql-server
apt-get install php7.0-mysql

安装过程会让你设置数据库root账户的密码

  • 强化MySQL的安全性(小白可跳过)
mysql_secure_installation

执行以上命令会安装安全插件,进入一个向导,询问是否要设置密码有效长度,密码强度,是否要禁止远程方式连接数据库,是否要禁用匿名帐户等等。

  • 安装PHP
sudo apt-get install php7.0 php7.0-fpm php7.0-curl php7.0-gd php7.0-json php7.0-opcache php7.0-xml mcrypt php7.0-cgi php7.0-xmlrpc php-pear

安装的组件比较多,但不都是必须的,前两个是必须的,后面的可以选择性安装,但是为了避免以后碰到奇奇怪怪的问题,还是安装比较好。
比如如果没有安装第四个“php7.0-gd”,那么安装了WordPress之后是无法使用其图片裁剪的功能的。

  • 将PHP集成进Nginx

PHP-FPM 与 Nginx 的通信有两种方式,一种是基于TCP的 Internet domain socket 方式,一种是 UNIX domain socket 方式。

UNIX domain socket 可以使同一台操作系统上的两个或多个进程进行数据通信。UNIX domain socket 的接口和 Internet domain socket 很像,但它不使用网络底层协议来通信。在服务器压力不大的情况下,这两种方式性能差别不大,但在服务器压力比较满的时候,用UNIX domain socket方式效果会比较好,这里采用UNIX domain socket方式。

配置文件“/etc/nginx/sites-available/default”中,Nginx已经为与 PHP-FPM的整合做好了准备,只需要将下面这部分配置前面的注释去掉并修改其sock文件的路径即可。以现在安装的PHP7.0为例,sock文件路径为:“/run/php/php7.0-fpm.sock”。

location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        # With php7.0-cgi alone:
        # fastcgi_pass 127.0.0.1:9000;
        # With php7.0-fpm:
        fastcgi_pass unix:/run/php/php7.0-fpm.sock;
    }

然后再修改 PHP-FPM的配置文件 “/etc/php/7.0/fpm/pool.d/www.conf”,在注释中标注着“unix socket”的下一行插入以下内容:

listen = /run/php/php7.0-fpm.sock

大功告成。接下来重启Nginx和MySQL,不熟悉的小伙伴可以直接使用我下面的命令。

systemctl restart nginx
systemctl restart mysql
  • 测试PHP环境

创建文件“/var/www/html/info.php”,在其中输入以下内容:

<?php  
phpinfo(); 
?>  

然后浏览器访问你服务器的IP+“/info.php”,没有意外的话将刷出php详情页,说明WEB部署完成,恭喜!

  • 让Nginx优先解析php首页

Nginx默认优先解析的是“index.html”文件作为首页,由于我们是用的是PHP作为网站的主要环境,所以配置Nginx让它优先解析php文件

还是进入刚才的配置文件“/etc/nginx/sites-available/default”中,找到如下代码段

        root /var/www/html;

        # Add index.php to the list if you are using PHP
        index index.html index.htm index.nginx-debian.html;

        server_name _;

在”index”后、”index.htm”前插入一个”index.php”,如下所示

index index.php index.html index.htm index.nginx-debian.html;
  • 安装WordPress

从WordPress官网下载最新版本的WordPress

wget https://wordpress.org/latest.zip

然后安装解压工具包,并将下载完的文件解压到WEB目录,创建上传目录并设置相关权限

apt-get install unzip
unzip -q latest.zip -d /var/www/html/
cd /var/www/html/wordpress
cp -a * ..
cd ..
rm -r wordpress/
chown www-data:www-data -R /var/www/html/
mkdir -p /var/www/html/wp-content/uploads
chown www-data:www-data -R /var/www/html/wp-content/uploads
  • 创建给WordPress专用的MySQL用户
mysql -u root -p

输入root用户的密码后进入MySQL命令行模式,在该模式下逐句执行以下命令,注意将用户名和密码设置为你自己的。

CREATE DATABASE wordpress character set utf8 collate utf8_bin;  
GRANT ALL PRIVILEGES on wordpress.* to 'your_wpuser_name'@'localhost' identified by 'your_password';  
FLUSH PRIVILEGES;  
exit

至此,所有的准备都已就绪。

  • 初始化WordPress

在浏览器中访问你的服务器IP+“/wp-login.php”
没有意外的话,将进入WordPress安装界面,依次选择安装语言,将刚刚设置的MySQL用户名和密码填入对应的框中,按照提示一步步执行,大功告成!享受博客时光吧!

如果安装完之后出现了 403 Forbidden错误,为目录权限设置的问题。执行以下命令

chmod 755 -R /var/www/html/

没有意外的话将恢复正常。


欢迎关注公众号「字节莫」,探讨更多技术、人文的思考,一起进步!

点击量:78