Quantcast
Channel: IT社区推荐资讯 - ITIndex.net
Viewing all 15843 articles
Browse latest View live

23web app实现上下左右滑动

$
0
0

转载请说明出处: http://blog.csdn.net/wowkk/article/category/1619287 (创意系列)

    /*近期项目需要苹果电脑,如果您支持学生创业并愿意赞助我们一台,请联系我QQ696619,您可以提前获取16页创意文档,或者我们可以帮助开发一些小项目*/


原本要做一种效果:上下左右滑动页面,可以切换到图片(表格布局)。

效果实现了,但还没应用上。

//--------图片滑动导航---------
            var startX; //触摸起始横坐标
            var startY; //触摸起始纵坐标
            var moveSpave; //移动的距离
            var isMoveX = true; //判断是否为左右移动
            var isFirst = true;   //是否要判断touchmove方向
            $("#imgSelect").on("touchstart touchmove touchend touchcancel", function (e) {
                e.preventDefault(); //该区域禁止滑动切换页面
                if (e.originalEvent.type == "touchstart") {
                    startX = e.originalEvent.touches[0].pageX; //触摸起始位置赋值
                    startY = e.originalEvent.touches[0].pageY; //触摸起始位置赋值
                    isFirst = true;
                }
                else if (e.originalEvent.type == "touchmove") {
                    var moveX = e.originalEvent.touches[0].pageX
                    var moveY = e.originalEvent.touches[0].pageY;
                    if (isFirst) {
                        Math.abs(moveX - startX) - Math.abs(moveY - startY) >= 2 ? isMoveX = true : isMoveX = false;
                        isFirst = false;
                        return;
                    }

                    if (isMoveX) {
                        //水平滑动
                        moveSpave = moveX - startX;
                    }
                    else {
                        //竖直滑动
                        moveSpave = moveY - startY;
                    }

                }
                else if (e.originalEvent.type == "touchend") {
                    if (isMoveX) {
                        if (moveSpave < 0 && j <= 2) {
                            //向左滑动
                            Add("#topLight", j+1); //开关对应灯
                            j = j + 1;
                        }
                        else if (moveSpave > 0 && j >= 1) {
                            //向右滑动
                            Sub("#topLight", j+1);
                            j = j - 1;
                        }
                    }
                    else {
                        if (moveSpave < 0 && i <= 2) {
                            //向上滑动
                            Add("#rightLight", i + 1); //开关对应灯
                            i = i + 1;
                        }
                        else if (moveSpave > 0 && i >= 1) {
                            //向下滑动
                            Sub("#rightLight", i + 1); //开关对应灯
                            i = i - 1;
                        }
                    }
                    $("#imgClick").attr("src", arrImg[i][j]);
                }

//------
            function Add(id, x) {
                var idd = id + x;
                $(idd).attr("src", "img/Select_Off.png");
                x = x + 1;
                idd = id + x;
                $(idd).attr("src", "img/Select_On.png");
            }
            function Sub(id, x) {
                var idd = id + x;
                $(idd).attr("src", "img/Select_Off.png");
                x = x - 1;
                idd = id + x;
                $(idd).attr("src", "img/Select_On.png");
            }

<span id="topLight"><!--横向指示灯--><img id="topLight1" src="img/Select_On.png" /><img id="topLight2" src="img/Select_Off.png" /><img id="topLight3" src="img/Select_Off.png" /><img id="topLight4" src="img/Select_Off.png" /></span><div id="rightLight"><!--竖向指示灯--><img id="rightLight1" class="rightImg" src="img/Select_Off.png" /><img id="rightLight2" class="rightImg" src="img/Select_On.png" /><img id="rightLight3" class="rightImg" src="img/Select_Off.png" /><img id="rightLight4" class="rightImg" src="img/Select_Off.png" /></div>   


作者:wowkk 发表于2014-5-4 0:19:24 原文链接
阅读:140 评论:0 查看评论

JavaScript动画工作原理之(完结篇)

$
0
0

原作者:Steven Riche

发布时间:2014年2月18日

原文链接:http://code.tutsplus.com/tutorials/javascript-animation-that-works-part-4-of-4--net-35263

翻译:子毅 --------- 将JavaScript进行到底

碎碎两句 

折腾了一个多月,杂七杂八的事情加在一起,简直糟透了。博客停了大概有一个月了,从今天起一切都是新的,做好自己就OK了

----------------------------------------------------------------------------------------------

在本系列的 第一篇文章中,我们介绍了用精灵在web上创建简单、跨浏览器的可交互动画。在 第二篇文章中,我们让动画工作起来,而在第三篇文章中,我们整理好我们的代码,并为它在web上使用做好准备


介绍


现在,在我们的最后一部分中,我们将通过建立事件处理程序,而不是在点击按钮时机器人做出响应,我们的机器人将在屏幕上跟随着鼠标而移动。在这个过程中,我们将讨论跨浏览器的代码,并且触摸屏也可用

假如你看一下我们 上一次的代码,你可以看到,尽管我们的代码运行得很好(并且同时有多个机器人),然而这里没有一个简单的方式来运行代码。

事件处理程序


事件处理程序是这样的命令,当特定的事件触发时,它告诉某些代码运行。例如,任何时候,你可以让一个用户点击有“my_div' id的‘div’时,'my_function()'执行。或者,当用户在‘my_other_div'上移动鼠标时,'my_other_function()'执行

理论上,这个一个相当简单且直观的主意。不幸的是,当你卷入 不同的浏览器,这将会有一点迷惑。理想情况下,每个浏览器都会以同样的方式在解释代码,而开发者将只写需要写一次代码就可以让每个用户看到相同的结果。在真实世界中,不同的浏览器可能会有完全不同的命名来做同一件事(*咳**咳* IE),所以有时候想要一段代码在所有的浏览器中都运行得一样,会让人觉得像是在放牧一群猫。最近,情况已经变得很好了,Chrome,Firefox, Safgari,Opera对代码的响应已经非常相似了,IE9和IE10已经变得比早期的版本更加符合标准。并且几乎没有人在使用IE7和IE6了。因此,我们代码将使得事件处理程序在现代浏览器和IE8种可工作

作为一方面的说明,这是一种使用一个强大JavaScript库的原因,比如jQuery。jQuery已经为你做好了所有的跨浏览器测试,因此,你只需要输入一个命令,jQuery会在幕后翻译它使得它在每个浏览器中都可用。此外,许多jQuery命令都比核心JavaScript更加直观、简单。

但是,由于我的固执,并且这是一次学习的机会,我们将继续在这条艰难的路上努力,只用JavaScript来做这一切,不依赖任何库

页面交互


我们第一步要决定到底将怎么和页面交互。当我在舞台区域移动鼠标时,我想要所有的机器人朝着鼠标移动的方向跑。当它们抵达鼠标或者鼠标正好在它们上面,我想要它们停止移动。假如鼠标放在它们身上,我想要它们跳起来。最后,当鼠标离开舞台,我想要它们停止跑动。我们将从绑定事件到RobotMaker函数内部开始。

stage.addEventListener('mousemove', stage_mousemove_listener, false);
robot.addEventListener('mouseover', robot_mouseover_listener, false);
stage.addEventListener('mouseout', stage_mouseout_listener, false);

因此,在上面的几行代码中,我们说过,无论什么时候用户在舞台(stage)元素上移动鼠标,将触发一个叫做stage_mousemove_listener()的函数(注意,在命令中,我们并没有包含参数)。相似地,当用户在robot元素上移动鼠标,将触发robot_mouseover_listener(),当用户从舞台上移开鼠标,触发stage_mouseout_listenr()

不幸的是,之前我们提到过,IE8及其低版本使用不同(但是相似)的命令来做相同的事,因此,我们需要进行测试,以便知道用户的浏览器将会理解哪条命令,并执行相应的方法。

if (stage.addEventListener){ // We will test to see if this command is available
  stage.addEventListener('mousemove', stage_mousemove_listener, false);
  robot.addEventListener('mouseover', robot_mouseover_listener, false);
  stage.addEventListener('mouseout', stage_mouseout_listener, false);
} else { // If not, we have to use IE commands
  stage.attachEvent('onmousemove', stage_mousemove_listener);
  robot.attachEvent('onmouseover', robot_mouseover_listener);
  stage.attachEvent('onmouseout', stage_mouseout_listener); 
}

你可能会注意到那些命令的格式非常相似,但是还是有一些主要的区别:一个叫做‘addEventListener',一个叫做'attachEvent',一个叫做'mousemove',然而另一个叫做'onmousemove'。一个需要第三个参数,而另个只用了两个。混淆它们之间的任何一个都会导致命令不执行。这一系列的事会使你有用脑袋撞墙的冲动。不幸的是,为了是具有跨浏览器的能力,这并不是我们需要额外编写的最后代码

监听函数


接下来,我们将编写监听函数。从编写用户在舞台上移动而触发的函数开始。正因为它是一个mousemove侦听器,当鼠标每次在舞台区域内移动时,都将触发它(这意味着在一秒钟内将会触发多次)这个函数需要将机器人的位置和鼠标的位置作比较,并使机器人见机行事。每次函数触发,它将检测机器人需要继续朝相同的方向跑动,还是变换为其他行为。因此,它需要像下面这样编写。


// Inside of RobotMaker
// We will need to introduce a few extra variables to track
var mouseX; // For tracking horizontal mouse position
var running_dir = ''; // For tracking if (and where) robot is currently running
var stageOffset; // For tracking the position of the stage
function stage_mousemove_listener(e){
  // Find the horizontal position of the mouse inside of the stage ...  
  // That position will be saved in 'mouseX'
  // Then we compare 'mouseX' to the robot, and decide if we need to run differently
  if (((robot.offsetLeft + (15 * run_speed)) < (mouseX - robot.offsetWidth)) && running_dir !== 'r' && (!jump_timer || jump_timer === undefined)){ 
    // If the mouse is in the stage and to the right of the robot, make run right, if not already
    running_dir = 'r';
    clearTimeout(run_timer);
    run_r(1, robot.offsetLeft);
  } else if ((mouseX < robot.offsetLeft - (15 * run_speed)) && running_dir !== 'l' && (!jump_timer || jump_timer === undefined)) {
    // If the mouse is in the stage and to the left of the robot, make run left, if not already
    running_dir = 'l';
    clearTimeout(run_timer);
    run_l(1, robot.offsetLeft);
  } else if ((robot.offsetLeft < mouseX) && ((robot.offsetLeft + robot.offsetWidth) > mouseX) && running_dir !== '' && (!jump_timer || jump_timer === undefined)) {
    // If the mouse is in the stage and over a robot, stop and clear running_dir
    running_dir = '';
    clearTimeout(run_timer);
    if (face_right){
      robot.style.backgroundPosition = "0px 0px";
    } else {
      robot.style.backgroundPosition = "0px -50px";
    }
  }
  // If none of the above is true, then we let our current behavior continue
}


在上面的函数中,一旦我们找到mouseX,我们就可以和机器人的位置作比较,如果需要的话,触发或停止不同的跑动函数。不幸的是,找出mouseX有一些棘手,因为鼠标位置是另一件不同浏览器表现不同的事。为避免找出mouseX而进行复杂冗长的解释,正如灵感来自优秀的 Quirksmode博客 (这是一个学习更多高级JavaScript技术的伟大源地):

function stage_mousemove_listener(e){
  var posX = 0;
  if (!e){
    var e = window.event;
  }
  if (e.pageX) {
    posX = e.pageX;
  } else if (e.clientX) {
    posX = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
  }
  mouseX = posX - stageOffset.xpos; // 我们找到了mouseX!
}

我们有个叫做e的参数在函数中,尽管我们没有传递任何东西给它,但是这是一个事件侦听器,我们将自动拥有一个叫做e的变量,这个变量存储有和事件相关的信息,比如鼠标数据。但是不同浏览器存储的方式不同,我们不得不增加额外的一个步骤。

我们最终通过找到posX(鼠标在页面上的水平坐标)并用它减去stage左边距离页面的值(存储在stageOffset.xpos),进而找到mouseX,这给出了我们鼠标距离stage左边界的距离,并且我们可以用它直接和robot.offsetLeft作比较。根据布局,stage可能会位于不同的位置,我们同样需要找出stage精确的像素偏移,并将它存储在stageOffset中。幸运的是,我们可以使用一个巧妙的花招来找到元素的绝对偏移量,这个函数来自于 Vishal Astik的博客

// 在RobotMaker内
var x = 0;
var y = 0;
function find_stage_offset (el){
  x = el.offsetLeft;
  y = el.offsetTop;
  el = el.offsetParent;
     
  while(el !== null) {
    x = parseInt(x) + parseInt(el.offsetLeft);
    y = parseInt(y) + parseInt(el.offsetTop);
    el = el.offsetParent;
  }
 
  return {xpos: x, ypos: y};
}
var stageOffset = find_stage_offset(stage);

现在我们已经写好了mousemove侦听器,其他将会容易很多。对于机器人mouseover侦听器,我们只需要检测机器人是否在跳跃,如果不是,停止跑动,使之跳跃。

function robot_mouseover_listener(){
  if (!jump_timer || jump_timer === undefined){
    clearTimeout(run_timer);
    jmp(true, robot.offsetTop);
  }
}

mouseout侦听器同样也很简单。只需要重置一些我们用来跟踪robot的变量,假如机器人没有跳跃,则将机器人置为站立的精灵

function stage_mouseout_listener(){
  mouseX = undefined;
  running_dir = '';
  if (!jump_timer || jump_timer === undefined){
    clearTimeout(run_timer);
    if (face_right){
      robot.style.backgroundPosition = "0px 0px";
    } else {
      robot.style.backgroundPosition = "0px -50px";
    }
  }
}

动画函数


这一次,跑跳运动函数并没有改变很多。我们只是增添了一些跟踪变量running_dir,取出机器人将要撞击到强的声明(因为这是mouseout函数的冗余,并增加一些代码到跳跃函数,它用于再次检测,假如鼠标仍在stage内,当机器人在跳跃后落下,它是否需要开始跑动。下面是最终的代码(相当大):

function run_r(phase, left){
  face_right = true;
  running_dir = 'r';
  if ((left + (15 * run_speed)) < (mouseX - robot.offsetWidth)){ // if mouse is to the right, run
    left = left + (15 * run_speed);
    robot.style.left = left+"px";
    switch (phase){
      case 1:
        robot.style.backgroundPosition = "-40px 0px";
        run_timer = setTimeout(function(){run_r(2, left);}, 200);
        break;
      case 2:
        robot.style.backgroundPosition = "-80px 0px";
        run_timer = setTimeout(function(){run_r(3, left);}, 200);
        break;
      case 3:
        robot.style.backgroundPosition = "-120px 0px";
        run_timer = setTimeout(function(){run_r(4, left);}, 200);
        break;
      case 4:
        robot.style.backgroundPosition = "-80px 0px";
        run_timer = setTimeout(function(){run_r(1, left);}, 200);
        break;
    }
} else if ((left + (15 * run_speed)) < mouseX) { // if mouse if above, stop
    robot.style.backgroundPosition = "0px 0px";
    running_dir = '';
} else { // if mouse is to the left, run left
    running_dir = 'l';
    run_l(1, robot.offsetLeft);
  }
}
function run_l(phase, left){
  face_right = false;
  running_dir = 'l';
  if (mouseX < robot.offsetLeft - (15 * run_speed)){ // if mouse is to the left, run
    left = left - (15 * run_speed);
    robot.style.left = left+"px";
    switch (phase){
      case 1:
        robot.style.backgroundPosition = "-40px -50px";
        run_timer = setTimeout(function(){run_l(2, left);}, 200);
        break;
      case 2:
        robot.style.backgroundPosition = "-80px -50px";
        run_timer = setTimeout(function(){run_l(3, left);}, 200);
        break;
      case 3:
        robot.style.backgroundPosition = "-120px -50px";
        run_timer = setTimeout(function(){run_l(4, left);}, 200);
        break;
      case 4:
        robot.style.backgroundPosition = "-80px -50px";
        run_timer = setTimeout(function(){run_l(1, left);}, 200);
        break;
    }
} else if (mouseX < (robot.offsetLeft + robot.offsetWidth - (15 * run_speed))){ // if mouse overhead, stop
    robot.style.backgroundPosition = "0px -50px";
    running_dir = '';
} else { // if mouse is to the right, run right
    running_dir = 'r';
    run_r(1, robot.offsetLeft);
  }
}
function jmp(up, top){
  running_dir = '';
  if (face_right){
    robot.style.backgroundPosition = "-160px 0px";
  } else {
    robot.style.backgroundPosition = "-160px -50px";
  }
  if (up && (robot.offsetTop > (20 * (1 / jump_height)))){
    top = top - (top * 0.1);
    robot.style.top = top+"px";
    jump_timer = setTimeout(function(){jmp(up, top);}, 60);
  } else if (up) {
    up = false;
    jump_timer = setTimeout(function(){jmp(up, top);}, 60);
  } else if (!up && (robot.offsetTop < 115)){
    top = top + (top * 0.1);
    robot.style.top = top+"px";
    jump_timer = setTimeout(function(){jmp(up, top);}, 60);
  } else {
    robot.style.top = "120px";
    if (face_right){
      robot.style.backgroundPosition = "0px 0px";
    } else {
      robot.style.backgroundPosition = "0px -50px";
    }
    jump_timer = false;
    if (mouseX !== undefined){
      if (((robot.offsetLeft + (15 * run_speed)) < (mouseX - robot.offsetWidth)) && running_dir !== 'r'){ 
        // make run right, if not already
        running_dir = 'r';
        clearTimeout(run_timer);
        run_r(1, robot.offsetLeft);
      } else if ((mouseX < robot.offsetLeft - (15 * run_speed)) && running_dir !== 'l') {
        // make run left, if not already
        running_dir = 'l';
        clearTimeout(run_timer);
        run_l(1, robot.offsetLeft);
      }
    }
  }
}

现在我们完全重写了函数并且跨浏览器工作得很好……除非浏览器有触屏输入。我们仍需要向前进一步,使得我们的机器人可以在任何设备上跑动。因为触摸屏表现得有些不同,我们需要在事件侦听器上做一些额外的编码。

支持触摸屏


我们需要为触摸屏制定一些新规则:在stage上,屏幕被触摸到任何地方,机器人都会向那个点跑去,直到指尖离开。假如用户触摸机器人,机器人则跳起来。总之,我们需要为之前的函数添加一些额外的事件处理器,并且我们将以这样的方式来写代码:无论什么时候RobotMaster函数被调用,它都会自动运行。

(function (){
  if (stage.addEventListener){
    stage.addEventListener('touchstart', stage_mousemove_listener, false);
    stage.addEventListener('touchmove', stage_mousemove_listener, false);
    stage.addEventListener('touchend', stage_mouseout_listener, false);
    stage.addEventListener('mousemove', stage_mousemove_listener, false);
    robot.addEventListener('mouseover', robot_mouseover_listener, false);
    stage.addEventListener('mouseout', stage_mouseout_listener, false);
  } else {
    stage.attachEvent('onmousemove', stage_mousemove_listener);
    robot.attachEvent('onmouseover', robot_mouseover_listener);
    stage.attachEvent('onmouseout', stage_mouseout_listener);
  }
})();

我们不需要担心触摸事件在IE8中的格式,假如有任何设备不支持触摸触摸,它将忽略这些侦听器。现在,假如浏览器具有触摸功能,我们需要更新stage_mousemove_listener()函数使具有不同的表现,。

function stage_mousemove_listener(e){   
/*
 * First we check if this is a touch screen device (if it has e.touches)
 */
  if (e.touches){
    e.preventDefault(); // we want to cancel what the browser would usually do if touched there
    // If the touch was within the boundaries of the stage...
    if ((e.touches[0].pageX > stageOffset.xpos) && (e.touches[0].pageX < (stageOffset.xpos + stage.offsetWidth))&& (e.touches[0].pageY > stageOffset.ypos)&& (e.touches[0].pageY < (stageOffset.ypos + stage.offsetHeight))){
      // we set the mouseX to equal the px location inside the stage
      mouseX = e.touches[0].pageX - stageOffset.xpos; 
    } else { // if the touch was outside the stage, we call the mouseout listener
      stage_mouseout_listener();
    }
    /*
     * If the touch is directly on the robot, then we stop the run timer and make the robot jump
     */
    if ((e.touches[0].pageX > robot.offsetLeft) && (e.touches[0].pageX < (robot.offsetLeft + robot.offsetWidth))&& (e.touches[0].pageY > (stageOffset.ypos + stage.offsetHeight - robot.offsetHeight))&& (e.touches[0].pageY < (stageOffset.ypos + stage.offsetHeight))&& (!jump_timer || jump_timer === undefined)){
      clearTimeout(run_timer);
      jmp(true, robot.offsetTop);
    }
  } else { // Finding the mouseX for non-touch devices...
    // All of our non-touch device code here
  }
}

你可能注意到,在RobotMaker函数里,这里不再具有任何”门“,但是,因为我们调用的所有具有事件处理程序的代码都在RobotMaker里,所以我们不再需要它们。对于我们的stage和characters,需要为 触摸设备添加一点特别的CSS,使得在用户手指按住它们时,它们不会尝试剪切和粘贴任何图片。

#stage, .character {
  -webkit-user-select: none;
}

最后,我们声明所有的机器人在页面的底部,使用相同的格式,当页面加载时,事件处理器使得代码自动运行- 这个方法同样阻止了那些机器人对象成为全局变量,因此,在整个脚本中,我们唯一有的全局变量就是RobotMaker()函数

(function(){
  var j = RobotMaker(document.getElementById('j'), 1, 1);
  var j2 = RobotMaker(document.getElementById('j2'), .8, 5);
  var j3 = RobotMaker(document.getElementById('j3'), 1.1, .5);
  var j4 = RobotMaker(document.getElementById('j4'), .5, .75);
})();

请在glory中查看 最终的结果

总结


我强烈建议你学习 全部的代码(和所有的注释),同时你可以在这里 下载  四个  机器人 精灵

Happy animating!
作者:tianxuezhi_s 发表于2014-5-4 0:02:28 原文链接
阅读:121 评论:0 查看评论

Java 8那些被冷落的新特性

$
0
0

lambda表达式,lambda表达式,还是lambda表达式。一提到Java 8就只能听到这个,但这不过是其中的一个新功能而已,Java 8还有许多新的特性——有一些功能强大的新类或者新的用法,还有一些功能则是早就应该加到Java里了。

这里我准备介绍它的10个我个人认为非常值得了解的新特性。总会有一款适合你的,开始来看下吧。

  1. default方法

这是Java语言的一个新特性,现在接口类里可以包含方法体(这就是default方法)了。这些方法会隐式的添加到实现这个接口的每个子类中。

这使得你可以在不破坏代码的前提下扩展原有库的功能。它绝对是个利器。但从另一个方面来说,这使得接口作为协议,类作为具体实现的界限开始变得有点模糊。但好处就是,它通过一个很优雅的方式使得接口变得更智能,同时还避免了代码冗余,并且扩展类库。不好的地方就是,我估计很快就会看到有在接口方法里获取this引用然后强制转化成某个具体类型的写法了。

  1. 终止进程

一旦启动外部进程的话,当这个进程崩溃,挂起,或者CPU到达100%的时候,你就得回来擦屁股了。Process类现在增加了两个新的方法,可以来教训下那些不听话的进程了。

第一个是isAlive()方法,有了它你可以判断进程是否还活着。第二个方法则更加强大,它叫destroyForcibly(),你可以用它来强制的杀掉一个已经超时或者不再需要的进程。

  1. StampedLock

提到这个不禁有点小激动。没有人会喜欢在代码中使用同步。用了它肯定会降低程序的吞吐量,更糟糕的话还会导致进程挂起。尽管这样,有时候你却不得不选择它。

当多个进程访问一个资源的时候,有多种方法可以进行同步。其中用得最多的一种是ReadWriteLock以及基于它的几种实现。它通过阻塞写线程的方式来允许多个线程并发的读,这样减少了线程之间的竞争。听起来还不错,但实际上这个锁实在是太太太慢了,尤其是当有许多写线程的时候。

因此Java 8引入了一个新的读写锁,叫做StampedLock。它不仅更快,同时还提供了一系列强大的API来实现乐观锁,这样如果没有写操作在访问临界区域的话,你只需很低的开销就能获取到一个读锁。访问结束后你可以查询锁来判断这期间是否发生了写操作,如果有的话再选择进行重试,升级锁,或者放弃这个操作。

这的确是一个非常强大的工具,它本身就值得专门花一篇文章来介绍。这个新玩意儿让我感到非常激动和兴奋,它真的是太棒了。

想了解更多请点击 这里

  1. 并发计数器

这是多线程程序会用到的另一个小工具。它提供了简单高效的新接口来实现多线程的并发读写计数器的功能,和AtomicInteger比起来,它要更快一些。相当赞的工具。

  1. Optional

不好,又有空指针了,这是所有Java开发人员的痛处。这估计是有史以来最常见的异常了,至少是 1965年以来。

Java 8借鉴了Scala和Haskell,提供了一个新的Optional模板,可以用它来封装可能为空的引用。这绝不是终结空指针的银弹,更多只是使API的设计者可以在代码层面声明一个方法可能会返回空值,调用方应该注意这种情况。正因为这个,这只对新的API有效,前提是调用方不要让引用逃逸出封装类,否则的话引用可能会在外面被不安全的废弃掉。

我对这个新的特性真的是又爱又恨。一方面,空指针是一个大问题,只要能解决这个问题的东西我都欢迎。但另一方面,我对它是否能担此重任执怀疑的态度。这是由于使用它的话需要全公司的集体努力,短期内很难会有见效。除非大力地推广,否则很可能会功亏一篑。

  1. 万物皆可注解

还有一个小的改进就是现在Java注解可以支持任意类型了。之前只有像类和方法声明之类的才能使用注解。在Java 8里面,当类型转化甚至分配新对象的时候,都可以在声明变量或者参数的时候使用注解。这是Java为了更好地支持静态分析及检测工具(比如FireBug)而做的工作中的一部分。这是个很不错的特性,但是和Java 7的invokeDynamic一样,它的真正价值取决于社区以后如何去使用它。

  1. 数值溢出

这些方法早就该出现在Java的核心类库里了。我有个癖好就是去测试整型超出2^32时溢出的情况,搞出一些恶心的随机BUG来(怎么会得到这么奇怪的一个值?)。

同样的,这也不是什么银弹,只不过是提供了一组函数,这样你在使用+/*操作符进行数值操作的时候,如果出现了溢出,会抛一个异常。如果我可以决定的话,我会把它作为JVM的默认模式,显式的标明函数会出现数值溢出。

  1. 目录遍历

遍历目录树这种事通常都得上Google搜下怎么实现(你很可能用的是Apache.FileUtils)。Java 8给Files类做了一次整容手术,增加了十个新的方法。我最喜欢的一个是walk()方法,它遍历目录后会创建出一个惰性的流(文件系统很大的情况下非常有用)。

  1. 增强的随机数生成

现在经常都在讨论密码或者密钥容易遭受攻击的事。程序的安全性是项很复杂的工程,并且很容易出错。这就是我为什么喜欢这个新的SecureRandom.getinstanceStrong()方法的原因,它能自动选择出当前JVM可用的最佳的随机数生成器。这样减少了获取失败的机率,同时也避免了默认的弱随机数生成器可能会导致密钥或者加密值容易被黑客攻破的问题。

  1. Date.toInstant()

Java 8引入了一个新的日期API。这不难理解,因为现有的这个实在是太难用了。实际上Joda一直以来都是Java日期API的首选。不过尽管有了新的API,但仍有一个严重的问题——大量的旧代码和库仍然在使用老的API。

并且我们还知道这种现状仍将继续存在下去。到底该怎么做呢?

Java 8很优雅的解决了这个问题,它给Date类增加了一个新的方法toInstant(),它可以将Date转化成新的实现。这样你马上就可以切换到新的API,尽管现有的代码还在使用老的日期API(并且在可预见的未来仍将继续这样)。

如果你觉得有什么遗漏的或者你觉得我有什么讲的不对的地方,请不吝赐教。下面的评论框就是为这个而准备的:-)

原创文章转载请注明出处: Java 8那些被冷落的新特性

英文原文链接

JBoss AS 7性能调优(二)

$
0
0

原文: http://www.mastertheboss.com/jboss-performance/jboss-as-7-performance-tuning/page-2

调优数据库连接池

建立与DBMS的JDBC连接过程可能是相当缓慢的。如果您的应用程序需要反复打开和关闭数据库连接,这可以成为一个显著的性能问题。在JBoss AS中数据源的连接池提供了一种有效的解决该问题的方法。

要强调的是,当客户端关闭一个数据源的连接时,该连接返回到池中,这样可用于其它的客户端,因此,连接本身并没有关闭。打开和关闭池管理的连接的成本可以以纳秒来衡量,所以它对性能的影响无关紧要。

在下面的例子中,我们将强调在第三章讲解的数据源配置,使用连接池配置来提供企业服务:

<datasource jndi-name="MySqlDS" pool-name="MySqlDS_Pool"

    enabled="true" jta="true" use-java-context="true" use-ccm="true">

    <connection-url>

        jdbc:mysql://localhost:3306/MyDB

    </connection-url>

    <driver>mysql</driver>

    <pool>

        <min-pool-size>10</min-pool-size>

        <max-pool-size>30</max-pool-size>

        <prefill>true</prefill>

    </pool>

    <timeout>

        <blocking-timeout-millis>30000</blocking-timeout-millis>

        <idle-timeout-minutes>5</idle-timeout-minutes>

    </timeout>

</datasource>

在这里,我们配置了10个连接的初始池的容量,最大可以增长到30。正如你可以从下面的MySQL管理控制台中看到的,当你设置pre-fill元素为true,则应用服务器试图在启动时预先创建连接。这可能会产生性能损失,特别是如果连接的获取很昂贵的时候。

如果应用服务器因为连接池的连接都在使用,而不能获得更多的连接,那么它会一直等待直到阻塞超时(blocking-timeout-millis),这时会抛出一个异常给客户端。

同时,如果连接空闲超过了参数idle-timeout-minute设置的时间,则他们被迫返回到池中。

调整池大小

要确定合适的池大小,你需要监视数据库连接的使用,这可以通过几种方式来完成。使用命令行,您可以监视数据源的运行时性能。下面是一个将在第4章讲述的应用程序的示例:

[standalone@localhost:9999 /]/subsystem=datasources/data-source="java:/MySqlDS":read-resource(include-runtime=true)
{
"outcome" => "success",
"result" => {
"ActiveCount" => "10",
"AvailableCount" => "29",
"AverageBlockingTime" => "0",
"AverageCreationTime" => "56",
"CreatedCount" => "10",
"DestroyedCount" => "0",
"MaxCreationTime" => "320",
"MaxUsedCount" => "5",
"MaxWaitCount" => "0",
"MaxWaitTime" => "1",
. . . .
}
}

此命令的输出有点长,然而最有趣的属性是输出的开头部分:尤其是 ActiveCount,它显示了当前活动的连接数, MaxUsedCount是由应用程序使用的连接的峰值数。

注意:如果您设置了预初始化连接池,如图所示前面的部分,这些连接将一直处于活跃状态。这可能造成误解,导致你认为他们一直很忙。

如果您无法使用CLI或只是你要好好利用你的DBA认证,有一些有效的可选方法:首先,最显而易见的是监视数据库会话。下表列出了一些有用的命令,这些命令可以用来跟踪在不同数据库上的活动的连接:

数据库

命令/ 表

Oracle

查询V$SESSION视图

MySQL

使用命令SHOW FULL PROCESSLIST

Postgre-SQL

查询PG_STAT_ACTIVITY表

另一种选择是使用像P6Spy的工具,它充当JDBC代理的驱动。 (我博客关于它的文章在 这里))。

一旦你找到应用程序使用连接的峰值,设置至少高出25-30%作为最大值。不要担心设置的最大值过高,因为如果你不需要这么多的连接,池将自动收缩,前提是你已经设置了idle-timeout-minutes。

另一方面,服务器日志在帮助你检查连接池运行问题时,仍然是一个非常宝贵手段。例如,如果你在你的服务器日志中看到这个异常,这说明你需要你去看看连接池是否正常:

21:57:57,781 ERROR [stderr] (http-executor-threads - 7) Caused by: javax.resource.ResourceException: IJ000655: No managed connections available within configured blocking timeout (30000 [ms])
21:57:57,782 ERROR [stderr] (http-executor-threads - 7)         at org.jboss.jca.core.connectionmanager.pool.mcp.SemaphoreArrayListManagedConnectionPool.getConnection

作者:wilbertzhou 发表于2014-5-3 21:19:02 原文链接
阅读:85 评论:0 查看评论

JBoss AS 7性能调优 (一)

$
0
0

原文: http://www.mastertheboss.com/jboss-performance/jboss-as-7-performance-tuning

 

调优JBoss应用服务器

虽然许多架构师和软件工程师都同意,约70-80%的应用程序的性能取决于应用程序本身的编码,配置不当的服务器环境可以显著影响你的用户体验,并最终影响到你的应用程序性能。

很多配置元素,可以显著地影响你的服务器的性能,他们中的一些配置值得特别注意:

•    JVM 调优
•    应用服务器资源池
•    日志
•    缓存数据

让我们看看每个元素的细节。

JVM调优

Java虚拟机(JVM)中运行的JBoss AS7,在JVM参数的正确配置下可以提供更好的性能。

JVM优化已经发展了很多年,实际上每一个Java的版本都有变化。由于在J2SE的5.0版中,JVM能够提供与您的环境相一致的一些默认的配置(“人类工程学”)。然而,人体工程学(Ergonomics)提供的聪明选择并不总是最优的,用户设置不合适的情况下,性能可能低于你的预期。

基本上, JVM的调优过程可以分成以下步骤:

•选择一个正确的JVM heap大小。它分为一个合适的heap初始值(-Xms) heap最大值(-Xmx).
•选择一个合适的垃圾回收算法。

让我们看看这两个元素的细节。

选择合适的 JVM 堆大小

Java对象在堆上创建,每个堆被分成三个部分,或三个垃圾回收代,被称为新生代( Young generation),年老代( Tenuredor Old generation),和持久代( Permanent)

新生代进一步分为Eden,Survivor 1,Survivor 2这三个空间。当一个对象被首先在堆中创建,它在新生代的Eden空间被创建,并且在随后的新生代GC(minor GC)后,如果该对象存活,它被转移到Survivor 1,然后被移到Survivor 2,直到发生年老代GC(major GC),对象被移动到年老代。

永久代空间有些特殊,它是用来存储JVM中的类和方法的元数据,它也存储了JVM中提供的String池。

为了调整JVM,你应该选择新生代(对象实例化后最初放置的地方)和年老代(存活期长的对象被移到这里)之间的正确比例。

对于大多数应用程序,新生代和年老代之间的正确比例范围在1/3 和1/2之间。

合适的堆最大值可以通过在使您的应用程序加载峰值负载的一段稳定的时间内测试来确定。一旦您已经确定了应用程序所需要内存的峰期,取决于应用程序的性质,你可以增加一个额外的25-40%的内存作为堆最大值。

至于初始堆大小,一个好的经验法则是将其设置和堆最大值相同。这增加了可预测性,避免了需要分配内存时扩展堆,这在生产环境特别有用,而开发者(拥有有限的资源)可能会选择一个更小的初始堆大小。

保持该配置为较小环境的建议配置,也可以作为更大环境的参考:
java -Xmx1024m -Xms1024m -XX:MaxNewSize=448m-XX:NewSize=448m -XX:SurvivorRatio=6
java –Xmx2048m –Xms2048m -XX:MaxNewSize=896m-XX:NewSize=896m -XX:SurvivorRatio=6

调整GC

GC是由Java虚拟机提供的,是从符合垃圾回收的对象中回收堆空间的机制。

一个对象符合GC条件是说,它不是来自引用可到达的任何活动线程或任何静态引用。换句话说,如果它的所有引用都为null,一个对象就符合了垃圾收集的条件。

选择正确的垃圾收集器的算法是一个关键的(但经常被忽视的)因素,有下列几种可用的垃圾收集器:

•    Serial collector(-XX:+UseSerialGC):它使用单个线程并通过停止其他JVM的线程来执行,此收集器适合于较小的应用程序,不建议使用在企业应用程序中。
•     Parallel collector (-XX:+UseParallelGC):它并行执行新生代GC, J2SE 5.0中也可以并行执行年老代GC(-XX:+ UseParallelOldGC)。此收集器适合于多处理器机器和需要高吞吐量的应用程序,同时建议用在产生更多Java堆碎片及在不同的时间中分配大尺寸对象的应用程序中。
•     Concurrent collector (-XX:+UseConcMarkSweepGC):它使用一个单个的GC线程,和应用程序并行地执行大部分工作,它适合于具有快速处理器的机器和严格的服务水平协议的应用程序。它可能是最好的选择,也适用于使用大集合较长存活时间的HttpSessions的应用程序。

新的 G1收集器

在Java 7中主要增强的是新的G1(“垃圾至上”)低延迟的垃圾收集器,以计划取代在Hotspot JVM中的CMS,它是一种服务器式收集器,针对多处理器机器并使用大量的内存。

G1的收集是从早期的在新生代和年老代之间具有物理隔离的收集器分离而来的。G1收集器,即使它有代之分,但是两个代之间没有物理隔离。 这个收集器将整个空间划分为多个区间,允许一组区间被收集,而不是在空间中分割成绝对的新生代和年老代。

G1收集器的关键特性是:

1.   G1采用当今硬件广泛使用的并行( parallelism),G1的主要优点是设计了这样一种方式,使用所有可用的CPU和利用所有CPU的处理能力,以此提高性能并加快垃圾收集速度。
2.    另外一个特性是它区别对待年轻的对象(新建)和年老对象(存活了一段时间),这在提升垃圾收集方面起了关键作用。G1主要关注在年轻的对象,他们通过变成年老对象可回收空间。
3.    堆压缩( Heap compaction)是为了消除碎片问题。从本质上说,是因为G1在处理时进行压缩,它会从堆中的一个区间拷贝对象到另一个区间。由于压缩,它不会遇到CMS的碎片问题。这样可能总能是从连续的可用空间中进行分配,使G1随着时间的推移具有一致的停顿时间。

相较于CMS,G1的收集器也更容易使用,因为它开关的数量较少,因此调整虚拟机更简单。G1已经在JDK 7中提供,现在人们可以尝试使用它。要使用G1,需要传递这两个开关参数:
-XX:+UnlockExperimentalVMOptions -XX:+UseG1GC

使用大内存页

在JVM的调优的另一个领域,应用程序通过使用大内存页可产生可观的提升。大内存页是现代CPU的一个特性,允许需要大内存的应用程序,以2-4 MB的块分配内存,而不是标准的4KB。从Java SE5.0开始,有一个跨平台的标志,需要大内存页设置:-XX:+ UseLargePages(在默认情况下Solaris是打开状态,Windows和Linux是关闭状态)。

大页面支持的目标是优化处理器转换后备缓冲器(TLB)。一个转换后备缓冲器是一个页面转换缓存,保存了最近使用的虚拟到物理地址的转换。TLB是一种稀缺的系统资源,TLB丢失可能导致昂贵的处理器必须再从分层页表中读出,这可能需要访问多个存储器。通过使用较大的页面尺寸,单个TLB项可以代表大内存范围,这对TLB造成较小的压力,存储器敏感型应用程序可以具有更好的性能。

在64位的JVM中可以设置大内存页。(红帽企业版Linux的32位操作系统上可以分配大页面,但启动JVM时你会得到一个非法参数的错误)。Sun JVM和OpenJDK一样,使用大页面需要在命令行上传递以下选项: -XX:+UseLargePages

应用程序资源池
应用服务器池提供了几个好处,比如:

•    改善性能: 您可以重新分配资源敏感型的对象,如数据库连接,而不是每一次使用都创建和销毁它们。
•    加强安全: 通过授予有限的资源数量,可以防止服务器资源被应用程序过度使用从而可能导致服务的中断。

JBoss应用服务器AS7使用多个资源池来管理不同种类的服务。应用服务器附带的所有资源池的默认配置可能仅仅适用于简单应用程序,如果是关键型的应用程序,你需要恰当地配置资源池。

我们将在下个章节特别对在性能调优方面发挥重要作用的下列池进行讨论:
•    数据库连接池
•    无状态EJB使用的EJB池及 MDB
•     Web服务器线程池

在撰写本文时,应用程序服务器并没有展示出这里我们提到的所有子系统(subsystem)的性能指标。尽管如此,通过管理界面来监控应用程序服务器池仍然是最佳方法,你仍然可以使用其他一些工具或以最小的示例应用程序来观察应用服务器池。这就是我们将在下节要讲的内容。(一旦性能调整指标可用,我们将及时更新这篇文章!)

作者:wilbertzhou 发表于2014-5-1 14:54:44 原文链接
阅读:105 评论:0 查看评论

迭代式开发技术

$
0
0

    迭代是一开发种技术,用来把系统功能传递到一系列的增量的完整版本,每个版本一个特定固定的时间段被开发,该时间段称之为迭代。

每个迭代的经历过程:


 整个迭代过程:

图中颜色代表每次开发每项活动所占的比重不同


迭代式开发的优点:

1、降低风险

2、得到早期用户反馈

3、持续测试和集成

4、适应变更


开发特征:

1、在进行大规模的投资前,就解决了关键的风险问题

2、使的早期用户反馈在初始迭代中就能出现

3、连续进行测试和集成。

4、各个目标里程碑提供了短期的焦点。

5、对过程的测量是通过实现的评定来进行的

6、可以对局部的实现进行部署。


迭代与传统瀑布式相比:

    传统的瀑布式开发,也就是从需求到设计,从设计到编码,从编码到测试,从测试到提交大概这样的流程,要求每一个开发阶段都要做到最好。特别是前期阶段,设计的越完美,提交后的成本损失就越少。我现在从事的外包项目就是这样的流程。

     迭代式开发则是有很多个很多个瀑布式开发的过程组成,其成果是一个可执行产品的一个版本,是最总系统系统产品的一个子集。通过多次迭代连续增加和精化系统,在每个迭代过程中逐步增加信息,进行细化。每次迭代多选择目前对风险影响最大的使用实例进行,以分解和降低风险。



 

 

作者:u010924878 发表于2014-5-3 20:15:22 原文链接
阅读:121 评论:0 查看评论

人脸识别算法终于超过了人类本身

$
0
0

计算机科学家已经开发出一种新的人脸识别算法,在识别人脸的能力上比人类本身更加强大。

我们每个人都有过认不出某个自己曾经认识的人的经历,在不同的姿势、光照和表情下,这其实是一件比较困难的事情。计算机识别系统同样存在这些问题。事实上,尽管全世界的计算机科学家努力了这么多年,还是没有任何一种计算机识别系统在识别人脸方面能够像人类一样强大。

但这并非是说人脸识别系统不够准确。恰恰相反,最好的人脸识别系统在理想情况下比人类识别的表现要好的多。但是一旦环境情况变糟,系统的表现就差强人意了。而计算机科学家们当然是非常想要开发出一种算法,在各种情况下都能够表现优异。

现在,中国香港大学的汤晓鸥教授和他的学生路超超(对不起,译者没有找到这名学生的名字,只能音译了)宣布他们攻克了这个难题。他们开发了一种叫“高斯”的人脸识别算法首次超过了人类自身。

新的识别系统对于各种平台都能够提供人类级别的识别能力,从手机到电脑游戏中的人脸识别,从安全系统到密码控制等等。

任何一个人脸自动识别程序,首先要考虑的就是去构建一个合适的数据集来测试算法。那需要一个非常大范围的,各种各样的,带着各种复杂动作、光线和表情的,不同脸的图像,各种人种、年龄和性别都要考虑在内。然后还要考察服装、发型以及化妆等其他因素的影响。

比较幸运的是,已经有这么一个拥有各种不同人脸的标准数据库——Labelled Faces。它拥有超过13,000张不同人脸的图片,它们是从网络上收集的6000个不同的公众人物。更重要的是,每个人都拥有不止一张人脸图片。

当然也存在其他的人脸数据库,但是Labelled faces目前是计算机科学家们所公认的最具参考价值的测试数据集。

面部识别的任务是去比较两张不同的图片,然后判断他们是否是同一个人。(你可以试试看,能否看出这里展示的每对图片是否是同一个人。)

人类在这个数据库上的表现可以达到97.53%的准确度。但是没有任何一个计算机算法能够达到这个成绩。

直到这个新算法的出现。新的算法依照5点图片特征,把每张脸图规格化成一个150*120的像素图,这些特征分别是:两只眼睛、鼻子和嘴角的位置。

然后,算法把每张图片划分成重叠的25*25像素的区域,并用一个数学向量来描述每一个区域的基本特征。做完了这些,就可以比较两张图片的相似度了。

但是首先需要知道的是到底要比较什么。这个时候就需要用到训练数据集了。一般的方法是使用一个独立的数据集来训练算法,然后用同一个数据集中的图片来测试算法。

但是当算法面对训练集中完全不同的两张图片的时候,经常都会识别失败。“当图片的分布发生改变的时候,这种训练方法就一点都不好了。”超超和晓鸥说到。

相反,他们用四个拥有不同图片的,完全不同的数据集来测试“高斯”算法。举个例子,其中一个数据集是著名的Multi-PIE数据库,它包含了337个不同的物体,从15种不同的角度,在19种不同的光照情况下,分别拍摄4组图片。另一个数据库叫做Life Photes包含400个不同的人物,每个人物拥有10张图片。

用这些数据库训练了算法后,他们最终让新算法在Labelled Faces数据库上进行测试。目标是去识别出所有匹配和不匹配的图片对。

请记住人类在这个数据库上的表现是97.53%的精确度。“我们的“高斯”算法能够达到98.52%的精确度,这也是识别算法第一次击败人类。”超超和晓鸥说到。

这是一个令人印象深刻的结果,因为数据中的照片包含各种各样不同的情况。

超超和晓鸥指出,仍然有很多挑战在等着他们。现实情况中,人们可以利用各种附加的线索来识别,比如脖子和肩膀的位置。“超过人类的表现也许只是一个象征性的成就罢了”他们说。

另一个问题是花费在训练新算法上的时间,还有算法需要的内存大小以及识别两幅图所需要的时间。这可以用并行计算和特制处理器等技术来加快算法的运行时间。

总之,精确的人脸自动识别算法已经到来了,而且鉴于现在的事实,这只会更快。

人脸识别算法终于超过了人类本身,首发于 博客 - 伯乐在线

使用Spring MVC统一异常处理实战

$
0
0
源:http://cgs1999.iteye.com/blog/1547197
评:
1 描述
在J2EE项目的开发中,不管是对底层的数据库操作过程,还是业务层的处理过程,还是控制层的处理过程,都不可避免会遇到各种可预知的、不可预知的异常需要处理。每个过程都单独处理异常,系统的代码耦合度高,工作量大且不好统一,维护的工作量也很大。
那么,能不能将所有类型的异常处理从各处理过程解耦出来,这样既保证了相关处理过程的功能较单一,也实现了异常信息的统一处理和维护?答案是肯定的。下面将介绍使用Spring MVC统一处理异常的解决和实现过程。

2 分析
Spring MVC处理异常有3种方式:
(1)使用Spring MVC提供的简单异常处理器SimpleMappingExceptionResolver;
(2)实现Spring的异常处理接口HandlerExceptionResolver 自定义自己的异常处理器;
(3)使用@ExceptionHandler注解实现异常处理;

3 实战
3.1 引言
为了验证Spring MVC的3种异常处理方式的实际效果,我们需要开发一个测试项目,从Dao层、Service层、Controller层分别抛出不同的异常,然后分别集成3种方式进行异常处理,从而比较3种方式的优缺点。

3.2 实战项目
3.2.1 项目结构


3.2.2 Dao层代码
Java代码  收藏代码

    @Repository("testDao") 
    public class TestDao { 
        public void exception(Integer id) throws Exception { 
            switch(id) { 
            case 1: 
                throw new BusinessException("12", "dao12"); 
            case 2: 
                throw new BusinessException("22", "dao22"); 
            case 3: 
                throw new BusinessException("32", "dao32"); 
            case 4: 
                throw new BusinessException("42", "dao42"); 
            case 5: 
                throw new BusinessException("52", "dao52"); 
            default: 
                throw new ParameterException("Dao Parameter Error"); 
            } 
        } 
    } 


3.2.3 Service层代码
Java代码  收藏代码

    public interface TestService { 
        public void exception(Integer id) throws Exception; 
         
        public void dao(Integer id) throws Exception; 
    } 
     
    @Service("testService") 
    public class TestServiceImpl implements TestService { 
        @Resource 
        private TestDao testDao; 
         
        public void exception(Integer id) throws Exception { 
            switch(id) { 
            case 1: 
                throw new BusinessException("11", "service11"); 
            case 2: 
                throw new BusinessException("21", "service21"); 
            case 3: 
                throw new BusinessException("31", "service31"); 
            case 4: 
                throw new BusinessException("41", "service41"); 
            case 5: 
                throw new BusinessException("51", "service51"); 
            default: 
                throw new ParameterException("Service Parameter Error"); 
            } 
        } 
     
        @Override 
        public void dao(Integer id) throws Exception { 
            testDao.exception(id); 
        } 
    } 


3.2.4 Controller层代码
Java代码  收藏代码

    @Controller 
    public class TestController { 
        @Resource 
        private TestService testService; 
         
        @RequestMapping(value = "/controller.do", method = RequestMethod.GET) 
        public void controller(HttpServletResponse response, Integer id) throws Exception { 
            switch(id) { 
            case 1: 
                throw new BusinessException("10", "controller10"); 
            case 2: 
                throw new BusinessException("20", "controller20"); 
            case 3: 
                throw new BusinessException("30", "controller30"); 
            case 4: 
                throw new BusinessException("40", "controller40"); 
            case 5: 
                throw new BusinessException("50", "controller50"); 
            default: 
                throw new ParameterException("Controller Parameter Error"); 
            } 
        } 
         
        @RequestMapping(value = "/service.do", method = RequestMethod.GET) 
        public void service(HttpServletResponse response, Integer id) throws Exception { 
            testService.exception(id); 
        } 
         
        @RequestMapping(value = "/dao.do", method = RequestMethod.GET) 
        public void dao(HttpServletResponse response, Integer id) throws Exception { 
            testService.dao(id); 
        } 
    } 


3.2.5 JSP页面代码
Java代码  收藏代码

    <%@ page contentType="text/html; charset=UTF-8"%> 
    <html> 
    <head> 
    <title>Maven Demo</title> 
    </head> 
    <body> 
    <h1>所有的演示例子</h1> 
    <h3> Dao正常错误</h3> 
    <h3> Dao参数错误</h3> 
    <h3> Dao未知错误</h3> 
     
     
    <h3> Service正常错误</h3> 
    <h3> Service参数错误</h3> 
    <h3> Service未知错误</h3> 
     
     
    <h3> Controller正常错误</h3> 
    <h3> Controller参数错误</h3> 
    <h3> Controller未知错误</h3> 
     
     
    <h3> 404错误</h3> 
    </body> 
    </html> 


3.3 集成异常处理
3.3.1 使用SimpleMappingExceptionResolver实现异常处理
1、在Spring的配置文件applicationContext.xml中增加以下内容:
Xml代码  收藏代码

    <bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver"> 
        <!-- 定义默认的异常处理页面,当该异常类型的注册时使用 --> 
        <property name="defaultErrorView" value="error"></property> 
        <!-- 定义异常处理页面用来获取异常信息的变量名,默认名为exception --> 
        <property name="exceptionAttribute" value="ex"></property> 
        <!-- 定义需要特殊处理的异常,用类名或完全路径名作为key,异常也页名作为值 --> 
        <property name="exceptionMappings"> 
            <props> 
                <prop key="cn.basttg.core.exception.BusinessException">error-business</prop> 
                <prop key="cn.basttg.core.exception.ParameterException">error-parameter</prop> 
     
                <!-- 这里还可以继续扩展对不同异常类型的处理 --> 
            </props> 
        </property> 
    </bean> 


2、启动测试项目,经验证,Dao层、Service层、Controller层抛出的异常(业务异常BusinessException、参数异常ParameterException和其它的异常Exception)都能准确显示定义的异常处理页面,达到了统一异常处理的目标。

3、从上面的集成过程可知,使用SimpleMappingExceptionResolver进行异常处理,具有集成简单、有良好的扩展性、对已有代码没有入侵性等优点,但该方法仅能获取到异常信息,若在出现异常时,对需要获取除异常以外的数据的情况不适用。

3.3.2 实现HandlerExceptionResolver 接口自定义异常处理器
1、增加HandlerExceptionResolver 接口的实现类MyExceptionHandler,代码如下:
Java代码  收藏代码

    public class MyExceptionHandler implements HandlerExceptionResolver { 
     
        public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, 
                Exception ex) { 
            Map<String, Object> model = new HashMap<String, Object>(); 
            model.put("ex", ex); 
             
            // 根据不同错误转向不同页面 
            if(ex instanceof BusinessException) { 
                return new ModelAndView("error-business", model); 
            }else if(ex instanceof ParameterException) { 
                return new ModelAndView("error-parameter", model); 
            } else { 
                return new ModelAndView("error", model); 
            } 
        } 
    } 


2、在Spring的配置文件applicationContext.xml中增加以下内容:
Xml代码  收藏代码

    <bean id="exceptionHandler" class="cn.basttg.core.exception.MyExceptionHandler"/> 


3、启动测试项目,经验证,Dao层、Service层、Controller层抛出的异常(业务异常BusinessException、参数异常ParameterException和其它的异常Exception)都能准确显示定义的异常处理页面,达到了统一异常处理的目标。

4、从上面的集成过程可知,使用实现HandlerExceptionResolver接口的异常处理器进行异常处理,具有集成简单、有良好的扩展性、对已有代码没有入侵性等优点,同时,在异常处理时能获取导致出现异常的对象,有利于提供更详细的异常处理信息。

3.3.3 使用@ExceptionHandler注解实现异常处理
1、增加BaseController类,并在类中使用@ExceptionHandler注解声明异常处理,代码如下:
Java代码  收藏代码

    public class BaseController { 
        /** 基于@ExceptionHandler异常处理 */ 
        @ExceptionHandler 
        public String exp(HttpServletRequest request, Exception ex) { 
             
            request.setAttribute("ex", ex); 
             
            // 根据不同错误转向不同页面 
            if(ex instanceof BusinessException) { 
                return "error-business"; 
            }else if(ex instanceof ParameterException) { 
                return "error-parameter"; 
            } else { 
                return "error"; 
            } 
        } 
    } 


2、修改代码,使所有需要异常处理的Controller都继承该类,如下所示,修改后的TestController类继承于BaseController:
Java代码  收藏代码

    public class TestController extends BaseController 


3、启动测试项目,经验证,Dao层、Service层、Controller层抛出的异常(业务异常BusinessException、参数异常ParameterException和其它的异常Exception)都能准确显示定义的异常处理页面,达到了统一异常处理的目标。

4、从上面的集成过程可知,使用@ExceptionHandler注解实现异常处理,具有集成简单、有扩展性好(只需要将要异常处理的Controller类继承于BaseController即可)、不需要附加Spring配置等优点,但该方法对已有代码存在入侵性(需要修改已有代码,使相关类继承于BaseController),在异常处理时不能获取除异常以外的数据。

3.4 未捕获异常的处理
对于Unchecked Exception而言,由于代码不强制捕获,往往被忽略,如果运行期产生了Unchecked Exception,而代码中又没有进行相应的捕获和处理,则我们可能不得不面对尴尬的404、500……等服务器内部错误提示页面。
我们需要一个全面而有效的异常处理机制。目前大多数服务器也都支持在Web.xml中通过<error-page>(Websphere/Weblogic)或者<error-code>(Tomcat)节点配置特定异常情况的显示页面。修改web.xml文件,增加以下内容:
Xml代码  收藏代码

    <!-- 出错页面定义 --> 
    <error-page> 
        <exception-type>java.lang.Throwable</exception-type> 
        <location>/500.jsp</location> 
    </error-page> 
    <error-page> 
        <error-code>500</error-code> 
        <location>/500.jsp</location> 
    </error-page> 
    <error-page> 
        <error-code>404</error-code> 
        <location>/404.jsp</location> 
    </error-page> 
     
    <!-- 这里可继续增加服务器错误号的处理及对应显示的页面 --> 


4 解决结果
1、运行测试项目显示的首页,如下图所示:
[点击查看原始大小图片]

2、业务错误显示的页面,如下图所示:
[点击查看原始大小图片]

3、参数错误显示的页面,如下图所示:
[点击查看原始大小图片]

4、未知错误显示的页面,如下图所示:
[点击查看原始大小图片]

5、服务器内部错误页面,如下图所示:
[点击查看原始大小图片]

5 总结
综合上述可知,Spring MVC集成异常处理3种方式都可以达到统一异常处理的目标。从3种方式的优缺点比较,若只需要简单的集成异常处理,推荐使用SimpleMappingExceptionResolver即可;若需要集成的异常处理能够更具个性化,提供给用户更详细的异常信息,推荐自定义实现HandlerExceptionResolver接口的方式;若不喜欢Spring配置文件或要实现“零配置”,且能接受对原有代码的适当入侵,则建议使用@ExceptionHandler注解方式。

6 源代码
源代码项目如下所示,为Maven项目,若需运行,请自行获取相关的依赖包。
点击这里获取源代码

7 参考资料
[1] Spring MVC统一处理异常的方法
http://hi.baidu.com/99999999hao/blog/item/25da70174bfbf642f919b8c3.html
[2] SpringMVC 异常处理初探
http://exceptioneye.iteye.com/blog/1306150
[3] Spring3 MVC 深入研究
http://elf8848.iteye.com/blog/875830
[4] Spring MVC异常处理
http://blog.csdn.net/rj042/article/details/7380442

    demo-exception.rar (12 KB)
    下载次数: 798


已有 0人发表留言,猛击->> 这里<<-参与讨论


ITeye推荐




简单实用的电商数据分析方法论

$
0
0

  导读:说到数据分析,大家可能就会想到回归,聚类什么的,不过对于电商的小伙伴来说,这些都太复杂了。而实际分析的时候,其实并不需要这么复杂的算法,大家需要的只是:

  对比

  细分

  转化

  分类

  只要掌握了这四种思想,基本上已经可以应付日常的分析工作了。

  对比思想

  数据对比主要是横向和纵向两个角度,指标间的横向对比帮助我们认识预期值的合理性,而指标自身在时间维度上的对比,即我们通常说的趋势分析。

  以店铺的成交额分析为例:

  纵向对比

  我们可以把最近30天的成交额显示在坐标轴上,这样就可以很明显的看到最近的成交额是否达到了预期,当然我们也可以以周或者月(或者季度,年等等)为单位。

  所有的分析其实都必须要考虑实际的场景,我们看到今天的成交额比昨天大也许说明的问题还是很有限,因为今天和昨天的性质可能未必一样,例如今天可能是周六,或者恰好是节假日等等。所以我们在做纵向对比的时候,例如要判断今天(假设是周六)的成交额是否合理,除了看最近30天的趋势数据,我们还可以考虑:

  最近10周的周六成交额趋势

  如果今天恰好是一个节日,例如双十一,那么可以考虑和上一年的双十一做一个对比。(说明:因为间隔时间比较长,数据反映出来的意义可能比较有限)

  横向对比

  例如我们说,店铺这周的成交额上涨了10%,那我们是不是应该高兴呢?

  当然应该高兴,不过这个上涨的背后是否隐含着什么危机呢?当然是有的,例如你的竞争对手们这周的成交额都上涨了20%!当你洋洋得意的时候,可能已经被竞争对手拉开距离了。

  也就是说,我们对一个现象判断好不好,这是需要一个参照系的。在现在的电商时代,你完全有可能知道竞争对手的成交额上涨了多少的。

  再举一个更常见的例子:

  假如我在不同的地方(或者平台)开了很多家店铺,某商品的成交额在A店铺上涨了10%,那这个是否值得高兴?

  这个显然未必,我们还要对比商品A在各家店铺的上涨情况,例如可以对比平均曲线。

  细分思想

  使用转化的思想,我们已经基本可以判断一个指标(例如成交额)是否合理了。不过还仅仅知道是否合理是不够的,我们还需要知道问题所在,这时可以用上细分的思想了。通过细分的思想,我们可以对分析对象剥丝抽茧,逐步定位到问题点。细分的角度可以有很多,越细分越能准确描述问题。

  例如,我们通过查看趋势,知道了这个月成交额下降了这个问题后,现在我们用细分的思想来找出问题的所在:

  成交额细分

  成交额 = 客单价 X 客户数

  对比客单价和客户数的趋势,就可以判断出影响成交额变化的主要因素是什么,如果是客户数问题,我们则对客户数进行细分,如果是客单价问题,则对客单价进行细分。

  客户数细分

  客户数 = 新客户 + 老客户老客户 = 二次成交客户 + 多次成交客户

  一段时间内的新客户反映的是店铺的引流效果,而老客户反馈的是店铺的产品质量,服务质量和客户维护营销等。

  对于店铺来说,促成二次成交都是非常重要的,特别是对于电商客户,因为对于电商,客户转移的成本比线下低很多。

  客单价细分

  客单价 = 成交价 X 人均成交数

  人均成交数这是店铺一个非常值得关注的指标,它能最直接地反馈出店铺在服务质量和客户维护营销等方面的效果,如果该值过小表明店铺的客户流失率很大,应该重点关注。

  成交价反馈的通常是导购的能力,促销活动的效果等,具体还可以对这个指标进行分解。

  成交价细分

  成交价 = 件单价 X 连带率

  成交价的上升或者下跌,反映的问题可能很多,对其进行分解后就很明确了。

  件单价的变化通常是有促销的力度,商品结构和消费结构(例如季节因素等)变化引起的。

  连带率这个反馈的是店铺内导购的能力,或者促销手段(例如买一送一等)的效果,也是店铺管理人员重点要关注的指标。

  细分思想其实就是不断用更小的量化指标去细分一个大的指标,从而达到定位问题的目的。

  转化思想

  细分的思想可以从纵向定位问题,但是单单细分是不够的。这些指标是从哪里来的,每一个步骤的转化率怎么样,哪一个步骤的转化不好,可以改善?这些通过转化率都可以分析出来。

  例如我们要分析本周的活跃客户数(有成交的客户数),那么我们就要分析这些活跃的客户数是从哪里来的,梳理一下可以简单分为以下4个步骤:

  进入店铺的客户数 ==》浏览过商品的客户数 ==》下单的客户数 ==》交易成功的客户数

  这里4个步骤就会有3个转化率,哪些步骤转化率比较高,哪些步骤转化率比较低,历史趋势怎么样,是否合理,是否有改进的空间等等。通过应用转化的思想,能够有效的指导和优化实际的运营工作。

  分类思想

  上面我们已经介绍了对比,细分和转化三种实用的数据分析思想,现在我们还有再介绍一种非常实用的思想,那就是分类思想。

  分类思想简单的说,就是把一些对象,按照某种规则,划分为若干个类别,然后分析各个类别的特征,并以此来指导我们的行动。

  严格说,分类其实也是细分的一种,不过因为它比较重要,所以独自开来。

  分类思想的应用很多,例如对客户的分类,我们可以用RFM分析模型,也可以用简单的利用某个指标的值(例如渠道标识,这样我们就可以分析到各个渠道客户的质量等)。基于这些客户的分类,我们就可以进行精准的客户营销了。

  在电商或者零售业上,我们经常做的分类还有商品分类,经典的有按照品类分类,或者ABC分类,这些对于我们做商品运营都是非常重要的。

  当然还有非常复杂的分类方法,例如聚类算法,不过这些不在我们的讨论范围内。

  小结

  对比,细分,转化和分类,其实都是很简单的数据分析思想,不过如果你掌握了,并且培养这样的意识,那一定会受益终身。

  最后:

  所有数据分析方法思想都只是术,真正的道是你对数据使用场景的深刻理解。离开了使用场景,数据就毫无价值。

  来源:网络转载

 

Java 性能调优指南 – 高性能Java

$
0
0

本文主要基于 Java performance tuning tips or everything you want to know about Java performance in 15 minutes的翻译。

这篇指南主要调优 java 代码而不是 JVM 设置。

一、JDK 类

Java 1.7.0_06 String 内部表示的改变

  • 从 Java 1.7.0_06 开始, String.substring总是为它创建的新字符串创建一个新的底层 char[]值。这意味着这个方法现在有线性的复杂度,之前是常量的复杂度。这个改变的好处是字符串需要更少的内存 footprint(比以前少8字节),也是避免 String.substring导致的内存泄露的一个保证。

Java 里二进制序列化的不同方法

  • 写单个字节到直接字节缓存是非常慢的。对写记录 — 大多数情况是单个字节的字段,你应该避免使用直接字节缓存。
  • 如果你有原始数组字段,总是使用bulk方法来处理它们(一次处理一批的)。ByteBuffer 的bulk 方法的性能接近于Unsafe的这些方法。如果你需要存储/加载任何其它原始数组 – 除字节型的,用 ByteBuffer.to[YouType]Buffer.put(array)方法调用,字节缓存的位置会自动更新。不要在循环里调用 ByteBuffer.put[YouType]方法。
  • 总是尝试用带本地字节顺序的直接缓存序列化原始数组。直接字节缓存的性能接近于Unsafe且是可移植的。

Java 集合概览

单线程并发
ListsArrayList : 一般基于数组
LinkedList :不要使用
Vector:废弃
CopyOnWriteArrayList:很少更新,经常遍历。
Queues/dequesArrayDeque:一般基于数组。
Stack:废弃。
PriorityQueue:有序的检索操作。
ArrayBlockQueue:有界阻塞queue。
ConcurrentLinkedDeque/ConcurrentLinkedQueue:无界链接queue(CAS)。
DelayQueue:在每个元素上带延迟的queue。
LinkedBlockingDeque/LinkedBlockingQueue:可选的有界链接queue(锁)。
LinkedTransferQueue:may transfer elements w/o storing。
PriorityBlockingQueue:并发的PriorityQueue。
SynchronousQueue:实现了Queue接口的Exchanger。
MapsHashMap:一般的map。
EnumMap:enum作为key的。Hashtable:废弃。
IdentityHashMap:用 == 比较键。
LinkedHashMap:保留了插入顺序。
TreeMap:有序的键。
WeakHashMap:可用于缓存。
ConcurrentHashMap:一般的并发map。
ConcurrentSkipListMap:有序的并发map。
SetsHashSet:一般的set。
EnumSet:enum的集合。
BitSet:比特位或稀疏整数的集合。
LinkedHashSet:保留了插入顺序。
TreeSet:有序集合。
ConcurrentSkipListSet:有序并发集合。
CopyOnWriteArraySet:很少更新,经常遍历。

ArrayList 性能指南

使用ArrayList时遵循下面的规则:

  • 添加元素到列表的末尾。
  • 从末尾删除元素。
  • 避免 contains, indexOf, remove(object)方法。
  • 甚至避免 removeAll, retainAll方法。
  • 使用 subList(int, int).clear()来快速清除列表的部分。

LinkedList 性能

如果你需要写一个快速的 LinkedList 代码,尝试坚持这些规则:
* 考虑用 ArrayDeque 用于基于 queue的算法。
* 与 LinkedList 一起使用 ListIterator。
* 避免任何接受或返回元素在列表里的下标的 LinkedList 方法。(基于链表的列表不适合随机检索)
* 如果你有理由使用 LinkedList.remove/removeFirst/removeLast则检查,用 pollFirst/pollLast替代。
* 尝试批处理 LinkedList。

Bit sets

  • 当你需要映射大量整数到boolean标记时不要忘记 bit sets。
  • 整数值的集合应该用 bit sets 替代,在大多数情况下可以节省很多内存。

IdentityHashMap

  • IdentityHashMap使用 System.identityHashCode方法来获取对象的标识哈希码。如果你的对象有主键字段则避免使用 IdentityHashMap
  • 不要尝试去迭代 IdentityHashMap里的内容,因为迭代顺序在你的程序的每次运行都是不同的,因而导致你的程序结果不一致。
  • 访问对象的标识哈希码是非常便宜的 Java 内部操作。
  • 注意,已计算了标识哈希码的对象不能用于 偏向锁

String 里正则有关的方法

  • 总是使用 MatcherPattern替换 String.matches, split, replaceAll, replaceFirst方法。
  • 在Java 7里,按单个非正则特殊字符的字符串分割字符串已在 String.split方法里优化了。
  • 在所有其他简单情况下,在时间关键的代码里对简单的情况考虑亲手写解析方法。通过手工制作的方法来替换 Pattern 方法,你可以很容易获得10倍提速。

java.util.Date, java.util.Calendar and java.text.SimpleDateFormat 性能

  • 不要使用 java.util.Date,除非必须。用一个普通的 long 来替代。
  • java.util.Calendar对于日期计算和国际化很有用,但避免大量存储这些对象或广泛地创建它们,它们消耗大量内存并且创建的开销很大。
  • java.text.SimpleDateFormat对于一般情况下的日期时间解析很有用,但最好避免用它,如果你需要以同样的格式解析大量的date。手工实现一个解析器更好。

Joda 时间库的性能

Java 8 里的日期时间库就是基于 Joda 时间库的。

  • 所有 Joda 时间 date/time对象都是构建在 long 时间戳之上,所以从 long 构建这些对象很便宜。
  • Joda 不在内部保存人类时间 – year/month/day/hour/min/second。这意味着在Joda对象上获取人类时间更昂贵,如果你需要获取多个字段。
  • Joda里 date/time的解析比 JDK SimpleDateFormat略微快。Joda 解析的优势是构建一个解析器 — DateTimeFormatter — 非常便宜,不像昂贵的 SimpleDateFormat,所以你不需要缓存解析器。

JSR 310 – Java 8 Date/Time library performance (as well as Joda Time 2.3 and j.u.Calendar)

  • Java 8 里的 date/time是构建在人类时间之上的 — year/month/day/hour/minute/second/nanos

java.io.ByteArrayOutputStream

不要在性能关键代码里使用 ByteArrayOutputStream 。

ByteArrayOutputStream 主要用于写未知长度的消息到输出流的情况。它是基于字节数组实现的,当底层数组不够时就新建一个更大的,把原有数据拷贝到新数组,再用新数组替代原有数组;所有方法都是同步的。

  • 在性能关键代码,尝试用 ByteBuffer 替代 ByteArrayOutputStream,如果一定要用,去除它的同步。
  • 如果你的方法写一些消息到未知的 OutputStream,总是先写消息到 ByteArrayOutputStream ,然后使用它的 writeTo(OutputStream)方法。在一些稀有的情况下从字节表示里构建字符串,可以使用 ByteArrayOutputStream.toString方法。
  • 通常情况下避免 ByteArrayOutputStream.toByteArray方法,它创建一个内部字节数组的拷贝。如果你的应用程序使用了GB内存,GC这些拷贝的时间是显著的。

  • 如果你预先知道你的消息大小(或至少知道它的上限),用 ByteBuffer 替代(或重用之前分配的),再写消息到它。

  • 如果是出于缓存的考虑,可以使用 BufferedOutputStream来替代。
  • ByteArrayOutputStream.writeTo(OutputStream)方法使用内部数组而不是创建拷贝。
  • ByteArrayOutputStream.toString(String charsetName)ByteArrayOutputStream.toString(String charsetName)允许直接从内部数组创建一个字符串。

BufferedInputStream 和 GZIPInputStream

  • BufferedInputStream 和 GZIPInputStream 都有内部的缓存。默认大小:前者是 8192 字节,后者是 512 字节。通常情况下它们可以增长到最多 65536。
  • 不要用 BufferedInputStream 作为 GZIPInputStream 的输入,而是在构造器里显式设置 GZIPInputStream 的缓存大小。虽然,保留一个 BufferedInputStream 仍然是安全的。
  • 如果你有一个新的 BufferedInputStream( new FileInputStream( file ) )对象,你可以更频繁地调用它的可用方法,考虑重写 BufferedInputStream 的可访问方法,将极大提升文件读取速度。

Byte、Short、Integer、Long、Character(装箱和拆箱)

  • 绝不使用 Number子类的 valueOf(String)方法。如果需要一个原始类型的值,用 parse[Type]。如果需要一个包装类的实例,仍然调用 parse[Type]并依赖于 JVM 实现的装箱。它将支持缓存最常使用的值。绝不要使用包装类的构造器,它们总是返回一个新对象,因为绕过了缓存。下面是支持缓存的不同类型的范围:
Byte, Short, LongCharacterIntegerFloat, Double
-128 至 1270 至 127-128 至 Integer.IntegerCache.high 或 127不缓存

Map.containsKey/Set.contains

  • 对于 sets, contains + add/remove调用对应该用单个 add/remove调用替代,即使有额外逻辑用于保证 contains调用。
  • 对于 maps, contains + get调用组合应该总是用 get后接对 get结果的非空检测替代。(这两个技巧都是减少了一次查找)
  • 对于 maps 键值对的遍历,应该通过 Map.entrySet方法的结果进行遍历,而不是通过 Map.keySetMap.get(key)的组合进行。

java.util.zip.CRC32 and java.util.zip.Adler32 performance

  • 如果你选择使用哪个checksum,首先尝试 Adler32。如果它的质量对你足够,用它而不是 CRC32。任何情况下,用 Checksum接口来获取 Adler32/CRC32的逻辑。
  • 尝试更新 checksum 至少要有 500 字节块。更短的块将要求显著的时间开销用于 JNI 调用。

hashCode 方法性能调优

  • 尝试提高 hashCode 方法的结果的分布性。这远比提高此方法的速度更重要。绝不要写一个返回常量的 hashCode 方法。
  • String.hashCode结果的分布性几乎完美,所以有时你可以用String的哈希码来替代它们。如果你与字符串集合一起工作,尝试用 BitSet来结束。(意思是 BitSet+哈希码可以完美替代字符串集合,并提升性能。)

在Java里抛出异常是很慢的

  • 绝不用异常作为返回码的替代 或 任何可能发生的事件。抛出异常是很昂贵的 — 对于简单的方法你将经历100倍减速。
  • 避免使用任何 Number子类的 parse*/valueOf(这些方法会抛出 NumberFormatException),如果你对你的每个数据片段调用它们并且你预期有很多非数字的数据。为更高性能,手动解析这些值。

Java 日志性能陷阱

如何用最新的性能损失写日志消息:

  • 如果你在为日志消息准备数据时需要做昂贵的计算,要么使用 Logger.isLoggable并在里面做所有的数据准备,要么写一个对象 — 它的 toString完成所有的计算。
  • 绝不调用 Object.toString方法来获取一个日志消息参数 — 仅仅传递原始对象。日志框架会调用你的对象的 toString方法。(注:这是一个延迟计算的问题,日志框架只会在需要的时候调用,也就是在真正需要时才计算;而代码直接调用则会在调用日志框架的方法时完成计算。)
  • 不要混淆日志参数的字符串串联 — 恶意串联的字符串将允许你的应用程序用户突破你的 logging/access 数据,那是假定不用于用户访问的。

Base64 编码和解码性能

  • 如果你在找一个快速可靠的 Base64 编解码器 — 非JDK以外。在Java8里有一个新的编解码器: java.util.Base64,也有一个隐藏的: javax.xml.bind.DatatypeConverter。都是快速、可靠且不会遭受正数溢出。
  • 有些第三方开源的Base64编解码器在能处理的数据的大小上是有限制的。
  • 不要在巨大的字符串上调用 String.getBytes(Charset),如果你的字符集是多字节的 — 你可能得到正数溢出有关的异常。

多线程环境下的 java.util.Random 和 java.util.concurrent.ThreadLocalRandom

  • 在任何情境下都不要在多个线程间使用 java.util.Random ,把它包装在 ThreadLocal 里。
  • 从 Java 7 开始,在所有情境下首选 java.util.concurrent.ThreadLocalRandom 而不是 java.util.Random — 它是向后兼容已有代码的,但内部使用更少的操作。

Java 7/8 里的字符串编码和解码

  • 总是倾向于国家字符集如 windows-1252 或 Shift_JIS 而不是 UTF-8:它们生成更紧凑的二进制表示(作为一个规则)并且它们编码/解码更快。
  • 在Java 7 和 8 里 ISO-8869-1 总是比 US-ASCII工作得更快。
  • 对于 US-ASCII/ISO-8859-1,你可以写一个快速的 String->byte[]转换,但你不能打败 Java 解码器,它们直接访问它们创建的字符串输出。

二、内存优化

Java 里内存节约技术概要

这篇文章将给你java里内存优化的基本建议。大多数Java内存优化技术都是基于这些建议的。

  • 首选基本类型而不是它们的包装对象。
  • 尝试减少你的对象数量。例如,首选基于数组的结构(如 ArrayList/ArrayDeque)而不是基于指针的结构(如 LinkedList)。

流行Java数据类型的内存消耗

EnumSet, BitSet1 bit per value
EnumMap4 bytes (for value, nothing for key)
ArrayList4 bytes (but may be more if ArrayList capacity is seriously more than its size)
LinkedList24 bytes (fixed)
ArrayDeque4 to 8 bytes, 6 bytes on average

Java 里的内存节约技术

这篇文章描述了静态内部类、字符串池、布尔标记集合和JDK里一些特殊类的微小集合的好处。

  • 默认让所有内部类是静态的。只有必须时才移除 static修饰符。
  • 如果你有通常是小集合的集合,尝试用 java.util.Collections.empty*/singleton*方法达到内存高效的存储微小集合。
  • 首选 BitSet而不是 boolean 的数组或列表、或整数类型的稀疏集合:位集合是 内存和CPU友好的。

Java 6,7,8 里的 String.intern – 字符串池

这篇文章描述了 Java 6 里 String.intern()是如何实现的, Java 7 和 8 里做了什么修改。

  • 在Java 6里远离 String.intern()方法,由于用于JVM字符串池存储的固定大小内存区域(PermGen)。
  • Java 7 和 8 在堆内存里实现了字符串池。这意味着在 Java 7 和 8 里对于字符串池你受限于整个应用内存。
  • 在 Java 7 和 8 用 JVM 参数 -XX:StringTableSize来设置字符串吃的map大小。它是固定的,因为它被实现为带链表位桶的 hash map。估算你的应用里不同字符串是数量,设置池的大小等于接近于池大小的 prime number(素数)。这将让 String.intern运行在常量时间,并对每个interned的字符串要求更小的内存开销。(对于同样的任务,显式使用 Java WeakHashMap将多消耗 4-5 倍的内存)
  • -XX:StringTableSize的默认值在 Java 7 里是 1009,Java 8 里是 25-50K

Java 6,7,8 里的 String.intern – 多线程访问

这篇文章描述了多线程调用 String.intern()的性能影响。

  • 随意地使用 String.intern()
  • JVM 字符串池不是线程本地的。

Java 6,7,8 里的 String.intern – 最佳实践

  • 虽然 Java 7+开始 String.intern()实现有了重大优化,它仍然需要显著的时间来运行(对于CPU敏感的应用程序是显著的)。你不应该用 String.intern()作为安全网络,把一切长期存活字符串丢给它。相反,应当只处理有限数量的不重复值。

注:还有一些,但感觉跟Java代码关系小,不翻译了。

三、小技巧

四、使用个例

NIO与传统IO的区别

$
0
0

感性认识:

 

传统的socket IO中,需要为每个连接创建一个线程,当并发的连接数量非常巨大时,线程所占用的栈内存和CPU线程切换的开销将非常巨大。使用NIO,不再需要为每个线程创建单独的线程,可以用一个含有限数量线程的线程池,甚至一个线程来为任意数量的连接服务。由于线程数量小于连接数量,所以每个线程进行IO操作时就不能阻塞,如果阻塞的话,有些连接就得不到处理,NIO提供了这种非阻塞的能力。

 

小量的线程如何同时为大量连接服务呢,答案就是就绪选择。这就好比到餐厅吃饭,每来一桌客人,都有一个服务员专门为你服务,从你到餐厅到结帐走人,这样方式的好处是服务质量好,一对一的服务,VIP啊,可是缺点也很明显,成本高,如果餐厅生意好,同时来100桌客人,就需要100个服务员,那老板发工资的时候得心痛死了,这就是传统的一个连接一个线程的方式。

 

老板是什么人啊,精着呢。这老板就得捉摸怎么能用10个服务员同时为100桌客人服务呢,老板就发现,服务员在为客人服务的过程中并不是一直都忙着,客人点完菜,上完菜,吃着的这段时间,服务员就闲下来了,可是这个服务员还是被这桌客人占用着,不能为别的客人服务,用华为领导的话说,就是工作不饱满。那怎么把这段闲着的时间利用起来呢。这餐厅老板就想了一个办法,让一个服务员(前台)专门负责收集客人的需求,登记下来,比如有客人进来了、客人点菜了,客人要结帐了,都先记录下来按顺序排好。每个服务员到这里领一个需求,比如点菜,就拿着菜单帮客人点菜去了。点好菜以后,服务员马上回来,领取下一个需求,继续为别人客人服务去了。这种方式服务质量就不如一对一的服务了,当客人数据很多的时候可能需要等待。但好处也很明显,由于在客人正吃饭着的时候服务员不用闲着了,服务员这个时间内可以为其他客人服务了,原来10个服务员最多同时为10桌客人服务,现在可能为50桌,60客人服务了。

 

这种服务方式跟传统的区别有两个:

1、增加了一个角色,要有一个专门负责收集客人需求的人。NIO里对应的就是Selector。

2、由阻塞服务方式改为非阻塞服务了,客人吃着的时候服务员不用一直侯在客人旁边了。传统的IO操作,比如read(),当没有数据可读的时候,线程一直阻塞被占用,直到数据到来。NIO中没有数据可读时,read()会立即返回0,线程不会阻塞。

 

NIO中,客户端创建一个连接后,先要将连接注册到Selector,相当于客人进入餐厅后,告诉前台你要用餐,前台会告诉你你的桌号是几号,然后你就可能到那张桌子坐下了,SelectionKey就是桌号。当某一桌需要服务时,前台就记录哪一桌需要什么服务,比如1号桌要点菜,2号桌要结帐,服务员从前台取一条记录,根据记录提供服务,完了再来取下一条。这样服务的时间就被最有效的利用起来了。

 

具体分析: 

一.java NIO 和阻塞I/O的区别 

1. 阻塞I/O通信模型 

假如现在你对阻塞I/O已有了一定了解,我们知道阻塞I/O在调用InputStream.read()方法时是阻塞的,它会一直等到数据到来时(或超时)才会返回;同样,在调用ServerSocket.accept()方法时,也会一直阻塞到有客户端连接才会返回,每个客户端连接过来后,服务端都会启动一个线程去处理该客户端的请求。阻塞I/O的通信模型示意图如下:

 

 

如果你细细分析,一定会发现阻塞I/O存在一些缺点。根据阻塞I/O通信模型,我总结了它的两点缺点:
1. 当客户端多时,会创建大量的处理线程。且每个线程都要占用栈空间和一些CPU时间

2. 阻塞可能带来频繁的上下文切换,且大部分上下文切换可能是无意义的。

在这种情况下非阻塞式I/O就有了它的应用前景。

2. java NIO原理及通信模型 

Java NIO是在jdk1.4开始使用的,它既可以说成“新I/O”,也可以说成非阻塞式I/O。下面是java NIO的工作原理:

1. 由一个专门的线程来处理所有的 IO 事件,并负责分发。 
2. 事件驱动机制:事件到的时候触发,而不是同步的去监视事件。 
3. 线程通讯:线程之间通过 wait,notify 等方式通讯。保证每次上下文切换都是有意义的。减少无谓的线程切换。 

阅读过一些资料之后,下面贴出我理解的java NIO的工作原理图:

 

 

(注:每个线程的处理流程大概都是读取数据、解码、计算处理、编码、发送响应。)

Java NIO的服务端只需启动一个专门的线程来处理所有的 IO 事件,这种通信模型是怎么实现的呢?呵呵,我们一起来探究它的奥秘吧。java NIO采用了双向通道(channel)进行数据传输,而不是单向的流(stream),在通道上可以注册我们感兴趣的事件。一共有以下四种事件:

 

事件名对应值
服务端接收客户端连接事件SelectionKey.OP_ACCEPT(16)
客户端连接服务端事件SelectionKey.OP_CONNECT(8)
读事件SelectionKey.OP_READ(1)
写事件SelectionKey.OP_WRITE(4)

 

 

服务端和客户端各自维护一个管理通道的对象,我们称之为selector,该对象能检测一个或多个通道 (channel) 上的事件。我们以服务端为例,如果服务端的selector上注册了读事件,某时刻客户端给服务端发送了一些数据,阻塞I/O这时会调用read()方法阻塞地读取数据,而NIO的服务端会在selector中添加一个读事件。服务端的处理线程会轮询地访问selector,如果访问selector时发现有感兴趣的事件到达,则处理这些事件,如果没有感兴趣的事件到达,则处理线程会一直阻塞直到感兴趣的事件到达为止。下面是我理解的java NIO的通信模型示意图:

 

 

二.java NIO服务端和客户端代码实现 

为了更好地理解java NIO,下面贴出服务端和客户端的简单代码实现。

服务端:

 

Java代码  
  1. package cn.nio;  
  2.   
  3. import java.io.IOException;  
  4. import java.net.InetSocketAddress;  
  5. import java.nio.ByteBuffer;  
  6. import java.nio.channels.SelectionKey;  
  7. import java.nio.channels.Selector;  
  8. import java.nio.channels.ServerSocketChannel;  
  9. import java.nio.channels.SocketChannel;  
  10. import java.util.Iterator;  
  11.   
  12. /** 
  13.  * NIO服务端 
  14.  * @author 小路 
  15.  */  
  16. public class NIOServer {  
  17.     //通道管理器  
  18.     private Selector selector;  
  19.   
  20.     /** 
  21.      * 获得一个ServerSocket通道,并对该通道做一些初始化的工作 
  22.      * @param port  绑定的端口号 
  23.      * @throws IOException 
  24.      */  
  25.     public void initServer(int port) throws IOException {  
  26.         // 获得一个ServerSocket通道  
  27.         ServerSocketChannel serverChannel = ServerSocketChannel.open();  
  28.         // 设置通道为非阻塞  
  29.         serverChannel.configureBlocking(false);  
  30.         // 将该通道对应的ServerSocket绑定到port端口  
  31.         serverChannel.socket().bind(new InetSocketAddress(port));  
  32.         // 获得一个通道管理器  
  33.         this.selector = Selector.open();  
  34.         //将通道管理器和该通道绑定,并为该通道注册SelectionKey.OP_ACCEPT事件,注册该事件后,  
  35.         //当该事件到达时,selector.select()会返回,如果该事件没到达selector.select()会一直阻塞。  
  36.         serverChannel.register(selector, SelectionKey.OP_ACCEPT);  
  37.     }  
  38.   
  39.     /** 
  40.      * 采用轮询的方式监听selector上是否有需要处理的事件,如果有,则进行处理 
  41.      * @throws IOException 
  42.      */  
  43.     @SuppressWarnings("unchecked")  
  44.     public void listen() throws IOException {  
  45.         System.out.println("服务端启动成功!");  
  46.         // 轮询访问selector  
  47.         while (true) {  
  48.             //当注册的事件到达时,方法返回;否则,该方法会一直阻塞  
  49.             selector.select();  
  50.             // 获得selector中选中的项的迭代器,选中的项为注册的事件  
  51.             Iterator ite = this.selector.selectedKeys().iterator();  
  52.             while (ite.hasNext()) {  
  53.                 SelectionKey key = (SelectionKey) ite.next();  
  54.                 // 删除已选的key,以防重复处理  
  55.                 ite.remove();  
  56.                 // 客户端请求连接事件  
  57.                 if (key.isAcceptable()) {  
  58.                     ServerSocketChannel server = (ServerSocketChannel) key  
  59.                             .channel();  
  60.                     // 获得和客户端连接的通道  
  61.                     SocketChannel channel = server.accept();  
  62.                     // 设置成非阻塞  
  63.                     channel.configureBlocking(false);  
  64.   
  65.                     //在这里可以给客户端发送信息哦  
  66.                     channel.write(ByteBuffer.wrap(new String("向客户端发送了一条信息").getBytes()));  
  67.                     //在和客户端连接成功之后,为了可以接收到客户端的信息,需要给通道设置读的权限。  
  68.                     channel.register(this.selector, SelectionKey.OP_READ);  
  69.                       
  70.                     // 获得了可读的事件  
  71.                 } else if (key.isReadable()) {  
  72.                         read(key);  
  73.                 }  
  74.   
  75.             }  
  76.   
  77.         }  
  78.     }  
  79.     /** 
  80.      * 处理读取客户端发来的信息 的事件 
  81.      * @param key 
  82.      * @throws IOException  
  83.      */  
  84.     public void read(SelectionKey key) throws IOException{  
  85.         // 服务器可读取消息:得到事件发生的Socket通道  
  86.         SocketChannel channel = (SocketChannel) key.channel();  
  87.         // 创建读取的缓冲区  
  88.         ByteBuffer buffer = ByteBuffer.allocate(10);  
  89.         channel.read(buffer);  
  90.         byte[] data = buffer.array();  
  91.         String msg = new String(data).trim();  
  92.         System.out.println("服务端收到信息:"+msg);  
  93.         ByteBuffer outBuffer = ByteBuffer.wrap(msg.getBytes());  
  94.         channel.write(outBuffer);// 将消息回送给客户端  
  95.     }  
  96.       
  97.     /** 
  98.      * 启动服务端测试 
  99.      * @throws IOException  
  100.      */  
  101.     public static void main(String[] args) throws IOException {  
  102.         NIOServer server = new NIOServer();  
  103.         server.initServer(8000);  
  104.         server.listen();  
  105.     }  
  106.   
  107. }  

 

 

客户端:

 

Java代码  
  1. package cn.nio;  
  2.   
  3. import java.io.IOException;  
  4. import java.net.InetSocketAddress;  
  5. import java.nio.ByteBuffer;  
  6. import java.nio.channels.SelectionKey;  
  7. import java.nio.channels.Selector;  
  8. import java.nio.channels.SocketChannel;  
  9. import java.util.Iterator;  
  10.   
  11. /** 
  12.  * NIO客户端 
  13.  * @author 小路 
  14.  */  
  15. public class NIOClient {  
  16.     //通道管理器  
  17.     private Selector selector;  
  18.   
  19.     /** 
  20.      * 获得一个Socket通道,并对该通道做一些初始化的工作 
  21.      * @param ip 连接的服务器的ip 
  22.      * @param port  连接的服务器的端口号          
  23.      * @throws IOException 
  24.      */  
  25.     public void initClient(String ip,int port) throws IOException {  
  26.         // 获得一个Socket通道  
  27.         SocketChannel channel = SocketChannel.open();  
  28.         // 设置通道为非阻塞  
  29.         channel.configureBlocking(false);  
  30.         // 获得一个通道管理器  
  31.         this.selector = Selector.open();  
  32.           
  33.         // 客户端连接服务器,其实方法执行并没有实现连接,需要在listen()方法中调  
  34.         //用channel.finishConnect();才能完成连接  
  35.         channel.connect(new InetSocketAddress(ip,port));  
  36.         //将通道管理器和该通道绑定,并为该通道注册SelectionKey.OP_CONNECT事件。  
  37.         channel.register(selector, SelectionKey.OP_CONNECT);  
  38.     }  
  39.   
  40.     /** 
  41.      * 采用轮询的方式监听selector上是否有需要处理的事件,如果有,则进行处理 
  42.      * @throws IOException 
  43.      */  
  44.     @SuppressWarnings("unchecked")  
  45.     public void listen() throws IOException {  
  46.         // 轮询访问selector  
  47.         while (true) {  
  48.             selector.select();  
  49.             // 获得selector中选中的项的迭代器  
  50.             Iterator ite = this.selector.selectedKeys().iterator();  
  51.             while (ite.hasNext()) {  
  52.                 SelectionKey key = (SelectionKey) ite.next();  
  53.                 // 删除已选的key,以防重复处理  
  54.                 ite.remove();  
  55.                 // 连接事件发生  
  56.                 if (key.isConnectable()) {  
  57.                     SocketChannel channel = (SocketChannel) key  
  58.                             .channel();  
  59.                     // 如果正在连接,则完成连接  
  60.                     if(channel.isConnectionPending()){  
  61.                         channel.finishConnect();  
  62.                           
  63.                     }  
  64.                     // 设置成非阻塞  
  65.                     channel.configureBlocking(false);  
  66.   
  67.                     //在这里可以给服务端发送信息哦  
  68.                     channel.write(ByteBuffer.wrap(new String("向服务端发送了一条信息").getBytes()));  
  69.                     //在和服务端连接成功之后,为了可以接收到服务端的信息,需要给通道设置读的权限。  
  70.                     channel.register(this.selector, SelectionKey.OP_READ);  
  71.                       
  72.                     // 获得了可读的事件  
  73.                 } else if (key.isReadable()) {  
  74.                         read(key);  
  75.                 }  
  76.   
  77.             }  
  78.   
  79.         }  
  80.     }  
  81.     /** 
  82.      * 处理读取服务端发来的信息 的事件 
  83.      * @param key 
  84.      * @throws IOException  
  85.      */  
  86.     public void read(SelectionKey key) throws IOException{  
  87.         //和服务端的read方法一样  
  88.     }  
  89.       
  90.       
  91.     /** 
  92.      * 启动客户端测试 
  93.      * @throws IOException  
  94.      */  
  95.     public static void main(String[] args) throws IOException {  
  96.         NIOClient client = new NIOClient();  
  97.         client.initClient("localhost",8000);  
  98.         client.listen();  
  99.     }  
  100.   
  101. }  

 

 

 

作者:shimiso 发表于2014-5-4 16:57:33 原文链接
阅读:0 评论:0 查看评论

高性能、高并发网络通信系统的架构设计

$
0
0

1 引言


    随着互联网和物联网的高速发展,使用网络的人数和电子设备的数量急剧增长,其也对互联网后台服务程序提出了更高的性能和并发要求。本文的主要目的是阐述如何进行高并发、高性能通信系统的架构设计,以及这样的系统需要用到的常用技术,但不对其中的技术细节进行讨论和实现。本篇只起抛砖引玉的之效,如有更好的设计方案和思路,望您不舍赐教! [注:此文中使用select来讲解,如果想使用epoll,设计思路是一致的]


2 设计方案


    我们首先来看看课本上或学习资料上关于处理并发网络编程的三种常用方案,以及对应的大体思路和优缺点,其描述如下所示:

    1)、IO多路复用

      思路:单进程(非多线程)调用select()函数来处理多个连接请求。

       优点:单进程(非多线程)可支持同时处理多个网络连接请求。

       缺点:最大并发为1024个,当并发数较大时,其处理性能很低。

    2)、子进程处理

       思路:当连接请求过来时,主进程fork产生一个子进程,让子进程负责与客户端连接进行数据通信,当客户端主动关闭连接后,子进程结束运行。

       优点:模式简单,易于理解;连接请求很小时,效率较高。

       缺点:当连接请求过多时,系统资源很快被耗尽。比如:当连接请求达到10k时,难道要启动10k个进程吗?

    3)、线程处理

       思路:首先启动多个工作线程,而主线程负责接收客户端连接请求,工作线程负责与客户端通信;当连接请求过来时,ACCEPT线程将sckid放入一个数组中,工作线程中的空闲线程从数组中取走一个sckid,对应的工作线程再与客户端连接进行数据通信,当客户端主动关闭连接后,此工作线程又去从指定数组中取sckid,依次重复运行。

       优点:拥有方案②的优点,且能够解决方案②的缺点。

       缺点:不能支持并发量大的请求和量稍大的长连接请求。


    通过对以上三种方案的分析,显然以上方案 均不能满足高并发、高性能的服务器的设计要求。针对以上设计方案问题的存在,那我们该如何才能设计一个既能满足高并发,又有高效率的网络通信系统呢?


