React的井字过三关(2)
这篇笔记是官方教程的延续笔记,所有代码基于第一篇笔记的结尾代码。旨在解决教程后面提出的五个问题。
一 . 用(X,Y)来取代原有的数字坐标
原来的数字坐标是这样的:
现在的要求是把原来的代码坐标改为二维坐标系的表达形式,并且在历史记录面板中打出转换后的坐标。
如果只是为了输出好看。只需要写一个转换方法,这些在顶层的Game
组件中实现就够了。而不需要修改原来的代码核心。
很自然想到用switch语句,其实都行,怎么喜欢怎么写。
convert:function(i){//i是一维坐标
var x=Math.floor(i/3)+1;
var y=0;
if((i+1)%3===0){
y=3;
}else{
y=(i+1)%3;
}
return [x,y];
},
调用就直接在渲染前调用。把它存到state里面去。
接下来问题是把这个方法怎么获取参数i,最直接的办法是写一个全局变量,然后从handleClick里面拿到i。但是全局变量不环保。或许再设一个顶层状态lastLocation
是个不错的选择,渲染队列是一个数组,姑且称之为historyLocation
。
根据React的价值观,能根据其它原有状态计算出来的东西,就不需要设置额外的状态。但如果思路不明确,就在这里先写出来。
回退步骤的本质
在上一篇笔记最后,官方文档没有说清楚一个问题。就是状态中的stepNumber是什么。现在再次遇到,需要写明白给自己提个醒。
回退步骤
每走一步,history
状态就会在最后追加一个最新版本。
stepNumber
实际上是一个指针,根据这个指针,发送history状态的版本(可能是旧的,也可能是最新的),用它来调控渲染状态。
点击回退步骤,就是把指针往前挪。
通过暂存器刷新状态
如果没有任何其它操作直接触发handleClick,stepNumber
指针直接指向最新的版本。
如果在回退步骤上发生了handleClick,那么将发生以下事情:
- 根据指针生成若干个状态暂存器,这个暂存器是独立且不具备任何效力的,抛开环境来看就是普通变量;
- 追加新的状态到暂存器;
- 再用这个暂存器替换掉原有的状态,在此,回退步骤列表将被刷新。
究竟有几个状态暂存器?在这里就两个:
一个储存history的当前指针版本:
handleClick:function(i){
//history指针版本暂存
var history=this.state.history.slice(0,this.state.stepNumber+1);
var lastHistory=history[this.state.stepNumber];
var squares=lastHistory.squares.slice();
一个储存二维坐标轴版本:
/**上接handleClick***/
//二维坐标数据暂存器
var historyLocation=this.state.historyLocation;
historyLocation=historyLocation.slice(0,this.state.stepNumber);
historyLocation.push(this.convert(i));//刷新状态暂存器
通过状态暂存器,既可以在指针位置重新开始,又能在屏幕上保留历史步骤数方便查看,即实现官方文档所谓的“时间旅行”。
在这里意识到状态暂存器其实应该只有一个是最好的。
judgeWinner
的完善
按理来说,判断胜负的函数judgeWinner
应该是在组件的里面,这样比较环保,也可以更好地调用组件中的状态。
现在就把它拿进来。直接生成渲染方法中的status
。并且添加一个和棋的判断。实现思路是调用指针版本的history状态数组。然后遍历这个数组对象,如果发现9个位置全部不为null,就返回和棋。
放进来之后,参数也没有必要再写,全部改为state相关的表达。
这样一来就没办法用原来的禁着点判断了。因为不好判断棋局是否完结。在此根据返回的结果进行indexOf判断,留下的坑后面填。
所以到此为止,Game组件应该是:
getInitialState:function(){
return {
history:[
{squares:Array(9).fill(null)}
],
turnToX:true,
stepNumber:0,
historyLocation:[]
}
},
// 判断胜负的函数,穷举法
judgeWinner:function(){
var history=this.state.history;
var lastHistory=history[this.state.stepNumber];
var squares=lastHistory.squares;
var win=[
[0,1,2],
[0,3,6],
[0,4,8],
[1,4,7],
[2,5,8],
[2,4,6],
[3,4,5],
[6,7,8]
];
for(var i=0;i<win.length;i++){
var winCase=win[i];
if(squares[winCase[0]]&&squares[winCase[0]]===squares[winCase[1]]&&squares[winCase[1]]===squares[winCase[2]]){//三子一线
return ('获胜方是:'+squares[winCase[0]]);//返回胜利一方的标识
}
}
// 定义当前棋盘上被填满的格子数量
var fill=lastHistory.squares.filter((item)=>item!=null).length;
if(fill==9){
return '和棋!'
}else{
var player=this.state.turnToX?'X':'O';
return ('轮到'+player+'走');
}
},
// 点击事件是把暂存器的内容存为真正的状态。
handleClick:function(i){
//历史squares暂存
var history=this.state.history;
history=history.slice(0,this.state.stepNumber+1);
var lastHistory=history[this.state.stepNumber];
var winner=this.judgeWinner();
var squares=lastHistory.squares.slice();
//历史步骤暂存器
var historyLocation=this.state.historyLocation;
historyLocation=historyLocation.slice(0,this.state.stepNumber);
historyLocation.push(this.convert(i));
if((winner.indexOf('轮到')==-1)||squares[i]){
return false;
//胜负已分或是已有子则不可落子。indexOf这是一种暂时的非主流写法
}
// 判断下棋的轮换色
squares[i]=this.state.turnToX?'X':'O';
this.setState({
history:history.concat([{squares:squares}]),
turnToX:!this.state.turnToX,
stepNumber:history.length,
historyLocation:historyLocation
});
},
// 历史步骤跳转是把状态还原到某个时间点,状态根据stepNumber呈现内容,但不会改变最终状态。
jumpTo:function(step){
this.setState({
stepNumber:step,
turnToX:step%2?false:true
});
},
// 坐标转换函数
convert:function(i){
var x=Math.floor(i/3)+1;
var y=0;
if((i+1)%3===0){
y=3;
}else{
y=(i+1)%3;
}
return [x,y];
},
render:function(){
var history=this.state.history.slice();
var lastHistory=history[this.state.stepNumber];//渲染方法遵照的是stepNumber而不是最后一步
var status=this.judgeWinner();//获胜状态
var arr=[];
var location=this.state.historyLocation.slice();
var _this=this;
history.map(function(step,move){
var content='';
if(move!==0){
content='Move#'+move+':'+'('+location[move-1][0]+','+location[move-1][1]+')';
//console.log(location[move-1])
}else{
content='游戏开始~';
}
arr.push(<li key={move}><a onClick={()=>_this.jumpTo(move)} href="javascript:;">{content}</a></li>);
});
return (
<div className="game">
<Board lastHistory={lastHistory.squares} onClick={(i)=>this.handleClick(i)} />
<div className="info">
<div>{status}</div>
<ol>{arr}</ol>
</div>
</div>
);
}
});
二. 对右方的被选中的当前记录进行加粗显示
样式这种东西,就交给CSS来实现吧!
.back-active{
font-weight: bolder;
color: #EE9611;
}
简单实现
思路就是加个class。操作方法是jumpTo。
问题在于,当前的jumpTo已经给定了参数。为了拿到e.target
还得在改改。
jumpTo在这个问题中实际上要完成两件事,删除所有a的class中可能.back-active
;给当前对象加个.back-active
。
有了e.target,就能用DOM找到该有的内容。比方说e.target.parentNode.parentNode.childNode
就代表所有点击对象上层的所有li集合
然而这个集合不是一个数组啊,不能map。只能用for循环。根据查到的性能资料,for循环还真的比其它迭代方法高。
jumpTo:function(e,step){
// console.log(e.target)
var aList=e.target.parentNode.parentNode.childNodes;
for(var i=0;i<aList.length;i++){
var item=aList[i];
if(item.childNodes[0].classList.contains('back-active')){
item.childNodes[0].classList.remove('back-active');
}
}
e.target.classList.add('back-active');
this.setState({
stepNumber:step,
turnToX:step%2?false:true
});
},
这个问题就算解决了。
点击实现高亮当前的步骤
其实就个人理解来说,不应该再对handleClick再加什么高亮当前步骤的操作了。当前步骤明摆着就是最后一个。纵观就当前的代码实现,用户体验已经很好了,进程不会乱七八糟,用户还可以很清晰地知道指针指向的还原点。还高亮什么?
但是假设老板就要求点击按钮时最后一步也高亮,那也只能照做。
显然,这个应该放渲染前判断:如果这是状态最后一步(是this.state.history.length-1
,不是this.state.stepNumber
),那么就高亮。反正样式也不要钱,就多写一个样式给它。
.process-active{
font-weight: bolder;
color: green;
}/*写在.back-active之后,方便覆盖*/
这样,渲染前的处理里还得多加一个判断:是最后一个就加.process-active
——这段获取历史步骤的方法已经变得太长了。为了阅读方便把它放一个getMoveList
函数里吧。
...
getMoveList:function(){
var history=this.state.history.slice();
var arr=[];
var location=this.state.historyLocation.slice();
var _this=this;
history.map(function(step,move){
var content='';
if(move!==0){
content='Move#'+move+':'+'('+location[move-1][0]+','+location[move-1][1]+')';
//console.log(location[move-1])
}else{
content='游戏开始~';
}
//console.log(_this.state.stepNumber)
if(arr.length==_this.state.history.length-1){
arr.push(<li key={move}><a className="process-active" onClick={(e)=>_this.jumpTo(e,move)} href="javascript:;">{content}</a></li>);
}else{
arr.push(<li key={move}><a onClick={(e)=>_this.jumpTo(e,move)} href="javascript:;">{content}</a></li>);
}
});
return arr;
},
...
这样,第二个问题就解决了。
三. 用两个循环重写Board组件,替代掉原来生硬的代码结构
因为只有9宫格,复用也毫无意义。所以写死也问题不大。
想到的处理方法就是这样了。
var Board=React.createClass({
renderSquare:function(i){
return <Square key={i} value={this.props.lastHistory[i]} onClick={() => this.props.onClick(i)} />
},
getSquare:function(rows){
var index=rows*3;
var arr=[];
for(var i=index;i<index+3;i++){
arr.push(this.renderSquare(i));
}
return arr;
},
getBoardRow:function(){
var arr=[];
for(var i=0;i<3;i++){
arr.push(<div key={i} className="board-row">{this.getSquare(i)}</div>)
}
return arr;
},
render:function(){
return (
<div clasName="board">
<div className="status"></div>
{this.getBoardRow()}
</div>
);
}
});
四. 对你的历史记录进行升降序排列
接下来又回到Game组件上面来了。在渲染结构中加一个按钮。点击,触发事件。大概就是这样。
<input type="button" value={this.state.isAscending} onClick={this.sortToggle} />
因为默认就是降序,因此这个toggleSort只做一件事:切换开关。至于是升序还是降序,又要多设置一个开关状态(isAscending,初始为降序排列)。
根据这个状态,getMoveList方法决定生成数组后是直接return还是return arr.reverse()
。
sortToggle:function(){
this.setState(function(prevState){
var sort=prevState.isAscending;
var content='';
if(sort=='升序排列'){
content='降序排列';
}else{
content='升序排列'
}
return {
isAscending:content
}
})
},
然后再到getMoveList
方法的最后,加一个判断:
.....
if(this.state.isAscending=='降序排列'){
return arr;
}else{
return arr.reverse();
}
}
嗯,第四个问题解决。
五. 高亮显示获胜的结果
扩展judgeWinner的功能
judgeWinner
判断函数已经被纳入到了组件中,而且只是返回一个status,现在要扩展它的功能,把胜负情况反应出来。
在原来的判断胜负函数里面加个console就可以知道胜负手了。
for(var i=0;i<win.length;i++){
var winCase=win[i];
if(squares[winCase[0]]&&squares[winCase[0]]===squares[winCase[1]]&&squares[winCase[1]]===squares[winCase[2]]){//三子一线
console.log(winCase)//这里的winCase就是胜负手
return ('获胜方是:'+squares[winCase[0]]);//返回胜利一方的标识
}
}
既然是扩展功能,再来大改就没必要了。可以考虑把return一个字符串改为return一个数组。第0项放status,第1项放winCase或null
。
有了这个方法,handleClick中那种非主流写法就可以删掉了。
var winner=this.judgeWinner();
if(winner[1]||squares[i]){
return false;
//胜负已分或已有子:则不可落子。
}
传递胜负手
再写一个 CSS
.win-case{
color: red;
}
现在可以通过winner[1]拿到胜负手了。它是一个数组。现在就得在Game组件render方法里面在var一个数据。通过props传下去,传到Board组件之后,做一个判断,看看参数是否符合点位条件,是的话就继续把class名传下去。
/********<Game/>*******/
render:function{
var winCase=this.judgeWinner()[1];//获胜状态
return (
<div className="game">
<Board winCase={winCase} lastHistory={lastHistory.squares} onClick={(i)=>this.handleClick(i)} />
...
/**********<Board/>***********/
renderSquare:function(i){
if(this.props.winCase){
for(var j=0;j<this.props.winCase.length;j++){
if(this.props.winCase[j]==i){
return <Square winCase="win-case" key={i} value={this.props.lastHistory[i]} onClick={() => this.props.onClick(i)} />
}
}
}
return <Square key={i} value={this.props.lastHistory[i]} onClick={() => this.props.onClick(i)} />
},
...
/************<square/>******************/
var Square=React.createClass({
render:function(){
if(this.props.winCase){
return (
<button className={"square "+this.props.winCase} onClick={() => this.props.onClick()}>{this.props.value}</button>
);
}else{
return (
<button className="square" onClick={() => this.props.onClick()}>{this.props.value}</button>
);
}
}
});
。。。。。。
那么第五个问题就完成了。
结束
现在,功能已经完备。思路已经理清。再看之前留下的大坑:historyLocation
。
之前提到过,historyLocation
是可以和history
相互计算得出的。historyLocation
只用于展示步数。组件的判断引擎是用兼容history
的一维数组实现的,为了后期实现AI书写方便,也显然是history
更好。还是删掉这个historyLocation。
不好之处在于每次都要多一点计算,相比React每次动辄重新渲染,这点计算也不是很多。
写一个根据history获取坐标的方法,拿到坐标之后在转换为二维坐标,这本质上是一件事,所以convert方法也可以删掉了。
getRectangular:function(){
var arr=[];
var mainArr=this.state.history.slice();
for(var i=0;i<mainArr.length;i++){
if(i<mainArr.length-1){
for(var j=0;j<9;j++){
//比较mainArr[i].squares和mainArr[i+1].squares[j])不同,拿到坐标值
if(mainArr[i].squares[j]!==mainArr[i+1].squares[j]){
arr.push(j);
}
}
}
}
var result=[]
for(var i=0;i<arr.length;i++){
var x=Math.floor(arr[i]/3)+1;
var y=(arr[i]+1)%3===0?3:(arr[i]+1)%3;
result.push([x,y])
}
return result;
},
可以再自己优化下算法和css,或者加个重置button之类的。把不必要的变量删掉。
效果:
下一篇笔记将解决最大的一个坑。
附录:组件代码
var Game=React.createClass({
getInitialState:function(){
return {
history:[
{squares:Array(9).fill(null)}
],
turnToX:true,
stepNumber:0,
isAscending:'降序排列'
}
},
// 判断胜负的函数,穷举法,返回一个数组,如果胜负已定,第二个元素就是胜负手
judgeWinner:function(){
var lastHistory=this.state.history[this.state.stepNumber];//获取指针版本
var squares=lastHistory.squares;
var win=[
[0,1,2],
[0,3,6],
[0,4,8],
[1,4,7],
[2,5,8],
[2,4,6],
[3,4,5],
[6,7,8]
];
for(var i=0;i<win.length;i++){
var winCase=win[i];
if(squares[winCase[0]]
&&squares[winCase[0]]===squares[winCase[1]]
&&squares[winCase[1]]===squares[winCase[2]]){//三子一线
return [('获胜方是:'+squares[winCase[0]]),winCase];//返回一个status和胜负情况
}
}
// 获取当前棋盘上被填满的格子数量
var fill=lastHistory.squares.filter((item)=>item!=null).length;
if(fill==9){
return ['和棋!',null];
}else{
var player=this.state.turnToX?'X':'O';
return [('轮到'+player+'走'),null];
}
},
// 点击事件是把暂存器的内容存为真正的状态。
handleClick:function(i){
//history指针版本暂存
var history=this.state.history.slice(0,this.state.stepNumber+1);
var lastHistory=history[this.state.stepNumber];
var squares=lastHistory.squares.slice();
var winner=this.judgeWinner();
if(winner[1]||squares[i]){
return false;
//胜负已分或是已有子则不可落子。
}
// 判断下棋的轮换色
squares[i]=this.state.turnToX?'X':'O';
//覆盖掉原来的状态!
this.setState({
history:history.concat([{squares:squares}]),
turnToX:!this.state.turnToX,
stepNumber:history.length
});
},
// 转化history状态为各个版本的平面直角坐标
getRectangular:function(){
var arr=[];
var mainArr=this.state.history.slice();
for(var i=0;i<mainArr.length;i++){
if(i<mainArr.length-1){
for(var j=0;j<9;j++){
//比较mainArr[i].squares和mainArr[i+1].squares[j])不同,拿到坐标值
if(mainArr[i].squares[j]!==mainArr[i+1].squares[j]){
arr.push(j);
}
}
}
}
var result=[]
for(var i=0;i<arr.length;i++){
var x=Math.floor(arr[i]/3)+1;
var y=(arr[i]+1)%3===0?3:(arr[i]+1)%3;
result.push([x,y])
}
return result;
},
// 历史步骤跳转是把状态还原到某个时间点,状态根据stepNumber呈现内容,但不会改变最终状态。
jumpTo:function(e,step){
var aList=e.target.parentNode.parentNode.childNodes;
for(var i=0;i<aList.length;i++){
var item=aList[i];
if(item.childNodes[0].classList.contains('back-active')){
item.childNodes[0].classList.remove('back-active');
}
}
e.target.classList.add('back-active');
this.setState({
stepNumber:step,
turnToX:step%2?false:true
});
},
// 坐标转换函数
convert:function(i){
var x=Math.floor(i/3)+1;
var y=(i+1)%3===0?3:y=(i+1)%3;
return [x,y];
},
// 获取历史步骤列表
getMoveList:function(){
var history=this.state.history.slice();
var arr=[];
var _this=this;
var rectangular=this.getRectangular();//获取二维坐标
history.forEach(function(step,move){
var content='';
if(move!==0){
content='Move#'+move+':'+'('+rectangular[move-1][0]+','+rectangular[move-1][1]+')';
}else{
content='游戏开始~';
}
// 高亮最后一个
if(arr.length==_this.state.history.length-1){
arr.push(
<li key={move}>
<a
className="process-active"
onClick={(e)=>_this.jumpTo(e,move)}
href="javascript:;">
{content}
</a>
</li>
);
}else{
arr.push(
<li key={move}>
<a onClick={(e)=>_this.jumpTo(e,move)}
href="javascript:;">
{content}
</a>
</li>
);
}
});
// 排序方式
if(this.state.isAscending=='降序排列'){
return arr;
}else{
return arr.reverse();
}
},
// 切换排序方式
sortToggle:function(){
this.setState(function(prevState){
var sort=prevState.isAscending;
var content='';
if(sort=='升序排列'){
content='降序排列';
}else{
content='升序排列'
}
return {
isAscending:content
}
})
},
// 重置
reset:function(){
this.setState({
history:[
{squares:Array(9).fill(null)}
],
turnToX:true,
stepNumber:0,
isAscending:'降序排列'
});
},
render:function(){
var lastHistory=this.state.history[this.state.stepNumber];//渲染遵照的是stepNumber而不是最后一步
var status=this.judgeWinner()[0];//获胜描述
var winCase=this.judgeWinner()[1];//获胜状态
return (
<div className="game">
<div>
<h1 classNme="status">React的井字过三关(2)</h1>
<Board
winCase={winCase}
lastHistory={lastHistory.squares}
onClick={(i)=>this.handleClick(i)} />
</div>
<div className="info">
<div>{status}</div>
<input
type="button"
value={this.state.isAscending}
onClick={this.sortToggle} />
<input
type="button"
value="重置"
onClick={this.reset} />
<ol>{this.getMoveList()}</ol>
</div>
</div>
);
}
});
var Board=React.createClass({
renderSquare:function(i){
if(this.props.winCase){
for(var j=0;j<this.props.winCase.length;j++){
if(this.props.winCase[j]==i){
return (
<Square
winCase="win-case"
key={i}
value={this.props.lastHistory[i]}
onClick={() => this.props.onClick(i)} />
);
}
}
}
return (
<Square key={i}
value={this.props.lastHistory[i]}
onClick={() => this.props.onClick(i)} />
);
},
getSquare:function(rows){
var index=rows*3;
var arr=[];
for(var i=index;i<index+3;i++){
arr.push(this.renderSquare(i));
}
return arr;
},
getBoardRow:function(){
var arr=[];
for(var i=0;i<3;i++){
arr.push(
<div key={i}
className="board-row">
{this.getSquare(i)}
</div>
);
}
return arr;
},
render:function(){
return (
<div clasName="board">
<div className="status"></div>
{this.getBoardRow()}
</div>
);
}
});
var Square=React.createClass({
render:function(){
if(this.props.winCase){
return (
<button
className={"square "+this.props.winCase}
onClick={() => this.props.onClick()}>
{this.props.value}
</button>
);
}
return (
<button
className="square"
onClick={() => this.props.onClick()}>
{this.props.value}
</button>
);
}
});
ReactDOM.render(
<Game />,
document.getElementById('container')
);
React的井字过三关(2)的更多相关文章
- React的井字过三关(1)
React的井字过三关(1) 本文系React官方教程的Tutorial: Intro To React的笔记.由笔者用ES5语法改写. 在本篇笔记中,尝试用React构建一个可交互的井字棋游戏. 开 ...
- React的井字过三关(3)
这是React井字棋项目的最后一篇笔记,记述AI实现. 一. 是开头都会说的原理 但凡懂一点围棋的人都知道"大场"这个概念,可以浅显地把它理解为布局时棋盘上各处的要点.棋谚&quo ...
- [HTML5实现人工智能]小游戏《井字棋》发布,据说IQ上200才能赢
一,什么是TicTacToe(井字棋) 本 游戏 为在下用lufylegend开发的第二款小游戏.此游戏是大家想必大家小时候都玩过,因为玩它很简单,只需要一张草稿纸和一只笔就能开始游戏,所以广受儿 ...
- 『HTML5实现人工智能』小游戏《井字棋》发布,据说IQ上200才能赢【算法&代码讲解+资源打包下载】
一,什么是TicTacToe(井字棋) 本游戏为在下用lufylegend开发的第二款小游戏.此游戏是大家想必大家小时候都玩过,因为玩它很简单,只需要一张草稿纸和一只笔就能开始游戏,所以广受儿童欢迎. ...
- Pascal小游戏 井字棋
一个很经典的井字棋游戏 Pascal源码Chaobs奉上 注意:1.有的FP版本不支持汉语,将会出现乱码.2.别想赢电脑了,平手不错了. 井字过三关: program TicTacToe; uses ...
- 使用 Vue.js 改写 React 的官方教程井字棋
React 的官方教程井字棋很好的引导初学者一步步走进 React 的世界,我想类似的教程对 Vue.js 的初学者应该也会有启发,于是使用 Vue.js 进行了改写 可以先查看最终的结果,尝试点击体 ...
- [CareerCup] 17.2 Tic Tac Toe 井字棋游戏
17.2 Design an algorithm to figure out if someone has won a game oftic-tac-toe. 这道题让我们判断玩家是否能赢井字棋游戏, ...
- quick cocos2d-x 入门---井字棋
学习quick cocos2d-x 第二天 ,使用quick-x 做了一个井字棋游戏 . 我假设读者已经 http://wiki.quick-x.com/doku.php?id=zh_cn阅读了这个链 ...
- 程序设计入门—Java语言 第五周编程题 2井字棋(5分)
2 井字棋(5分) 题目内容: 嗯,就是视频里说的那个井字棋.视频里说了它的基本思路,现在,需要你把它全部实现出来啦. 你的程序先要读入一个整数n,范围是[3,100],这表示井字棋棋盘的边长.比如n ...
随机推荐
- ABP督导项目(1)
创建实体 项目名TQMASP 在领域层创建entities文件夹存放实体类如图 创建Dbcontext public virtual IDbSet<Supervisor> Supervis ...
- Oracle forall bulk collect批量数据更新
对于数据量较大的插入操作可采用此种方法操作,注意: limit减少内存占用,如果数据量较大一次性全部加载到内存中,对PGA来说压力太大,可采用limit的方法一次加载一定数量的数据,建议值通常为100 ...
- 实时跟踪log变化的工具Apachetop
作为一个网站管理员,我们经常会有需要知道当前什么人正在访问我们的网站,谁正在频繁的抓取我们网站的内容,什么搜索引擎正在抓取我们网站?面对这些问题,我们虽然可以去查看log日志文件,但是却不能让我们实时 ...
- [Hadoop in Action] 第1章 Hadoop简介
编写可扩展.分布式的数据密集型程序和基础知识 理解Hadoop和MapReduce 编写和运行一个基本的MapReduce程序 1.什么是Hadoop Hadoop是一个开源的框架,可编写和运 ...
- Git权威指南 书摘
##$ git add welcome.txtwarning: LF will be replaced by CRLF in welcome.txt.The file will have its or ...
- kali linux之窥看女神上网隐私(ettercap+wireshark+zenmap +dsniff)
作者:小波 http://www.cnblogs.com/xiaobo-Linux/ 无聊就玩了玩,不要干一些坏事哟~~网上也有一些文章关于kali linux的,就实战了一番.kali是用的debi ...
- Java Generics and Collections-2.4-2.5
2.4 The Get and Put Principle Get and Put Principle: 用于取对象的泛型集合,声明为 <? extends T> 用于存对象的泛型集合,声 ...
- css实现页面元素居中
水平居中 对于已知宽度的块级元素实现水平居中只需要设置 margin-left:auto; margin-right:auto; 对于多个块级元素实现水平居中只需要设置 //1 父类{ text-al ...
- (转)Java:类与继承
原文地址: http://www.cnblogs.com/dolphin0520/p/3803432.html 对于面向对象的程序设计语言来说,类毫无疑问是其最重要的基础.抽象.封装.继承.多态这四大 ...
- [LeetCode] Range Sum Query - Immutable 区域和检索 - 不可变
Given an integer array nums, find the sum of the elements between indices i and j (i ≤ j), inclusive ...