wangyuheng's Blog

time for change

转载请注明出处WangYuheng’s Blog

起因

javascript对象就是各种属性的集合,业务场景为接收用户传来的options参数,如果默认defaultOptions存在传入的参数属性,则进行替换。所以存在一个检查对象是否包含某个属性的操作。
在原型属性中找到了hasOwnProperty和propertyIsEnumerable两个方法。那么这两个方法有什么区别呢?各自的使用场景又是什么呢?

hasOwnProperty

hasOwnProperty()方法可以接收一个字符参数,用来判断对象中是否存在以字符参数命名的属性。它只会查找自身属性,不会根据原型链进行查找。
注:原型链会再另一篇文章中做详细的介绍!

var obj = {x:"1"};
obj.y = function(){};
console.log(obj.hasOwnProperty("x")); //true
console.log(obj.hasOwnProperty("y")); //true 方法也是属性
console.log(obj.hasOwnProperty("z")); //false 属性不存在
console.log(obj.hasOwnProperty("toString")); //false hasOwnProperty是继承Object的属性,自身属性中不存在

propertyIsEnumerable

propertyIsEnumerable()方法在hasOwnProperty()的基础上,校验属性是否为枚举属性。也就是说属性必须满足为自身属性且为枚举属性时,才会返回true。

枚举属性

枚举属性,其实是可被枚举,表示该属性可以在对象中被遍历。javascript属性默认均为枚举属性。如

var obj = {x:"1"};
Object.defineProperty(obj, 'y', {value : '2', enumerable : true });
Object.defineProperty(obj, 'z', {value : '3', enumerable : false });
console.log(obj);//Object {x: "1", y: "2", z: "3"}
for (var i in obj) {
    console.log(i); //x,y
}

上述代码中属性z被手动指定为非枚举属性,因此没有被遍历。
结合上面的2例子,可以清晰的知道propertyIsEnumerable()函数的作用:

var obj = {x:"1"};
obj.y = function(){};
Object.defineProperty(obj, 'z', {value : '2', enumerable : true });
Object.defineProperty(obj, 'w', {value : '3', enumerable : false });
console.log(obj.propertyIsEnumerable("x")); //true 属性默认为枚举属性
console.log(obj.propertyIsEnumerable("y")); //true 方法也是属性
console.log(obj.propertyIsEnumerable("z")); //true
console.log(obj.propertyIsEnumerable("w")); //false 属性不是枚举属性
console.log(obj.propertyIsEnumerable("v")); //false 属性不存在
console.log(obj.propertyIsEnumerable("toString")); //false propertyIsEnumerable是继承Object的属性,自身属性中不存在

扩展一 !== undefined

有一种简单的方法可以判断属性是否存在,通过属性!== undefined来判断。此时会检测自身和继承来的属性。之所以使用!==而不是!=是因为!==可以区分undefined和null。但是此方法有一个弊端,当属性存在且值为undefined时,无法做出准确判断。如:

var obj = {x:"1", y:undefined, z:null};
console.log(obj.x !== undefined); //true 属性存在
console.log(obj.y !== undefined); //false 此时会出现歧义,不能准确判断属性是不存在还是属性值为undefined
console.log(obj.z !== undefined); //true 属性存在
console.log(obj.z != undefined); //false != 不能区分undefined和null,将两者同等对待
console.log(obj.w !== undefined); //false 属性不存在
console.log(obj.toString !== undefined); //true 存在toString函数属性。

扩展二 in

为了避免!==undefined带来的歧义,可以使用in运算符进行属性存在的检测。in是根据属性,而不是通过属性值来判断是否存在。

var obj = {x:"1", y:undefined, z:null};
console.log("x" in obj); //true 属性存在
console.log("y" in obj); //true 属性存在
console.log("z" in obj); //true 属性存在
console.log("w" in obj); //false 属性不存在
console.log("toString" in obj); //true 属性存在

总结

  • hasOwnProperty 自身存在的属性
  • propertyIsEnumerable 自身存在的属性,且为枚举属性
  • !== undefined 自身存在的属性,继承的属性,不能识别值为undefined的属性
  • in 自身存在的属性,继承的属性

根据具体业务场景,自由选择或组合对应的方法,可以实现对属性的检测。

转载请注明出处WangYuheng’s Blog

XML

XML (eXtensible Markup Language), 可扩展标记语言,发明之初是为了取代HTML,但在使用过程中,开发者发现这种规范的语言格式,在数据传输方面有着明显的优势。
这里只将XML作为一种数据交换格式。
比如说java语言本身的javaBean数据,在程序内使用没有问题,但是如果涉及到与其他语言进行交互,则会出现很多问题。所以通过XML进行数据传输通信,可以拥有更好的跨平台性和可移植性,并且让底层数据预备了可读性。
本篇文章的重点在于介绍如果通过java DOM对XML文件进行解析与操作。