2.1 接收与处理


    IO多路复用中1个select()最多可管理1024个Socket FD,而如果要求并发量达到10k时,其显然大大超过了1个select()的管理能力。此问题该如何解决?

    很容易便可发现10k并发约等于10 * 1024并发,因此,需要大约10个select()才能有效管理,那该如何调用10个select()来管理10k的并发呢?

    因FD在进程之间是独立的,虽然子进程在创建之时,会继承父进程的FD,但后续新的连接产生的FD却无法继承,因此,要实现对10k并发的有效管理,使用 多线程实现高并发要求 是唯一的选择。即:每个线程调用1个select(),而每个select()最多可以管理1024个并发。

    在理想情况下,启动10个接收线程,便可管理10 * 1024 = 10k的网络并发。通过此种方式,便可达到高并发的设计要求。

    为了进一步的提高处理效率,我们可以对线程进行分工划分:

        Recv线程:专门负责与多个客户端连接进行通信,也就是负责接和发送数据;

        Work线程:专门负责处理Recv线程接收到的数据。

    Recv线程和Work线程协助处理的流程大体过程:

        ①、Recv线程将接收到的数据随机的放入到某个接收队列中;[注:随机放入起着负载均衡的作用]

        ②、接收完毕之后,再随机通知其中一个Work线程到指定的队列中取数据;[注:随机通知起着负载均衡的作用]

        ③、Work线程接收通知后,到指定的接收队列中取走数据;

        ④、Work线程根据取到的数据做相应的处理。

    故,其大体设计架构如下图所示:


