大家好,我是Google+基础架构团队的工程师。早在7月份,当Joseph Smarr开发出了Ask Me Anything栏目后,很多人都想知道一些关于Google+技术架构方面的信息。我们几个工程师觉得应该写一些关于这个题目的文章,给大家分享。
对于Google+团队,我们头一个要认真处理的问题就是:页面生成速度。在谷歌,我们十分在意速度,下面就是我们用来提升速度的5项技术。
1. 我们喜欢Closure
我们喜欢Closure。非常的。我们使用Closure类库,模板以及编译器来生成Google+所有页面上的所有元素——包括驱动这些页面的JavaScript。但真正让我们获得速度的是以下几点:
— Closure模板即能用于Java也能用于JavaScript,生成的页面即能在Server端运行,也能在浏览器里运行。通过这种方式,内容总是能理解展现,我们还可以在后台加载JavaScript(“修饰”页面,在页面元素上挂载事件监听器)
— Closure能让我们在写JavaScript脚本时仍然可以享用严格的类型和错误检查,死代码清除,跨模块提示,以及其它的很多辅助优化便利。
(访问 http://code.google.com/closure/ 来获取更多关于Closure的信息)
2. 在正确的时间正确的使用JavaScript
为了管理驱动Google+的JavaScript,我们把它分割成小的模块,这样可以异步的分别加载它们。你只需要下载最少的必须的模块。由两种技术来实现这些:
— 客户端保存历史浏览记录的信息(URL里的字符串信息代表这你当前处在某个页面上),用这种方法来调配JavaScript模块。
— 如果JavaScript没有加载完成,这个页面上的任何操作都会被禁止,直到加载完成。
这种技术框架也是Google+在客户端页面导航时避免重新加载页面的技术基础。
3. 页面之间切换时避免刷新页面
一旦JavaScript被加载,所有的页面内容都使用JavaScript生成,不需要再到服务器端去取,这样做效率更高些。我们设置了一个全局的监听器,监听所有标记的点击事件。如果允许的话,我们会把点击转化成页面内部的切换。如果条件不允许,客户端会生成这个页面,如果你在链接上使用鼠标中键或控制键的点击,我们会让浏览器按常规链接打开这个页面。
页面上锚标记总是指向一个常规的URL(例如,你在HTML5里的历史记录里的URL),这样,你能容易的拷贝/分享这个页面链接。
4. 部分(HTML)页面块刷新
在客户端,一旦我们接收到部分数据,我们就立即生成这块内容,让它可见,不必等到整个页面加载后才能显示。
为了实现这些,我们通过:
— 首次请求时,我们就把所有数据异步的取回
— 只有在需要生成这部分页面数据时,才会遇到延迟现象
这种技术也能让我们尽早的加载CSS,JavaScript,图片以及其它资源,使网站更快,响应效果更好。
5. iFrame是我们的朋友
为了能并行的加载JavaScript,避免浏览器卡住(http://goo.gl/lzGq8),我在页面的body标记的顶部的一个Iframe里加载JavaScript。在iframe里加载JavaScript增加了代码的复杂度(通过Closure,我们很好的解决了这个问题),但是为了速度的提升,值得这样做。
做一个解释,你们也许注意到了我们是使用XHR,而不是使用style标记来加载CSS的 – 这并不是我们做的优化,这是做是因为我们达到了IE浏览器里每个样式表文件里CSS选择器的上限!
最后注解
这些只是整个Google+面纱下事情如何运转的一小部分介绍,我们以后会写更多像这样的文章。请在评论里留下你的想法!
译者注:Mark Knichel发布了这篇文章后,很多人在评论里表达了不同的观点,有些人认为iFrame是一种应该被淘汰的技术,有些人认为Closure template影响了程序的可维护性。但我反过来一些,这似乎正说明了谷歌的程序员在开发上享有很高的自由度,他们可以使用任何他们自己喜欢的技术。
[本文英文原文链接:Mark Knichel ]
首先看我们的<img>标签代码:<img src="Example.png" border="0" alt="放大镜" />我们将利用IE中特有的特效来满足这个要求,这就是AlphaImageLoader Filter (http://msdn.microsoft.com/library/default.asp?url=/workshop/author/filter/reference/filters/alphaimageloader.asp) 如何做?将下面代码保存为correctPNG.js:
代码如下:
function correctPNG()
{
for(var i=0; i<document.images.length; i++)
{
var img = document.images[i]
var imgName = img.src.toUpperCase()
if (imgName.substring(imgName.length-3, imgName.length) == "PNG")
{
var imgID = (img.id) ? "id='" + img.id + "' " : ""
var imgClass = (img.className) ? "class='" + img.className + "' " : ""
var imgTitle = (img.title) ? "title='" + img.title + "' " : "title='" + img.alt + "' "
var imgStyle = "display:inline-block;" + img.style.cssText
if (img.align == "left") imgStyle = "float:left;" + imgStyle
if (img.align == "right") imgStyle = "float:right;" + imgStyle
if (img.parentElement.href) imgStyle = "cursor:hand;" + imgStyle
var strNewHTML = "<span " + imgID + imgClass + imgTitle
+ " style=\"" + "width:" + img.width + "px; margin:6px; height:" + img.height + "px;" + imgStyle + ";"
+ "filter:progid:DXImageTransform.Microsoft.AlphaImageLoader"
+ "(src=\'" + img.src + "\', sizingMethod='scale');\"></span>"
img.outerHTML = strNewHTML
i = i-1
}
}
}
window.attachEvent("onload", correctPNG);
然后在你需要透明的网页中的<head>....</head>区加入:<script type="text/javascript" src="correctPNG.js"></script>
在<body>区加入多个与<img src="Example.png" border="0" alt="放大镜" />类似的PNG图片,试试看?
另一种方法:
代码如下:
<html>
<head>
<title>alpha image</title>
<style type="text/css">
.pngholder{
width:100px;
height:100px;
filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src='http://www.jb51.net/attachments/200510/03_124401_ie.png'); }
.pngalpha{
filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);
background:url(http://www.jb51.net/attachments/200510/03_124401_ie.png) no-repeat; width:100px;
height:100px;
}
</style>
</head>
<body bgcolor="#3399ff#">
<!- And this is your code to implement the image ->
<div>透明</div>
<div class="pngholder"><div class="pngalpha"></div></div>
<div>不透明</div>
<img src="http://www.jb51.net/attachments/200510/03_124401_ie.png"/> </body>
</html>
通常我们读取一个文件使用如下的步骤:
1、声明并使用File的OpenRead实例化一个文件流对象,就像下面这样
2、准备一个存放文件内容的字节数组,fs.Length将得到文件的实际大小,就像下面这样
3、开始读了,调用一个文件流的一个方法读取数据到data数组中
FileStream fs = File.OpenRead(filename); 或者
FileStream fs = FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read);
byte[] data = new byte[fs.Length];
fs.Read (data, 0, data.Length);
下面的方法提供了一个比上面方法更安全的方法,来保证文件被完全读出
public static void SafeRead (Stream stream, byte[] data)
{
int offset=0;
int remaining = data.Length; // 只要有剩余的字节就不停的读
while (remaining > 0)
{
int read = stream.Read(data, offset, remaining);
if (read <= 0)
throw new EndOfStreamException("文件读取到"+read.ToString()+"失败!"); // 减少剩余的字节数
remaining -= read; // 增加偏移量
offset += read;
}
}
有些情况下你不知道流实际的长度,比如:网络流。此时可以使用类似的方法读取流直到流里面的数据完全读取出来为止。我们可以先初始化一段缓存,再将流读出来的流信息写到内存流里面,就像下面这样:
public static byte[] ReadFully (Stream stream)
{
// 初始化一个32k的缓存
byte[] buffer = new byte[32768];
using (MemoryStream ms = new MemoryStream())
{
//返回结果后会自动回收调用该对象的Dispose方法释放内存 // 不停的读取
while (true)
{
int read = stream.Read (buffer, 0, buffer.Length); // 直到读取完最后的3M数据就可以返回结果了
if (read <= 0)
return ms.ToArray(); ms.Write (buffer, 0, read);
}
}
}
虽然上面的例子都比较简单,效果也不是很明显(大部分都是对的),也许你早就会了,没关系这篇文章本来就是写给初学者的。 下面的方法提供了一种使用指定缓存长度的方式读取流,虽然在很多情况下你可以直接使用Stream.Length得到流的长度,但是不是所有的流都可以得到。
public static byte[] Read2Buffer (Stream stream, int BufferLen)
{
// 如果指定的无效长度的缓冲区,则指定一个默认的长度作为缓存大小
if (BufferLen < 1)
{ BufferLen = 0x8000; } // 初始化一个缓存区
byte[] buffer = new byte[BufferLen];
int read=0; int block; // 每次从流中读取缓存大小的数据,知道读取完所有的流为止
while ( (block = stream.Read(buffer, read, buffer.Length-read)) > 0)
{
// 重新设定读取位置
read += block; // 检查是否到达了缓存的边界,检查是否还有可以读取的信息
if (read == buffer.Length)
{
// 尝试读取一个字节
int nextByte = stream.ReadByte(); // 读取失败则说明读取完成可以返回结果
if (nextByte==-1)
{
return buffer;
} // 调整数组大小准备继续读取
byte[] newBuf = new byte[buffer.Length*2]; Array.Copy(buffer, newBuf, buffer.Length);
newBuf[read]=(byte)nextByte; buffer = newBuf;
// buffer是一个引用(指针),这里意在重新设定buffer指针指向一个更大的内存
read++;
}
} // 如果缓存太大则使用ret来收缩前面while读取的buffer,然后直接返回
byte[] ret = new byte[read]; Array.Copy(buffer, ret, read);
return ret;
}
using System;
using System.IO;
using System.Collections;
namespace TextFileReader_csharp
{
class Class1
{
static void Main(string[] args)
{
StreamReader objReader = new StreamReader("c:\\test.txt");
string sLine="";
ArrayList arrText = new ArrayList();
while (sLine != null)
{
sLine = objReader.ReadLine();
if (sLine != null)
arrText.Add(sLine);
}
objReader.Close();
foreach (string sOutput in arrText)
Console.WriteLine(sOutput); Console.ReadLine();
}
}
}