SublimeText3常用快捷键和优秀插件

Standard

来源:http://www.cnblogs.com/manfredHu/p/4941307.html

SublimeText是前端的一个神器,以其精简和可DIY而让广大fans疯狂。好吧不吹了直入正题 -_-!!

首先是安装,如果你有什么软件管家的话搜一下就好,一键安装。然后,有钱的土豪就自己买个吧,穷逼就搜下注册码看下有没有土豪共享咯。

既然是神器,肯定有你不知道的东西不是,下面这部分来讲操作。PS:大部分图片和文字来自网络,这里只是略微排版方便查阅。

测试操作系统:Win10
测试软件版本:SublimeText3 3059


SublimeText3 操作部分

1. 就近选择相同项: ctrl+d

把光标放在一个单词上,按下ctrl+d,将选择这个单词。一直按住ctrl且按D多次,将选择当前选中项的下一个匹配项。通过按住ctrl,再按D三次,将选择三个相同的文本。

2. 选择当前文件所有匹配项: alt+f3

选择文件中的所有匹配项。小心使用这个,因为它能选择一个文件中的所有匹配项. .

3. 选择文本的包裹标签: ctrl+shift+` (ESC键下面的那个)

这是一个法宝。也许你希望所有属性保持不变,但只是想选择标签。这个快捷键为你这样做,会注意到你可以在一次操作多个标签。ps:需要Emmet插件(可以直接到后面看插件的安装)

4. 向上扩展一层: ctrl+shift+a

如果你把光标放在文本间再按下上面的键将选择文本,就像ctrl+d。但是再次按下它,将选择父容器,再按,将选择父容器的父容器。ps:需要Emmet插件(可以直接到后面看插件的安装)

5. 选择括号内的内容: ctrl+shift+m

这有助于选择括号之间的一切。同样适用于CSS。

6. 整行的上下移动: ctrl+shift+↑或 ctrl+shift+↓

7. 复制行或选中项: ctrl+shift+d

如果你已经选中了文本,它会复制你的选中项。否则,把光标放在行上,会复制整行。

8. 增加和减少缩进: ctrl+[ 或 ]

9. 单行剪辑或选中项: ctrl+x

10. 粘贴并复制格式: ctrl+shift+v

11. 用标签包裹行或选中项: alt+shift+w

12. 移除未闭合的容器元素: ctrl+shift+;

这会移除与你的光标相关的父标签。对清除标记很有帮助。

13. 大写和小写: 大写ctrl+k+u、小写ctrl+k+l

14. 注释选中项/行: ctrl+/

这个在所有语言下都可用, 对行和选中项都可用

15. 删除一行: ctrl+shift+k

这个就不用图了吧


SublimeText3 插件部分

首先是安装包管理器Package Control,SublimeText3的指令已经更新了,SublimeText2更新上来的童鞋注意下
Ctrl+`打开控制台或者View->Show Console菜单打开命令行

import urllib.request,os; pf = 'Package Control.sublime-package'; ipp = sublime.installed_packages_path(); urllib.request.install_opener( urllib.request.build_opener( urllib.request.ProxyHandler()) ); open(os.path.join(ipp, pf), 'wb').write(urllib.request.urlopen( 'http://sublime.wbond.net/' + pf.replace(' ','%20')).read())

就是上面这串东西了,然后就可以接下来的安装插件了

Tips: 插件名字链接到github,网络不好的童鞋自行下载包扔到Preferences->Browse Packages打开的文件夹下面,然后解压,重启Sublime就行

1. emmet

这个没有什么好说的,类似jQuery的语法,编码蹭蹭往上提。不过要求PyV8环境(安装完后你会看到有一个文件夹),最好还是选择在线装吧。
ctrl+shift+P 输入 install Package 等待读取服务器列表,输入emmet第一个就是了
ps:最好看一下 github里面的简单教程

2. 侧边栏增强插件SideBarEnhancements

这个也没有什么好说的,谁用谁知道,大大增强右键列表的功能,装上就能用。

3. 控制台呼出插件Terminal

用node,Grunt等等要调出控制台的娃知道的,简直神奇有木有,装上就能用。

Tips:快捷键 ctrl+shift+T呼出当前文件路径的控制台

4. 代码提示插件SublimeCodeIntel

这个也没什么废话吧,支持多语言的高速编码的代码提示工具。
装上后还不能直接使用,查了一下原因要配置
你可以点击 Preferences->Browse Packages->SublimeCodeIntel然后添加一个.codeintel文件夹再再在文件夹里面添加一个config文件(Windows创建.codeintel文件夹需要输入.codeintel.

config文件配置:

{
    "PHP": {
        "php": '/usr/bin/php',
        "phpExtraPaths": [],
        "phpConfigFile": 'php.ini'
    },
    "JavaScript": {
        "javascriptExtraPaths": []
    },
    "Perl": {
        "perl": "/usr/bin/perl",
        "perlExtraPaths": []
    },
    "Ruby": {
        "ruby": "/usr/bin/ruby",
        "rubyExtraPaths": []
    },
    "Python": {
        "python": '/usr/bin/python',
        "pythonExtraPaths": []
    },
    "Python3": {
        "python": '/usr/bin/python3',
        "pythonExtraPaths": []
    }
}

其实只要有JS就够了,不过或许某天你要写PHP了呢是吧,留着吧。

然后打开Sublime创建个文件试一下,如果还不行就按下 ctrl+shift+space 开启提示功能

5. 代码排版插件Sublime-HTMLPrettify

以前用的是什么TAG,CssComb和JSFormat,但是某一天发现这款集成prettify的插件后就一直没换过了,不要被插件的HTML迷惑,这是一款可以用于HTML,CSS,Javascript的集成排版插件

Tips:安装完快捷键ctrl+shift+h 一键格式化代码

6. CSS3前缀补充插件Autoprefixer

ctrl+shift+P输入install Package等待读取服务器列表,输入autoprefixer第一个就是了
要装Node.js,没有的话去下载安装吧
插件使用CanIUse资料库,能精准判断哪些属性需要什么前缀

Tips:使用方法:在输入CSS3属性后(冒号前)按Tab键


SublimeText3 添加右键菜单和快捷开启浏览器

添加右键菜单

有时候要开个文件要开个SublimeText3,又要拉文件,麻烦。这里介绍将Sublime添加到右键菜单。

  1. 打开注册表,开始→运行→regedit
  2. 在 HKEY_CLASSSES_ROOT→ * → Shell 下面新建项命名为SublimeText
  3. 右键SublimeText项,新建字符串值,命名为Icon,值为 “sublime_text.exe所在路径,0”,例如:C:\Program Files\Sublime Text 3\sublime_text.exe,0
  4. 右键SublimeText项,新建项,命名为command,默认值为 “sublime_text.exe所在路径 %1”,例如:C:\Program Files\Sublime Text 3\sublime_text.exe %1

搞定后随便右击个文本文件试试,是不是看到了Sublime打开的选项了捏?Perfect

一键浏览文件

Preferences->Key Bindings - User打开用户快捷键设置,copy下面的设置

[
    //firefox
    {
        "keys": ["f1"],
        "command": "side_bar_files_open_with",
        "args": {
            "paths": [],
            "application": "C:\\Program Files\\Mozilla Firefox\\firefox.exe",
            "extensions": ".*"
        }
    },
    //chorme
    {
        "keys": ["f2"],
        "command": "side_bar_files_open_with",
        "args": {
            "paths": [],
            "application": "C:\\Users\\manfr\\AppData\\Local\\Google\\Chrome\\Application\\chrome.exe",
            "extensions": ".*"
        }
    },
    //IE
    {
        "keys": ["f3"],
        "command": "side_bar_files_open_with",
        "args": {
            "paths": [],
            "application": "C:\\Program Files\\Internet Explorer\\iexplore.exe",
            "extensions": ".*"
        }
    },
    //safari
    {
        "keys": ["f4"],
        "command": "side_bar_files_open_with",
        "args": {
            "paths": [],
            "application": "C:\\Program Files (x86)\\Safari\\Safari.exe",
            "extensions": ".*"
        }
    }
]

稍微解释下,keys是按键,application是浏览器应用程序路径,注意反斜杠的要转义。extensions是匹配所有的文件后缀格式。

Tips:查了下默认的快捷键,SublimeText3中f1-f12中只有f11被默认为全屏命令,其他的没设置。也就是说,你可以装十个八个浏览器一字排开按过去测试。


SublimeText3 问题部分(自己遇到过的)

1.自动更新

有时候会弹出自动更新的框,解决方法:

  1. 找到Preferences -> Settings-User(设置用户)
  2. 在最后一个花括号结尾(“}”)前添加一句:"update_check":false
  3. 然后请关闭Submine Text并重启,即不会再弹出更新提醒了

2.不能获取插件列表 Package Control:There are no packages available for installation

  1. cmd下输入ping sublime.wbond.net链接一下看下sublime.wbond.net这个域名的ip
  2. 打开C:\Windows\system32\drivers\etc\hosts文件。
    在最后面加上例如 50.116.34.243 sublime.wbond.net这样的对应关系,IP是上面测试的
  3. 然后请关闭Submine Text并重启,即不会再弹出更新提醒了

The 10 Best Online Tools for Testing Code Snippets

Standard
http://designsparkle.com/testing-code-snippets/
code testing snippets

Online tools for testing code snippets make it an indispensable assistant to the developers to check the quality of their code in reducing errors and keep coding practices easier. Here are 10 of the best online tools for testing code snippets in order to help find security flaws and reduce developments costs. 

You May Also Like:

Online Tools for Testing Code Snippets

CodePen

Build, Explore, and Teach the Web, Instantly. CodePen is a web-based HTML, CSS, and JavaScript code editor that lets you experiment with code right in the browser.

code editor

JSBin

JS Bin is a webapp specifically designed to help JavaScript and CSS folk test snippets of code, within some context, and debug the code collaboratively.

code editor

SQLFiddle

SQLFiddle is an online SQL query processing tool. You can run your SQL statements online without having a locally installed database. It can overcome the feature where you can have a database inside a portable disk and plug it for use anywhere. It is also designed to support multiple databases.

code snippet

jsFiddle

jsFiddle is a free code-sharing tool that allows you to edit, share, execute and debug Web code within a browser.

Advertisements


code editors

Liveweave

Liveweave is a HTML5, CSS3 & JavaScript playground for web designers and developers.

liveweave

RegExr

RegExr is a HTML/JS based tool for creating, testing, and learning about Regular Expressions.

testing code

Dabblet

Dabblet is an interactive playground for quickly testing code snippets of CSS and HTML. It uses -prefix-free, so that you won’t have to add any prefixes in your CSS code. You can save your work in Github gists, embed it in other websites and share it with others.

code testing snippets

iDeone

Ideone is an online compiler and debugging tool which allows you to compile source code and execute it online in more than 60 programming languages. Choose a programming language, enter the source code with optional input data and you are ready to go!

code testing snippets

TinkerBin 

Tinkerbin lets you play around with HTML, JavaScript, and CSS without creating files or uploading to servers. It also supports CoffeeScript, Sass(with Compass), Less, HAML, and more.

code testing tools

CSSDesk

CSSDesk is a online HTML/CSS sandbox. Experiment with CSS, see the results live, and share your code with others.

sandbox tools

30 Pro jQuery Tips, Tricks and Strategies

Standard

原文:http://www.problogdesign.com/coding/30-pro-jquery-tips-tricks-and-strategies/

Whether you’re a developer or a designer, a strong jQuery skillset is something you can’t afford to be without. Today, I’m going to show you 30 handy jQuery coding tricks that will help you make your scripts more robust, elegant and professional.

Getting Started

These tips and tricks all have one thing in common- they are all smashingly useful. With this stuff in your back pocket, you’ll be ready to go change the world, and even better, write jQuery like you know what you’re doing. It’s gonna be fun.

We’ll start with some basic tricks, and move to some more advanced stuff like actually extending jQuery’s methods and filters. Of course, you should be familiar with the basics of jQuery first. If you haven’t used jQuery before, I highly recommend browsing the documentation and watching jQuery for Absolute Beginners Video Series. Otherwise, you’re ready to dig in!

#1 – Delay with Animate()

This is a very quick, easy way to cause delayed actions in jQuery without using setTimeout. The way we make it work is to add an animate function into your chain and animate the element to 100% opacity (which it’s already at), so it looks like nothing is happening.

For instance, let’s say that you wanted to open a dialog and then fade it away after 5 seconds. Using animate, you can do it like this:

1
2
3
$(function(){
	$("body").append("<div class='dialog'></div>").<em>animate({ opacity : 1.0 }, 5000)</em>.fadeOut();
});

Don’t you just love jQuery chaining? You’re welcome to read more about this technique from Karl Swedberg.

UPDATE: jQuery 1.4 has eliminated the need for this hack with a method called delay(). It is just what is sounds like – a function specifically made to delay an animation effect. Way to go, jQuery!

#2 – Loop through Elements Backwards

One of my personal favorites is being able to loop backwards through a set of elements. We all know each() lets us easily loop through elements, but what if we need to go backwards? Here’s the trick:

1
2
3
4
5
6
7
8
$(function(){
	var reversedSet = $("li").get().reverse();
	//Use get() to return an array of elements, and then reverse it
 
	$(reversedSet).each(function(){
		//Now we can plug our reversed set right into the each function. Could it be easier?
	});
});

#3 – Is There Anything in the jQuery Object?

Another very elementary but regularly useful trick is checking if there are any elements in the jQuery object. For example, let’s say we need to find out if there are any elements with a class of ‘active’ in the DOM. You can do that with a quick check of the jQuery object’s length property like this:

1
2
3
4
5
$(function(){
	if( $(".active").length ){
		//Now the code here will only be executed if there is at least one active element
	}
});

This works because 0 evaluates false, so the expression only evaluates true if there is at least one element in the jQuery object. You can also use size() to do the same thing.

#4 – Access iFrame Elements

Iframes aren’t the best solution to most problems, but when you do need to use one it’s very handy to know how to access the elements inside it with Javascript. jQuery’s contents() method makes this a breeze, enabling us to load the iframe’s DOM in one line like this:

1
2
3
4
5
6
7
$(function(){
	var iFrameDOM = $("iframe#someID").contents();
	//Now you can use <strong>find()</strong> to access any element in the iframe:
 
	iFrameDOM.find(".message").slideUp();
	//Slides up all elements classed 'message' in the iframe
});

#5 – Equal Height Columns

This was one of CSS Newbie’s most popular posts of 2009, and it is a good, solid trick to have in your toolbox. The function works by accepting a group of columns, measuring each one to see which is largest, and then resizing them all to match the biggest one. Here’s the code (slighly modified):

1
2
3
4
5
6
7
8
9
10
11
12
$(function(){
	jQuery.fn.equalHeight = function () {
		var tallest = 0;
		this.each(function() {
			tallest = ($(this).height() > tallest)? $(this).height() : tallest;
		});
		return this.height(tallest);	
	}
 
	//Now you can call equalHeight
	$(".content-column").equalHeight();
});

An interesting and similar concept is the awesome jQuery masonry plugin, if you’re interested in checking it out.

#6 – Find a Selected Phrase and Manipulate It

Whether you’re looking to perform find and replace, highlight search terms, or something else, jQuery again makes it easy with html():

1
2
3
4
5
6
7
8
9
10
11
12
$(function(){
	//First define your search string, replacement and context:
	var phrase = "your search string";
	var replacement = "new string";
	var context = $(body);
 
	//
	context.html(
		context.html().replace('/'+phrase+'/gi', replacement);
	);
 
});

#7 – Hack Your Titles to Prevent Widows

Nobody likes to see widows – but thankfully with some jQuery and a little help from &nbsp; we can stop that from happening:

1
2
3
4
5
6
7
8
$(function(){
	//Loop through each title
	$("h3").each(function(){
		var content = $(this).text().split(" ");
		var widow = "&amp;nbsp;"+content.pop();
		$(this).html(content.join(" ")+widow);
	});
});

This technique was suggested in a comment by Bill Brown on Css-Tricks.

#8 – Add Pseudo-Selector Support in IE