图1 数据接收与处理


2.2 连接分发


    总所周知,使用TCP绑定指定端口后,其他线程或进程想再次绑定指定端口时,肯定会失败。而如果让每个Recv线程将Listen FD加入到select()的FD_SET中进行监听后,当有1个新的连接到来时,所有的Recv线程都会被惊醒,这也就是“惊群”现象的一种,这显然是不被允许的。为了避免此问题的出现, 只能让一个Accept线程专门负责Accept客户端的连接,再将新生成的Socket FD随机的分发给Recv线程,让Recv线程与客户端进行通信。[注:随机分发起着负载均衡的作用]

图2 连接分发


2.3 消息通知机制


    看到这时你可能还会有如下几个疑问:

        ①、Accept线程通过何种方式将新连接的Socket FD分发给Recv线程呢?

        ②、Recv线程通过何种方式通知Work线程到指定的接收队列取数据呢?

    在之前的一篇博文 《日志系统系能对比分析》中对TCP、UDP、Unix-TCP、Unix-UDP的性能做了对比测试和分析,可发现在这几种传输协议中,Unix-UDP的效率是最高的,因此,本系统选择Unix-UDP作为该系统内部消息通知机制的传输协议。


    此系统技术要点:

    ① 非阻塞IO ② 事件触发机制 ③ 线程池机制 ④ 负载均衡机制 ⑤Unix-UDP传输消息 ⑥ 内存池机制 ⑦ 高效算法和技巧的使用等等。


    在此只是对此系统的设计思路的大概描述,不做细节的讨论和分析。此设计思路在其他的应用场合可做适当的扩展变更,产生更适合具体应用场景的设计方案。