dom

DOM是W3C处理XML的标准API,多种语言都实现了该标准,java对dom的实现在org.w3c.dom包内。很多工具类都是在此基础上进行了封装和扩充,如jdom、dom4j等,这里使用原生实现来完成对xml文档的基本操作。
DOM的实现原理是将XML作为树结构全部读入内存,再进行操作。好处是简单快捷,可以修改结构和数据,而造成的隐患则是是在读取大型XML文件时,可能会造成过多的内存占用。

代码

读取解析xml文件

需要读取的xml文件如下,传递了商品订单信息,包括商品名、价格、购买数量。通过程序读取数据,并计算出订单总价格。
因为本次重点在于xml的解析操作,所以价格直接用float类型处理。如果是生产环境,一定要使用BigDecimal操作,避免float的精度问题!!

<?xml version="1.0" encoding="UTF-8" ?>
<shopping>
    <goods>
        <name>品名1</name>
        <price>3</price>
        <number>4</number>
    </goods>
    <goods>
        <name>品名2</name>
        <price>1.2</price>
        <number>3</number>
    </goods>
</shopping>

java 读取XML代码

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

public class XmlParser {
    
    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
    
    public Document parseDoc(String filePath) {
        Document document = null;
        try {
            DocumentBuilder builder = factory.newDocumentBuilder();
            document = builder.parse(new File(filePath));
        } catch (ParserConfigurationException e) {
            e.printStackTrace();
        } catch (SAXException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return document;
    }
    
    public static void main(String[] args) {
        XmlParser parser = new XmlParser();
        Document document = parser.parseDoc("D://shopping.xml");
        Element rootElement = document.getDocumentElement();
        List<Goods> goodsList = new ArrayList<Goods>();
        NodeList goodsNodeList = rootElement.getElementsByTagName("goods");
        for (int i = 0; i < goodsNodeList.getLength(); i++) {
            Element child = (Element) goodsNodeList.item(i);
            Goods goods = new Goods(child);
            goodsList.add(goods);
        }
        
//        NodeList goodsNodeList = rootElement.getChildNodes();
//        for (int i = 0; i < goodsNodeList.getLength(); i++) {
//            Node node = goodsNodeList.item(i);
//            if (node.getNodeType() == Node.ELEMENT_NODE) {
//                Element child = (Element) node;
//                Goods goods = new Goods(child);
//                goodsList.add(goods);
//            }
//        }
        float total = 0;
        int sum = 0;
        for (Goods goods : goodsList) {
            total += goods.getTotal();
            sum += goods.getNumber();
        }
        System.out.println(total);
        System.out.println(sum);
    }
    
    static class Goods {
        private float price;
        private int number;
        public Goods(Element element) {
            this.price = Float.parseFloat(element.getElementsByTagName("price").item(0).getTextContent());
            this.number = Integer.parseInt(element.getElementsByTagName("number").item(0).getTextContent());
        }
        public float getTotal(){
            return this.price * this.number;
        }
        public int getNumber(){
            return number;
        }
    }
}

node和element的关系

element一定是node但是node不一定是element,node可能是元素节点、属性节点、文本节点,而element表示包含开始标签和结束标签的完整元素。
所以上面的代码中 用

NodeList goodsNodeList = rootElement.getElementsByTagName("goods");

获取了NodeList,可以直接转型为Element: Element child = (Element) node;
如果获取的是node节点

NodeList goodsNodeList = rootElement.getChildNodes();

则必须在循环中增加判断if (node.getNodeType() == Node.ELEMENT_NODE) {} 判断当前节点是否为Element元素。

生成xml文件

将统计后的订单信息以xml格式输出,生成文件格式如下

<?xml version="1.0" encoding="utf-8"?>
<order>
    <total>15.6</total>
    <sums>7</sums>
</order>

生成xml的操作和读取的顺序类似,先创建rootElement,然后添加childElement,再将rootElement放到document中,最后通过io输出xml文件到指定路径。
代码如下:

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

public class XmlParser {
    
    DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
    TransformerFactory transformerFactory = TransformerFactory.newInstance();
    