Whether or not to support IE (especially 6) is a hotly debated issue, but if you are of the “let’s-make-the-best-of-this” camp, it’s nice to know that you can add pseudo-selector support with jQuery. And we aren’t just limited to :hover, although that’s the most common:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$(function(){
	<strong>//ADD HOVER SUPPORT:</strong>
	function hoverOn(){
		var currentClass = $(this).attr('class').split(' ')[0]; //Get first class name
		$(this).addClass(currentClass + '-hover');
	}
	function hoverOff(){
		var currentClass = $(this).attr('class').split(' ')[0]; //Get first class name
		$(this).removeClass(currentClass + '-hover');
	}
	$(".nav-item").hover(hoverOn,hoverOff);
 
	<strong>//ADD FIRST-CHILD SUPPORT:</strong>
	jQuery.fn.firstChild = function(){
		return this.each(function(){
			var currentClass = $(this).attr('class').split(' ')[0]; //Get first class name
			$(this).children(":first").addClass(currentClass + '-first-child');
		});
	}
	$(".searchform").firstChild();
});

The great thing about setting it up that way firstChild(), hoverOn() and hoverOff() are very reusable. Now, in the CSS we can simply add the ‘nav-item-hover’ or ‘searchform-first-child’ classes as additional selectors:

1
2
3
4
5
6
7
8
.nav-item:hover, <strong>.nav-item-hover</strong>{
	background:#FFFFFF;
	border: solid 3px #888;
}
.searchform:first-child, <strong>.searchform-first-child</strong>{
	background:#FFFFFF;
	border: solid 3px #888;
}

It’s not pretty, but it is valid and it works. I’ve got to say, though, that I sure am looking forward to the day we won’t have to bother with this stuff!

#9 – Manage Search Box Values

A popular effect is to fill a site’s search box with a value (like ‘search…’) and then use jQuery to clear the default value when the field receives focus, reverting if the field is empty when blurred. That is easily accomplished with a couple lines of jQuery:

1
2
3
4
5
6
7
8
9
$(function(){
	//set default value:
	$("#searchbox")
	  .val('search?');
	  .focus(function(){this.val('')})
	  .blur(function(){
		(this.val() === '')? this.val('search?') : null;
	  });
});

#10 – Create a Disappearing ‘Back-to-Top’ Link

The disappearing back-to-top link was inspired by Brian Cray. All you have to do is add a back-to-top link at the bottom of your content like normal, and then jQuery performs the magic:

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
$(function(){
/* set variables locally for increased performance */
	var scroll_timer, 
		displayed = false,
		$message = $('#message a'),
		$window = $(window),
		top = $(document.body).children(0).position().top;
 
	/* react to scroll event on window */
	$window.scroll(function () {
		window.clearTimeout(scroll_timer);
		scroll_timer = window.setTimeout(function () { // use a timer for performance
			if($window.scrollTop() <= top) // hide if at the top of the page
			{
				displayed = false;
				$message.fadeOut(500);
			}
			else if(displayed == false) // show if scrolling down
			{
				displayed = true;
				$message.stop(true, true).show().click(function () { $message.fadeOut(500); });
			}
		}, 100);
	});
});

Brian also added some nice-looking CSS, which you could add as a css file or define in an object literal and apply it using jQuery.css(). Feel free to go check out his in-depth explanation if you want to learn more.

#11 – Easily Respond to Event Data

One of my favorite things about jQuery is its convenient remapping of event data, virtually eliminating cross-browser inconsitencies and making events much easier to respond to. jQuery passes an event parameter into all bound/triggered functions, which is commonly called e:

1
2
3
4
5
6
7
8
9
10
11
12
$(function() {
	//We can get X/Y coordinates on click events:
	$("a").click(function(<em>e</em>){
		var clickX = e.pageX;
		var clickY = e.pageX;
	});
 
	//Or detect which key was pressed:
	$("window").keypress(function(<em>e</em>){
		var keyPressed = e.which;
	});
});

You can check out the jQuery docs on this one to see all the possibilites, or view this keycode reference if you’d like to look up a certain key’s character code.

#12 – Encode HTML Entities

The first place I saw this mentioned was over at Debuggable, and I have to say they really came up with something good here. The idea is to produce a jQuery result similar to PHP’s htmlentities(). Check this out:

1
2
3
4
5
6
7
8
9
$(function(){
	var text = $("#someElement").text();
	var text2 = "Some <code> & such to encode";
	//you can define a string or get the text of an element or field
 
	var html = $(text).html();
	var html2 = $(text2).html();
	//Done - html and html2 now hold the encoded values!
});

#13 – Friendly Text Resizing

Originally mentioned at ShopDev, this is an excellent way to include some user-centricity in your code (allowing them to control the font-size):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$(function(){
  // Reset Font Size
  var originalFontSize = $('html').css('font-size');
    $(".resetFont").click(function(){
    $('html').css('font-size', originalFontSize);
  });
  // Increase Font Size
  $(".increaseFont").click(function(){
    var currentFontSize = $('html').css('font-size');
    var currentFontSizeNum = parseFloat(currentFontSize, 10);
    var newFontSize = currentFontSizeNum*1.2;
    $('html').css('font-size', newFontSize);
    return false;
  });
  // Decrease Font Size
  $(".decreaseFont").click(function(){
    var currentFontSize = $('html').css('font-size');
    var currentFontSizeNum = parseFloat(currentFontSize, 10);
    var newFontSize = currentFontSizeNum*0.8;
    $('html').css('font-size', newFontSize);
    return false;
  });
});

As I said, this is nice trick to know and adds some of that dynamic friendliness that people enjoy so much.

#14 – Open External Links in a New Window

This external links hack has been mentioned before at Cats Who Code, and although imperfect it’s a good way to open external links in new windows without causing validation errors in XHTML 1.0 Strict.

1
2
3
4
5
$(function(){
	$('a[rel$='external']').click(function(){
		this.target = "_blank";
	});
});

This works by grabbing all links with an external rel and adding a blank target. Same result, it’s just not hardcoded into the site.

#15 – Gracefully Degrading AJAX Navigation

AJAX navigation is great – but not for users and bots who can’t use it. The good news is, it’s possible to offer direct links to your content while still presenting AJAX functionality (to users who have that capability) by catching links before they go anywhere, returning false on them and loading the AJAX content instead. It could look like this:

1
2
3
4
5
6
$(function(){
	$("a").bind("click",function(){
		//Put your AJAX request code here
		return false;
	});
});

Of course, this is very basic, but it’s an essential facet to any AJAX navigation. You can also check out SpeckyBoy’s post on Easy-to-Use Free Ajax Navigation Solutions.

#16 – Create an Array of GET variables

Although this is not specifically a jQuery trick, it’s useful enough to be included here. Using GET variables in Javascript code doesn’t happen everyday, but when it does you’ll want to know a quick and efficient way to read them. All we have to do is get document.location.search and do some parsing on it:

1
2
3
4
5
6
7
8
9
10
 
var searchArray = document.location.search.substring(1).split("&");
//Take off the '?' and split into separate queries
 
//Now we'll loop through searchArray and create an associative array (object literal) called GET
var GET = []; 
for (searchTerm in searchArray){
	searchTerm.split("="); //Divide the searchTerm into property and value
	GET[searchTerm[0]] = searchTerm[1]; //Add property and value to the GET array
}

#17 – Partial Page Refresh Using load()

This excellent technique found at the Mediasoft Blog is way cool and very handy for creating a regularly updating dashboard/widget/etc. It works by using jQuery.load() to perform a AJAX request:

1
2
3
4
5
$(document).ready(function() {
	setInterval(function() {
		$("#content").load(location.href+" #content>*","");
	}, 5000);
});

Voila! It works – no iframes, meta refreshes or other such nonsense.

#18 – Skin with jQuery UI

If you’re going to write any jQuery plugins (I hope you have already!), you should know that a great way to add flexibility and elegance is to incorporate jQueryUI theming classes into any widgets/visible elements your plugin produces. The great thing about doing this is that it cuts or eliminates the css you have to provide with the plugin, and it adds a lot of customizability, too (which is one of the key factors in a successful plugin).

And the actual implementation is as simple as learning how the classes work and attaching them to plugin elements. I can see this would be especially great with plugins like form beautifiers and photo sliders, making it easy to keep a consistent look throughout a website or app. You can check out the Theming Reference here.

#19 – Include Other Scripts

Stylesheet switchers are nothing new, but adding other scripts with jQuery is something that is often overlooked. The advantage is twofold:

  1. It makes it easy to have lazy script loading.
  2. It also allows us to add scripts at runtime, which could be useful in a whole host of situations.

It’s as easy as using append() to add a new script to the head of the document:

1
2
3
4
5
6
7
8
$(function(){
	$("head").append("<script type='text/javascript' src='somescript.js'></script>");
 
	//Or, loading only when the slow stuff is ready:
	$("img,form").load(function(){
		$("head").append("<script type='text/javascript' src='somescript.js'></script>");
	});
});

#20 – Use Body Classes for Easy Styling

Do you want to save on code and keep your styling in the css file where it should be? Body classes (another great ideas suggested by Karl Swedberg) allow you to do that. In short, you can use jQuery to add a ‘JS’ class to the body element, which will enable you to set styles in your css that will only be applied if Javascript is enabled. For example:

1
2
3
$(document).ready(function() {
	$("body").addClass("JS");
});

For Javascript users the body now has a JS class, so in our CSS we can add styles like this:

ul.navigation{
	display:block;
}
.JS ul.navigation{
	display:none;
}

This gives us a great way to change styles based on whether or not Javascript is supported/enabled, and we can still keep the styling in the CSS file. Another interesting related use of this technique is to add browser classes to the body, enabling easy browser-specific styling. You can read more about that here.

#21 – Optimize Your Performance

Experienced coders don’t make clients wait – they write code that runs fast! There are several ways you can make your code run faster like:

  • Reference id’s rather than classes (id selection is native and therefore quicker)
  • Use for instead of each()
  • Limit DOM manipulation by adding elements in one big chunk rather than one at a time
  • Take advantage of event delegation
  • Link to Google’s jQuery copy rather than hosting your own – it’s faster and always up to date

Basically, it all boils down to not making jQuery do any more work than it has to (and using native abilites whenever possible). Giulio Bai wrote an excellent post on jQuery perfomance, if you’d like to dig in deeper.

#22 – Adapt Your Scripts to Work Cross-Browser – The Right Way

Thankfully, jQuery’s cross-browser compatibility really cuts down the need for browser hacks. Sometimes, though, it is good to be able to get information about the client, and we can do that cleanly and unobtrusively with jQuery.support:

1
2
3
4
5
//Does this client follow the W3C box model?
var boxModel = $.support.boxModel;
 
//Does this client support 'opacity'?
var opacity = $.support.opacity;

It’s definitely better practice to use feature-detection rather than browser sniffing, and this is a very efficient way to do it. Read more about jQuery.support’s properties here.

#23 – Configure jQuery to be Compatible with Other Libraries

We all find ourselves in situations where multiple libraries are needed, and because jQuery isn’t the only library that uses the $ alias, compatiblity issues sometimes pop up. Thankfully, this is easy to fix using jQuery’s noConflict(). You can even define a custom alias to replace the $:

1
2
3
4
var $j = jQuery.noConflict();
 
//Now you can use '$j' just like '$'
$j("div").hide();

An alternate technique is to wrap jQuery calls in an anonymous function and pass jQuery in as a parameter. Then you can use whatever alias you want, including the ‘$’. This is especially useful for plugin authoring:

1
2
3
4
5
(function($){
	$(document).ready(function(){
		//You can use normal jQuery syntax here
	});
})(jQuery);

#24 – Efficiently Store Element-Specific Information with data()

data() is probably one of the lesser used jQuery methods, although it certainly shouldn’t be. It allows us to attach/retrieve as much data as we want to DOM elements without misusing attributes, and is especially useful for more complex scripts. For example, Stefan Petre’s Colorpicker plugin uses data a lot because it’s tracking lots of fields and colors, converting rgb to hex, etc. Here’s are some examples of how data() works:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$(document).ready(function() {
	//Set status to 'unsaved'
    $("button:first").data("status", "unsaved");
 
	//Retrieve status
	var buttonStatus = $("button:first").data("status");
 
	//Change status, this time defining an object literal
	$("button:first").data("status", {saved : true, index : 1});
 
	//Retrieve status of index property
	var buttonStatusIndex = $("button:first").data("status").index;
 
	//Remove status data
	$("button:first").removeData("status");
});

I’m sure you can imagine the huge extent of possibilities this presents. Again, it’s worth reading the documentation if you haven’t used data() before.

#25 – Extend/Modify Existing jQuery Functions

Nobody says you can’t use the existing jQuery platform as a springboard for new ideas – and many have done just that using extend(), another wonderful jQuery method. The popular Easing plugin, for example, adds some animation variety by extending the easing object, an already existing object that is passed to animate() and others:

1
2
3
4
5
6
7
8
9
10
11
12
13
jQuery.extend({
	easing: {
		easein: function(x, t, b, c, d) {
			return c*(t/=d)*t + b; // in
		},
		easeinout: function(x, t, b, c, d) {
			if (t < d/2) return 2*c*t*t/(d*d) + b;
			var ts = t - d/2;
			return -2*c*ts*ts/(d*d) + 2*c*ts/d + c/2 + b;		
		},
		easeout: function(x, t, b, c, d) {
			return -c*t*t/(d*d) + 2*c*t/d + b;
		}...

By extending and improving jQuery’s default functionality, you can open up a whole new world of cool possibilities.

#26 – Reverse Engineer before() and after()

I always appreciated how jQuery provided append()/prepend() and appendTo()/prependTo(), enabling us to easily perform an append in either direction. I’ve wished, though, that a similar ability was provided with before() and after(). To change that, we can easily add two functions called putBefore() and putAfter() that will fulfill that purpose. Here’s how:

1
2
3
4
5
6
7
8
9
10
11
12
$(function(){
	jQuery.fn.putBefore = function(dest){
		return this.each(function(){
			$(dest).before($(this));
		});
	}
	jQuery.fn.putAfter = function(dest){
		return this.each(function(){
			$(dest).after($(this));
		});
	}
});

#27 – Add an isChildOf() Test

I’m sure we all have found ourselves in this situation – needing to know if an element is a descendant of another element. The good news is, with one line of code we can extend jQuery to allow this:

1
2
3
4
5
6
7
8
$(function(){
	jQuery.fn.isChildOf = function(b){return (this.parents(b).length > 0);};
 
	//Now we can evaluate like this:
	if ( $("li").isChildOf("ul") ){
		//Obviously, the li is a child of the ul so this code is executed
	}
});

Thanks to Dan Switzer II for this contribution!

#28 – Add Custom Selectors

This is another one that has been talked about a lot in the development community, so you may have this already figured out. If not, get ready because this will open some whole new windows for jQuery efficiency. The short story is, jQuery allows us to extend its expression object, which means we can add whatever custom selectors we want. For example, say we wanted to add a selector version of the isChildOf() method we wrote earlier:

1
2
3
4
5
6
7
8
9
10
$(function(){
	jQuery.extend(jQuery.expr[':'], {   
		'child-of' : function(a,b,c) {
			return (jQuery(a).parents(c[3]).length > 0);
		}   
	});
 
	//'child-of' is now a valid selector:
	$("li:child-of(ul.test)").css("background","#000");  
});

Debuggable has a great post on this one as well, if you’d like to read more about how the parameters work, etc.

#29 – Smooth Scrolling Without Plugin

Karl Swedberg posted this one a while back on the Learning jQuery site, and it is definitely worth a look. Of course, there are a couple of plugins to accomplish this (and they have more features), but I think the real value in this is the excercise of doing it yourself. Plus, look at how tiny it is:

1
2
3
4
5
6
7
8
9
10
11
12
13
$(document).ready(function() {
  $('a[href*=#]').click(function() {
    if (location.pathname.replace(/^\//,'') == this.pathname.replace(/^\//,'') 
      && location.hostname == this.hostname) {
       var $target = $(this.hash);
       $target = $target.length && $target || $('[name=' + this.hash.slice(1) +']');
       if ($target.length) {
           $target.ScrollTo(400);
           return false;
       }
    };
  });
});

#30 – Add Tabs without a Plugin

jQuery tabs are often covered but also often used, and like the scrolling trick above it’s important to know how to do these without a plugin. The first thing to do is write our markup, which should be perfectly presentable in the absence of Javascript:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<div class="widget">
	<h3>Popular</h3>
	<ul>
		<li>Item #1</li>
		<li>Item #2</li>
		<li>Item #3</li>
	</ul>
</div>
<div class="widget">
	<h3>Recent</h3>
	<ul>
		<li>Item #1</li>
		<li>Item #2</li>
		<li>Item #3</li>
	</ul>
</div>

With a bit of styling, this would look just fine, so we know our jQuery is going to degrade gracefully. Now we can write the code:

1
2
3
4
5
6
7
8
9
10
11
$(document).ready(function() {
	$("div.widget").hide().filter(":first").before("<ul class='tabs'></ul><div class='tab-content'></div>").add("div.widget").each(function(){
		$(this).find("ul").appendTo(".tab-content");
		$("ul.tabs").append("<li>" +$(this).find(":header:first").text()+ "</li>");
	});
	$("ul.tabs li").click(function(){
		$(this).addClass("active");
		$(".tab-content ul").slideUp().eq($("ul.tabs li").index(this)).slideDown();
	});
	$("ul.tabs li:first").click();
});

Of course, that’s just one way to do it. If you’d like to see some other approaches, see Extra Tuts’ jQuery Tabs Tutorials collection.

Wrapping Up

Thanks for reading, I hope you’ve enjoyed it! In case you’re all fired up on jQuery now, here are some links to more great tips n’ tricks collections:

As you can see, there are infinite opportunities for amazing innovation with jQuery. So keep experimenting, keep trying things, keep thinking, “What if?” You could be the next jQuery supergeek!

CTE公用表表达式和With用法总结

Standard

CTE(Common Table Expression) 公用表表达式,它是在单个语句的执行范围内定义的临时结果集,只在查询期间有效。它可以自引用,也可在同一查询中多次引用,实现了代码段的重复利用。

CTE最大的好处是提升T-Sql代码的可读性,可以更加优雅简洁的方式实现递归等复杂的查询。

CTE可用于:
⒈ 创建递归查询,这个应该是CTE最好用的地方
⒉ 在同一语句中多次引用生成的表
3. 减少子查询和表变量,提高执行效率

CTE优点:
1. 使用 CTE 可以获得提高可读性和轻松维护复杂查询的优点。同时,CTE要比表变量的效率高得多。
2. 可以用来定义一个SQL片断,该SQL片断会被整个SQL语句所用到。有的时候,是为了让SQL语句的可读性更高些,也有可能是在UNION ALL的不同部分,作为提供数据的部分。
3. 查询可以分为单独块、简单块、逻辑生成块。之后,这些简单块可用于生成更复杂的临时 CTE,直到生成最终结果集。

下面是CTE的语法:

WITH cte_name ( column_name [,...n] )
AS
(
    CTE_query_definition –- Anchor member is defined.
)

 

使用示例
1. 查询临时结果集

复制代码
WITH cte(CategoryID,CategoryName,ParentID,CategoryLevel)
AS (
  SELECT CategoryID
      ,CategoryName
      ,ParentID
      ,CategoryLevel
  FROM Category(NOLOCK)
  WHERE Status = 1 and parentid = 23
)
select * from cte;
复制代码

注意: 1.使用CTE的SQL语句应紧跟在相关的CTE后面。
2.多重CTE中间用逗号,分隔。
3.可以被紧跟着的一条SQL语句所使用多次,但不能被紧跟着的多条SQL语句使用。

2. 创建递归查询

复制代码
WITH cte(CategoryID ,CategoryName,ParentID,CategoryLevel)
AS (
  SELECT CategoryID
      ,CategoryName
      ,ParentID
      ,CategoryLevel
      FROM Category(NOLOCK)
  WHERE Status= 1 and parentid in (21,22,23,25,26)
  UNION ALL
  SELECT t.CategoryID
      ,t.CategoryName
      ,t.ParentID
      ,t.CategoryLevel
  FROM Category(NOLOCK) AS t
  INNER JOIN cte AS c ON t.parentid = c.CategoryID where Status= 1
)
select * from cte;
复制代码

 

3. cte结果集和数据表关联

复制代码
WITH cte(CategoryID,CategoryName,ParentID,CategoryLevel)
AS (
  SELECT CategoryID
      ,CategoryName
      ,ParentID
      ,CategoryLevel
  FROM Category(NOLOCK)
  WHERE Status = 1 and parentid = 23
)
select p.ProductId,p.ProductName,c.CategoryID,c.CategoryName,c.CategoryLevel 
from product p(NOLOCK)
inner join cte c(NOLOCK) on p.CategoryId=c.CategoryID
复制代码

 

浅谈 PHP 中的多种加密技术及代码示例

Standard

原文:http://www.cnblogs.com/nixi8/p/4926689.html

同样是一道面试答错的问题,面试官问我非对称加密算法中有哪些经典的算法? 当时我愣了一下,因为我把非对称加密与单项散列加密的概念弄混淆了,所以更不用说什么非对称加密算法中有什么经典算法,结果当然也让面试官愣了一下,所以今天就花点时间说说PHP中的信息加密技术

信息加密技术的分类

单项散列加密技术(不可逆的加密)

属于摘要算法,不是一种加密算法,作用是把任意长的输入字符串变化成固定长的输出串的一种函数

MD5

string md5 ( string $str [, bool $raw_output = false ] ); //MD5加密,输入任意长度字符串返回一个唯一的32位字符

md5()为单向加密,没有逆向解密算法,但是还是可以对一些常见的字符串通过收集,枚举,碰撞等方法破解;所以为了让其破解起来更麻烦一些,所以我们一般加一点盐值(salt)并双重MD5;

md5(md5($password).'sdva'); 

sdva就是盐值,该盐值应该是随机的,比如md5常用在密码加密上,所以在注册的时候我会随机生成这个字符串,然后通过上面的方法来双重加密一下;

Crypt

很少看到有人用这个函数,如果要用的话有可能是用在对称或非对称的算法里面,了解一下既可;

string crypt ( string $str [, string $salt ] ) //第一个为需要加密的字符串,第二个为盐值(就是加密干扰值,如果没有提供,则默认由PHP自动生成);返回散列后的字符串或一个少于 13 字符的字符串,后者为了区别盐值。
<?php
$password='testtest.com';
echo crypt($password);
//输出:$1$DZ3.QX2.$CQZ8I.OfeepKYrWp0oG8L1
/*第二个$与第三个$之间的八个字符是由PHP生成的,每刷新一次就变一次
*/
echo "<hr>";

echo crypt($password,"testtest");
//输出:tesGeyALKYm3A
//当我们要加自定义的盐值时,如例子中的testtest作为第二个参数直接加入, 超出两位字符的会截取前两位
echo "<hr>";

echo  crypt($password,'$1$testtest$');
//输出:$1$testtest$DsiRAWGTHiVH3O0HSHGoL1
/*crypt加密函数有多种盐值加密支持,以上例子展示的是MD5散列作为盐值,该方式下
盐值以$1$$的形式加入,如例子中的testtest加在后两个$符之间,
超出八位字符的会截取前八位,总长为12位;crypt默认就是这种形式。
*/
echo "<hr>";
//crypt还有多种盐值加密支持,详见手册

Sha1加密:

string sha1 ( string $str [, bool $raw_output = false ]); //跟md5很像,不同的是sha1()默认情况下返回40个字符的散列值,传入参数性质一样,第一个为加密的字符串,第二个为raw_output的布尔值,默认为false,如果设置为true,sha1()则会返回原始的20 位原始格式报文摘要
<?php
$my_intro="zhouxiaogang";
echo sha1($my_intro); // b6773e8c180c693d9f875bcf77c1202a243e8594
echo "<hr>";
//当然,可以将多种加密算法混合使用
echo md5(sha1($my_intro));
//输出:54818bd624d69ac9a139bf92251e381d
//这种方式的双重加密也可以提高数据的安全性

非对称加密

非对称加密算法需要两个密钥来进行加密和解密,这两个秘钥是公开密钥(public key,简称公钥)和私有密钥(private key,简称私钥);

1b4c510fd9f9d72a79ac165bd72a2834359bbbaf.jpg-182.2kB

如图所示,甲乙之间使用非对称加密的方式完成了重要信息的安全传输。

  1. 乙方生成一对密钥(公钥和私钥)并将公钥向其它方公开。
  2. 得到该公钥的甲方使用该密钥对机密信息进行加密后再发送给乙方。
  3. 乙方再用自己保存的另一把专用密钥(私钥)对加密后的信息进行解密。乙方只能用其专用密钥(私钥)解密由对应的公钥加密后的信息。

在传输过程中,即使攻击者截获了传输的密文,并得到了乙的公钥,也无法破解密文,因为只有乙的私钥才能解密密文
同样,如果乙要回复加密信息给甲,那么需要甲先公布甲的公钥给乙用于加密,甲自己保存甲的私钥用于解密。

在非对称加密中使用的主要算法有:RSA、Elgamal、背包算法、Rabin、D-H、ECC(椭圆曲线加密算法)等。其中我们最见的算法是RSA算法

以下是从网上摘抄的一段PHP通过openssl实现非对称加密的算法

<?php
/**
 * 使用openssl实现非对称加密
 * @since 2010-07-08
 */
class Rsa {
    /**
     * private key
     */
    private $_privKey;
    /**
     * public key
     */
    private $_pubKey;
    /**
     * the keys saving path
     */
    private $_keyPath;
    /**
     * the construtor,the param $path is the keys saving path
     */
    public function __construct($path) {
        if (empty($path) || !is_dir($path)) {
            throw new Exception('Must set the keys save path');
        }
        $this->_keyPath = $path;
    }
    /**
     * create the key pair,save the key to $this->_keyPath
     */
    public function createKey() {
        $r = openssl_pkey_new();
        openssl_pkey_export($r, $privKey);
        file_put_contents($this->_keyPath . DIRECTORY_SEPARATOR . 'priv.key', $privKey);
        $this->_privKey = openssl_pkey_get_public($privKey);
        $rp = openssl_pkey_get_details($r);
        $pubKey = $rp['key'];
        file_put_contents($this->_keyPath . DIRECTORY_SEPARATOR . 'pub.key', $pubKey);
        $this->_pubKey = openssl_pkey_get_public($pubKey);
    }
    /**
     * setup the private key
     */
    public function setupPrivKey() {
        if (is_resource($this->_privKey)) {
            return true;
        }
        $file = $this->_keyPath . DIRECTORY_SEPARATOR . 'priv.key';
        $prk = file_get_contents($file);
        $this->_privKey = openssl_pkey_get_private($prk);
        return true;
    }
    /**
     * setup the public key
     */
    public function setupPubKey() {
        if (is_resource($this->_pubKey)) {
            return true;
        }
        $file = $this->_keyPath . DIRECTORY_SEPARATOR . 'pub.key';
        $puk = file_get_contents($file);
        $this->_pubKey = openssl_pkey_get_public($puk);
        return true;
    }
    /**
     * encrypt with the private key
     */
    public function privEncrypt($data) {
        if (!is_string($data)) {
            return null;
        }
        $this->setupPrivKey();
        $r = openssl_private_encrypt($data, $encrypted, $this->_privKey);
        if ($r) {
            return base64_encode($encrypted);
        }
        return null;
    }
    /**
     * decrypt with the private key
     */
    public function privDecrypt($encrypted) {
        if (!is_string($encrypted)) {
            return null;
        }
        $this->setupPrivKey();
        $encrypted = base64_decode($encrypted);
        $r = openssl_private_decrypt($encrypted, $decrypted, $this->_privKey);
        if ($r) {
            return $decrypted;
        }
        return null;
    }
    /**
     * encrypt with public key
     */
    public function pubEncrypt($data) {
        if (!is_string($data)) {
            return null;
        }
        $this->setupPubKey();
        $r = openssl_public_encrypt($data, $encrypted, $this->_pubKey);
        if ($r) {
            return base64_encode($encrypted);
        }
        return null;
    }
    /**
     * decrypt with the public key
     */
    public function pubDecrypt($crypted) {
        if (!is_string($crypted)) {
            return null;
        }
        $this->setupPubKey();
        $crypted = base64_decode($crypted);
        $r = openssl_public_decrypt($crypted, $decrypted, $this->_pubKey);
        if ($r) {
            return $decrypted;
        }
        return null;
    }
    public function __destruct() {
        @fclose($this->_privKey);
        @fclose($this->_pubKey);
    }
}
//以下是一个简单的测试demo,如果不需要请删除
$rsa = new Rsa('ssl-key');
//私钥加密,公钥解密
echo 'source:我是老鳖<br />';
$pre = $rsa->privEncrypt('我是老鳖');
echo 'private encrypted:<br />' . $pre . '<br />';
$pud = $rsa->pubDecrypt($pre);
echo 'public decrypted:' . $pud . '<br />';
//公钥加密,私钥解密
echo 'source:干IT的<br />';
$pue = $rsa->pubEncrypt('干IT的');
echo 'public encrypt:<br />' . $pue . '<br />';
$prd = $rsa->privDecrypt($pue);
echo 'private decrypt:' . $prd;
?>  

对称加密算法

对称加密(也叫私钥加密)指加密和解密使用相同密钥的加密算法。有时又叫传统密码算法,就是加密密钥能够从解密密钥中推算出来,同时解密密钥也可以从加密密钥中推算出来。而在大多数的对称算法中,加密密钥和解密密钥是相同的,所以也称这种加密算法为秘密密钥算法或单密钥算法。它要求发送方和接收方在安全通信之前,商定一个密钥。对称算法的安全性依赖于密钥,泄漏密钥就意味着任何人都可以对他们发送或接收的消息解密,所以密钥的保密性对通信性至关重要。

对称加密的常用算法有: DES算法,3DES算法,TDEA算法,Blowfish算法,RC5算法,IDEA算法

在PHP中也有封装好的对称加密函数

Urlencode/Urldecode

string urlencode ( string $str ) 
/*
1. 一个参数,传入要加密的字符串(通常应用于对URL的加密)
2. urlencode为双向加密,可以用urldecode来加密(严格意义上来说,不算真正的加密,更像是一种编码方式)
3. 返回字符串,此字符串中除了 -_. 之外的所有非字母数字字符都将被替换成百分号(%)后跟两位十六进制数,空格则编码为加号(+)。
*/

通过Urlencode函数解决链接中带有&字符引起的问题:

<?php
$pre_url_encode="zhougang.com?username=zhougang&password=zhou"; //在实际开发中,我们很多时候要构造这种URL,这是没有问题的
$url_decode    ="zhougang.com?username=zhou&gang&password=zhou";//但是这种情况下用$_GET()来接受是会出问题的;
/*
Array
(
  [username] => zhou
  [gang] => 
  [password] => zhou
)
 */


//如下解决问题:
$username="zhou&gang";
$url_decode="zhougang.com?username=".urlencode($username)."&password=zhou";
?>

常见的urlencode()的转换字符

?=> %3F
= => %3D
% => %25
& => %26
\ => %5C

base64

string base64_decode ( string $encoded_data )
  1. base64_encode()接受一个参数,也就是要编码的数据(这里不说字符串,是因为很多时候base64用来编码图片)
  2. base64_encode()为双向加密,可用base64_decode()来解密
$data=file_get_contents($filename);
echo base64_encode($data);
/*然后你查看网页源码就会得到一大串base64的字符串,
再用base64_decode()还原就可以得到图片。这也可以作为移动端上传图片的处理方案之一(但是不建议这样做哈)
*/

严格的来说..这两个函数其实不算是加密,更像是一种格式的序列化

以下是我们PHP程序中常用到的对称加密算法

discuz经典算法

<?php
function authcode($string, $operation = 'DECODE', $key = '', $expiry = 0) {   
    // 动态密匙长度,相同的明文会生成不同密文就是依靠动态密匙   
    $ckey_length = 4;   
       
    // 密匙   
    $key = md5($key ? $key : $GLOBALS['discuz_auth_key']);   
       
    // 密匙a会参与加解密   
    $keya = md5(substr($key, 0, 16));   
    // 密匙b会用来做数据完整性验证   
    $keyb = md5(substr($key, 16, 16));   
    // 密匙c用于变化生成的密文   
    $keyc = $ckey_length ? ($operation == 'DECODE' ? substr($string, 0, $ckey_length): 
substr(md5(microtime()), -$ckey_length)) : '';   
    // 参与运算的密匙   
    $cryptkey = $keya.md5($keya.$keyc);   
    $key_length = strlen($cryptkey);   
    // 明文,前10位用来保存时间戳,解密时验证数据有效性,10到26位用来保存$keyb(密匙b), 
//解密时会通过这个密匙验证数据完整性   
    // 如果是解码的话,会从第$ckey_length位开始,因为密文前$ckey_length位保存 动态密匙,以保证解密正确   
    $string = $operation == 'DECODE' ? base64_decode(substr($string, $ckey_length)) :  
sprintf('%010d', $expiry ? $expiry + time() : 0).substr(md5($string.$keyb), 0, 16).$string;   
    $string_length = strlen($string);   
    $result = '';   
    $box = range(0, 255);   
    $rndkey = array();   
    // 产生密匙簿   
    for($i = 0; $i <= 255; $i++) {   
        $rndkey[$i] = ord($cryptkey[$i % $key_length]);   
    }   
    // 用固定的算法,打乱密匙簿,增加随机性,好像很复杂,实际上对并不会增加密文的强度   
    for($j = $i = 0; $i < 256; $i++) {   
        $j = ($j + $box[$i] + $rndkey[$i]) % 256;   
        $tmp = $box[$i];   
        $box[$i] = $box[$j];   
        $box[$j] = $tmp;   
    }   
    // 核心加解密部分   
    for($a = $j = $i = 0; $i < $string_length; $i++) {   
        $a = ($a + 1) % 256;   
        $j = ($j + $box[$a]) % 256;   
        $tmp = $box[$a];   
        $box[$a] = $box[$j];   
        $box[$j] = $tmp;   
        // 从密匙簿得出密匙进行异或,再转成字符   
        $result .= chr(ord($string[$i]) ^ ($box[($box[$a] + $box[$j]) % 256]));   
    }   
    if($operation == 'DECODE') {  
        // 验证数据有效性,请看未加密明文的格式   
        if((substr($result, 0, 10) == 0 || substr($result, 0, 10) - time() > 0) &&  
substr($result, 10, 16) == substr(md5(substr($result, 26).$keyb), 0, 16)) {   
            return substr($result, 26);   
        } else {   
            return '';   
        }   
    } else {   
        // 把动态密匙保存在密文里,这也是为什么同样的明文,生产不同密文后能解密的原因   
        // 因为加密后的密文可能是一些特殊字符,复制过程可能会丢失,所以用base64编码   
        return $keyc.str_replace('=', '', base64_encode($result));   
    }   
} 

加解密函数encrypt()

<?php
//$string:需要加密解密的字符串;$operation:判断是加密还是解密,E表示加密,D表示解密;$key:密匙
function encrypt($string,$operation,$key=''){ 
    $key=md5($key); 
    $key_length=strlen($key); 
      $string=$operation=='D'?base64_decode($string):substr(md5($string.$key),0,8).$string; 
    $string_length=strlen($string); 
    $rndkey=$box=array(); 
    $result=''; 
    for($i=0;$i<=255;$i++){ 
           $rndkey[$i]=ord($key[$i%$key_length]); 
        $box[$i]=$i; 
    } 
    for($j=$i=0;$i<256;$i++){ 
        $j=($j+$box[$i]+$rndkey[$i])%256; 
        $tmp=$box[$i]; 
        $box[$i]=$box[$j]; 
        $box[$j]=$tmp; 
    } 
    for($a=$j=$i=0;$i<$string_length;$i++){ 
        $a=($a+1)%256; 
        $j=($j+$box[$a])%256; 
        $tmp=$box[$a]; 
        $box[$a]=$box[$j]; 
        $box[$j]=$tmp; 
        $result.=chr(ord($string[$i])^($box[($box[$a]+$box[$j])%256])); 
    } 
    if($operation=='D'){ 
        if(substr($result,0,8)==substr(md5(substr($result,8).$key),0,8)){ 
            return substr($result,8); 
        }else{ 
            return''; 
        } 
    }else{ 
        return str_replace('=','',base64_encode($result)); 
    } 
}
?>
————————-END————————-

掌握VI编辑器

Standard

原文:http://www.daxixiong.com/?/article/10

资料来源:University of Hawaii at Manoa College of Engineering。

引言
VI编辑器是一个被许多Unix用户使用的基于屏幕的编辑器。VI编辑器具备强大的功能来帮助程序员,但是许多初学者因为要面对很多不同的指令而对使用VI敬而远之。写作本教程的目的就是要帮助初学者适应VI编辑器的使用,当然,也有一些小节的内容与VI的惯用者有关。在讲解的同时举出了很多的例子,最佳的学习方法就是试着在Unix下运行这些例子,并试着举一反三。在这个世界上,没有比自己亲自去经历更好的学习方式了。

约定
在本教程中,使用以下的约定:
^X表示一个控制字符。例如,如果你在教程中看到:^d,其意是你按下了ctrl键,然后敲入了相应的字母。对于本例来说,你就按下ctrl键,然后敲d。

开始之前
VI编辑器使用全屏幕,因此了解你使用的是何种类型的终端是有必要的。当你登录的时候,wiliki会问你的终端是什么。提示信息像这个样子:TERM = (vt100)。
如果你知道自己的终端是一个vt100(或者一个能够当作vt100用的模拟器),在你登录的时候,就为终端类型敲击回车键。如果你有一个hp终端,为终端类型输入“hp”并回车。如果你不确定自己的终端类型,问一个实验室的管理员,或者是请别人帮助你设置正确的终端类型。
如果在登录的时候,你犯了一个错误,输入了错误的终端类型,不要紧张,退出就行了。你可以输入以下的命令来修补设置:
首先,告诉你的shell你的终端是何种类型。(如果你不确定你的shell是什么,可以输入这个命令:echo $SHELL)。对于上面已经给出的例子,终端类型是“vt100”。用你拥有的任何终端类型去替换它。对于C shell(/bin/csh),命令是这个:set term=vt100。对于Bourne Shell(/bin/sh)或者是Korn Shell(/bin/ksh),命令如下:export TERM   TERM=vt100。下一步,用这个命令重设你的终端:tset。
现在,终端的类型被正确设置(希望如此吧),你已经准备好来开始使用VI了。

启动VI编辑器
VI编辑器允许用户生成新的文件或编辑已存在的文件。启动VI编辑器的命令是:vi,紧接着是文件名。例如,为了要编辑一个叫做temporary的文件,你要输入“vi temporary”并回车。你也可以不用文件名来启动vi,但是当你想保存自己的工作的时候,你必须要告诉VI将要把这些内容保存到哪个文件中。
当你第一次启动VI的时候,你会看到在屏幕的左边充满了波浪号(像“~”)。在文件结束之后的任何空行都是如此显示。在屏幕的底部,文件名被显示出来。如果专门指定了一个已经存在的文件,文件的大小也会被显示出来,就像这样:”filename” 21 lines, 385 characters。
如果你指定的文件不存在,系统会告诉你这是一个新文件,就像这样:”newfile” [New file]。
如果你不要文件名而启动VI,当VI启动的时候,屏幕的底部会是空白。如果屏幕没有显示这些预期的结果,你的终端类型可能被错误地设置了。输入:q并回车来退出VI,并且修补你的终端类型。如果你不知道怎么做,问一下实验室管理员。

退出VI
你已经知道了如何进入VI,了解一下如何退出它也是很好的。VI编辑器有两种模式,为了退出VI,必须要处于命名(command)模式。敲击“Escape”或“Esc”键(如果你的终端没有这个键,可以试试^[,或control-[)来进入命名模式。如果在你敲击“Escape”的时候,你已经在命令模式之下,不要担心。系统会发出警告,但是你仍然处于命名模式。
离开VI的命令是:q。当处于命名模式之下,输入冒号以及“q”,并回车。如果你的文件被修改过了,编辑器会警告你,同时也不让你退出。为了忽略此消息,不保存就退出VI的命令是:q!。它让你不用保存任何改动而退出VI。
当然,一般说来,在一个编辑器中,你还是想保存你做出的改变。保存编辑器内容的命令是:w。你可以将以上命令和退出命令结合起来,即:wq。你可以指定一个不同的文件名来保存这些内容,这是通过在:w之后指定文件名来实现的。例如,你想将你正在编辑的文件保存为另外一个叫做filename2的文件名,你可以输入: w filename2并回车。
另外一种保存你的改变并退出VI的方法是ZZ命令。在命令模式下,输入ZZ,它会做与:wq相同的事情。如果文件的内容有任何改变,这些改变会被保存下来。这是离开编辑器的最容易的方法,只需要敲击键盘两次。

VI的两种模式
大多数用户了解VI编辑器的第一件事就是它有两种模式:command(命令)和insert(插入)。command模式允许命令条目来操纵文字。这些命令通常是一个或两个字符长,可以敲几下键盘便被输入了。insert模式将任何在键盘上敲击的内容输入现在的文件中。
VI是以command模式启动的。有几个命令来将VI编辑器转入insert模式。最常用的命令是a和i。这两个命令在前面已经描述过了。当你在insert模式下时,敲击退出(Escape)按钮,你就退出此模式了。如果你的终端没有这个键, ^[或control-[也行。你可以快速敲两下退出(Escape)按钮,这时VI肯定会在command模式之下的。当你已经在command模式之下时敲击退出(Escape)按钮并不会让编辑器退出command模式。系统会提醒你已经在此模式下了。

如何在命令模式下输入命令
命令(command)模式下的命令的格式一般是这样(括号中是可选的参数):[count] command [where]。
大多数命令只有一个字符长,包括那些使用控制字符的命令。本节中描述的命令是在VI编辑器中最经常使用的。
count可以是1到9之间的任何一个。例如,x命令删除在光标之下的那个字符。如果你在命令模式下输入23x,会有23个字符被删除。
一些命令使用一个可选的where参数,你可以指定命令影响到多少行或者是文件的多少部分。where参数也能够是任何移动光标的命令。

一些简单的VI命令
以下是一个简单的命令集合,它们足以让初学者起航。也有许多其它的方便的命令,这将在后续章节中讨论。

  • a:进入插入(insert)模式,输入的字符会被插入到当前光标位置之后。如果你指定了数目(count),插入的所有文字会被重复那么多次。
  • h:向左把光标移动一个字符的位置。
  • i:进入插入(insert)模式,输入的字符会被插入到当前光标位置之前。如果你指定了数目(count),插入的所有文字会被重复那么多次。
  • j:将光标向下移动一行。
  • k:将光标向上移动一行。
  • l:向右把光标移动一个字符的位置。
  • r:将光标所在位置的字符替换掉。指定数目(count)来替换许多字符。
  • u:撤销对文件所作的最后一次修改。再一次输入u会恢复最后一次修改。
  • x:删除光标所在位置的字符。count指出了要删除多少字符。光标之后的字符会被删除掉。

VI中的文字缓存
VI编辑器有36个缓存来存储文字片,同时也有一个通用目的缓存(general purpose buffer)。任何时候,在文件中,当一个文字块被删除或整形,它被放入通用目的缓存中。VI的大多数用户很少使用其它缓存,因此在没有其它缓存的情况下也能够活得很快活。如果被指定的话,文字块也能够被存储在其它缓存中。用”命令来指定缓存。在输入”之后,必须要输入指明缓存的字母或数字。例如,命令:”mdd使用了缓存m,最后的两个字符代表删除当前行。类似地,可以使用p或P命令来粘贴文字。”mp在当前光标位置之后粘贴缓存m的内容。对于之后两节所使用的任何命令,这些缓存被指定用于文字或段落的临时存储。

剪切与整形
用于剪切的常用命令是d。此命令从文件中删除文字。在这个命令之前是一个可选的count,之后是一个移动说明。如果你输入dd,会删除当前行。以下是这些命令的一些组合:

  • d^:删除从行首到当前光标所在位置的内容。
  • d$:删除从当前光标所在位置到行末的内容。
  • dw:删除从当前光标所在位置到字末的内容。
  • 3dd:从当前光标所在位置向下删除3行。

与d命令的功能类似,y命令从文件中提取文字而不删除文字。

粘贴
粘贴的命令是p或P。它们的区别仅在于当粘贴的时候相对于光标的位置。p在当前光标之后粘贴专门的或一般的缓存,而P则在当前光标之前粘贴专门的或一般的缓存。在粘贴命令之前指定数目(count)会将文字粘贴数次。

缩进代码与检查
VI编辑器有功能来帮助程序员将它们的代码布局得更加的整洁。有一个变量来为代码中的各级嵌套设定缩进。为了使用这个功能,可以阅读本教程的“customization section”一节。例如,将偏移宽度设为4个字符的命令是:set sw=4。
以下的命令缩进你的代码或移除缩进,同时也能够用count来指定:

  • <<:将当前行向左移动一个偏移宽度。
  • >>:将当前行向右移动一个偏移宽度。

VI编辑器也有一个有用的功能来帮助你在遇到悬挂圆括号或大括号的时候检查你的源代码。%命令会寻找与一个特别的右括号相对应的左括号,或与之相反。将光标放到一个括号上并敲击%来将光标移动到相应的括号。这个功能对于检查未闭合的括号是很有用的。如果有一个不匹配的括号存在,VI会发出嘟嘟声,这是在提示你没有发现配对的符号。

文字与字符搜索
VI编辑器有两类搜索:字符串和字符。对于一个字符串搜索,使用/和?命令。当你开始使用这个命令的时候,在最底部一行会显示你敲入的命令,在命令后面可以输入你想要搜索的特殊字符串。这两个命令仅在搜索发生的方向上有区别。在文件中,/命令向前(向下)搜索,?命令向后(向上)搜索。n和N命令分别在相同或相反的方向上重复之前的搜索命令。一些字符对于VI来说,有特殊的意义,因此在它们前面必须要放置一条斜线(\)来被当作搜索表达式的一部分。
特殊字符:

  • ^:行的开始(一个搜索表达式的开始)。
  • .:匹配一个单字符。
  • *:匹配0个或多个之前的字符。
  • $:行的结束(一个搜索表达式的结束)。
  • [:开始一系列匹配,或者是非匹配的表达式。例如:/f[iae]t匹配三者之一:fit fat fet。在这种形式下,它不会匹配这些:/a[^bcd]不会匹配任何字符串,除了带一个a和另外一个字母:ab ac ad。
  • <:将之放在以反斜线结束的表达式中来寻找一个字的结束或开始。例如,/\<the\>只会发现the,而不是这些字:there和other。
  • >:参考对于“<”的描述。

字符搜索在一行之内搜索来寻找在命令之后输入的一个字符。f和F命令只在当前行上搜索一个字符。f向前搜索,而F向后搜索,同时,光标会移动到所发现字符的位置。
t和T命令只在当前行上搜索一个字符,对于t来说,光标移动到字符之前的位置,而T向后搜索行到字符之后的位置。
这两套命令使用;和,命令来重复,其中;在相同方向上重复上一条字符搜索命令,而,在相反方向上重复上一条字符搜索命令。

VI(以及EX)的设置
你能够在启动的时候个性化(customization)VI的行为。有几个编辑选项使用:set命令,以下是在Wiliki上的VI和EX编辑器选项(你可以在命令模式下通过输入:set all并回车来获得此列表):

有一些选项具有用等号“=”设置的值,而其它选项有的有,有的没有。(这些开关类型叫做Boolean,在它们的前面有“no”来暗示它们不是设置的。)在此展示的选项是没有进行个性化设置的选项。下面用缩写给出了这些选项的描述。例如,命令设置自动缩进,可以输入:set autoindent或set ai。为了去除设定,你可以输入:set noautoindent或set noai。

  • autoindent (ai):此选项对编辑器进行设置以使得在一个缩进行之后的行像前面行那样缩进。如果你想back over此缩进,可以在第一个字符的位置输入^D。^D工作在插入(insert)模式,并不在命令(command)模式。当然,可以用shiftwidth来设置缩进的宽度,下面有解释。
  • exrc:在启动的过程中,会读入当前目录下的.exrc文件。这可以在环境变量EXINIT或你的主目录下的.exrc文件中设置。
  • mesg:如果对选项解除设置,要关闭消息,使用:set nomesg,这样做以使得当你在使用编辑器的时候没有人能够打扰你。
  • number (nu):用在左边的行号来显示行。
  • shiftwidth (sw):此选项带有一个值,用此值来定义一个软件制表位(tabstop)的宽度。(这个软件制表位用于<<和>>命令。)例如,你可以用此命令来设置偏移宽度为4:set sw=4。
  • showmode(smd):此选项用于显示你所用的编辑器的实际模式。如果你在插入(insert)模式下,屏幕的最底下一行会显示INPUT MODE。
  • warn:如果你修改了文件,但是没有保存,该选项会警告你。
  • window(wi):该选项设定VI使用的屏幕上的行数。例如,要设定VI编辑器只使用你的屏幕的12行(因为你的modem很慢),你可以使用这个:set wi=12。
  • wrapscan(ws):此选项会影响到文字搜索的行为。如果wrapscan被设置了,要是没有在文件的底部找到要寻找的文字,它会试着在开始部分寻找它。
  • wrapmargin(wm):如果此选项有大于0的值,编辑器会自动“word wrap”。也就是说,如果你占用左边部分太多的空间,文字会转向下一行而不用敲回车。例如,要设定wrap边界为2个字符,可以输入:set wm=2。

写和将密钥(关键字)映射到其它密钥(关键字)
一个在VI编辑器中有用的EX编辑器命令是abbreviate命令。它让你为特殊的字符串设定缩写。此命令像这样:ab string thing to substitute for。例如,如果要敲入名字“Humuhumunukunukuapua`a”,但是你不想敲入整个名字,那么你可以使用缩写。在此例中,像这样敲入命令:ab 9u Humuhumunukunukuapua`a。
现在,当你单独敲入9u的时候,VI会敲入它所代表的整个字。如果你敲入了9university,它就不会替换这个字。
去除之前定义的缩写的命令是unabbreviate。例如,去除之前例子的命令就是”:una 9u”。如果你要获取缩写列表,只需要简单地输入:ab,而不用任何定义。
另外一个对于个性化很有帮助的EX编辑器命令就是映射(mapping)命令。有两类映射(mapping)命令。一个用于命令模式,另一个用于插入模式。它们分别是:map和:map!。映射和缩写的工作方式类似,你给系统一个关键序列,并给系统另外一个关键序列去替换之前的序列。(被替换掉的关键序列通常是VI命令。)

EXINIT环境变量和.exrc文件
有两种方式来个性化VI编辑器。如果你在主目录下生成了一个叫做.exrc的文件,当VI启动的时候,那里面所有的命令都会被读到。另外一种方法是设置一个叫做EXINIT的环境变量。该选项在你的shell的建立文件里被设置。如果你使用/bin/csh (C-Shell),命令如下(被放置在.cshrc文件里面):setenv EXINIT ‘…’。
如果你使用/bin/sh or /bin/ksh,命令如下(被放置在.profile文件中):export EXINIT EXINIT=’…’。
就像例子中所说的,不要放在…。在这个空间中,放置你想要建立的命令。例如,如果你想自动缩进,行编号,以及wrap边界三个字符,setenv命令(对于C shell来说)像这样:setenv EXINIT ‘set ai nuwm=3’。
如果你想在setenv EXINIT中放置不止一个命令,用竖线(|)将命令隔开。例如,在命令模式中,要将“g”命令映射到“G”字符,命令是:map g G,与上面的命令结合,可以得到:setenv EXINIT ‘set ai nuwm=3|map g G’。
如果你想生成叫做.exrc的文件,你可以在文件中放置与EXINIT之后的引用一样的东西。

当终端出现问题的时候恢复你的工作
VI编辑器编辑你的文件的一个临时副本,当编辑结束之后,或者当你叫它保存的时候,它就将临时文件的内容放到原始文件中。如果在你编辑文件的时候发生了故障,VI编辑器会试图保存你正在做的任何工作,并且为了之后的恢复而存储它。(注意:如果在你编辑文件的时候VI死掉了,它会给你发一封邮件告诉你如何恢复它。-r选项代表恢复。如果你正在编辑文件vitalinfo,而你意外退出了,“vi”编辑器的-r选项可以帮你忙。该命令像这样:vi -r vitalinfo。在使用-r选项一次之后,你必须要将你恢复的内容保存到实际的文件中。-r选项只能在每一个失败的VI会话中使用一次。)

有关在工作台上使用VI的警告
当你使用工作台时,必须要知道两件事情:一次(连续)编辑相同的文件许多次,以及改变屏幕的大小。
因为VI编辑你的原始文件的一个副本,并且将那个副本的内容保存到原始文件中,如果你登录了好几次,并且使用VI编辑相同的文件好几次,如果你一次保存在一个窗口上,然后又保存到另外一个窗口上,第一次保存的对于文件的改变会被覆盖掉。确保对于每个文件,你只是运行一个副本。
如果你使用一个工作台的一个终端程序,你可以通过拖动窗口的边界来改变屏幕的大小。如果你对大小还不尽满意,输入以下命令:eval `resize`。如果这个命令无效,另外一个命令如下:eval `/usr/bin/X11/resize`。
如果大小是错误的,编辑器不会正常运行。如果你对于屏幕尺寸存在任何疑问,可以向计算机实验室的管理员求助,他会帮你设置正确的尺寸。

VI命令的总结
以下是按照功能分类的VI命令的一个总结列表。有可能还会有其它命令,可以查看VI的在线手册。为了方便,你可以以文本文件方式保存该文件,然后删除你认为自己不会用的一些命令,并打印出剩下的较短的文件。

剪切与粘贴/删除文字

  • “:指定一个任何命令使用的缓存。在”之后输入一个字母或数字,它们会对应一个缓存。
  • D:从当前光标所在位置向后删除直到行尾。
  • P:在当前光标位置或行之前粘贴专门的缓存。如果没有指定缓存(使用”命名),“P”就使用通用缓存。
  • X:删除光标之前的字符。
  • Y:将当前行整形到指定的缓存。如果没有指定缓存,就使用通用缓存。
  • d:删除,直到where。“dd”删除当前行。一个数字就表示删除那么多行。被删除的内容放置在由”命令指定的缓存中。如果没有指定缓存,就使用通用缓存。
  • p:在当前光标位置或行之后粘贴专门的缓存。如果没有指定缓存(使用”命名),“p”就使用通用缓存。
  • x:删除光标之下的字符。输入一个数字表示要删除多少字符。被删除的字符位于光标之后。
  • y:整形,将结果放到一个缓存中。“yy”整形当前行。输入一个数字表示要整形的行数。可以用”命令来指定缓存。如果没有指定缓存,就使用通用缓存。

插入新的文字

  • A:在当前行之后追加。
  • I:在一行的开头处插入。
  • O:在当前光标位置的上面一个新行进入插入模式。
  • a:进入插入模式,输入的字符会在当前光标位置之后插入。如果在命令之前输入一个数字,那么会插入内容多次。
  • i:进入插入模式,输入的字符会在当前光标位置之前插入。如果在命令之前输入一个数字,那么会插入内容多次。
  • o:在当前光标位置之下的一个新行进入插入模式。

在文件内移动光标

  • ^B:向后回滚一页。输入数字就会回滚那么多页。
  • ^D:向前滚动半个窗口。输入数字会滚动那么多行。
  • ^F:向前滚动一页。输入数字会滚动那么多页。
  • ^H:将光标向左移动一个空格。输入数字会移动那么多个空格。
  • ^J:在同一列向下移动光标一行。输入数字会向下移动那么多行。
  • ^M:移动到下一行的第一个字符处。
  • ^N:在同一列向下移动光标一行。输入数字会向下移动那么多行。
  • ^P:在同一列向上移动光标一行。输入数字会向上移动那么多行。
  • ^U:向后回滚半个窗口。输入数字会回滚那么多行。
  • $:将光标移动到当前行的末尾。输入数字会移动到下面行的末尾。
  • %:将光标移动到匹配的括号处。
  • ^:将光标移动到第一个非空白的字符处。
  • (:将光标移动到一个句子的开头。
  • ):将光标移动到下一个句子的开头。
  • {:将光标移动到前一个段落。
  • }:将光标移动到下一个段落。
  • |:将光标移动到指定的列(由count指定)。
  • +:将光标移动到下一行的第一个非空白字符处。
  • -:将光标移动到之前一行的第一个非空白字符处。
  • _:将光标移动到当前行的第一个非空白字符处。
  • 0:将光标移动到当前行的第一列。
  • B:将光标回移一个字,跳过punctuation。
  • E:将光标向前移动到一个字的结尾,跳过punctuation。
  • G:跳到由count指定的行处。如果没有指定数目,就跳转到文件的末尾。
  • H:将光标移动到屏幕顶端的第一个非空白字符处。
  • L:将光标移动到屏幕底端的第一个非空白字符处。
  • M:将光标移动到屏幕中间的第一个非空白字符处。
  • W:将光标向前移动到一个字的开头,跳过punctuation。
  • b:将光标回移一个字。如果光标在字的中间,就将光标移动到那个字的第一个字符处。
  • e:将光标前移一个字。如果光标在字的中间,就将光标移动到那个字的最后一个字符处。
  • h:将光标向左移动一个字符的位置。
  • j:将光标向下移动一行。
  • k:将光标向上移动一行。
  • l:将光标向右移动一个字符的位置。
  • w:将光标向前移动一个字。如果光标在字的中间,就将光标移动到下一个字的第一个字符处。

在屏幕上移动光标

  • ^E:向前滚动一行。用count指定滚动的行数。
  • ^Y:向后滚动一行。用count指定滚动的行数。
  • z:用以下选项重画屏幕。“z<回车>”将当前行放到屏幕的顶部;“z.”将当前行放到屏幕的中间;“z-”将当前行放到屏幕的底部。如果你在“z”命令之前指定一个数字,它就将当前行变到指定的行处。例如,“16z.”将第16行放到屏幕的中间。

替换文字

  • C:从当前光标位置处变到行的结尾。
  • R:用输入的一系列字符(以Esc键结尾)替换屏幕上的字符。S:改变一整行。
  • c:改变直到。“cc”改变当前行。用count指定改变的行数。
  • r:替换光标下的一个字符。用count指定替换的字符数。
  • s:替换(Substitute)光标下的一个字符,并进入插入模式。用count指定替换的字符数。在最后一个替换的字符处放一个美元($)符号。

搜索文字或字符

  • ,:在相反方向上重复上一个f,F,t或T命令。
  • /:在文件里向下搜索/之后的字符串。
  • ;:重复上一个f,F,t或T命令。
  • :在文件里向上搜索之后的字符串。
  • F:在当前行向后搜索“F”命令指定的字符。如果找到了,就将光标移动到那个位置。
  • N:重复由“/”或“”给出的搜索,不往相反方向搜索。
  • T:在当前行向后搜索“F”命令指定的字符。如果找到了,就移动到那一列。
  • f:在当前行搜索“f”命令之后指定的字符。如果找到了,就将光标移动到那个位置。
  • n:重复上一个“/”或“”搜索。
  • t:在当前行搜索“t”命令之后指定的字符。如果找到了,就将光标移动到那个字符位置之前的一列。

操纵字符/行格式

  • ~:转换光标之下的字符事例(Switch the case of thecharacter under the cursor)。
  • <:Shift the lines up towhere to the left by one shiftwidth. “<<” shifts the currentline to the left,and can be specified with a count。
  • >:Shift the lines up towhere to the right by one shiftwidth. “>>” shifts the currentline to theright, and can be specified with a count。
  • J:将当前行和下一行合并起来。用count指定合并的行数。

保存与退出

  • ^\:退出“VI”模式,进入“EX”模式。EX编辑器是行编辑器,VI就是建立在其上的。重新进入VI的EX命令是“:vi”。
  • Q:退出“VI”模式,进入“EX”模式。ex编辑器是一个逐行(line-by-line)编辑器。重新进入VI的EX命令是“:vi”。
  • ZZ:退出编辑器,如果有任何改动就保存。

其它一些指令

  • ^G:显示当前的文件名和状态。
  • ^L:清除并重画屏幕。
  • ^R:重画屏幕并移除假的行。
  • ^[:退出键。取消部分形成的命令。
  • ^^:回到上次编辑的文件处。
  • !:执行一个shell。如果指定了a,使用!执行的程序将特定的行作为标准输入,同时也会替换带执行程序的标准输出的那些行。“!!”将当前行作为输入来执行一个程序。例如,“!4jsort”会从当前光标位置拿掉五行并执行sort。在键入命令之后,会有一个你可以输入命令的单独的exclamation点。
  • &:重复之前的“:s”命令。
  • .:重复最后一次修改文件的那个命令。
  • ::开始输入一个EX编辑器命令。当用户输入回车的时候,此命令马上执行。
  • @:输入在特定缓存中存储的命令。
  • U:将当前行恢复到光标进入行之前的状态。
  • m:用“m”命令之后的特定字符来标记当前位置。
  • u:撤销对文件所作的最后一次修改。再次输入“u”会恢复修改。

EX命令
VI编辑器建立在另外一个叫做EX的编辑器之上。EX编辑器只通过行来编辑。在VI编辑器中,用:命令来开始键入一个EX命令。以下的列表并不完全,但是给出的命令是用得比较多的。如果用某些命令(如“:s”和“:w”)来修改不止一行,在命令之前必须指定范围。例如,要替换掉从第3行到第15行的内容,命令是“:3,15s/from/this/g”。
:abstring strings
缩写。如果在VI中输入一个与strings相关的字,编辑器会自动插入相应的字。例如,缩写“:ab usa United States ofAmerica”会在输入“usa”的时候插入字“United States of America”。
:mapkeys new-seq
映射。此命令将一个关键字或一个关键字序列映射到另外一个关键字或一个关键字序列。
:q
退出VI。如果对内容有任何改动,编辑器会发出一个警告信息。
:q!
不保存而退出VI。
:s/pattern/to_pattern/options
替换。此命令用to_pattern中的字符串替换指定的pattern。如果没有参数(选项),此命令只是替换第一个出现的pattern。如果给定了“g”,所有出现的pattern都会被替换掉。例如,命令“:1,$s/Dwayne/Dwight/g”会替换掉将所有出现的“Dwayne”替换为“Dwight”。
:set[all]
给VI和EX设定一些个性化的选项。“:set [all]”命令给出了所有可能的选项。
:unastring
移除之前由“:ab”定义的缩写。
:unmkeys
移除由“:map”定义的移除映射。
:vifilename
开始编辑一个新文件。如果没有保存对内容作出的改动,编辑器会给出一个警告。
:w
写出当前文件。
:wfilename
将缓存写到指定的文件名。
:w>> filename
将缓存的内容追加到文件中。
:wq
写缓存并退出。

近20个绚丽实用的jQuery/CSS3侧边栏菜单

Standard

原文:http://www.codeceo.com/article/20-jquery-css3-side-menu.html

jQuery作为一款主流的JavaScript前端开发框架,深受广告开发者的亲睐,同时jQuery有着不计其数的插件,特别是菜单插件更为丰富,本文将要为大家介绍20个绚丽而实用的jQuery侧边栏菜单,这些侧边栏菜单可以用在不同风格的网页上,如果你觉得不错,也可以进入其下载页面下载菜单源代码。

1、jQuery 3D 垂直多级菜单 可筛选菜单项

这是一款手风琴样式的垂直多级菜单,其特点是利用CSS3特性让菜单外观显得3D立体的效果,同时你也可以在搜索框中输入关键字来筛选菜单项。

jquery-3d-menu-with-search

在线演示        源码下载

2、CSS3手机端侧滑菜单 4种滑动菜单特效

这是一款基于CSS3的手机端侧滑菜单,一共有4种侧滑动画特效。这款CSS3菜单的特点是鼠标划过时即可以各种动画方式展开和隐藏菜单项,该动画方式由CSS3中的transition-delay属性来完成,具体效果大家可以看演示。

css3-mobile-slider-menu

在线演示1        在线演示2        在线演示3        在线演示4        源码下载

3、纯CSS3垂直菜单 菜单项滑动动画

这款CSS3菜单的特点是菜单项有一个非常特别的背景,并且背景可随着鼠标滑过而滑动,挺有创意的滑动动画。这款CSS3菜单是垂直样式的,很适合做网页的侧边栏菜单。

pure-css3-ver-slider-menu

在线演示        源码下载

4、jQuery左侧带小图标的多级下拉菜单

今天我们要来分享一款很不错的jQuery左侧带小图标的多级下拉菜单,菜单是垂直的,每一个菜单项带有一个小图标,看起来非常专业。并且菜单支持无限极下拉,所以对各位Web开发者来说非常实用。菜单时基于jQuery的,所以基本可以支持所有的浏览器。

jquery-side-icon-dropdown-menu

在线演示        源码下载

5、CSS3带小图标垂直下拉菜单

这是一款效果非常不错的CSS3垂直下拉菜单,菜单左侧是每一个菜单项的功能小图标,右侧也可以定义一些数字小图标,并且在菜单项最右侧是tooltip的样式。另外,当鼠标滑过菜单项时将会改变菜单的背景颜色,其中的圆角效果由简单的CSS3属性完成。

css3-dropdown-menu-icon

在线演示        源码下载

6、jQuery多层级垂直手风琴菜单

这款手风琴菜单是多层级的,你可以通过HTML结构生成任意层级的菜单。由于是基于jQuery的,因此这款手风琴菜单的兼容性还不错。

jquery-level-accord-menu

在线演示        源码下载

7、CSS3垂直图标菜单 带Tooltip提示框

今天我们要来分享一款CSS3菜单,菜单外观很漂亮,是垂直排列的小图标,鼠标滑过菜单项时,菜单项的背景会填充渐变的颜色,另外还会弹出该菜单项描述的Tooltip提示框。之前我们也分享过很多CSS3垂直菜单,像这款CSS3二级下拉动画菜单 菜单背景滑动动画纯CSS3垂直动画菜单等都是非常不错的CSS3垂直菜单按钮。

css3-ver-nav-with-icon

在线演示        源码下载

8、CSS3手风琴下拉菜单 支持多菜单展开

这又是款基于CSS3的下拉菜单,这款CSS3菜单是手风琴样式的,今天的这款CSS3手风琴菜单也类似,菜单具有3种模式,一种是同时展开多个菜单,一种是只能同时展开一个菜单,还有一种是可以默认展开一个菜单。应该说,这款CSS3手风琴菜单非常的实用。

css3-accord-menu-mult-flip

在线演示        源码下载

9、HTML5/CSS3仿Google Play垂直菜单

这款CSS3菜单也是垂直菜单,是一款仿Google Play的垂直菜单,另外菜单左侧还有非常漂亮的小图标。

css3-google-play-store-menu

在线演示        源码下载

10、CSS3手风琴菜单 可同时折叠多个菜单

这次分享的CSS3手风琴菜单可以同时折叠展开多个菜单项,菜单整体是黑色的风格,并且在每一个菜单项上都有很漂亮的小图标,小图标像是嵌入在里面一样很有质感。

css3-multiple-accordion-menu

在线演示        源码下载

11、CSS3垂直手风琴折叠菜单

这款CSS3垂直手风琴折叠菜单也非常不错,这款CSS3手风琴菜单的每一个菜单项都有小图标,而且只能有一项展开,更有意思的是,在菜单折叠和展开式右侧的箭头也会有不错的动画效果。

css3-ver-accordion-menu

在线演示        源码下载

12、纯CSS3垂直动画菜单 效果很酷

今天我们来分享一款CSS3垂直菜单,这款垂直菜单不仅有漂亮的小图标,而且鼠标滑过时还有非常酷的CSS3动画,大家可以试试这款CSS3垂直菜单。

pure-css3-vertical-menu

在线演示        源码下载

13、CSS3二级下拉动画菜单 菜单背景滑动动画

这款CSS3菜单的特点是在菜单展开时,菜单的背景会出现滑动的动画效果。

css3-animation-bg-menu

在线演示        源码下载

14、jQuery/CSS3带未读提醒的垂直菜单

这是一款基于jQuery和CSS3的垂直动画菜单,这款jQuery菜单的特点是菜单整体悬浮在一张大气的背景图片上,很有立体的视觉效果。其次这款菜单带有信箱未读提醒,并且不断地闪烁来提示用户打开邮件箱check邮件。

jquery-css3-menu-with-inbox

在线演示        源码下载

15、jQuery/CSS3可折叠侧边栏菜单

这次我们要分享的一款很棒的jQuery菜单插件,这款菜单插件是可折叠的侧边栏菜单,菜单的特点是点击按钮可以展开和折叠菜单,并伴随动画效果。而且每一个菜单项都有一个小图标,非常清新漂亮。当然折叠的效果需要CSS3的支持。

jquery-css3-slide-menu

在线演示        源码下载

16、jQuery仿Google Nexus菜单样式插件

这是一款基于jQuery和CSS3的多功能菜单,菜单样式是仿Google Nexus的,菜单整体看上去是一个封闭的结构,也就是说既有水平菜单,也有垂直菜单,而且每一个菜单项的左侧都有一个漂亮的小图标,是一款外观非常不错的jQuery菜单导航。

jquery-google-nexus

在线演示        源码下载

17、清新小图标的HTML5/CSS3侧边栏菜单

这款CSS3侧边栏菜单和之前这款菜单类似,也带有漂亮可爱的小图标,鼠标滑过菜单项时背景会出现淡入淡出的效果。

html5-css-side-menu-with-icon

在线演示        源码下载

18、CSS3垂直菜单 菜单有立体动画视觉

这款基于CSS3的垂直菜单实现很简单,该CSS3垂直菜单有几个特点:

  1. 菜单外观呈立体视觉效果,非常有质感
  2. 鼠标滑过菜单项时,菜单项会出现伸缩动画。

css3-vertical-menu

在线演示        源码下载

19、CSS3手风琴菜单 下拉展开带弹性动画

这款是CSS3手风琴菜单,菜单项在展开和收缩的时候菜单项会有弹性动画效果。每一层父级菜单有一个小三角,菜单项在展开的时候这个小三角也会出现动画,非常酷。

css3-accordion-menu

在线演示        源码下载

以上这些jQuery侧边栏菜单是不是对你有所帮助,欢迎你的评价。

10大好用的Linux实用工具推荐

Standard

https://winclient.cn/10-useful-utilities-linux/

本文我们收集了对 Linux 用户非常有用的 10 个工具,其中包括网络监控、系统审计或其它有用命令,这 10 个 Linux工具可以帮助大家提高工作和使用效率,非常实用。

1. w

对,你没看错,就是 w 命令。使用该命令我们可以查看到当前登录系统的用户是谁,以及执行了哪些命令。

10大好用的Linux实用工具推荐

2. nmon

Nmon 是一个可以监控当前系统性能的小工具,使用之前需要先用如下命令进行安装:

  1. sudo aptget install nmon

安装好后执行 nmon 命令即可打开:

  1. nmon

10大好用的Linux实用工具推荐

nmon 可以查看网络、CPU、内存和磁盘的使用情况。

打开之后按 c 查看 CPU 信息:

10大好用的Linux实用工具推荐

打开之后按 n 查看网络信息:

10大好用的Linux实用工具推荐

3. ncdu

ncdu 命令可以用来查看和分析 Linux 中各目录对磁盘空间占用情况的工具,请使用如下命令进行安装:

  1. aptget install ncdu

安装好后执行如下命令即可从根目录开始分析:

  1. ncdu /

10大好用的Linux实用工具推荐

注意:执行上述命令会占用大量磁盘 I/O

分析完成后,会生成类似如下截图的输出:

10大好用的Linux实用工具推荐

我们可以在结果界面按 n 按名称进行排序或按 s 按大小进行排序。

4. slurm

slurm 是一个网卡带宽监控命令行实用程序,它会自动生成 ASCII 图形输出。使用之前先用如下命令进行安装:

  1. aptget install slurm

使用如下命令进行输出:

  1. slurm i <网卡名称>

10大好用的Linux实用工具推荐

slurm 界面中可以执行如下选项:

  • I:显示lx/tx状态
  • c:切换到经典界面
  • r:手动刷新界面
  • q:退出工具

5. findmnt

Findmnt 是一个 Linux 内置的命令行工具,它主要用于查找挂载的文件系统状态。Findmnt 可以查看到当前系统中已挂载的设备,在必要时还可进行 mount 或 unmount 操作。

执行 findmnt 命令后会看到如下输入:

10大好用的Linux实用工具推荐

当然,还有如下参数可用:

  • findmnt -l :以列表方式进行输出
  • findmnt -s :输出 fstab 中挂载的设备
  • findmnt -t ext4 :按文件系统类型进行输出

6. dstat

dstat 是一个可以非常灵活使用和进行组合使用的工具,它可用于监控内存、进行、网络及磁盘性能,可用于替代  ifstat、iostat、dmstat等工具。使用之前需先执行如下命令进行安装:

  1. aptget install dstat

执行如下命令可以看到所有监控数据:

  1. dstat

10大好用的Linux实用工具推荐

其可选参数非常多,常用的有:

  • dstat -c : 监控CPU
  • dstat -cdl -D sda1 :监控CPU详细信息
  • dstat -d :监控磁盘

7. saidar

saidar 是另一个 CLI 系统数据监控和统计工具,可提供有关磁盘、网络、存储和 SWAP 的监控信息。使用之前需先使用如下命令进行安装:

  1. sudo aptget install saidar

安装完成后可直接执行 saidar 进行输出,但我们通常使用带参数的命令生成带颜色输出:

  1. saidar c

10大好用的Linux实用工具推荐

8. ss

ss 全称 socket statistics,是一个可以替代 netstat 的网络连接查看工具。

直接执行 ss 即可进行查看:

10大好用的Linux实用工具推荐

常用参数有:

ss -A tcp :指定查看协议

ss -ltp :显示进程名称和 PID

9. ccze

ccze 非常有用,它可以用不同颜色高亮日志,协助管理员进行区分和查看分析。使用之前需先使用如下命令进行安装:

  1. aptget install ccze

我们可以使用类似如下方式进行使用:

  1. tailf /var/log/syslog | ccze

10大好用的Linux实用工具推荐

而使用 ccze -l 参数可以查看其支持的日志类型。

10大好用的Linux实用工具推荐

10. ranwhen.py

我们最后介绍的 ranwhen.py 是一个 python 工具,它可以以图形方式显示系统活动。

要使用该工具需要先安装 python 语言支持:

  1. sudo aptaddrepository ppa:fkrull/deadsnakes
  2. sudo aptget update
  3. sudo aptget install python3.2

然后下载ranwhen.py

  1. wget https://github.com/p-e-w/ranwhen/archive/master.zip
  2. unzip master.zip && cd ranwhenmaster

使用如下命令即可执行ranwhen.py

  1. python3.2 ranwhen.py

10大好用的Linux实用工具推荐

小结

本文介绍的 10 大好用 Linux 实用工具都还比较有意思,希望大家能喜欢。

c#中的Struct与Byte[]互转

Standard

原文:http://www.cnblogs.com/juneapple/articles/1769016.html

c#中要让struct转换为byte[],首先要在struct申明中说明struct的结构,如下:


/// <summary>
/// 数据包内容的头信息
/// </summary>
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct TransHead
{
/// <summary>
/// 本结构的字节大小
/// </summary>
public static readonly int Size = Marshal.SizeOf(typeof(TransHead));

/// <summary>
/// 请求类型
/// </summary>
[MarshalAs(UnmanagedType.I4)]
public RequestType Type;
/// <summary>
/// 请求子类型
/// </summary>
[MarshalAs(UnmanagedType.I4)]
public REFRESH SubType;
/// <summary>
/// 协议版本
/// </summary>
public int Edition;
/// <summary>
/// 标识(系统返回值)
/// </summary>
public int Flag;
}
/// <summary>
/// 数据包内容,头信息加定长数据
/// </summary>
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct TransHeadAndData
{
/// <summary>
/// 本结构的字节大小
/// </summary>
public static readonly int Size = Marshal.SizeOf(typeof(TransHeadAndData));
/// <summary>
/// 本结构数据体的字节大小(结构大小-结构头)
/// </summary>
public static readonly int SubSize = Size - TransHead.Size;

[MarshalAs(UnmanagedType.Struct)]
public TransHead DataHead;

/// <summary>
/// 具体数据 x460
/// </summary>
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 460)]
public byte[] Data;
}
public struct TransUplink
{
/// <summary>
/// 本结构的字节大小
/// </summary>
public static readonly int Size = Marshal.SizeOf(typeof(TransUplink));// - 4;
/// <summary>
/// 数据类型
/// </summary>
[MarshalAs(UnmanagedType.I4)]
public RequestType Type;
/// <summary>
/// /// 数据内容
/// </summary>
[MarshalAs(UnmanagedType.Struct)]
public TransHeadAndData Data;
///// <summary>
///// 发送上去的数据头长度(仅用户客户端,用于接收数据后删除重发列表时的计算,计算结构大小时不加入该项)
///// </summary>
//public int HeadLenght;
}

以下是基本转换方法


/// <summary>
/// 将struct类型转换为byte[]
/// </summary>
public static byte[] StructToBytes(object structObj, int size)
{
IntPtr buffer = Marshal.AllocHGlobal(size);
try//struct_bytes转换
{
Marshal.StructureToPtr(structObj, buffer, false);
byte[] bytes = new byte[size];
Marshal.Copy(buffer, bytes, 0, size);
return bytes;
}
catch (Exception ex)
{
throw new Exception("Error in StructToBytes ! " + ex.Message);
}
finally
{
Marshal.FreeHGlobal(buffer);
}
}
/// <summary>
/// 将byte[]还原为指定的struct,该函数的泛型仅用于自定义结构
/// startIndex:数组中 Copy 开始位置的从零开始的索引。
/// length:要复制的数组元素的数目。
/// </summary>
public static T BytesToStruct<T>(byte[] bytes, int startIndex, int length)
{
if (bytes == null) return default(T);
if (bytes.Length <= 0) return default(T);
IntPtr buffer = Marshal.AllocHGlobal(length);
try//struct_bytes转换
{
Marshal.Copy(bytes, startIndex, buffer, length);
return (T)Marshal.PtrToStructure(buffer, typeof(T));
}
catch(Exception ex)
{
throw new Exception("Error in BytesToStruct ! " + ex.Message);
}
finally
{
Marshal.FreeHGlobal(buffer);
}
}

犹豫频繁的重复调用 IntPtr buffer = Marshal.AllocHGlobal(size);语句,会出现缓存创建错误的情况(溢出之类的错误)

所以在写入列表型数据时使用下面的优化方法


/// <summary>
/// 将List<struct>类型转换为byte[]并写入流
/// </summary>
public static void SetFromListToStream<T>(List<T> inList, int size, FS.FileStreamEx stream, long offset, int listStart, int listCount)
{
IntPtr buffer = Marshal.AllocHGlobal(size);
try//struct_bytes转换
{
if (listCount < 0) listCount = inList.Count;
//若需写入的数据条数太大,则将其分为N块批量写入
int num = listCount;
if (num > 100)
{
num = num / 100;
}
else
{
num = 1;
}

byte[] numByte = new byte[num * size];
byte[] bytes = new byte[size];
for (int i = 0; i < listCount; i++)
{
//批量写入模式
if (num > 1 && num + i < listCount)
{

for (int j = 0; j < num; j++)
{
Marshal.StructureToPtr(inList[listStart + i + j], buffer, false);
Marshal.Copy(buffer, numByte, j * size, size);
}
stream.WriteBytes(offset + i * size, numByte);
i += num - 1;//-1因为外面的for循环中i是+1的
}
//逐条写入模式
else
{
Marshal.StructureToPtr(inList[listStart + i], buffer, false);
Marshal.Copy(buffer, bytes, 0, size);
stream.WriteBytes(offset + i * size, bytes);
}
}
}
catch(Exception ex)
{
throw new Exception("Error in SetFromListToStream ! " + ex.Message);
}
finally
{
Marshal.FreeHGlobal(buffer);
}
}

同理,在读出时也应该使用优化方法


public static int SetFromStreamToList<T>(ref List<T> inList, int size, FS.FileStreamEx stream, long offset, int lenght, bool clear)
{
if (clear) inList.Clear();
if (offset < 0) offset = 0;
if (lenght <= 0) lenght = (int)(stream.Length - offset);
byte[] tmpByte = stream.ReadBytes(offset, lenght);
if (tmpByte == null) return ErrCode.Success;
return SetFromBytesToList<T>(ref inList, size, tmpByte, 0, 0);
}

/// <summary>
/// 将byte[]内容转为指定结构,并添加到相应的List中,该函数的泛型仅用于自定义结构
/// </summary>
/// <typeparam name="T">将要转换的类型,同时也是List的类型</typeparam>
/// <param name="inList">将要添加数据的List</param>
/// <param name="size">指定类型的长度,传入长度是为了避免每次都去计算结构长度</param>
/// <param name="inByte">将要转换的数据</param>
/// <param name="offset">便宜量,从数组中指定位置开始转换,取值范围:0至lenght</param>
/// <param name="lenght">将要转换的数据长度,
/// 若lenght大于等于inByte.Lenght则lenght=inByte.Lenght;
/// 若lenght小于等于0则lenght=inByte.Lenght</param>
/// <param name="clear">添加前是否清除旧数据</param>
/// <returns>是否成功</returns>
public static int SetFromBytesToList<T>(ref List<T> inList, int size, byte[] inByte, long offset, int lenght, bool clear)
{
lock (inList)
{
if (clear) inList.Clear();
if (lenght <= 0) lenght = inByte.Length;
if (lenght <= 0) return ErrCode.Success;

IntPtr buffer = Marshal.AllocHGlobal(size);
int i;
try//struct_bytes转换
{
for (i = (int)offset; i + size <= lenght; i += size)
{
Marshal.Copy(inByte, i, buffer, size);
inList.Add((T)Marshal.PtrToStructure(buffer, typeof(T)));
}
}
catch(Exception ex)
{
throw new Exception("Error in SetFromBytesToList ! " + ex.Message);
}
finally
{
Marshal.FreeHGlobal(buffer);
}
return ErrCode.Success;
}
}

 

把《编程珠玑》读薄

Standard
August 11, 2013
作者:Hawstein
出处:http://hawstein.com/posts/make-thiner-programming-pearls.html
声明:本文采用以下协议进行授权: 自由转载-非商用-非衍生-保持署名|Creative Commons BY-NC-ND 3.0 ,转载请注明作者及出处。

目录

  1. 开篇
  2. 啊哈!算法
  3. 数据决定程序结构
  4. 编写正确的程序
  5. 编程中的次要问题
  6. 程序性能分析
  7. 粗略估算
  8. 算法设计技术
  9. 代码调优
  10. 节省空间
  11. 排序
  12. 取样问题
  13. 搜索
  14. 字符串

开篇

具体化你的解决的问题。下面是A和B的对话。

A:我该如何对磁盘文件进行排序?
B:需要排序的内容是什么?文件中有多少条记录?每个记录的格式是什么?
A:该文件包含至多10,000,000个记录,每条记录都是一个7位整数。
B:如果文件那么小,为什么要使用磁盘排序呢?为什么不在主存中对它排序?
A:该功能是某大型系统中的一部分,大概只能提供1MB主存给它。
B:你能将记录方面的内容说得更详细一些吗?
A:每个记录是一个7位正整数,没有其它的关联数据,每个整数至多只能出现一次。
... ...

经过一系统的问题,我们可以将一个定义模糊不清的问题变得具体而清晰:

输入:
所输入的是一个文件,至多包含n个正整数,每个正整数都要小于n,这里n=10^7。
如果输入时某一个整数出现了两次,就会产生一个致命的错误。
这些整数与其它任何数据都不关联。
输出:
以增序形式输出经过排序的整数列表。
约束:
大概有1MB的可用主存,但可用磁盘空间充足。运行时间至多允许几分钟,
10秒钟是最适宜的运行时间。

如果主存容量不是严苛地限制在1MB,比如说可以是1MB多,或是1~2MB之间, 那么我们就可以一次性将所有数据都加载到主存中,用Bitmap来做。 10,000,000个数就需要10,000,000位,也就是10,000,000b = 1.25MB。

程序可分为三个部分:第一,初始化所有的位为0;第二,读取文件中每个整数, 如果该整数对应的位已经为1,说明前面已经出现过这个整数,抛出异常,退出程序 (输入要求每个整数都只能出现一次)。否则,将相应的位置1;第三, 检查每个位,如果某个位是1,就写出相应的整数,从而创建已排序的输出文件。

如果主存容量严苛地限制在1MB,而使用Bitmap需要1.25MB, 因此无法一次载入完成排序。那么,我们可以将该文件分割成两个文件, 再分别用Bitmap处理。分割策略可以简单地把前一半的数据放到一个文件, 后一半的数据放到另一个文件,分别排序后再做归并。 也可以把文件中小于某个数(比如5,000,000)的整数放到一个文件,叫less.txt, 把其余的整数放到另一个文件,叫greater.txt。分别排序后, 把greater.txt的排序结果追加到less.txt的排序结果即可。

啊哈!算法

第2章围绕3个问题展开。

  • 给定一个包含32位整数的顺序文件,它至多只能包含40亿个这样的整数, 并且整数的次序是随机的。请查找一个此文件中不存在的32位整数。 在有足够主存的情况下,你会如何解决这个问题? 如果你可以使用若干外部临时文件,但可用主存却只有上百字节, 你会如何解决这个问题?

这是CTCI中的一道题目,详细解答请戳以下链接:

请猛戳我

  • 请将一个具有n个元素的一维向量向左旋转i个位置。例如,假设n=8,i=3, 那么向量abcdefgh旋转之后得到向量defghabc。

这个问题很常见了,做3次翻转即可,无需额外空间:

reverse(0, i-1); // cbadefgh
reverse(i, n-1); // cbahgfed
reverse(0, n-1); // defghabc
  • 给定一本英语单词词典,请找出所有的变位词集。例如,因为“pots”, “stop”,“tops”相互之间都是由另一个词的各个字母改变序列而构成的, 因此这些词相互之间就是变位词。

这个问题可以分3步来解决。第一步将每个单词按字典序排序, 做为原单词的签名,这样一来,变位词就会具有相同的签名。 第二步对所有的单词按照其签名进行排序,这样一来,变位词就会聚集到一起。 第三步将变位词分组,形成变位词集。示意图如下:

数据决定程序结构

恰当的数据视图实际上决定了程序的结构。 我们常常可以通过重新组织内部数据来使程序变得小而美。

发明家悖论:更一般性的问题也许更容易解决。(有时候吧)

程序员在节省空间方面无计可施时,将自己从代码中解脱出来, 退回起点并集中心力研究数据,常常能有奇效。数据的表示形式是程序设计的根本。

下面是退回起点进行思考时的几条原则:

  • 使用数组重新编写重复代码。冗长的相似代码常常可以使用最简单的数据结构—— 数组来更好地表述。
  • 封装复杂结构。当需要非常复杂的数据结构时,使用抽象术语进行定义, 并将操作表示为类。
  • 尽可能使用高级工具。超文本,名字-值对,电子表格,数据库, 编程语言等都是特定问题领域中的强大的工具。
  • 从数据得出程序的结构。在动手编写代码之前,优秀的程序员会彻底理解输入, 输出和中间数据结构,并围绕这些结构创建程序。

提到的书籍:Polya的《How to Solve it》,中文书《怎样解题》; Kernighan和Plauger的《Elements of Programming Style》;Fred Brooks的《人月神话》 Steve McConnell的《代码大全》;《Rapid Development》; 《Software Project Survival Guide》

编写正确的程序

本章以二分搜索为例子,讲述了如何对程序进行验证及正确性分析。

深入阅读:David Gries的《Science of Programming》 是程序验证领域里极佳的一本入门书籍。

编程中的次要问题

到目前为止,你已经做了一切该做的事:通过深入挖掘定义了正确的问题, 通过仔细选择算法和数据结构平衡了真正的需求,通过程序验证技术写出了优雅的代码, 并且对其正确性相当有把握。万事俱备,只欠编程。

  • 使用断言assert
  • 自动化测试程序

进阶阅读:《Practice of Programming》第5章(调试),第6章(测试) 《Code Complete》第25章(单元测试),第26章(调试)

程序性能分析

下图展示了一个程序的性能提升过程, 该程序的作用是对三维空间中n个物体的运动进行仿真。从图中可以看出, 一个程序可以从多方面进行性能提升,而其中算法和数据结构的选择又显得尤为重要。

从设计层面提升程序性能:

  1. 问题定义。良好的问题定义可以有效减少程序运行时间和程序长度。
  2. 系统结构。将大型系统分解成模块,也许是决定其性能的最重要的单个因素。
  3. 算法和数据结构。这个不用说了。
  4. 代码调优。针对代码本身的改进。
  5. 系统软件。有时候改变系统所基于的软件比改变系统本身更容易。
  6. 硬件。更快的硬件可以提高系统的性能。

深入阅读:Butler Lampson的“Hints for Computer System Design”, 该论文特别适合于集成硬件和软件的计算机系统设计。

粗略估算

这一章讲述了估算技术,我认为是相当有用的一章。

文中先抛出一个问题:密西西比河一天流出多少水?如果让你来回答, 你会怎么答,注意不能去Google哦。

作者是这么回答这个问题:假设河的出口大约有1英里宽和20英尺深(1/250英里), 而河水的流速是每小时5英里,也就是每天120英里。则可以计算出一天的流量:

1英里 * 1/250英里 * 120英里/天  约等于  1/2 英里^3/天

上述算式非常简单,可是在看到这些文字之前,如果有人真的问你, 密西西比河一天流出多少水?你真的能答上来吗?还是愣了一下后,摆摆手,说: 这我哪知道!

对于上面的问题,我们至少可以注意到以下两点:

  1. 你需要把问题转换成一个可计算的具体模型。这一点往往不需要太担心, 因为我们做的是估算,所以可以忽视很多无关紧要的因素,可以去简化你的模型, 记住我们要的只是一个粗略计算的结果。比如对于上面的问题, 计算密西西比河一天流出多少水其实就是计算其一天的流量,利用中学所学知识, 流量 = 截面积 x 流速,那我们就只需计算密西西比河的出水口的截面积和流速即可。 我们可以将出水口简化成一个矩形,因此就只需要知道出水口的宽和深即可。
  2. 你需要知道常识性的东西。上面我们已经把问题转换成了一个可计算的具体模型: 流量 = 出水口宽 x 出水口深 x 流速。接下来呢?你需要代入具体的数值去求得答案。 而这就需要你具备一些常识性的知识了。比如作者就估计了密西西比河的出口有1英里宽, 20英尺深(如果你估计只有几十米宽,那就相差得太离谱了)。 这些常识性的知识比第1点更值得关注,因为你无法给出一个靠谱的估算值往往是因为这点。

当我们懂得如何把一个问题具体化定义出来并为其选用适当的模型, 并且我们也积累了必要的常识性的知识后,回答那些初看起来无从下手的问题也就不难了。 这就是估算的力量。

以下是估算时的一些有用提示:

  • 两个答案比一个答案好。即鼓励你从多个角度去对一个问题进行估算, 如果从不同角度得到的答案差别都不大,说明这个估算值是比较靠谱的。
  • 快速检验。即量纲检验。即等式两边最终的量纲要一致。 这一点在等式简单的时候相当显而易见。比如位移的单位是米,时间单位是秒, 速度单位是米/秒,那显然我们应该要用位移去除以时间来得到速度, 这样才能保证它们单位的一致。你可能会说,我了个去,这种小学生都懂的事, 你好意思拿出来讲。其实不然,当你面对的是一个具有多个变量的复杂物理公式, 或者你提出某种物理假设,正在考虑将其公式化,该方法可以切切实实地帮你做出检验。
  • 经验法则。“72法则”:1.假设以年利率r%投资一笔钱y年,如果r*y = 72, 那么你的投资差不多会翻倍。2.如果一个盘子里的菌群以每小时3%的速率增长, 那么其数量每天(24小时)都会翻倍。在误差不超过千分之五的情况下, \pi秒就是一个纳世纪。也就是说:

    3.14秒 = 10-9 * 100年 = 10-7 年

也就是说,1年大概是3.14×107 秒。所以如果有人告诉你,一个程序运行107 秒, 你应该能很快反应出,他说的其实是4个月。

  • 实践。与许多其他活动一样,估算技巧只能通过实践来提高。

如果问题的规模太大,我们还可以通过求解它的小规模同质问题来做估算。比如, 我们想测试某个程序运行10亿次需要多长时间,如果你真去跑10亿次, 说不定运行几个小时都没结束,那不是很悲剧?我们可以运行这个程序1万次或是10万次, 得出结果然后倍增它即可。当然,这个结果未必是准确的, 因为你没法保证运行时间是随着运行次数线性增加的。谨慎起见,我们可以运行不同的次数, 来观察它的变化趋势。比如运行10次,100次,1000次,10000次等, 观察它的运行时间是否是线性增加的,或是一条二次曲线。

有时候,我们需要为估算的结果乘上一个安全系数。比如, 我们预估完成某项功能需要时间t,那根据以往经验,也许我们需要为这个值乘上2或4, 这样也许才是一个靠谱的预估值。

Little定律:系统中物体的平均数量等于物体离开系统的平均速率和每个物体在系统中停留 的平均时间的乘积。(如果物体离开和进入系统的总体出入流是平衡的, 那么离开速率也就是进入速率)

举个例子,比如你正在排除等待进入一个火爆的夜总会, 你可以通过估计人们进入的速率来了解自己还要等待多长时间。根据Little定律, 你可以推论:这个地方可以容纳约60人,每个人在里面逗留时间大约是3小时, 因此我们进入夜总会的速率大概是每小时20人。现在队伍中我们前面还有20人, 也就意味着我们还要等待大约一个小时。

深入阅读:Darrell Huff的《How To Lie With Statistics》;关键词: 费米近似(Fermi estimate, Fermi problem)

算法设计技术

这一章就一个小问题研究了4种不同的算法,重点强调这些算法的设计技术。 研究的这个小问题是一个非常常见的面试题:子数组之和的最大值。 如果之前没有听过,建议Google之。

深入阅读:Aho,Hopcroft和Ullman的《Data Structures and Algorithms》 Cormen,Leiserson,Rivest和Stein的《Introduction to Algorithms》

代码调优

前面各章讨论了提高程序效率的高层次方法:问题定义,系统结构, 算法设计及数据结构选择。本章讨论的则是低层次的方法:代码调优。

代码调优的最重要原理就是尽量少用它。不成熟的优化是大量编程灾害的根源。 它会危及程序的正确性,功能性以及可维护性。当效率很重要时, 第一步就是对系统进行性能监视,以确定其运行时间的分布状况。 效率问题可以由多种方法来解决,只有在确信没有更好的解决方案时才考虑进行代码调优。

事实上,如果不是十分十分必要,不要去做代码调优, 因为它会牺牲掉软件的其他许多性质。

so,just skip this chapter。

节省空间

本章讲述了节省空间的一些重要方法。

减少程序所需数据的存储空间,一般有以下方法:

  • 不存储,重新计算。
  • 稀疏数据结构。下面着重讲一下这点。
  • 数据压缩。可以通过压缩的方式对对象进行编码,以减少存储空间。
  • 分配策略。只有在需要的时候才进行分配。
  • 垃圾回收。对废弃的存储空间进行回收再利用。

以下是节省代码空间的几种通用技术:

  • 函数定义。用函数替换代码中的常见模式可以简化程序,同时减少代码的空间需求。
  • 解释程序。用解释程序命令替换长的程序文本。
  • 翻译成机器语言。可以将大型系统中的关键部分用汇编语言进行手工编码。

稀疏数据结构

假设我们有一个200 x 200的矩阵(共40000个元素),里面只有2000个元素有值, 其它的都为0,示意图如下:

显然这是一个稀疏矩阵,直接用一个200 x 200 的二维数组来存储这些数据会造成大量的空间浪费,共需要200x200x4B=160KB。 所以,我们应该想办法用另一种形式来存储这些数据。

方法一

使用数组表示所有的列,同时使用链表来表示给定列中的活跃元素。 如下图所示:

该结构中,有200个指针(colhead)和2000条记录(每条记录是两个整数和一个指针), 占用空间是200x4B + 2000x12B = 24800B = 24.8KB, 比直接用二维数组存储(160KB)要小很多。

方法二

我们可以开三个数组来保存这些数,如下图所示:

firstincol是一个长度为201的数组,对于第i列,在数组row中, 下标为firstincol[i]到firstincol[i+1]-1对应的行元素非0, 其值存储在相应的pointnum数组中。

比如对于上图,在第0列中,元素值非0的行有3行,分别是row[0],row[1],row[2], 元素值是pointnum[0],pointnum[1],pointnum[2];在第1列中,元素值非0的行有2行, 分别是row[3],row[4],元素值是pointnum[3],pointnum[4]。依次类推。

该结构所需要的存储空间为2x2000x4B + 201x4B = 16804B = 16.8KB。 由于row数组中的元素全部都小于200,所以每个元素可以用一个unsigned char来保存, firstincol数组中元素最大也就2000,所以可以用一个short(或unsigned short)来保存, pointnum中的元素是一个4B的int, 最终所需空间变为:2000x4B + 2000x1B + 201x2B = 10402B = 10.4KB。

深入阅读:Fred Brooks的《人月神话》

排序

本章先简单介绍了插入排序,然后着重讲述快速排序。

插入排序

// 版本1
void InsertSort(int a[], int n) {
    for(int i=1; i<n; ++i)
        for(int j=i; j>0 && a[j-1]>a[j]; --j)
            swap(a[j-1], a[j]);
}
// 版本2
void InsertSort1(int a[], int n) {
    for(int i=1; i<n; ++i) {
        int t = a[i];
        int j = i;
        for(; j>0 && a[j-1]>t; --j)
            a[j] = a[j-1];
        a[j] = t;
    }
}

快速排序

我们在这里规定:小于等于pivot的元素移到左边,大于pivot的元素移到右边。

实现1:单向移动版本

这个版本的关键是设置一快一慢两个指针,慢指针左侧都是小于等于pivot(包含慢指针所在位置), 慢指针到快指针之间的值是大于pivot,快指针右侧的值是还未比较过的。示意图如下:

小于等于pivot    |    大于pivot    |    ?
             slow                fast

快指针一次一步向前走,遇到大于pivot什么也不做继续向前走。遇到小于等于pivot的元素, 则慢指针slow向前走一步,然后交换快慢指针指向的元素。一次划分结束后, 再递归对左右两侧的元素进行快排。代码如下:

// 数组快排
void QSort(int a[], int head, int end) {
    if(a==NULL || head==end) return;
    int slow = head, fast = head + 1;
    int pivot = a[head];
    while(fast != end) {
        if(a[fast] <= pivot)
            swap(a[++slow], a[fast]);
        ++fast;
    }
    swap(a[head], a[slow]);
    QSort(a, head, slow);
    QSort(a, slow+1, end);
}

排序数组a只需要调用QSort(a, 0, n)即可。该思路同样可以很容易地在链表上实现:

// 单链表快排
void qsort(Node *head, Node *end){
    if(head==NULL || head==end) return;
    Node *slow = head, *fast = head->next;
    int pivot = head->data;
    while(fast != end){
        if(fast->data <= pivot){
            slow = slow->next;
            swap(slow->data, fast->data);
        }
        fast = fast->next;
    }
    swap(head->data, slow->data);
    qsort(head, slow);
    qsort(slow->next, end);
}

排序头指针为head的单链表只需调用qsort(head, NULL)即可。

实现2:双向移动版本

版本1能能够快速完成对随机整数数组的排序,但如果数组有序, 或是数组中元素相同,快排的时间复杂度会退化成O(n2 ),性能变得非常差。

一种缓解方案是使用双向移动版本的快排,它每次划分也是使用两个指针, 不过一个是从左向右移动,一个是从右向左移动,示意图如下:

小于等于pivot    |    ?    |    大于pivot
               i            j

指针j不断向左移动,直到遇到小于等于pivot,就交换指针i和j所指元素 (指针i一开始指向pivot);指针i不断向右移动,直到遇到大于pivot的, 就交换指针i和j所指元素。pivot在这个过程中,不断地换来换去, 最终会停在分界线上,分界线左边都是小于等于它的元素,右边都是大于它的元素。 这样就避免了最后还要交换一次pivot的操作,代码也变得美观许多。

int partition(int a[], int low, int high){
    int pivot = a[low], i=low, j=high;
    while(i < j){
        while(i<j && a[j]>pivot) --j;
        if(i < j) swap(a[i], a[j]);
        while(i<j && a[i]<=pivot) ++i;
        if(i < j) swap(a[i], a[j]);
    }
    return i;
}
void quicksort(int a[], int first, int last){
    if(first<last){
        int k = partition(a, first, last);
        quicksort(a, first, k-1);
        quicksort(a, k+1, last);
    }
}

当然,如果对于partition函数,你如果觉得大循环内的两个swap还是做了些无用功的话, 也可以把pivot的赋值放到最后一步,而不是在这个过程中swap来swap去的。代码如下:

int partition(int a[], int low, int high){
    int pivot = a[low], i=low, j=high;
    while(i<j){
        while(i<j && a[j]>pivot) --j;
        if(i<j) a[i++] = a[j];
        while(i<j && a[i]<=pivot) ++i;
        if(i<j) a[j--] = a[i];
    }
    a[i] = pivot;
    return i;
}

如果数组基本有序,那随机选择pivot(而不像上面那样选择第一个做为pivot) 会得到更好的性能。在partition函数里,我们只需要在数组中随机选一个元素, 然后将它和数组中第一个元素交换,后面的划分代码无需改变, 就可以达到随机选择pivot的效果。

进一步优化

对于小数组,用插入排序之类的简单方法来排序反而会更快,因此在快排中, 当数组长度小于某个值时,我们就什么也不做。对应到代码中, 就是修改quicksort中的if条件:

if(first < last)  改为  if(last-first > cutoff)

其中cutoff是一个小整数。程序结束时,数组并不是有序的, 而是被组合成一块一块随机排列的值,并且满足这样的条件: 某一块中的元素小于它右边任何块中的元素。我们必须通过另一种排序算法对块内进行排序。 由于数组是几乎有序的,因此插入排序比较适用。

这种方法结合了快排和插入排序,让它们去做各自擅长的事情,往往比单纯用快排要快。

深入阅读:Don Knuth的《The Art of Computer Programming, Volume 3: Sorting and Searching》;Robert Sedgewick的《Algorithms》; 《Algorithms in C》,《Algorithms in C++》,《Algorithms in Java》。

取样问题

本章讲述了一个小的随机抽样问题,并用不同的方法来解决它。

问题:对于整数m和n,其中m<n,输出0~n-1范围内m个随机整数的有序列表, 不允许重复。

比如m=3, n=5,那么一种可能输出是0,2,3(要求有序)。实现1来自Knuth的TAOCP, 时间复杂度O(n):

void GenKnuth(int m, int n) {
    for(int i=0; i<n; ++i) {
        if((bigrand()%(n-i)) < m) {
            cout<<i<<endl;
            --m;
        }
    }
}

其中,bigrand()的作用是返回一个很大的随机整数。

实现2:在一个初始为空的集合里面插入随机整数,直到个数足够。代码如下:

void GenSets(int m, int n) {
    set<int> s;
    while(s.size() < m)
        s.insert(bigrand() % n);
    set<int>::iterator i;
    for(i=s.begin(); i!=s.end(); ++i)
        cout<<*i<<endl;
}

实现3:把包含整数0~n-1的数组顺序打乱,然后把前m个元素排序输出。 该方法的性能通常不如Knuth的算法。代码如下:

void GenShuf(int m, int n) {
    int x[n];
    for(int i=0; i<n; ++i)
        x[i] = i;
    for(int i=0; i<m; ++i) {
        int j = randint(i, n-1);
        swap(x[i], x[j]);
    }
    sort(x, x+m);
    for(int i=0; i<m; ++i)
        cout<<x[i]<<endl;
}

深入阅读:Don Knuth的《The Art of Computer Programming, Volume 2: Seminumerical Algorithms》

搜索

本章详细研究这样一个搜索问题:在没有其他相关数据的情况下,如何存储一组整数? 为些介绍了5种数据结构:有序数组,有序链表,二叉搜索树,箱,位向量。

其中,二叉搜索树应该熟练掌握,以下是一种实现:

struct Node {
    int data;
    Node *lchild, *rchild, *parent;
    Node(): lchild(NULL), rchild(NULL), parent(NULL) { }
};

class BST {
private:
    static const int kMax = 1000;
    Node *root_, *parent_, nodes_[kMax];
    int size_;

private:
    Node* minimum(Node* node);
    Node* maximum(Node* node);
    Node* successor(Node* node);
    Node* predecessor(Node* node);
    void Insert(Node* &node, int x);
    void InorderTraver(Node* node);
    Node* Find(Node* node, int x);
    
public:
    BST(): root_(NULL), parent_(NULL), size_(0) {
        memset(nodes_, '\0', sizeof(nodes_));
    }
    void Insert(int x);
    void InorderTraver();
    Node* Find(int x);
    void Remove(Node* z);
};

Node* BST::minimum(Node* node) {
    if(node == NULL) return NULL;
    while(node->lchild)
        node = node->lchild;
    return node;
}

Node* BST::maximum(Node* node) {
    if(node == NULL) return NULL;
    while(node->rchild)
        node = node->rchild;
    return node;
}

Node* BST::successor(Node* node) {
    if(node->rchild)
        return minimum(node->rchild);
    Node *y = node->parent;
    while(y && node==y->rchild) {
        node = y;
        y = node->parent;
    }
    return y;
}

Node* BST::predecessor(Node* node) {
    if(node->lchild)
        return maximum(node->lchild);
    Node *y = node->parent;
    while(y && node==y->lchild) {
        node = y;
        y = node->parent;
    }
    return y;
}

void BST::Insert(Node* &node, int x) {
    if(node == NULL) {
        nodes_[size_].data = x;
        nodes_[size_].parent = parent_;
        node = &nodes_[size_];
        ++size_;
        return;
    }
    parent_ = node;
    if(x < node->data)
        Insert(node->lchild, x);
    else
        Insert(node->rchild, x);
}

void BST::Insert(int x) {
    Insert(root_, x);
}

void BST::InorderTraver(Node* node) {
    if(node == NULL) return;
    InorderTraver(node->lchild);
    cout<<node->data<<" ";
    InorderTraver(node->rchild);
}

void BST::InorderTraver() {
    InorderTraver(root_);
}

Node* BST::Find(Node* node, int x) {
    if(node == NULL) return NULL;
    if(x < node->data) return Find(node->lchild, x);
    else if(x > node->data) return Find(node->rchild, x);
    else return node;
}

Node* BST::Find(int x) {
    return Find(root_, x);
}

void BST::Remove(Node* z) {
    if(!z->lchild && !z->rchild) {
        if(z == root_) root_ = NULL;
        else if(z == z->parent->lchild)
            z->parent->lchild = NULL;
        else
            z->parent->rchild = NULL;
    }

    else if(z->lchild==NULL || z->rchild==NULL) {
        if(z == root_) {
            if(z->lchild) root_ = z->lchild;
            else root_ = z->rchild;
            root_->parent = NULL;
        }
        else {
            if(z==z->parent->lchild && z->lchild) {
                z->parent->lchild = z->lchild;
                z->lchild->parent = z->parent;
            }
            else if(z==z->parent->lchild && z->rchild) {
                z->parent->lchild = z->rchild;
                z->rchild->parent = z->parent;
            }
            else if(z==z->parent->rchild && z->lchild) {
                z->parent->rchild = z->lchild;
                z->lchild->parent = z->parent;
            }
            else {
                z->parent->rchild = z->rchild;
                z->rchild->parent = z->parent;
            }
        }
    }

    else {
        Node *s = predecessor(z);
        z->data = s->data;
        if(z == s->parent)
            s->parent->lchild = s->lchild;
        else
            s->parent->rchild = s->lchild;

        if(s->lchild)
            s->lchild->parent = s->parent;
    }
}

本章主要介绍堆,下面是关于堆的一些主要操作:

// 最大堆实现, 数组下标从1开始,a[0]不使用。

// 交换两数
void swap(int &a, int &b) {
    int t = a;
    a = b;
    b = t;
}

// 把第i个元素向上移动
void ShiftUp(int a[], int i) {
    while(i>1 && a[i]>a[i/2]) {
        swap(a[i], a[i/2]);
        i >>= 1;
    }
}

// 把第i个元素向下移动
void ShiftDown(int a[], int n, int i) {
    while((i=2*i) <= n) {
        if(i+1<=n && a[i+1]>a[i]) ++i;
        if(a[i] > a[i/2]) swap(a[i], a[i/2]);
        else break;
    }
}

// 把数组a变成具备最大堆性质的数组
void MakeHeap(int a[], int n) {
    for(int i=n/2; i>0; --i)
        ShiftDown(a, n, i);
}

// 向堆中插入元素x
void Insert(int a[], int &n, int x) {
    a[++n] = x;
    ShiftUp(a, n);
}

// 删除堆中第i个元素
void Del(int a[], int &n, int i) {
    a[i] = a[n--];
    if(i>1 && a[i]>a[i/2]) ShiftUp(a, i);
    else ShiftDown(a, n, i);
}

// 堆排序,时间复杂度O(nlogn)
void HeapSort(int a[], int n) {
    MakeHeap(a, n);
    for(int i=n; i>1; --i) {
        swap(a[i], a[1]);
        ShiftDown(a, i-1, 1);
    }
}

字符串

程序1:循环输入并将每个单词插入集合S(忽略重复单词),然后排序输出。

int main(void) {
    set<string> s;
    set<string>::iterator j;
    string t;
    while(cin >> t)
        s.insert(t);
    for(j=s.begin(); j!=s.end(); ++j)
        cout<<*j<<endl;
    return 0;
}

程序2:单词计数

int main(void) {
    map<string, int> m;
    map<string, int>::iterator j;
    string t;
    while(cin >> t)
        m[t]++;
    for(j=m.begin(); j!=m.end(); ++j)
        cout<<j->first<<" "<<j->second<<endl;
    return 0;
}

程序3:建立自己的哈希表(散列表),以下是一种实现:

class Hash {
public:
    Hash(): seed_(131), size_(0) {
        memset(head_, 0, sizeof(head_));
    }
    
    void Insert(const char* str) {
        unsigned int id = hash(str);
        char *dst = (char*)node_[size_].word;
        while(*dst++ = *str++);
        node_[size_].next = head_[id];
        head_[id] = &node_[size_];
        ++size_;
    }

    bool Find(const char* str) {
        unsigned int id = hash(str);
        for(Node* p=head_[id]; p; p=p->next) {
            char* dst = (char*)p->word;
            int i = 0;
            for(; *(str+i) && *(str+i)==*(dst+i); ++i);
            if(!*(str+i) && !*(dst+i)) return true;
        }
        return false;
    }
    
private:
    unsigned int hash(const char* str) {// BKDR Hash Function
        unsigned int hash = 0;
        while(*str) {
            hash = hash * seed_ + (*str++);
        }
        return (hash & 0x7FFFFFFF) % kHashSize;
    }
    
private:
    unsigned int seed_;
    unsigned int size_;
    static const int kWordSize = 26 + 1;
    static const int kNodeSize = 20000;
    static const int kHashSize = 10001;
    struct Node {
        char word[kWordSize];
        Node *next;
    };
    Node node_[kNodeSize];
    Node* head_[kHashSize];
};

后缀数组

假设我们有以下字符串及一个char*数组:

 char c[20] = "hawstein";
 char* pc[20];

我们让指针pc[i]指向字符串的第i个字符,即:

for(int i=0; i<8; ++i)
    pc[i] = &c[i];

这时候我们输出pc[i],会得到字符串”hawstein”的所有后缀:

hawstein
awstein
wstein
stein
tein
ein
in
n

然后,我们对数组pc进行排序,将所有后缀按字典序排序:

sort(pc, pc+8, cmp);

其中,比较函数cmp如下:

inline bool cmp(char* p, char*q) {
    return strcmp(p, q) < 0;
}

这时,我们再输出pc[i],会得到排序后的结果:

awstein
ein
hawstein
in
n
stein
tein
wstein

我们把数组pc称为“后缀数组”。这里需要注意,数组pc 中存储的是指向每个后缀首字符的地址。我们也可以存储每个后缀首字符在原数组中的下标, 效果是一样的。

本章中用后缀数组解决了一个小问题:可重叠最长重复子串。比如对于字符串”banana”, 其后缀数组为:

a
ana
anana
banana
na
nana

扫描一次数组,比较相邻子串,找到相邻子串的最长公共前缀即可。本例为”ana”, 其中一个a是重叠的。

后缀数组是处理字符串的有力工具,常见的两种实现方法是:倍增算法和DC3算法。 推荐阅读以下材料来学习后缀数组:

许智磊,《后缀数组》

罗穗骞,《后缀数组——处理字符串的有力工具》