作者:邹祁峰

2014.05.04 16:00

作者:RoyalApex 发表于2014-5-4 16:26:17 原文链接
阅读:7 评论:0 查看评论

关于删除MySQL Logs的一点记录 - 刘浩de技术博客

$
0
0

五一前,一个DBA同事反馈,在日常环境中删除一个大的slow log文件(假设文件大小10G以上吧),然后在MySQL中执行flush slow logs,会发现mysqld hang住。

 

今天尝试着重现了此问题,这里简要分析下原因。

 

重现步骤:

1. 构造slow log (将long_query_time设成了0);

2. 观察删rm slow log瞬间,tps/qps变化;

3. 观察执行flush slow logs瞬间,tps/qps变化;

4. 记录flush slow logs执行时, pstack打出的调用栈情况;

 

第一步,没啥好说的。

第二步,tps/qps没啥变化。

第三步,会发现tps/qps瞬间跌0,如下所示:

[ 639s] threads: 32, tps: 1121.00, reads/s: 15843.98, writes/s: 4490.99
[ 640s] threads: 32, tps: 792.99, reads/s: 10803.89, writes/s: 3150.97
[ 641s] threads: 32, tps: 0.00, reads/s: 0.00, writes/s: 0.00
[ 642s] threads: 32, tps: 0.00, reads/s: 0.00, writes/s: 0.00
[ 643s] threads: 32, tps: 471.01, reads/s: 6860.08, writes/s: 1908.02