    public Document parseDoc(String filePath) {
        Document document = null;
        try {
            DocumentBuilder builder = documentBuilderFactory.newDocumentBuilder();
            document = builder.parse(new File(filePath));
        } catch (ParserConfigurationException e) {
            e.printStackTrace();
        } catch (SAXException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return document;
    }
    
    public void generateXml(String filePath, Document document){
        DOMSource source = new DOMSource(document);
        Transformer transformer = createtransformer();
        PrintWriter pw = null;
        try {
            pw = new PrintWriter(new FileOutputStream(filePath));
            StreamResult result = new StreamResult(pw);
            transformer.transform(source, result);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (TransformerConfigurationException e1) {
            e1.printStackTrace();
        } catch (TransformerException e) {
            e.printStackTrace();
        } finally {
            pw.close();
        }
    }
    
    public Document createDoc() {
        Document document = null;
        try {
            DocumentBuilder builder = documentBuilderFactory.newDocumentBuilder();
            document = builder.newDocument();
            document.setXmlStandalone(true);
        } catch (ParserConfigurationException e) {
            e.printStackTrace();
        }
        return document;
    }
    
    public Transformer createtransformer(){
        Transformer transformer = null;
        try {
            transformer = transformerFactory.newTransformer();
            //default former
//            transformer.setOutputProperty(OutputKeys.STANDALONE, "yes");
            transformer.setOutputProperty(OutputKeys.ENCODING, "utf-8");
            transformer.setOutputProperty(OutputKeys.INDENT, "yes");
        } catch (TransformerConfigurationException e) {
            e.printStackTrace();
        }
        return transformer;
    }
    
    public static void main(String[] args) {
        XmlParser parser = new XmlParser();
        Document document = parser.parseDoc("D://shopping.xml");
        Element rootElement = document.getDocumentElement();
        List<Goods> goodsList = new ArrayList<Goods>();
        NodeList goodsNodeList = rootElement.getElementsByTagName("goods");
        for (int i = 0; i < goodsNodeList.getLength(); i++) {
            Element child = (Element) goodsNodeList.item(i);
            Goods goods = new Goods(child);
            goodsList.add(goods);
        }
        float total = 0;
        int sum = 0;
        for (Goods goods : goodsList) {
            total += goods.getTotal();
            sum += goods.getNumber();
        }
        Document orderDocument = parser.createDoc();
        Order order = new Order(total, sum);
        Element orderElement = order.getElement(orderDocument);
        orderDocument.appendChild(orderElement);
        
        parser.generateXml("D://order.xml", orderDocument);

    }
    
    static class Order {
        private float total = 0;
        private int   sum   = 0;
        
        public Order(float total, int sum) {
            this.total = total;
            this.sum = sum;
        }
        
        public Element getElement(Document document) {
            Element rootElement = document.createElement("order");
            
            Element totalElement = document.createElement("total");
            totalElement.setTextContent(String.valueOf(this.total));
            rootElement.appendChild(totalElement);
            
            Element sumElement = document.createElement("sum");
            sumElement.setTextContent(String.valueOf(this.sum));
            rootElement.appendChild(sumElement);
            
            return rootElement;
        }
        
    }
    
    static class Goods {
        private float price;
        private int   number;
        
        public Goods(Element element) {
            this.price = Float.parseFloat(element.getElementsByTagName("price").item(0).getTextContent());
            this.number = Integer.parseInt(element.getElementsByTagName("number").item(0).getTextContent());
        }
        
        public float getTotal() {
            return this.price * this.number;
        }
        
        public int getNumber() {
            return number;
        }
    }
}

standalone

这里有一个小坑 就是生成的xml中有一个属性为standalone=”no”
standalone表示是否为独立文件,也就是说是否依赖于其他外部文件,如果为yes,则表示为不依赖其他外部文件的独立文件,默认为yes。
但是生成之后的standalone=”no”,不符合预期,并且

transformer.setOutputProperty(OutputKeys.STANDALONE, "yes");

设置格式之后standalone=”no”仍然为no。这时需要设置document中的setXmlStandalone属性,

document.setXmlStandalone(true);

再次输出,可以去掉standalone属性。

结语

理解xml的结构之后,和dom的树形结构后,无论是使用原生支持还是通过第三方类库去操作xml文件,都可以很容易的上手。
and 我喜欢用json

转载请注明出处WangYuheng’s Blog

起因

业务场景为一个type=text的表单元素,通过ajax进行搜索。但是遇到一个bug,就是在输入之后按回车,就会自动提交表单。查找之后发现了浏览器的表单提交特性

如果只有一个text表单元素,回车会自动提交表单!!

为了避免这种bug,将表单回车后提交的场景都测试了一遍。

场景一:只有一个type=text表单元素

<form action="http://blog.crick.wang">
    <input type="text" name="username" />
</form>

在此场景下,只要在input中按下回车键,就会自动提交表单。
为了避免自动提交,可以设置一个隐藏的text元素,如

<form action="http://blog.crick.wang">
    <input type="text" style="display:none" />
    <input type="text" name="username" />
</form>

场景二:有n个type=text和一个type=submit表单元素

<form action="http://blog.crick.wang">
    <input type="text" name="username" />
    <input type="text" name="password" />
    <input type="submit" value="Submit!" />
</form>

在此场景下,只要在input中按下回车键,就会自动提交表单。
为了避免自动提交,网上常见的例子是在form表单中监听onkeydown事件,如果是回车输入,则返回false,阻止表单提交。

<form action="http://blog.crick.wang" onkeydown="if(event.keyCode==13){return false;}">
    <input type="text" name="username" />
    <input type="text" name="password" />
    <input type="submit" value="Submit!" />
</form>

但是这种写法存在1个问题:如果是textarea,可能会需要回车操作。

为了解决上述问题,需要增加针对textarea的判断条件,而且还要注意兼容ie。

<script type="text/javascript">
document.onkeydown = function(event) {
    event = event || window.event;
    var param = event.target || event.srcElement;
    // if (param.name=="username"){return true;}
    if (event.keyCode == 13) {
        if ("INPUT" == param.tagName) {
            return false;
        }
    }
};
</script>
<form action="http://blog.crick.wang">
    <input type="text" name="username" />
    <input type="text" name="password" />
    <textarea name="introduction"></textarea>
    <input type="submit" value="Submit!" />
</form>

根据*if (param.name==”username”){return true;}*就可以自定义在某个表单元素中,点击回车键,就可以提交表单。

场景三:有n个type=text,没有type=submit元素

此时点击回车键时,表单不会提交。可以通过监听onkeydown事件,利用submit()方法,根据keyCode和tagName或者表单元素的name来自定义提交规则

<script type="text/javascript">
document.onkeydown = function(event) {
    event = event || window.event;
    var param = event.target || event.srcElement;
    if (event.keyCode == 13 && param.name=="password"){
        document.getElementById("entityForm").submit();
    }
};
</script>
<form action="http://blog.crick.wang" id="entityForm">
    <input type="text" name="username" />
    <input type="text" name="password" />
    <textarea name="introduction"></textarea>
</form>

场景四:有n个type=text和一个button标签元素

button标签,如果未指定type,则在ie6和ie7下为type=”button”, 在ie8及chrome和火狐下为type=”submit”,为了避免这种混乱,button标签一定要指定type。
如果指定为type=”button”,则和场景三的处理方式一致,
如果是type=”submit”,则同场景二。

结语

ie的特性让web开发者抓狂,本来很多浏览器自身属性可以方便解决的问题,都需要重新造一个轮子,达到浏览器兼容。
制造轮子和规则,为了规避约定或者隐藏特性带来的不可预知的麻烦。

转载请注明出处WangYuheng’s Blog

起因

需要实现一个日历功能,网上找了几个示例,都是根据各种库和插件,居然没有纯净的js完成的日历插件,不免有些诧异,正好有时间,准备通过js编写一个简单的日历demo基础,可自由根据使用的插件进行显示层的定制。

效果图

javascript-calendar-01
javascript-calendar-02

前提

算法

说一下日历的算法

  1. (本月第一天的星期数+本月的天数)/7 可以知道本月需要占据几行。
  2. 将日历看为二维数组,第一级遍历条件为本月行数,第二级为0~7(直观形象为一个日历table,自左向右,自上而下循环)。
  3. n为0~7循环参数, 计算当前日历方格显示数值为当前日期: 行数*7 + n - 本月第一天 + 1。
  4. 二月的天数根据闰年会有不同,闰年的计算方式: 当前年份能被400整除,或者当前的年份能被4整除且不能被100整除。

Date

日历功能实现必须通过js的Date对象提供的基础数值。介绍一下用到的方法功能

new Date().getFullYear(); //当前年份 2015
new Date().getMonth(); //当前月份 0~11 
new Date().getDate(); //当前月中的某一天 1~31
new Date().getDay(); //一周中的某一天 0~6
new Date(year, month, day); //根据构造函数返回Date对象

实现

这里写出来编写代码思路分析的过程,可以跳过直接看下方的完整代码。
先抽象出二维数组模型,7列已经确定,需要先根据算法1算出本月的日历行数,但是二月的天数不一致,所以先要根据是否为闰年算出二月的天数。

var calendar = {
    isLeap: function(vYear) {
        return vYear % 400 == 0 || (vYear % 4 == 0 && vYear % 100 != 0);
    },
    getFebruaryDays: function(vYear) {
        return this.isLeap(vYear) ? 29 : 28;
    }
}

本月第一天的星期数可以根据new Date(currentYear, currentMonth, 1).getDay()得出。
monthFirstDay为本月第一天的星期数,monthDays为12月的天数组成的数组。根据构造函数计算赋值,并返回this对象方便链式调用。

var calendar = {
    year: null,
    month: null,
    date: null,
    day: null,
    monthDays: null,
    monthFirstDay: null,
    ctor: function(vNow) {
        var now = vNow || new Date(); //如果为指定date对象,则为当前日期对象
        this.year = now.getFullYear();
        this.month = now.getMonth();
        this.date = now.getDate();
        this.day = now.getDay();
        this.monthFirstDay = new Date(this.year, this.month, 1).getDay();
        this.monthDays = [
            31, this.getFebruaryDays(this.year), 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
        ];
        return this;
    },
    isLeap: function(vYear) {
        return vYear % 400 == 0 || (vYear % 4 == 0 && vYear % 100 != 0);
    },
    getFebruaryDays: function(vYear) {
        return this.isLeap(vYear) ? 29 : 28;
    }
}

计算日历行数

getMonthDay: function() {
    return this.monthDays[this.month];
},
getCalTr: function() {
    return (this.monthFirstDay + this.getMonthDay()) / 7;
},

循环二维数组得到日历, *console.log(n);*可结合使用的框架进行输出展示。

cal: function() {
    var that = this;
    for (var i = 0; i < this.getCalTr(); i++) {
        for (var j = 0; j < 7; j++) {
            var n = (i * 7 + j) - that.monthFirstDay + 1;
            
            if (n > 0 && n <= that.getMonthDay()) {
                console.log(n);
            }
        }
    }
}

完整代码,在控制台查看输出的日历

var calendar = {
    year: null,
    month: null,
    date: null,
    day: null,
    monthDays: null,
    monthFirstDay: null,
    days: [
        "星期一",
        "星期二",
        "星期三",
        "星期四",
        "星期五",
        "星期六",
        "星期日"
    ],

    ctor: function(vNow) {
        var now = vNow || new Date(); //如果为指定date对象,则为当前日期对象
        this.year = now.getFullYear();
        this.month = now.getMonth();
        this.date = now.getDate();
        this.day = now.getDay();
        this.monthFirstDay = new Date(this.year, this.month, 1).getDay();
        this.monthDays = [
            31, this.getFebruaryDays(this.year), 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
        ];
        return this;
    },
    isLeap: function(vYear) {
        return vYear % 400 == 0 || (vYear % 4 == 0 && vYear % 100 != 0);
    },
    getFebruaryDays: function(vYear) {
        return this.isLeap(vYear) ? 29 : 28;
    },
    format: function() {
        return (this.month + 1) + "/" + this.date + "/" + this.year + "  " + this.dayMap[this.day];
    },
    getDay: function() {
        return this.days[this.day - 1]
    },
    getMonthDay: function() {
        return this.monthDays[this.month];
    },
    getCalTr: function() {
        return (this.monthFirstDay + this.getMonthDay()) / 7;
    },
    cal: function() {
        var that = this;
        console.log("%c   日   一   二   三   四   五   六   ",'background: #222; color: #bada55');
        console.log("-----------------------------------");
        for (var i = 0; i < this.getCalTr(); i++) {
            var temp = "";
            for (var j = 0; j < 7; j++) {
                var tempData = (i * 7 + j) - that.monthFirstDay + 1
                if (tempData < 1) {
                    temp = temp + "prev ";
                } else if (tempData > that.getMonthDay()) {
                    temp = temp + " next";
                } else {
                    if (tempData > 9) {
                        temp = temp + "  " + tempData + " ";
                    } else {
                        temp = temp + "   " + tempData + " ";
                    }
                }
            }
            console.log(temp);
            console.log("-----------------------------------");
        }
    }
}
calendar.ctor().cal();
calendar.ctor(new Date(2015, 10)).cal();

总结

不足

本例中的ctor并非构造函数,而是针对一个对象进行变更,重新执行ctor后会替换calendar的属性值。
未对之前和之后的日期进行获取,如需相关功能,可在ctor中设置prevMonth 和 nextMonth的值,需要注意跨年度问题,时间和需求问题,暂未实现。

思考

是否采用构造函数的方式生成多个对象? (如果是工具类的话,不需要构造函数)
方法中采用this.param 还是获取外部参数 如何界定?

收获

用面向对象的方式编写js,思路清晰,语感更好。

转载请注明出处WangYuheng’s Blog

起因

在编写的cms系统中,提供了图片上传功能,但是用户经常(或者说从不)按照推荐的比例上传图片,当然这不是主要问题,关键点在于,经常会上传一些超大分辨率的图片,严重影响整个网站的加载速度。
面对这个问题,除了限制上传图片的大小外,是否还有更自动化的解决方案?

待解决问题:

  1. 图片长宽比
  2. 图片大小
  3. 图片格式
  4. 水印(用户要求)
    关于添加水印的问题,其实用户希望制作一款不能被下载图片的网站,但个人能力有限,实在不能胜任。于是用户提出水印要求,本身可能会觉得水印是本地图片操作的职责,但为了体验系统的自动化,决定增加自动添加水印功能。

解决方案

java 的io操作一直是我较为困扰的部分。原因无它,因为可以实现的方式实在是太多了,在数十个工具类和数个知名类库中该如何选择?而java在图像处理方面,私以为并不占有优势。
经过多方比较,适用,最终采用了thumbnailator这一开源解决方案

thumbnailator

thumbnailator是一个开源的java生成缩略图类库,没有依赖其他外部库,不需要自己去学习Image I/O API, Java 2D API, image processing, image scaling techniques, BufferedImages,Graphics2D 等技术,可以简单方便的实现这些功能。

来源

thumbnailator是一个简单的jar文件,也支持通过maven进行依赖管理

<dependency>
    <groupId>net.coobird</groupId>
    <artifactId>thumbnailator</artifactId>
    <version>0.4.8</version>
</dependency>

使用方法

thumbnailator提供的功能简单、强大,且API规则良好,可以容易的理解接口含义。下面针对上面的问题,介绍一下thumbnailator的主要功能。

重置图片大小

Thumbnails.of(new File("original.jpg"))
    .size(640, 480)
    .outputFormat("jpg")
    .toFiles(Rename.NO_CHANGE);

new File(“original.jpg”) 可以是listFiles()来实现对目录下所有文件的操作,也可以直接输入String类型”original.jpg”
toFiles表示文件输出位置,单个文件可以自定义指定文件路径。
在处理多文件时,可以根据Rename提供的方法Rename.NO_CHANGE表示变更当前图片文件,还有Rename.PREFIX_DOT_THUMBNAIL等增加前缀、后缀等方式,此时会保留原文件和生成后的图片文件。
outputFormat现支持ImageIO.getWriterFormatNames();提供的格式类型,本测试环境为jdk1.6.0_38,提供的类型有BMP bmp jpg JPG wbmp jpeg png PNG JPEG WBMP GIF gif

生成一个旋转90°带水印的缩略图

Thumbnails.of(new File("original.jpg"))
        .size(160, 160)
        .rotate(70)
        .watermark(Positions.BOTTOM_RIGHT, ImageIO.read(new File("watermark.png")), 0.5f)
        .outputQuality(0.8)
        .toFile(new File("image-with-watermark.jpg"));
        

rotate表示图片旋转角度,如果不是进行直角(n*90°)旋转的话,图片的size会发生变化。
watermark指定了水印,通过Positions指定水印位置,BufferedImage指定水印图片,第三个参数指定了水印的不透明性,范围为(01.0f),1.0f为不透明。
outputQuality 指定输出图片的质量,范围同样为(0
1.0f)。

除了toFile,Thumbnails还可以将图片输出为OutputStream(toOutputStream)和BufferedImage(asBufferedImage),或者是指定的输出目录(toFiles)。

按比例缩略图片

Thumbnails.of(new File("original.jpg"))
    .scale(0.2)
    .outputFormat("jpg")
    .toFiles(Rename.NO_CHANGE);

scale提供了按照比例缩略图片的方法,也可以通过大于1的数字扩大图片。

存在的问题

在测试环境中,发现水印图片过大导致显示不全和旋转图片后的图片比例变更等问题,都是因为使用不当。
在生产环境中,暂时未遇到使用问题。如果有问题,会在这里指出,并提供解决方案。

结语

thumbnailator是难得的让我通过java简单的使用io和图像处理的工具类,很喜欢它提供api方式,正在学习源码。

Best regards
Wang Yuheng

转载请注明出处WangYuheng’s Blog

简单说一下业务场景,前台用户通过input输入内容,在离开焦点时,将内容在div中显示。
这时遇到一个问题,如果用户输入了html标签,则在div显示中,标签被解析。
由于是纯前端操作,不涉及后端,因此需要通过js对输入内容进行转义。

这里提供一个非常简单有效的转义方案,利用了innerHTMLinnerText
注:火狐不支持innerText,需要使用 textContent 属性,而IE早期版本不支持此属性,为了同时兼容IE及火狐,需要进行判断操作.

因为innerText(textContent)会获取纯文本内容,忽略html节点标签,而innerHTML会显示标签内容,
所以我们先将需转义的内容赋值给innerText(textContent),再获取它的innerHTML属性,这时获取到的就是转义后文本内容。
代码如下:

function HTMLEncode(html) {
    var temp = document.createElement("div");
    (temp.textContent != null) ? (temp.textContent = html) : (temp.innerText = html);
    var output = temp.innerHTML;
    temp = null;
    return output;
}

var tagText = "<p><b>123&456</b></p>";
console.log(HTMLEncode(tagText));//&lt;p&gt;&lt;b&gt;123&amp;456&lt;/b&gt;&lt;/p&gt; 

通过测试结果,可以看到html标签及&符都被转义后保存。
同理,反转义的方法为先将转义文本赋值给innerHTML,然后通过innerText(textContent)获取转义前的文本内容

function HTMLDecode(text) { 
    var temp = document.createElement("div"); 
    temp.innerHTML = text; 
    var output = temp.innerText || temp.textContent; 
    temp = null; 
    return output; 
} 
var tagText = "<p><b>123&456</b></p>";
var encodeText = HTMLEncode(tagText);
console.log(encodeText);//&lt;p&gt;&lt;b&gt;123&amp;456&lt;/b&gt;&lt;/p&gt;
console.log(HTMLDecode(encodeText)); //<p><b>123&456</b></p> 

Best regards
Wang Yuheng

转载请注明出处WangYuheng’s Blog

由于网络连接不稳定,在连接局域网内的mysql开发服务器时,连续多次断开,结果无法连接到mysql服务器,报错内容如下:

Host 'host_name' is blocked because of many connection errors.
Unblock with 'mysqladmin flush-hosts'

这是mysql自带的一种保护机制,如果客户端连续连接失败次数max_connect_errors,超过默认值,则会拒绝客户端继续连接。
可以通过

SHOW VARIABLES LIKE 'max_connect_errors'
    

查看允许连接失败的次数,默认为100次。由于网络不稳,可能会让JDBC等连接很快超过此默认值,可以通过修改此默认值来避免出现连接失败的情形,如

SET GLOBAL max_connect_errors=10000;

如果本机已被拒绝连接,可以通过另一台可连接到mysql服务器的主机执行以下命令

 mysqladmin flush-hosts -h localhost -uroot -p *****
 

此命令会清空所有的hosts缓存信息,执行后即可链接

Best regards
Wang Yuheng

转载请注明出处WangYuheng’s Blog

mysql 查询缓存

在sql调优的过程中,发现原本很慢的一条sql(将近1分钟) 在第二次运行时, 瞬间就完成了(0.04sec)。
这是因为mysql自带的缓存机制,将查询结果进行缓存,如果table数据未发生变化,再次使用同一条sql进行查询时,直接从上次的查询结果缓存中读取数据,而不是重新分析、执行sql。
如果table数据发生变化,所有与之相关的缓存都会被释放刷新,这样就不会出现数据脏读问题。

The query cache stores the text of a SELECT statement together with the corresponding result that was sent to the client. If an identical statement is received later, the server retrieves the results from the query cache rather than parsing and executing the statement again. The query cache is shared among sessions, so a result set generated by one client can be sent in response to the same query issued by another client.


是否使用查询缓存

为了避免缓存,可以在sql查询语句的字段前增加 SQL_NO_CACHE 关键字
如:

select * from t_user;

select SQL_NO_CACHE * from t_user;

反之,你也可以使用 SQL_CACHE 关键字,强制mysql从缓存中读取数据

select SQL_CACHE * from t_user;

mysql还提供了一种释放全部缓存的方法

reset query cache;  

设置查询缓存

查看是否有查询缓存。

SHOW VARIABLES LIKE 'have_query_cache';

注意,只要数据库拥有查询缓存功能,这个VALUE就是YES,无论查询缓存是否启用。

则查询缓存为启用状态。mysql默认为启用状态

mysql查询缓存可以通过两个变量来控制,query_cache_typequery_cache_size


query_cache_type

SHOW VARIABLES LIKE 'query_cache_type';

query_cache_type包含三种状态

  • 0 or OFF 此时不会从缓存中读取查询数据
  • 1 or ON 表示除非声明了SELECT SQL_NO_CACHE,否则都会从缓存中读取数据
  • 2 or DEMAND 表示所有语句都会从缓存中读取,相当于所有查询语句都使用了SELECT SQL_CACHE

通过如下命令可以设置查询缓存状态(需要管理员权限),执行后,需要重启mysql服务才能生效。

SET GLOBAL query_cache_type = 1;  

但是此命令会影响所有的使用此mysql服务的client。可以通过如下命令,关闭此客户端的查询缓存状态,但是同样需要重启server后才能生效。

SET SESSION query_cache_type = OFF;

SHOW VARIABLES LIKE 'query_cache_type';

query_cache_size

SHOW VARIABLES LIKE 'query_cache_size';

query_cache_size表示缓存大小,默认为1M。如果设置为0,则相当于query_cache_size=OFF

同样可通过 SET GLOBAL 进行设置

SET GLOBAL query_cache_size=40000;

需要注意的是,设置的query_cache_size,并不全是用于存储数据,还有约40KB的空间来维护查询缓存的结构。


Best regards
Wang Yuheng

转载请注明出处WangYuheng’s Blog

java.lang#split方法简介

java.lang提供的split方法,可以根据指定分隔符,将String字符串,切割并返回String[]字符串数组。

public String[] split(String regex)
public String[] split(String regex, int limit)

regex为指定分隔符,支持正在表达式
limit为切割后的数组长度,默认为0,不会超过实际长度
例:

public static void main(String[] args) {
    String str = "a,b,c,d,e";
    String[] arr1 = str.split(","); // ["a","b","c","d","e"] length=5
    String[] arr2 = str.split(",", 3); //["a", "b", "c,d,e"] length=3
    String[] arr3 = str.split(",", 10); //["a","b","c","d","e"] length=5
    String[] arr4 = str.split("nan"); //["a,b,c,d,e"] length=1
    String[] arr5 = "".split("nan"); //[""] length=1
}

特殊用法

因为split支持正则参数,可能会需要根据多个分隔符进行分隔,如 id=? and gender=? or age>?,希望根据andor两个分隔符进行分隔,则可通过正则实现。

public static void main(String[] args) {
    String str = "id=? and gender=? or age>?";
    String[] arr1 = str.split("and | or"); // ["id=? ","gender=?"," age>?"] length=3
}

注意上述中的空格符, 如果只根据关键字分隔,不能输入空格。**”and|or”**,可实现分隔效果。


易现exception

NullPointerException

字符串为””时, 返回结果为String[]{“”},但是字符串为null,会抛出空指针异常java.lang.NullPointerException

ArrayIndexOutOfBoundsException

标题中出现的java.lang.ArrayIndexOutOfBoundsException, 是一个容易出现的bug,因为支持正则参数,如果传入参数为转义符,则会出现非预期的结果数据。
如:

public static void main(String[] args) {
    String str = "a.b.c.d.e";
    String[] arr1 = str.split("."); // length=0 如果读取数组 则会出现数组越界异常ArrayIndexOutOfBoundsException
    String[] arr2 = str.split("\\."); // ["a","b","c","d","e"] length=5
}

同理, 根据**”|”分隔,需要使用“\|”**

java.util.regex.PatternSyntaxException

同样是由于正则表达式参数,**”+”** “*”需要使用“\+” **”\*”**进行转义,否则,会报错java.util.regex.PatternSyntaxException

public static void main(String[] args) {
    String str = "a+b+c+d+e";
    String[] arr1 = str.split("\\+"); // ["a","b","c","d","e"] length=5
    String[] arr2 = str.split("+"); // java.util.regex.PatternSyntaxException: Dangling meta character '+' near index 0
}

Best regards
Wang Yuheng

转载请注明出处WangYuheng’s Blog

Markdown是一种可以使用普通文本编辑器编写的标记语言,通过简单的标记语法,它可以使普通文本内容具有一定的格式。

初识markdown

       第一次接触markdown,用在blog的编辑器上,发现并没有传统的富文本支持,自己的代码格式又是一片混乱,然后发现了一句“支持markdown语法”。经过查找之后发现,markdown确实可以提供非常良好的写作体验。
markdown给我的第一感觉是类似于HTML语言,同样都是通过简单的标记实现样式显示,但是markdown要简单的多,并且通过普通的文本编辑器就可书写,也可以非常方便的进行移植。

markdown优点

  • 纯文本实现,兼容各种文本编辑器,移植行好
  • 标签简单,可读性好
  • 插件众多,扩展性强
  • 可导出为HTML、PDF等多种格式

我最看重的一点是,对于blog而言,markdown能让我更专注于文字内容本身,而不用关心布局。这有助于让我摆脱网站布局和繁重的word。


学习markdown

markdown语言简单,容易记忆,只需了解一些常用的标记方式,就可实现文字的编辑工作。
(**注:**markdown的标准语言为:标签和文字内容之间要保留一个空格。)

常用标记

标题

将一句话作为文章的标题,只需在文字前加上**#**即可。
# 一级标题
## 二级标题
### 三级标题
以此类推,markdown和HTML一样,也提供了6种不同大小的标题方式。

列表

列表分为有序列表和无序列表两种:

无序列表只需在文字前加上**-或***,可混合使用,推荐使用**-**
- 列表1
* 列表2
- 列表3

  • 列表1
  • 列表2
  • 列表3

有序列表直接在文字前加上“1.” “2.”,可以全部输入数字“1.” ,markdown会自动实现排序
1. 列表1
1. 列表2
1. 列表3

  1. 列表1
  2. 列表2
  3. 列表3

格式

提供了非常方便的粗体和斜体格式规则 一个* 包含的文字为斜体, 两个*包含则为粗体

*斜体格式*

斜体格式

**粗体格式**

粗体格式

引用

引用外部一段文字的语法非常简单,只需在文字前加上大于号**>**
>这是一段引用的文字

这是一段引用的文字

链接

[链接](http://wangyuheng.github.io)

链接

图片

图片和链接的使用方式类似,差别在于图片需要前面加一个!
![](http://wangyuheng.github.io/images/searchicon.png)

表格

| Tables        | Are           | Cool  |
| ------------- |:-------------:| -----:|
| col 3 is      | right-aligned | $1600 |
| col 2 is      | centered      |   $12 |
| zebra stripes | are neat      |    $1 |
Tables Are Cool
col 3 is right-aligned $1600
col 2 is centered $12
zebra stripes are neat $1

分割线

分割线的语法很简单,只需要三个*****就可以,相当于HTML中的
,文本会另起一行
***


markdown相关软件


结语

通过上面的简单介绍,已经可以在提供了markdown的环境下,简单的使用这一写作神奇了。至于更高级的功能、软件,以及熟练程度,都需要在实际的使用过程中慢慢积累。
最后,祝您写作愉快!


Best regards
Wang Yuheng

0%