mysql命令行会发现,flush slow logs执行时间刚好为3s左右。

第四步,我们看下pstack的输出结果,只记录相关的:

610 Thread 5 (Thread 0x2afdc4101700 (LWP 30762)):
611 #0 0x0000003c6e40a7d6 in pthread_rwlock_rdlock () from /lib64/libpthread.so.0
612 #1 0x0000000000825135 in inline_mysql_rwlock_rdlock ()
613 #2 0x0000000000838004 in LOGGER::lock_shared() ()
614 #3 0x00000000008283bf in LOGGER::slow_log_print(THD*, char const*, unsigned int, unsigned long long) ()
615 #4 0x0000000000832b30 in slow_log_print(THD*, char const*, unsigned int, unsigned long long) ()
616 #5 0x0000000000609f23 in log_slow_statement(THD*) ()
617 #6 0x00000000006099d1 in dispatch_command(enum_server_command, THD*, char*, unsigned int) ()
618 #7 0x0000000000606e02 in do_command(THD*) ()
619 #8 0x00000000006f070f in do_handle_one_connection(THD*) ()
620 #9 0x00000000006f020d in handle_one_connection ()
621 #10 0x0000003c6e4077f1 in start_thread () from /lib64/libpthread.so.0
622 #11 0x0000003c6e0e570d in clone () from /lib64/libc.so.6

623 Thread 4 (Thread 0x2afdd0080700 (LWP 30763)):
624 #0 0x0000003c6e40a7d6 in pthread_rwlock_rdlock () from /lib64/libpthread.so.0
625 #1 0x0000000000825135 in inline_mysql_rwlock_rdlock ()
626 #2 0x0000000000838004 in LOGGER::lock_shared() ()
627 #3 0x00000000008283bf in LOGGER::slow_log_print(THD*, char const*, unsigned int, unsigned long long) ()
628 #4 0x0000000000832b30 in slow_log_print(THD*, char const*, unsigned int, unsigned long long) ()
629 #5 0x0000000000609f23 in log_slow_statement(THD*) ()
630 #6 0x00000000006099d1 in dispatch_command(enum_server_command, THD*, char*, unsigned int) ()
631 #7 0x0000000000606e02 in do_command(THD*) ()
632 #8 0x00000000006f070f in do_handle_one_connection(THD*) ()
633 #9 0x00000000006f020d in handle_one_connection ()
634 #10 0x0000003c6e4077f1 in start_thread () from /lib64/libpthread.so.0
635 #11 0x0000003c6e0e570d in clone () from /lib64/libc.so.6

636 Thread 3 (Thread 0x2afdd0101700 (LWP 30764)):
637 #0 0x0000003c6e40a7d6 in pthread_rwlock_rdlock () from /lib64/libpthread.so.0
638 #1 0x0000000000825135 in inline_mysql_rwlock_rdlock ()
639 #2 0x0000000000838004 in LOGGER::lock_shared() ()
640 #3 0x00000000008283bf in LOGGER::slow_log_print(THD*, char const*, unsigned int, unsigned long long) ()
641 #4 0x0000000000832b30 in slow_log_print(THD*, char const*, unsigned int, unsigned long long) ()
642 #5 0x0000000000609f23 in log_slow_statement(THD*) ()
643 #6 0x00000000006099d1 in dispatch_command(enum_server_command, THD*, char*, unsigned int) ()
644 #7 0x0000000000606e02 in do_command(THD*) ()
645 #8 0x00000000006f070f in do_handle_one_connection(THD*) ()
646 #9 0x00000000006f020d in handle_one_connection ()
647 #10 0x0000003c6e4077f1 in start_thread () from /lib64/libpthread.so.0
648 #11 0x0000003c6e0e570d in clone () from /lib64/libc.so.6

649 Thread 2 (Thread 0x2afe18080700 (LWP 30855)):
650 #0 0x0000003c6e40e54d in close () from /lib64/libpthread.so.0
651 #1 0x00000000008f56ed in my_close ()
652 #2 0x0000000000825c16 in inline_mysql_file_close ()
653 #3 0x000000000082b305 in MYSQL_LOG::close(unsigned int) ()
654 #4 0x000000000082b634 in MYSQL_QUERY_LOG::reopen_file() ()
655 #5 0x0000000000828283 in LOGGER::flush_slow_log() ()
656 #6 0x000000000071d8fc in reload_acl_and_cache(THD*, unsigned long, TABLE_LIST*, int*) ()
657 #7 0x0000000000610200 in mysql_execute_command(THD*) ()
658 #8 0x000000000061534d in mysql_parse(THD*, char*, unsigned int, Parser_state*) ()
659 #9 0x00000000006086a0 in dispatch_command(enum_server_command, THD*, char*, unsigned int) ()
660 #10 0x0000000000606e02 in do_command(THD*) ()
661 #11 0x00000000006f070f in do_handle_one_connection(THD*) ()
662 #12 0x00000000006f020d in handle_one_connection ()
663 #13 0x0000003c6e4077f1 in start_thread () from /lib64/libpthread.so.0
664 #14 0x0000003c6e0e570d in clone () from /lib64/libc.so.6

会发现Thread 2在执行flush slow logs操作,其他的线程都在等待锁LOCK_log上边。

 

背后的原因其实很简单,在shell中执行rm slow log操作时,由于mysqld仍有文件句柄打开此文件,所以实际上此时文件并未删除。执行flush logs操作,其实际执行的是1)close;2)open 操作(logger.flush_slow_log -> mysql_slow_log.reopen_file),在close操作执行时,文件系统真正删除文件,此时该线程占用着LOCK_log锁。

删除时会执行刷脏(当然我构造这个场景很极端,基本所有slow log文件的内容都在文件系统缓存中),这个会很耗时间,比如我执行这个语句耗了3s。此时间段内,如果连接发来的语句需要记log(server层的log:slow log/binlog/general log共有LOCK_log这把锁)就会处于等待状态,那么系统对外的反应就是hang住了。

 

flush slow logs中调用执行的close所需时间和文件大小、以及文件系统缓存中该文件脏页比例都有关系,比如我在执行flush slow logs前使用sysctl vm.drop_caches=3清空

了文件系统缓存的话,同样大小的flush slow logs操作执行时间是0.42s,相应的阻塞时间也会减少不少。

可以考虑在slow logs的文件句柄上执行posix_fadvise调用,促使不会缓存很大的log文件内容(slow log也没啥需要缓存的),这有篇霸爷的文章,可以参考下  posix_fadvise清除缓存的误解和改进措施 。

另外,peter在07年就讨论过这个问题,  Be careful rotating MySQL logs 其给出的建议是先mv file,然后flush logs,再执行删除文件的操作,让真正的删除行为由自己而不是mysqld完成。比较遗憾的是,五年过去了,LOCK_log这把锁的问题还没有完整的解决掉。

 

PS:

文章结尾记一点备忘,通过close/rm操作删除一个10G大小的文件,在执行sysctl vm.drop_caches=3清空缓存后,此操作的耗时仍在百毫秒量级(我的机器上是200ms+),其背后做了什么事情还需要找内核组的同事了解下。

 


本文链接: 关于删除MySQL Logs的一点记录,转载请注明。

UI产品设计流程中的14个要点

$
0
0

自从我在Dribbble上贴了一幅我的产品设计成果,受到了大家伙热烈的反馈,对此我深受鼓励,我决定写下这篇文章,用来记录我这两年里作为产品设计师,所学到的东西。

说起来有点惭愧,这几年我一直都在使用同一套产品设计的流程,但是我觉得这套流程最适合我,对我来说是最理想的,所以就很少去更换。我的这套工作流程我觉得有4个地方可以和大家分享一下——前期工作、具体设计、后期工作以及一些提高效率的小细节

一,前期工作

1.画!

无论是一张纸、笔记本,还是一片什么能写的玩意儿,都可以。我需要用纸笔记录下脑中的创意,避免遗忘。有些时候,灵感经常不经意出现,而一时半会儿又找不到合适的纸,因此我会用银行收据、餐馆账单、书籍封面等做暂时记录。

UI产品设计流程中的14个要点

草图记录理念

可以专门用一个笔记本来收集你的创意,不时的翻翻,可能会找到一些非常有趣的旧想法,你可以根据这些旧点子想一些新玩意儿。

2. 收集图片

  UI产品设计流程中的14个要点

“一位艺术家必定是一位收藏家。他懂得品味藏品,而不是单纯的‘贮藏’。他们懂得有选择得去收藏。他们会根据自己内心的喜好选择收藏的东西“

还有一项前期要做的工作就是收集图片,我每天都会收集一些图片。收集图片的方法可能有好几百种,但是我还是习惯最Old-School的方法——Dropbox文件夹分类(例如,我会分成后台界面设计、iOS设计、插画设计等等)每当我有新的项目的时候,我就会看看这些图片,用来寻找灵感。

3. 情绪板和准备工作

Dribbble,Behance,Pttrns,Pinterest——我们有很多可以需找灵感的地方。而且很容易就能找到和你项目相关的作品。多去逛逛,你可能会从别人的经验中学会解决问题。

当我开始新项目的时候,我总会准备4个文件夹——PSD,屏,资源,灵感。我会把和这个项目相关的东西全部按下面分类丢到文件夹里面。

UI产品设计流程中的14个要点

可以是Behance上整套案例的研究,也可以是整个应用中的一张界面设计图。

二、开始着手设计

4. 不必在乎线框图的质量

我不喜欢精雕细琢线框图。毕竟,这玩意儿只是一个过渡,而且客户能够预先准备好线框图就更好了。真正的业界好客户(而且肯定对品质也有追求的)肯定预先会以草图或者线框图的形式来表述需求。

线框图的作用是就是让彼此更好的理解目的,而不是最终结果。线框图能够帮助架构层级,让你了解大概需要多少屏界面,让你了解客户的想法

设计师必须懂得去“敏捷”设计,如果真的太在意线框图的细节,那么整个设计流程会拉长,设计师应该学会去取舍。

5. PSD - 大尺寸画布

我记得7个月前开始Badoo项目的时候,我最开始观摩了一下同事的工作方法,我觉得他不是很得要领。最好用PS做一个大尺寸画布,用来承载流程中的一些细节。我一般会创建8000*5000的画布,不是用来画出整个应用的UI套件,而是用来记录元素在不同阶段的不同状态——也就是流程。设计复用很方便,开发看到这种东西也会工作的更快。

6. 所有屏都放入一个PSD中

如果是移动应用,我喜欢把所有屏的界面设计全部装入到一个PSD中。

在设计移动应用时,我喜欢把整个流程中所有屏的界面设计全部装入到一个PSD中,整体的对比效果会更好,也更容易让他人理解你的设计理念。

UI产品设计流程中的14个要点

元素的复用也非常方便,只需要复制一下其他屏的图层/图层组,修改一下背景或者几个图标就成了。

7. 整理、规范图层和图层组

我是一个崇尚整齐的人,因此我的PSD结构非常的有序。我个人的规则是,如果超过8个图层,那么就创建一个图层组。

这里推荐一下《PS礼仪手册》,有兴趣的可以去看看

8. 和朋友沟通

我非常重视那些能够给出专业性反馈意见的人。留意他们的反应和初次看到你设计时的想法,这未尝不是一种用户测试。而且多聆听别人的意见,能够让你从不同的角度看待问题。在整个设计流程中,我尽可能的多和别人沟通,避免出现主观差错。每个人都可以给出一些不错的意见。还记得那句话吗?“只要你进了这个屋子,你就是用户体验设计师。”

9.图示

当所有屏的设计都装入到一个PSD中后。应该做一点图示,用来暗示交互流程,我觉得这算是一种早期原型设计吧,与此同时,还能充分感受到视觉设计的细节。集中到一个PSD中的好处是,如果你画多个交互流程,画多个以上的时候,指示线的视觉风格可能会混乱,而一个PSD能够让你整体视觉高度一致。

UI产品设计流程中的14个要点

三 种不同的指示线——第一种指示线用来指示界面流程(并带有序号),第二种用来指示屏幕内的链接或元素,第三种用来指示外部链接或应用

  UI产品设计流程中的14个要点

指示线搭配内容的效果

  UI产品设计流程中的14个要点

整体预览

很多人喜欢把连线搞得满屏都是,但是我采用了一种更灵活的方法,我的线可以画得很短,只需要连接到一个圆圈数字,就可以代表连接到第几屏。减少连线的繁杂感。

为了让你们更好理解我上述的心得,这里我给出一份PSD,各位请参考:

https://www.dropbox.com/s/lnxg174ib9iqpa8/Diagram-Template.zip

 三、后期工作——视觉规范

完成了设计工作后,要做的事情就是出一份视觉规范,然后检查一下整体的视觉层级。我个人认为,无论是小型项目还是大型项目,视觉规范都有其存在的必要性。无论是对你自己,还是对于同事,都能起到很好的规范作用,避免不一致的设计。

10. 配色表

扁平化的要领之一是配色精简。也方便了我们展现配色表。

UI产品设计流程中的14个要点

11. 字体表

要完善的记录Logo使用的字体,文本使用的字体,标题使用的字体等等,这对于开发会大有裨益。个人复查作品时也会很有帮助。

UI产品设计流程中的14个要点

12. UI 套件

UI套件非常重要,尤其对于工作对接来说,能够保持视觉高度的一致性。前端也非常需要这东西。做好了之后丢给前端,他要问你,你就说这东西在UI套件中,他立马就能明白了。这一条非常重要,关系到整体的视觉一致性。

四、提高效率的一些建议

13. 待办事项

前面已经提过,我是一个崇尚有序的人。因此我高度依赖待办事项清单。我推荐Cultured Code的Things,或者直接用一张纸来做真正的待办事项清单也可以。这样当你完成整体清单时,感受会非常的棒。以前可能我会5项工作同时展开,但是最近我发现,如果仅仅做1-2项工作,我会更集中,效果也自然会更好。

14. 目标

必须要清除的了解,你想要达成什么目标,但是也不要过于受目标限制。我一般会指定14天的目标(类似冲刺计划)和嫉妒目标。我会设定一些对我来说是新里程碑的目标(比如用AE完成我个人的第一个动效设计)以及日常目标(完成2个Behance案例研究)

其他

个人不用鼠标,用的是绘图板,PS没有打开工具面板,因为所有工具的快捷键我都记住了。然后PS作品iPhone预览我用的是Skala Preview。个人想学习Sketch和AE。原型设计,网页设计我用InVision应用,iOS设计我用Marvel应用。有时候还会用POP进行一些早期原型设计。

结语

这就是我的工作流程,希望你能从中得到帮助。

本文转载自 设计之家


(关注更多人人都是产品经理观点,参与微信互动(微信搜索“人人都是产品经理”或“woshipm”)

HTML5的页面资源预加载技术(Link prefetch)加速页面加载

$
0
0

不管是浏览器的开发者还是普通web应用的开发者,他们都在做一个共同的努力:让Web浏览有更快的速度感觉。有很多已知的技术都可以让你的网站速度变得更快:使用CSS sprites,使用图片优化工具,使用.htaccess设置页面头信息和缓存时间,JavaScript压缩,使用CDN等。我曾经介绍过本站上使用的一些速度优化技术。而在 HTML5里,出现了一个新的用来优化网站速度的新功能:页面资源预加载/预读取(Link prefetch)。

页面资源预加载/预读取(Link prefetch)是什么?来自 MDN的解释:

页面资源预加载(Link prefetch)是浏览器提供的一个技巧,目的是让浏览器在空闲时间下载或预读取一些文档资源,用户在将来将会访问这些资源。一个Web页面可以对浏览器设置一系列的预加载指示,当浏览器加载完当前页面后,它会在后台静悄悄的加载指定的文档,并把它们存储在缓存里。当用户访问到这些预加载的文档后,浏览器能快速的从缓存里提取给用户。

简单说来就是:让浏览器预先加载用户访问当前页后极有可能访问的其他资源(页面,图片,视频等)。而且方法超级的简单!

HTML5页面资源预加载(Link prefetch)写法

<!-- 预加载整个页面 --><link rel="prefetch" href="http://www.webhek.com/misc/3d-album/" /><!-- 预加载一个图片 --><link rel="prefetch" href=" http://www.webhek.com/wordpress/
wp-content/uploads/2014/04/b-334x193.jpg " />

HTML5页面资源预加载/预读取(Link prefetch)功能是通过 Link标记实现的,将 rel属性指定为“prefetch”,在 href属性里指定要加载资源的地址。火狐浏览器里还提供了一种额外的属性支持:

<link rel="prefetch alternate stylesheet" 
title="Designed for Mozilla" href="mozspecific.css" /><link rel="next" href="2.html" />

HTTPS协议资源下也可以使用prefetch。

什么情况下应该预加载页面资源

在你的页面里加载什么样的资源,什么时候加载,这完全取决于你。下面是一些建议:

  • 当页面有幻灯片类似的服务时,预加载/预读取接下来的1-3页和之前的1-3页。
  • 预加载那些整个网站通用的图片。
  • 预加载网站上搜索结果的下一页。

禁止页面资源预加载(Link prefetch)

火狐浏览器里有一个选项可以禁止任何的页面资源预加载(Link prefetch)功能,你可以这样设置:

user_pref("network.prefetch-next", false);

页面资源预加载(Link prefetch)注意事项

下面是一些关于页面资源预加载(Link prefetch)的注意事项:

  • 预加载(Link prefetch)不能跨域工作,包括跨域拉取cookies。
  • 预加载(Link prefetch)会污染你的网站访问量统计,因为有些预加载到浏览器的页面用户可能并未真正访问。
  • 火狐浏览器从2003年开始就已经提供了对这项预加载(Link prefetch)技术的支持。

利用浏览器空闲时间加载一些额外的资源文件,看起来是既刺激又危险,你想试试这些技术吗?

原文来自: webhek


谈教育云平台

$
0
0
今天看了下在网上能够搜索到的教育云平台的建设方案和规划,做些简要思考和记录。

教育云平台前面已经谈到过,可以是公有云模式,也可以是私有云模式。公有云模式的重点是工作人员的在教育,职称和资格认证教育,自我驱动的主动学习型教育,这种模式必须是学习人员有强大的自我内驱力保证教育的可持续性,教育云本身仅仅是搭建一个开放平台,最终老师,学生,课件等各种资源最终汇聚上来;而对于当前学校阶段的教育,特别是小学和中学阶段的教育,更加适合的是私有云平台,这种平台更加强调的是教育协同,强调的是学校,家长,老师和学生之间的强粘性驱动,一种被动+主动结合的模式,毕竟在学生阶段学生有强大的内驱力自我学习是一件相当困难的事情。

学堂在线是一个我比较喜欢的网站,最近课程也越来越多,https://www.xuetangx.com/,这种MOOC大型开放式网络课程的在线教育和学习模式将逐步被更多的人所接受。

教育云平台我们仍然把它理解为两个重要的层次,对于IaaS云数据中心资源平台层各家的理解没有太大的出入,有出入的地方也是还是有不是厂家把简单的资源集中后虚拟化为理解为IaaS层的全部。要知道IaaS层设计到计算,存储和网络各方面能力的虚拟化和集成,并提供给上层进一步的开放能力接口。这是后续能够更好的支撑上层PaaS的基础,不能简单的将虚拟化认为IaaS层的全部。特别是面对教育行业,有大量的课件,视频等教育资源而涉及到混合存储,这对IaaS层的分布式存储架构和存储能力开放也提出了更高的要求。

在IaaS层之上则是教育云平台最重要的PaaS平台,首先看下IaaS层只解决了物理计算存储等资源的共享和最大化利用,在物理资源之上是逻辑资源。这个逻辑资源还得从两个方面来谈:

逻辑资源A:传统PaaS层谈到的逻辑资源,包括数据库资源,应用中间件资源,各种技术平台资源,如CT能力,视频能力,一卡通平台,流程平台,4A平台等,这些统一纳入第一类。

逻辑资源B:这类资源可以讲是相当重要的,即我们说的学生资源,老师资源,课件资源(PPT和Word课件,视频课件)等。这类资源的集中建设和共享将为教育云平台真正发挥巨大的作用,否则一个空的技术平台不会有太大的业务价值。

依托第一类资源构建教育云的技术能力平台,依托第二类资源构建教育云的数据资源平台,这两种逻辑资源的能力最终需要通过SOA共享服务平台开放出来供上层业务系统使用。从这个意义上讲教育云的PaaS平台至少需要包括技术能力平台,数据资源平台和共享服务平台三大平台的内容。很多教育云平台在这一层的理解相当不正确,虽然在教育云平台里面可能会弱化传统公有云APaaS平台的内容,但是整个PaaS平台层的构建思想必须具备。

在朝上是私有云的SaaS应用层,首先对于学校而言本身也是一个园区或社区,对于智慧园区该有的信息化类应用不在这里进一步阐述和说明。除了这个以外可以将信息化应用分为三个层面的内容。

其一:从教育局和学校管理方面入手的,教务管理类应用。 其二:从老师,家长,学生多方协同入手的教学协同类应用,对老师来说是教学活动,而学生来说是主动或被动学习类活动,从家长来说是参与和监督类活动。 其三:即核心课件和教学资源的创作,产生,分析和改进类活动。

一个真正有价值的教育云平台一定是围绕第二类的教学和协同类活动为核心驱动力,课件为这类活动提供核心的资源。在这类活动中学生,家长和学校老师形成一个有机的整体。实现主动和被动学习的结合,课题外和课题内学习的结合,在线和离线学习的结合,人人参与的类SNS模式的共同学习和价值创造,教学或学习活动不再是单向的而是双向或多向互动的学习和知识分享平台。

平台搭建好后,从某一个城市或区域角度来说,可以看到原有的学校边界,课件资源的边界,老师的边界,学生的边界都会逐渐模式,形成真正的大资源共享和协同。资源没有共享就是重复,资源共享后没有协同就无法优化和价值提升。共享+协同是最终平台层构建后要达到的一个真正目的。这个问题已经不再是技术层面的问题,而是管理层面的问题,只有解决了这个问题才可能形成一个良好的私有云教育生态环境。

  青春就应该这样绽放   游戏测试:三国时期谁是你最好的兄弟!!   你不得不信的星座秘密

支付宝下拉菜单JS代码

世界还能如何改变?看看谷歌正在发力的12种方式

$
0
0

  谷歌喜欢致力于一些别人未曾发力的事物,CEO拉里·佩奇也认为谷歌是变革的“代言人”。谷歌已通过Search改变了我们获取信息的方式,也通过Chromecast改变了我们消费媒体内容的方式,而且还通过Glass改变了我们如何消费信息的方式。另外,谷歌还因为其“登月实验室”而闻名。

  如果谷歌随心所欲,那么我们的世界将发生12种不可思议方式的改变。

  1.通过谷歌的无人驾驶汽车实验,每年交通事故所带来的死亡人数可能会减少。汽车在高速公路上已能掌握行驶,但谷歌宣布其测试车能处理好带有行人道的城市街道。

  2. Google X还想出了气垫板的想法,它认为使用磁铁能保持气垫板向上,但由于磁铁倾向于转向极性,气垫板将不断翻转。研究团队也考虑过使用其他材料,但最终认为太昂贵,尤其是对于一个只会轻微影响社会和经济的产品来说更是如此。

  3. 谷歌CEO拉里-佩奇甚至还想过发明出空中自行车,希望鼓励更多人骑自行车。他在最近的TED演讲表示:“这看起来完全是疯了”。

  4. 谷歌还希望通过建立新的初创企业加利福尼亚生命公司(California Life Company)来解决衰老甚至死亡问题。

  5. 谷歌眼镜使人类比以往任何时候都更接近科技。今年,维珍航空开始测试谷歌眼镜的用途,使用谷歌眼镜为旅客提供服务。

  6.谷歌正在研发一款智能隐形眼镜,旨在帮助糖尿病患者追踪血糖水平。同时,最近浮出水面的一项专利申请详细说明了谷歌是如何为隐形眼镜添加传感器和微型摄像头的。

  7. 目前,大约三分之二的地球人仍无法上网,谷歌试图通过 Project Loon(热气球互联网服务计划)来将互联网接入到整个世界。

  8. “Project Ara”是谷歌为60亿人专门设计的模块化手机计划。通过 Ara,人们基本上可以定制自己的智能手机的方方面面。

  9. 一部到空中的电梯可能会改变太空旅行,但据报道,由于材料的限制,谷歌在研发中碰了壁,但是,谁又能说谷歌不会再次考虑该产品呢?

  10. 谷歌正致力于智能可穿戴技术。今年3月,谷歌宣布计划通过Android Wear项目将Android引进智能手表。

  11. Google Fiber旨在提供千兆互联网速。谷歌的超高速互联网服务Fiber可能让我们用前所未见的速度来享用信息

  12. 谷歌似乎在使得拍摄风景变得更加容易方面有自己的独特眼光。2013年,谷歌被授予一项专利:拐杖内嵌入一个摄像头。

55个人就足够打造一家伟大的公司

$
0
0

1997年以前,是韦尔奇的天下,他崇尚“末位淘汰制”或者说是“360度评估”:把团队内人员按20%、70%、10%的比例分为最优、中等和最差三个层次。最优的20%人士得到晋升和提薪,70%的人给予培训,激励让他们上升至最优秀20%的人当中,最差的10%员工必须走人,切不能把过多的时间和精力耗在这10%的人身上,这3个层次始终都是保持动态,随时更换人员。

55人

这是1997年以前,在那之后乔布斯回归苹果,至今想来,他在那一年说的两句话和做的一件事,推动了这个星球进入移动互联网时代,及影响了后来的各种思维,思潮和管理方法,包括今日的人力资源管理趋势。

他的两句话是“think different(不同凡想)”与“要么杰作、要么狗屎”,他做的那件事:将Mac变成了半透明状,今天我们讲各种互联网思维,用think different来诠释,概莫能外;今天我们猜想人类的思维正在透明化,引子不过那个半透明的Mac;今天引流潮流的企业如facebook,小米等在选才、用人的标准和方法上极速趋向二维化:要么杰作,要么狗屎,不杰出必须走人。连互联网钉子户公司netflix也只讲:我们只和“成年人”一起做事。至于雷军,那更是在习大大面前坦诚:自己成功最重要的是靠人。

移动互联网时代考核评估体系已经滞后了吗?将要失效了吗?我们需要什么新的方法?为什么扎克伯格说:一名优秀的工程师能够抵得上100个普通工程师?怎么找到那名优秀的工程师?怎么用好他?

2013年中,据说雷军又给光景不好的凡客开了药方:去管理层,陈年也不叫总裁了,就叫创始人,公司内部以后也不安排什么副总、助理总裁了;去KPI,让各事业部专心做好产品;去贪婪,产品没必要做多。三个“去他X的”中,居然两条和人力资源管理有牵扯。药方有用吗?陈看了五年没能看明白,雷就一针见血一药回春吗?现在还不得而知。

但小米模式已然是确确凿凿,立在那了,背后的逻辑大家都懂:时代的商业环境已经发生了巨大的变化。盯住典型用户的核心需求,死磕,实现良好用户体验,然后借由互联网的传播,那些剩下的、所谓非典型用户也竟然迅速的被影响为典型用户,其过程宛如病毒传播,最后取得的成绩,令人炫目。而这一切发生,主要“靠人”。

近日,麦当劳宣布其首席营运官(COO)Tim Fenton将在今年10月份退休,而接下来一个略显意外的消息是麦当劳宣布不会再任命新的COO,彻底取消了这个职位。在现代企业中,COO曾经是个很关键的职位,但从数据上看,现在这个职位已经不再重要了。不仅是麦当劳,《商业周刊》的报道称,2012年只有38%的财富500强公司设有COO位置,而2000年这个比例是48%。去COO,去管理层,追求扁平化…

无独有偶,海尔张瑞敏也屡屡提出“管理无边界,企业无领导”,虽然他貌似并不是那个倡导“无边界组织”的最合适人选。

在《消失的组织架构—超级节点化》一文中提及,以流程为导向的工作团队的建立,即以发起流程的“节点”来驱动业务的进程,即人人都是核心,人人都不是核心,完全根据企业的需要可以随环境的变化而变化。企业部门间和组织内部的界限日益趋于模糊,大多数工作将通过“虚拟团队”来完成。网络经济下的企业组织将越来越多地在企业内部和外部使用跨职能的任务团队,人们必须学会(显然现在绝大多数还没学会)在没有固定职务、没有命令权威、既不是被控制也不是控制他人的情况下去进行管理,去完成任务,去实现目标。 移动互联网时代的人力资源管理何去何从?

1、我们知道,传统企业的组织架构中,中层的作用主要是上传下达:即向下级传达上级的指令并监督执行,同时向上级反馈来自下一级的信息,其次才是人才培养和企业文化传递。但这部分工作将伴随着信息技术的发展和管理工具的广泛运用而逐渐被取代(现代管理学之父德鲁克早在20世纪90年代就有此预言)。

2、传统企业组织架构象朝鲜人民军,兵种清晰,层级分明,基础作战单元以旅/团为主;而创新公司组织架构象美军,由于信息技术发达,基础作战单元超级扁平化,以特种部队(介于排和连的编制)为主,纵观美军最近几年的行动,无论是抓萨达姆,还是刺杀本拉登,都是由五角大楼直接指挥特种部队完成的。

3、也就是说,基于互联网的创新公司老板本人必须要充当“五角大楼”的角色,而中层应该从一个纯管理者变成特种部队的队长。传统商业模式由于足够成熟,老板一个月真正重大决策不会超过三次,但移动互联网时代的公司,由于变化快,现场管理和临机决断的事宜太多,所以必须缩短决策半径,必须扁平化。

小米副总裁王川有句诛心的话:当年我们误入歧途,都抓管理去了。2011年的冬天,索尼迎来了更惨痛的噩耗—年度亏损63亿美金,为什么会这样?天外伺郎(索尼常务副总)说:绩效主义毁了索尼!因实行绩效主义,职工逐渐失去工作热情。在这种情况下是无法产生“激情集团”的……公司为统计业绩,花费了大量的精力和时间,而在真正的工作上却敷衍了事,出现了本末倒置的倾向。如果你还死心不改,可以回想下诺基亚被收购的惨剧。

至于小米“毁三观”的管理文化更是把“去管理化”做到了极致:没有KPI;没有管理层,超级扁平化;第三,没有组织架构,不开会。没有绩效管理,没有级别体系,没有“员工活动”,没有“内部沟通”,没有“内部培训”,没有“企业文化”。

如果你说这尚是个例,那么在北京东南方向千里之外位于有韩国硅谷之称的城南市Techno Valley H-square 6层的Kakao Talk办公区中,同样的组织变革在进行,在Kakao这个拥有500多名员工的公司中,自上而下只有CEO、部长(职能部门负责人或项目组组长)、组员三个级别。

在传统的韩国公司中如果一名工作多年的员工在晋升部长后重新被降职为主任,通常是一种变相开除的行为,此外新员工在短时间内升职也是不被允许的,而在Kakao这个公司中,员工们已经习惯了这种职能变换,特别是技术和产品部门,甚至没有固定的组织架构,通常一个新的项目组成立时,过往的组长可能会因为一些技术特点而重新充当组员的角色,而那个提出新产品理念的人则会被任命为组长,没人会对此有所芥蒂。

为了让员工们感到更加平等,在Kakao公司内,所有的员工都用英文名字互相称呼,譬如大家都直接称呼创始人金范秀叫做 Brian,此外即便是新员工,如果有好的建议和意见都可以直接向社长或是CEO表达,而在传统的韩国企业中这必须经过层层汇报才可以实现,跨级汇报甚至会被视为企业的一条红线。

沿着这条不归路推演下去,商业组织既然有可能三层化,那么也就有可能两层化,如果两层化,那么管理者必然被leader(领导者)取代,如果管理者被取代,那么新生的领导者必须同时具备管理和执行的能力,我们可以称之为“移动互联网时代的leader波粒二象性”,就像量子物理学中描述的光的特性,同时具有管理的波特性,和执行的粒子特性。再大胆想象,在新的管理变革中,更多的“去他X的”将如雨后春笋:

从去管理层、去组织架构、去绩效评估开始,到去销售佣金、去开会、去副总裁、去人事部、去财务部、去运营部、去总裁办…

而创业家,创新者需要做的仅仅是:让业务管理来驱动员工管理;管理外部化“透明化”(让用户广泛参与);游戏化管理 (人人都是游戏的一分子 )

好吧,这个时代你拥有了更大的能量和权力,你知道,打造一个伟大的公司的,你只需要55个人就够了(WhatsApp的55人团队创造市值190亿美金的价值)。最后再免费送你一句话:“营销很重要,商业模式比营销重要,产品比商业模式更重要,但牛B的顶尖人物才是最重要的!

文章转自稻田报告

 

© 2006-2014 by 望月的博客 | 固定链接 | 我要评论 | 广告投放 | 产品推荐
也许您还喜欢:

西祠兴衰路:创始人性格决定公司命运

来自一位卖家的微信营销实战笔记-20140426早读课

谷歌加强电商服务,收购英国创业公司

硅谷创业家:企业为什么要并购?

众里寻Ta千百度,一见神马甩了Ta!
无觅

蚊子是人类头号杀手

$
0
0
美国微软公司始创人比尔·盖茨,最近在其网站上列出了15种最致命动物和它们每年的杀人数目,蚊子是人类的第一号杀手。


科学探索来源:腾讯科技

 
比尔·盖茨:蚊子是人类头号杀手
 

  综合美国媒体报道,美国微软公司始创人比尔·盖茨,最近在其网站上列出了15种最致命动物和它们每年的杀人数目,蚊子是人类的第一号杀手,而人类本身因自相残杀竟然占据了名单的第二位,连淡水螺每年也令约一万人丧命。相反,鲨鱼、狮子这些张牙舞爪的危险猎食动物,杀人数目却远远不及蚊子。

  蚊子是传播疟疾的主要途径,每年有70多万人被蚊子叮咬后死亡,当中被叮后患上疟疾致死的就占了约60万人,多达100个国家的人民受此影响,造成每年数以十亿计美元的经济损失。盖茨夫妇成立的慈善基金会,正致力消灭疟疾。

  排在蚊子后的是人类本身,人类发动战争及互相杀戮,每年便导致约47万多人丧命。大部分致命动物都是因传播疾病而使人类死亡。狗只传播疯狗症,每年导致约2万多人死亡;横行于非洲的舌蝇因传播锥虫病(又称昏睡病)每年亦使一万人丧命;就连不甚起眼的淡水螺,每年也有一万人因牠们所传播的血吸虫病而死。

  相较之下,平日使人闻之色变的鲨鱼,每年只有约10人死在其血盆大口下。其他猎食动物例如狮子,每年约杀100人;鳄鱼每年则约杀1000人。

Viewing all 15843 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>