er3456qi

er3456qi 查看完整档案

填写现居城市  |  填写毕业院校  |  填写所在公司/组织填写个人主网站
编辑

是个好人

个人动态

er3456qi 回答了问题 · 2016-02-20

解决作为一位c#程序员。已基本入门。求推荐书籍进行提升

如果只是基本入门,《CLR via C#》先不要看。先看看《C# 本质论》,然后《深入理解C#》,然后《Framework Design Guidelines》,后面再考虑看看CLR相关的

关注 8 回答 7

er3456qi 发布了文章 · 2016-02-16

Try Redis : Redis 入门教程

开篇

Redis 是一种以键值对(key-value)存储数据的NoSQL数据库。

键值对存储数据的本质是以某个键存储某个值。之后你可以用这个键把存储的值取出来。可以用SET命令以键‘servername’存储值‘fido’:

SET servername 'fido'

这样,数据就被存储了,之后可以使用GET取出刚刚存储的数据:

GET servername // 返回 "fido"

对于数据的操作,还有一些基本的命令,比如INCRDEL

INCR 用于原子地递增一个数值数据。而DEL则是删除一个值。

SET connections 10
INCR connections // 返回 11
INCR connections // 返回 12
DEL connections
INCR connections // 返回 1

给值指定寿命

可以通过EXPIRE设置一个值的存活时间,过了这个时间,该值就会被删除。通过TTL可以查看值的存活时间。

对于TTL

  • 如果一个值没有设置存活时间,那么TTL会返回-1,表示这个值不会过期(这是值的默认寿命:长生);

  • 如果一个值设置了存活时间,在存活时间内,对值使用TTL会返回相应的生命剩余时间;

  • 如果对一个不存在的值或是已经超过存活时间(会被删除)的值使用TTL,会返回-2

注意,每使用SET设置一个值时,该值的TTL都会被重置为默认。

例子:

SET resource:lock 'Redis Demo 1'
TTL resource:lock // 返回 -1

EXPIRE resource:lock 120 //设置存活时间为120秒

// 7秒后
TTL resource:lock // 返回 113
// 120秒以后
TTL resource:lock // 返回 -2

SET resource:lock 'Redis Demo 2'
TTL resource:lock // 返回 -1
 

列表(list)

Redis也支持一些复杂的/复合的(complex)数据结构。这里第一个要说的是列表。列表是一系列有序的值的集合。
与列表交互的几个重要方法有:RPUSH, LPUSH, LLEN, LRANGE, LPOPRPOP

  • RPUSHLPUSH用于在列表的右端和左端插入数据。

  • LLEN返回列表的长度。

  • LRANGE返回一个子列表,它接收两个参数,它们标识你所要的子序列的首尾元素在原序列的位置。
    如果第二个元素是-1,则表示到序列的末尾。

  • LPOPRPOP删除并返回左右两端的第一个元素(跟栈的pop一样)。

例子(不用显式的创建列表,在向一个不存在的列表中插入值时,列表会被自动创建,当列表中的最后一个元素被pop后,列表会被自动删除):

RPUSH friends "Alice" // 创建一个列表friends并对其添加一个元素"Alice"
RPUSH friends "Bob"  // 向friends添加元素"Bob"
LPUSH friends "Sam" // 向friends添加元素"Sam"

LRANGE friends 0 -1 // 返回 1) "Sam", 2) "Alice", 3) "Bob"
LRANGE friends 0 1 // 返回 1) "Sam", 2) "Alice"
LRANGE friends 1 2 // 返回 1) "Alice", 2) "Bob"

LLEN friends // 返回 3
LPOP friends // 返回 "Sam"
RPOP friends // 返回 "Bob"

LLEN friends // 返回 1
LRANGE friends 0 -1 // 返回 1) "Alice"

集合(set)

集合跟列表类似,但是集合是无序的,且集合内元素唯一。

集合的几个常用命令为:SADD, SREM, SISMEMBER, SMEMBERSSUNION

  • SADD 向集合中添加值。

  • SREM 从集合中删除给定的值。

  • SISMEMBER 接收一个参数,用以判断该参数的值是否在集合中,若在集合中返回1,否则返回0。
    如果不给参数,则返回整个列表。

  • SMEMBERS 返回集合中所有元素。

  • SUNION 合并两个集合。

例子(跟列表一样,集合也不用显式创建):

SADD superpowers "flight"
SADD superpowers "x-ray vision"
SADD superpowers "reflexes"

SREM superpowers "reflexes"

SISMEMBER superpowers "flight" // 返回 1
SISMEMBER superpowers "reflexes" // 返回 0

SMEMBERS superpowers // 返回 1) "flight", 2) "x-ray vision"

SADD birdpowers "pecking"
SADD birdpowers "flight"
SUNION superpowers birdpowers // 返回 1) "pecking", 2) "x-ray vision", 3) "flight"

有序集合(Sorted Sets)

集合是个很好用的数据结构,但是因为它是无序的,在某些情况下使用会不太方便。所以Redis 1.2 引入了有序集合。

有序集合的命令是Z开头,比如:有序集合的数据插入用的是ZADD而不是SADD
有序集合跟常规集合类似,不过有序集合的每个值都有一个与其关联的分数(associated score),这个分数用于排序集合内元素。

来一个例子:

ZADD hackers 1940 "Alan Kay"
ZADD hackers 1906 "Grace Hopper"
ZADD hackers 1953 "Richard Stallman"
ZADD hackers 1965 "Yukihiro Matsumoto"
ZADD hackers 1916 "Claude Shannon"
ZADD hackers 1969 "Linus Torvalds"
ZADD hackers 1957 "Sophie Wilson"
ZADD hackers 1912 "Alan Turing"

在例子中,第一个参数(出生年)是排序的分数,下面获取索引值2到4的元素(从0开始):

ZRANGE hackers 2 4 // 返回 1) "Claude Shannon", 2) "Alan Kay", 3) "Richard Stallman"

Hashes

Hashes 是字符串字段和字符串值之间的映射。所以它是表示对象的最佳数据类型:

HSET user:1000 name "John Smith"
HSET user:1000 email "john.smith@example.com"
HSET user:1000 password "s3cret"

使用HGETALL获得存储的数据(返回所有的字段名和字段值):


HGETALL user:1000

也可以把对象的属性一次设置完:

HMSET user:1001 name "Mary Jones" password "hidden" email "mjones@example.com"

获取某个特定字段:

HGET user:1001 name // 返回 "Mary Jones"

数值类型在hash字段中也是很好用的,比如原子地步进一个数啥的都是可以的:

HSET user:1000 visits 10
HINCRBY user:1000 visits 1 // 返回 11
HINCRBY user:1000 visits 10 // 返回 21
HDEL user:1000 visits
HINCRBY user:1000 visits 1 // 返回 1

结束

本文翻译自 Try Redis

到此为止,try redis教程结束。更多内容,请看下面链接:

查看原文

赞 1 收藏 31 评论 2

er3456qi 回答了问题 · 2016-02-15

Java真数组

第一次听说真数组,不过听过为伪数组,就像是前面同学说的,JavaScript的数组就是典型的伪数组。不过非要说真数组的话,你看的那个百科里的解释应该也算对。

但是说到数组,还有一个多维数组,它在不同的语言里实现也不一样,比如在C++里面,多维数组在内存里是连续的,但是在java里,多维数组的每一维在内存里很可能不是连续的。所以这个了解了就好,没有必要细究。

关于健壮性,没啥感觉。。。

关注 5 回答 4

er3456qi 回答了问题 · 2016-02-15

解决关于java的==的疑问

给你一个不太详细回答(因为不想再捣鼓java了,所以就没细查资料):

首先,int == Integer,这个比较应该是Integer对象发生了自动拆箱,
其实实际是这样比较的:
int == integer.intValue()

也就是说,

System.out.println("a==b:"+(a == b));

System.out.println("a==b:"+(a == b.intValue()));

编译后的结果是一样的。

你可以找java的反编译工具(比如jad),看看上面两种方式的class文件。

另外,对象跟对象使用 == 比较,确实比较的是地址,所以一般对象之间要比较的话都用equals,但是值类型的比较,比较的是值,所以你的结果就是那样的。

不过,还有一个类似的例子你可以试试:

Integer d = 127;
Integer e = 127;
System.out.println(d == e);

Integer g = 128;
Integer h = 128;
System.out.println(g == h);

看看结果。

最后,C sharp 大法好,退java保平安。

关注 8 回答 7

er3456qi 回答了问题 · 2016-02-06

x经问题: Why choose Ruby or Python over PHP for web development?

喜欢python不喜欢ruby,学了挺长时间的python 3,后来学了一点框架,webpy,django,tornado都入门了,然后后来发现国内好多公司用的都是python 2.× ,但是我想写2.x的代码,所以后来决定学了nodejs。。。

关注 4 回答 7

er3456qi 回答了问题 · 2016-02-03

怎么把c#程序转成java?

最简单的方法是手动重写一遍

关注 12 回答 11

er3456qi 回答了问题 · 2016-02-03

python解析html,哪个库好用点?

BeautifulSoup

关注 8 回答 6

er3456qi 发布了文章 · 2016-02-03

Typescript Handbook 精简版之变量声明

变量声明

letconst 是JavaScript新出的两个变量声明的方式。前面说过letvar类似,但是它们的作用域是不一样的。

关于作用域,在ES6之前的Javascript中,函数体是唯一能能够创建新作用域的地方。那时候没有let,用var声明的变量,作用域要么是全局,要么是函数体,没有块级的作用域(块作用域变量在包含它们的块外部或for循环外部是不能被访问的)。而新版的Javascript引入了let关键字,用以声明一个块级域的本地变量,这样能避免一些问题。

至于const,它的作用域和let一样,但是是声明创建一个只读常量,这里要注意一下,这并不意味着该常量指向的值不可变,而是该常量只能被赋值一次!

举个例子:

// numLivesForCat的值不能再变了
const numLivesForCat = 9;
const kitty = {
    name: "Aurora",
    numLives: numLivesForCat,
}

// 错误,kitty指向的对象不能变
kitty = {
    name: "Danielle",
    numLives: numLivesForCat
};

// 没问题,对象的属性可以变化
kitty.name = "Rory";
kitty.name = "Kitty";
kitty.name = "Cat";
kitty.numLives--;

另外,使用const定义常量时,一定要初始化。

Typescript作为Javascript的超集,自然也是支持letconst的。

该用哪个

const没什么好说的,你需要常量就用它,不需要就不用。但是letvar要比较一下。

使用var声明变量时,不管你声明多少次,你得到的只有一个变量,而使用let时,同一个变量名在同一作用域内声明一次以上会报错。

使用var时很容易出bug,比如:

function sumMatrix(matrix: number[][]) {
    var sum = 0;
    for (var i = 0; i < matrix.length; i++) {
        var currentRow = matrix[i];
        for (var i = 0; i < currentRow.length; i++) {
            sum += currentRow[i];
        }
    }

    return sum;
}

里层for循环中的i会覆盖外层的i,因为i引用的都是相同的函数作用域内的变量。

但是如果把var换成let,内层的for循环自己是一个块级作用域,会屏蔽外部的作用域中的相同名字的变量,所以这两个i会井水不犯河水。

另外,因为var是函数作用域,所以对于一个var声明的变量,你可以先使用再声明:

bla = 2;
var bla;
// ...

// 可以理解为下面这样:

var bla;
bla = 2;

引用一段mozilla文档中的话:

由于变量声明(以及其他声明)总是在任意代码执行之前处理的,所以在代码中的任意位置声明变量总是等效于在代码开头声明。这意味着变量可以在声明之前使用,这个行为叫做“hoisting”。

嗯,使用var时,这叫变量声明提升,但是如果你使用let时也这样做,这叫错误! 块级作用域的变量的一个特点是,它们不能在被声明之前使用

最后,建议尽量用let替换var

本文参考:

查看原文

赞 0 收藏 4 评论 0

er3456qi 发布了文章 · 2016-02-02

Typescript Handbook 精简版之基础类型

简介

Typescript支持与Javascript几乎一样的数据类型:布尔值、数字、字符串,结构体等,此外Typescript还提供了很实用的枚举类型。

Boolean(布尔值)

let isDone: boolean = false;

Number(数字)

和Javascript一样,Typescript中的数字都是浮点数。
Typescript中的数字支持二进制、八进制、十进制、十六进制。

let binary: number = 0b1010;
let octal: number = 0o744;
let decimal: number = 6;
let hex: number = 0xf00d;

String(字符串)

单双引号都可以,

let name: string = "bob";
name = 'smith';

支持跟C#一样的内嵌表达式,即使用反引号`包围内嵌字符串,然后以${}的方式嵌入变量:

let name: string = `Gene`;
let age: number = 37;
let sentence: string = `Hello, my name is ${ name }.

I'll be ${ age + 1 } years old next month.`

Array(数组)

let list: number[] = [1, 2, 3];

或者这样定义:

let list: Array<number> = [1, 2, 3];

Tuple(元组)

跟python中的元祖一样,当然跟C#中的元组更像(因为有元素的类型声明):

// Declare a tuple type
let x: [string, number];
// Initialize it
x = ['hello', 10]; // OK
// Initialize it incorrectly
x = [10, 'hello']; // Error

Enum(枚举)

enum Color {Red, Green, Blue};
let c: Color = Color.Green;

枚举默认是从0开始为元素编号,也可以手动指定成员的数值:

enum Color {Red = 1, Green, Blue};
let c: Color = Color.Green;

或者全部指定元素编号:

enum Color {Red = 1, Green = 2, Blue = 4};
let c: Color = Color.Green;

可以通过枚举值获得枚举的名字:

enum Color {Red = 1, Green, Blue};
let colorName: string = Color[2];

alert(colorName); // Green

Any(任意类型)

通常为那些在编程阶段还不能够确认数据类型的变量指定为Any类型。这些值可以来自动态内容,比如用户输入或是第三方库。
这种情况下,我们不希望类型检查器对这些值进行检查而是直接让它们通过编译时的检查。
这时候可以使用 any 类型:

let notSure: any = 4;
notSure = "maybe a string instead";
notSure = false; // okay, definitely a boolean

你可能觉得 anyObject 的作用一样。但是,实际并不是这样。
你的确可以给 Object 类型赋任何类型的值,然后你却不能在它上面调用任意方法(即使那个对象真的有这些方法)! 但是Any类型可以!

let notSure: any = 4;
notSure.ifItExists(); // okay, ifItExists might exist at runtime
notSure.toFixed(); // okay, toFixed exists (but the compiler doesn't check)

let prettySure: Object = 4;
prettySure.toFixed(); // Error: Property 'toFixed' doesn't exist on type 'Object'.

当你只知道一部分数据的类型的时候, any 类型也很有用。比如你有一个数组,数组内包含不同类型(如果类型全都知道,可是用Tuple):

let list: any[] = [1, true, "free"];

list[1] = 100;

Void(空类型)

void 有点类似于 any 的反义词: 它表示没有任何类型。通常用在没有返回值的方法:

function warnUser(): void {
    alert("This is my warning message");
}

声明一个 void 类型的变量没啥用,因为你只能给它赋值 undefined 或是 null

let unusable: void = undefined;

关于let

let 是Javascript新添加的一个关键字,功能了var类似,但是它们的作用域不一样(详细自己查)。let更像是其他语言中定义变量的关键字,所以尽量用let来替代var

注:原内容来自github上的官方Handbook,这里我只是做了精简加翻译。

写这个的目的是作为学习Typescript的笔记,当然发出来也是为了分享给对Typescript感兴趣的同学。学Typescript,最好有编程语言基础,最好是学过Javascript或是C#。

另外:这里有人翻译的完整版。

查看原文

赞 1 收藏 2 评论 0

er3456qi 发布了文章 · 2015-07-28

回调函数,就是回头再调用的函数

又遇到了回调函数,这次打算写下来分享一下。

所谓回调函数,或者在面向对象语言里叫回调方法,简单点讲,就是回头在某个时间(事件发生)被调用的函数。

再详细点:就是一个函数A,作为参数,传入了另一个函数B,然后被B在某个时间调用。

这里可以有疑问了,既然是一个函数调用另一个函数,可以在函数体里面调用啊,为什么还要把函数作为参数传到另一个函数里被调用?何况还有一些语言(比如java)不支持把函数作为参数。

对的,确实可以在函数体里调用另一个函数,功能上好像是没差别的,但是这里有一个问题,就是你要调用的这个函数被写死了,也就是说这样函数B只能调用函数A了,这样如果在另一个情景下,有个与A不同实现的函数C也需要在B的某个时刻被调用,那怎么办。

下面继续说回调函数,在c/c++里,回调函数可以使用函数指针作为参数被另一个函数调用;在c#里,可以使用委托,如果是事件方法的话,还有event关键字;在python和javascript里,可以直接把函数当对象传参,这些语言都很好实现回调函数(方法),可是, java呢? 先说点题外话,自从学了C#,就不喜欢java了,曾经一度打算以后不再用java,可是现实并没有那么理想,我现在要做android,所以还是不能放下java,而且今天遇到这个回调函数的问题,也是从java里遇到的,我个人觉得,在这个博客里出现的语言,除了java外,对于回调,都可以既容易,又好理解的实现,但是java,我觉得并不是那样,不然我也不会来写这篇博客。

好了继续说,关于java中的回调方法的实现。这篇博客的重点就是说java的。 在java中,回调方法是用借用接口来实现的,我在网上找到一句话:

“把实现某一接口的类所创建的对象的引用,赋值给该接口声明的接口变量,那么该接口变量就可以调用被实现的接口的方法”。

很绕哈,简单解释下:
有一个接口,接口里有一个方法(这个方法就是要回调的方法):

interface CallBackInterface {
    void callBackMethod();
}

我们知道,接口对象不能直接用,因为里面的方法都没有实现。所以要找个类实现这个接口。
所以现在加一个类,实现这个接口:

interface CallBackInterface {
    void callBackMethod();
}

class CallBackClass implements CallBackInterface{

    @Override
    public void callBackMethod() {
        System.out.println("hello");
    }
}

好了,最后一步:把实现了接口的类的对象赋值给声明的接口变量(我给写进一个方法里了,然后外面加了个类的壳子):

public class CallBackTest {

    interface CallBackInterface {
        void callBackMethod();
    }

    class CallBackClass implements CallBackInterface {

        @Override
        public void callBackMethod() {
            System.out.println("hello");
        }
    }

    public void showCallBack() {
        CallBackInterface itfs = new CallBackClass();
        itfs.callBackMethod();
    }
}

现在可以调用试试看了:

public class Test {
    public static void main(String[] args) {
        new CallBackTest().showCallBack();
    }
}

没意外的话,会成功输出hello,反正我这边是的.

例子看完了,所以说我做了什么呢? 再详细点说,我们有一个要在某一个方法里被调用的方法(这个方法就是回调方法), 前面我们也说了,最好不要直接把想要回调方法做的事直接写在调用方法里, 又因为java里没法把方法当做参数传递,所以我们只好把这个回调方法放在了接口里(为什么不是类?不是抽象类?而是接口?你可以自己去找下抽象类与接口的异同,自己解决这个问题)。有接口的话,就要被类实现,然后,只要是给接口的对象赋予实现类的对象,这个接口的对象就可以调用那个方法了。理解这里的话,有一个重点,就是多态, 这里用到的多态知识就是,接口的对象可以顺利被子类赋值,并且调用子类的重写方法(类也有类似的概念)。

再多说一点,这里任何实现了CallbackInterface接口的类,都可以像下面这样放在new后面(就是赋值):

public class CallBackTest {
    interface CallBackInterface {
        void callBackMethod();
    }

    class CallBackClass implements CallBackInterface {

        @Override
        public void callBackMethod() {
            System.out.println("hello");
        }
    }

    class Controller {
        private CallBackInterface cbitf;
        // 这个boolean只是为了模拟有事件,没啥实用价值
        public boolean somethingHappend;
        // 这里确实可以直接把CallBackClass做参数,而且省掉接口的定义
        // 但是这样做的话,就像是回调函数直接写在了调用函数里一样
        // 不明白的话就好好理解下"约定"和"调用者不管回调函数是怎么实现的"吧
        public Controller(CallBackInterface itfs) {
            somethingHappend = true;
            this.cbitf = itfs;
        }

        public void doSomething() {
            if(somethingHappend) {
                cbitf.callBackMethod();
            }
        }
    }

    public void showCallBack() {
        CallBackClass cbc = new CallBackClass();
        Controller ctrlr = new Controller(cbc);
        ctrlr.doSomething();
        // 其实上面也可以这样写在一行里
        // new Controller(new CallBackClass()).doSomething();
    }
}

最后多说一点,其实这种应用在android里会经常遇到,我就是在学android的时候遇到的。

查看原文

赞 2 收藏 15 评论 0

er3456qi 赞了回答 · 2015-07-09

解决Python:推荐爬虫框架

scrapy略重型了,如果自己写的话,可以用自带的urllib2,也可以用requests,解析可以使用lxml,BeautifulSoup,实现动态解析,还有splinter等框架,还可以用threading模块实现多线程,或者使用协程框架gevent。

关注 13 回答 6

er3456qi 回答了问题 · 2015-07-09

有没有短小精悍的学习Python的“好”代码?

for x in range(101):
    print('fizz'[x%3*4:] + 'buzz'[x%5*4:] or x)

之前从一篇文章里看到的,忘记是哪个大神写的了,总之个人觉得很厉害的一段代码。
这两行要解决的问题是:在数字0-100里,遇到3的倍数输出fizz,遇到5的倍数输出buzz,遇到3和5的倍数输出fizzbuzz,其他的原样输出数字。

关注 8 回答 5

er3456qi 回答了问题 · 2015-06-22

你要学习的下一门编程语言是什么

第一门语言是c++,之后java,后来学了一点c#,再后来,php,javascript,然后python,如果还要再学一门语言,也许是Scala,或是F#

关注 5 回答 23

er3456qi 回答了问题 · 2015-06-22

学过PYTHON语言之后在去学习PHP javascript 这些编程语言是不是会更好的能快速理解

没有必要再学php,python和php是并列的,而且python的用途更广。另外学python的经验对学php一点帮助没有,如果想走php路线,有java当基础会更好(当你php学到类那一块时你就能发现了,感谢@eechen 提醒)。

关注 15 回答 18

er3456qi 发布了文章 · 2015-06-20

Javascript数组不完全解析

上一篇说了数组的索引,这一篇说下数组的使用。

数组的大小

js的数组可以动态调整大小,更确切点说,它没有数组越界的概念,a[a.length]没什么问题。比如声明一个数组a = [1, 3, 5],现在的数组大小是3,最后一个元素的索引是2,但是你依然可以使用a[3],访问a[3]返回的是undefined,给a[3]赋值:a[3] = 7,是给数组a添加了一个元素,现在数组a的长度是4了。你可以试试把下面这段代码放到浏览器里运行下:

    var a = [];
    for(int i = 0; i <= a.length; i++)
    {
        a[a.length] = i;
    }

在我的电脑上,火狐会立马崩掉,chrome这一个标签cpu占用99%(使用chrome的任务管理器查看的)。

js的length的值会随着数组元素的改变而改变,当然你也可以手动设置数组的 length 属性,设置更大的length不会给数组分配更多的空间,但是设置更小的length则会导致所有下标大于等于新length的属性被删除。

另外有一点就是,数组的length值是怎么来的,有的资料说是最大一个数字索引值加一,应该是对的,不过如果把空槽也算数的话,length值就是数组的元素数。上张图解释下:

图片描述

从图里可以看到,有个数组a,a[0]a[10]都已赋值,这时候a的length是11,中间有9个empty slot(姑且就翻译为空槽好了)。那这九个空槽算不算数呢,我觉得应该算,这样就能合理的解释length值了。那这些空槽的值是什么呢?undefined!所以呢,如果在chrome里,使用foreach遍历(forin),那么这些空槽正好都能跳过,而使用for遍历,则会打印出undefined。至于在firefox里,表现不太一样,自己试吧。

数组的遍历

昨天在看微博上转的js教程的时候,里面说在遍历数组的时候,判断语句i<a.length会造成每循环一次都要计算一次长度,从而对性能有一点点影响。这一点我表示疑问,我不确定到底是不是这样,而且不同的浏览器对这种情况可能还有不同的优化,我在网上搜了下,找到了几个网页说了要缓存数组大小,但是都是抄的,没主见,也没说明原理,所以我表示很怀疑,而且我在chrome测试了下,性能上没什么明显差异。关键是我觉得,length是个数组的属性,每次调用a.length的时候,只是访问这个属性而已,属性都是用hash的方式存储的,所以访问的时间复杂度是O(1)。这是我的看法,如果不对,请告诉我。

关于数组的foreach遍历,js的方式相对于java/c#等语言是很奇怪的:

        for(var name in ['huey', 'dewey', 'louie']) {
            console.log(name);
        }
        /*
            打印结果:
            0
            1
            2
        */

可以看到,打印的结果不是数组的元素,而是数字索引值(感觉这好像也可以说明,js的数组也是用hash的方式存储的),不管怎样,这一点要注意。(至于为什么这样,我觉得数组元素都是数组的属性,这个遍历是遍历的length值,从0到length。而不是逐个输出数组的元素,因为元素是属性,数组又不只数字索引这一种属性,那么为什么这样遍历的时候只输出它们呢,而不是length,push,join等方法?公平起见,只好输出数组的数字索引了。当然,这只是我自己的看法,具体怎么样我没研究。)

数组的一些方法

数组有pushpop方法,这样数组就像堆栈一样了。对数组使用delete,可以将数组中某个元素移除,但是那样会在数组中留下一个空洞(也就是说delete也可以删除数组中的元素,但是只是删除该位置的值,不改变数组大小,原位置类型是undefined),这是因为排在被删除元素之后的元素保留着它们最初的属性,所以应该使用splice对进行过delete操作的数组进行瘦身,它会将被删除的属性移除,但这样效率并不是很高。数组中还有mapreducefilter等方法,这里就不多说了(跟python中的list挺像的)。

补充

最后补充一点,我前面说过,js中的数组就是对象(废话,本来就是对象),那么是不是说,数组和对象可以互相互替换着用呢?答案是可以的。不过为了明确,还是分开用比较好,下面说下什么时候该用数组,什么时候该用对象(参考《javascript语言精粹》):

当属性名是小而连续的整数时,应该使用数组,否则,使用对象。
另外由于js中对数组和对象使用 typeof 的结果都是 Object,因此判断一个对象是否为数组的方法:

    var is_array = function(value) {
    return Object.prototype.toString.apply(value) === '[object Array]';
    };

番外

觉得闭包被神化了,可能语言层面上的实现有技术,但是在应用层面我觉得就应该那样啊,使用的时候都感觉不到那是在用闭包。但是这个闭包却几乎成了面试前端必问的概念了。

查看原文

赞 1 收藏 7 评论 2

er3456qi 发布了文章 · 2015-06-20

Javascript数组索引不完全解析

从题目说起,之所以是不完全,是因为有些东西比如数组的方法怎么用这个我都不打算讲,因为那个看一下都会,下面讲的都是我觉得重要的,只关于数组对象本身。另外,由于我的Javascript实战经验不多,所以可能有些东西没涉及到,有些内容说的有误,请发现问题的同学不吝指教。

首先,Javascript(下称js)的数组定义,这不是重点,简单说下,下面两句都是创建一个空的数组:

    var arr = [];
    var arr2 = new Array(); // 不写new也可以。

在创建之后,你就可以随时往数组里添加元素。数组的大小是不固定的,可以像a[0] = 1这样随意添加。

然后到重点了,关于往数组里添加元素这件事。首先,你要知道数组是一个对象,而对象是一个键值对的集合(类似于java里面的map,python里面的dict,c#里面的Dictionary),对象可以有属性,对象的函数叫方法,对象的属性或方法可以使用方括号或点号的方式访问,其中使用方括号引用要加引号,点号的使用只有在属性名称是一个合法的变量名称的时候才可以使用,也就是属性不包含任何空格连字符并且不以数字开头时才可以,看个例子:

    var person = {};
    person.age = 22;
    person.sayhi = function(){console.log('hi');};

    person.age; // 22
    person['age']; // 22

    person.sayhi(); // hi
    person['sayhi'](); // hi

嗯,这就是对象,好像也没什么特别的(除了用方括号取值外),不过对象说些就够了,后面开始说数组。

对于上面的这些,数组全可以做到,也就是说,下面这段代码也可以正常运行(只有第一行跟上面的不一样):

    var person = [];
    person.age = 22;
    person.sayhi = function(){console.log('hi');};

    person.age; // 22
    person['age']; // 22

    person.sayhi(); // hi
    person['sayhi'](); // hi

因为数组就是对象,这里不要把方括号的字符串索引和通常讲的数字索引弄混了,还没开始说(马上说)数字索引呢。

和普通的对象不同,数组对象的元素有数字索引,或者说特殊的键(前面说了对象是键值对),这和我们在其他语言比如java、c#等语言中的见到的数组是一样的。在js中,这个键有一些特殊的要求,它可以是数字,也可以是能转换成数字的字符串,而合理的数字,需要是范围在0到4294967295(2^32-1)的整数(事实上,这个索引在词法分析的时候都是当做字符串的,js把这个字符串转换成32位的整数,然后再把32位的整数转换成字符串跟原字符串比较,如果相同的话,则说明这个索引值是合法的数字,否则就是一个普通的字符串键)。简单举个例子:

    a = [1, 3, 5, 7];
    console.log(a[0]); // 1
    console.log(a['0']); // 1

    a['2'] = 12;
    console.log(a[2]); // 12

上面的代码都是可以在浏览器里运行的,注释是输出值。这和我们在其他语言中见到的数组好像没啥区别。这个a['2'] = 12;因为'2'被转换成整数再转换成字符串还是'2',所以跟a[2]一样。不过其他语言里,我们使用数组都是定义一个固定大小数组的对不对?这里好像不是哈,而且这里还说了数组的索引范围。所以,为什么?简单点回答就是这里的数组是对象,是js中的对象。这一点和其他语言(python等函数式的除外)不同,具体哪里不同,我没深入研究,也说不太清楚,我理解的是,c/java等语言定义数组的时候,是在内存中划分了一块固定大小的区域,有一个指针存储着这块区域的首地址。而js中好像不是这样的,就像前面说的一样,数组是对象,键值对结构,所以我觉得js中的数组都是用hash的方式存储元素的,元素之间的内存不一定是连续的。不过我现在没找到查看js变量内存地址的方法,所以没法确定这件事。不过这不是本文要讲的重点。

我们把重点放到索引上,前面说了索引的范围,不过有同学可能试了,就是a[-1] = 2;或是a[4294967296] = 10;这种语句也没有问题。对的,这并不是错误,这是正常的语句,当然不会有问题。但是问题是前面不是说了索引必须是0到4294967295的整数么?对,是的,这也没错。那疑问出在哪里呢?

先贴两张firefox控制台的截图:

jsarray1
jsarray2

你有没有发现什么问题呢?当我们使用正常的索引添加元素,在打印数组的时候,添加的元素会被打印出来,而使用非正常的“索引”添加元素时,打印的数组里面却没有添加的元素,但是你看右侧的Array对象,所有添加的元素却一个不少。再看一张图,这次多添加几个元素,再给数组添加一个属性(注意看右侧Array对象的元素索引):

jsarray3

不知道你有没有发现。在右侧,上面几个是数字索引,打印数组的时候能被打印的,而下面几个是属性,打印数组不会打印属性!也就是说,a[-2] = 2;这种语句,这个-2是属性的键,而不是特殊的数字索引的键,-2在被强制转换成正整数的时候,被认为是一个字符串,所以这个-2和4294967296和'name'一样,都是数组的一个属性的键!所以前面说的负索引或是超出范围的索引(应该说是属性的键)都是合法的,它们都是普通的字符串键。

这里一个问题,就是既然说-2这种键是普通的属性键,那有人可能会说为什么使用a.-2或是a.'-2'访问-2这个键的值会报错,而a[-2]就不会报错?对呀,为什么?前面在讲对象的时候,有一句加粗的话:对象的属性或方法可以使用方括号或点号的方式访问,其中使用方括号引用要加引号,点号的使用只有在属性名称是一个合法的变量名称的时候才可以使用,也就是属性不包含任何空格连字符并且不以数字开头时才可以。所以,-2这种键的属性,是不能用点号的方式访问的!

另外还有一个小问题,就是方括号,当我们想访问数组的name属性的时候,需要这样:a['name'],也就是name被引号包起来了,而-2是和name一样的属性的键,为何-2可以不用(也可以用)引号包起来?其实,方括号内的所有字符会被当成一个表达式,单纯的一个数字-2,是个合法的表达式,但name如果没有被定义成变量名,name就不是合法的表达式,同样x^b&c这种也不是合法的,因为它会被当成变量x、b、c组成的某种表达式,但是x、b、c是不是变量还不确定,而且里面的符号不一定是被js所支持的,所以a[name]的问题出在name上,而不是[]上。如果还不好理解,你可以把name想象成x+y,在x、y没有被定义成变量的时候,x+y这个表达式肯定有问题对不对?那a[x+y]也会有问题对不对?而a['x+y']就没有问题,因为'x+y'是个字符串。

后面补充一点,js中,变量名字是可以由数字、字母、下划线任意组合的,其中数字不能放在开始位置。而对象的属性键的命名要宽松一些,合法的可以不用引号,不合法的用引号包起来就可以。

好了,说的差不多了,总结一下:文章首先简单介绍了下对象,然后说了数组也是对象,最后解释了下一些疑问,然后总结。

写这篇文章的原因是我昨天在微博里看了一条js的教程,对数组的讲解产生了一个疑问,然后评论的字数超出了140,所以我就查资料单独写出来了。目的是让自己弄明白,也愿能帮助到学js的同学。数组范围和索引转换那里是参考的《Speaking Javascript》,其他地方都是自己的理解和看法。

最后,感谢观看,因为是分两次写的,可能语句有些乱,有的地方不乱但是上下文也完整,有的上下文完整但是废话太多,总之,就这样吧。下篇见。

查看原文

赞 6 收藏 17 评论 4

er3456qi 发布了文章 · 2015-06-13

Android的异步任务AsyncTask

AsyncTask,顾名思义,异步任务。说到异步,最简单的理解就是不同步。再复杂一点理解,就得举例子了。

假设我要去火车站买票,刚到火车站我突然发现我忘了带身份证。怎么办?怎么办!

想办法,想办法!我想我应该找个在学校的同学帮我送过来,因为我不能自己回去拿啊,还要排队呢,走不开。嗯,要找人送过来。但是问题来了,我找人送身份证了,我去排队了,如果排到第一位了身份证还没到怎么办?叮,脑袋上面突然亮了一个小灯泡,机智的我在排队前想到了两种方案:

第一种方案,让售票员等着我,我后面队伍里买票的人也等着我,我一直在窗口第一位置等着同学来送身份证,直到,我的身份证被送来,然后顺利买票。

另一种方案呢,就是我跟售票员说一下,让我在一边等着送身份证,后面的人继续买票,等我的身份证送来的时候我通知下售票员,就可以尽快排到队伍第一位(不一定是立即排到第一位,因为万一有人正在买票,我不能过去打断他)然后买票。

所以呢,选第一种还是第二种?我肯定选第二种,因为选第一种肯定会被后面排队的人骂死,而且还有可能被售票员骂,搞不好还会挨揍,毕竟因为我一个人,浪费了这么多人的时间,也拖慢了售票员的工作效率。

好了,例子就说到这里。在例子里,第二种方法就是异步的。异步往往和多线程有关,而且异步任务也大多是交由一个单独的线程完成,然后返回结果给主线程。这里售票员相当于cpu,而排队买票的人相当于等待被执行的任务,而我是个被标记为异步的任务(因为我知道我带身份证,不能立即买到票,所以排队前就想好了第二种方案),当cpu执行到我这个任务的时候,发现我这个任务可执行的条件(身份证)不具备,所以由我发起了一个异步任务(同学送票),去获取可执行的条件,之后立即把位置让出来,让其他排队的任务继续执行。直到我的身份证拿来,然后立马通知cpu准备接待我。

嗯,差不多就是这样了。开始说正文,android里面的AsyncTask。先上一段官网的引用:

AsyncTask enables proper and easy use of the UI thread. This class allows to perform background operations and publish results on the UI thread without having to manipulate threads and/or handlers.

是英语,虽然我能懂大概是什么意思,但是还是不翻译了,怕误人子弟,等我英语学的再好些再来翻译吧。不过还是要解释下大概的意思,就是说AsyncTask可以在UI线程上做一些后台操作,也能返回操作结果到UI线程上。我们知道UI线程是不能做一些耗时的操作的,但是有了AsyncTask,我们可以这样做了。但是,

AsyncTask is designed to be a helper class around Thread and Handler and does not constitute a generic threading framework. AsyncTasks should ideally be used for short operations (a few seconds at the most.) If you need to keep threads running for long periods of time, it is highly recommended you use the various APIs provided by the java.util.concurrent package such as Executor, ThreadPoolExecutor and FutureTask.

对于耗时比较久的任务,还是不建议放在AsyncTask中执行。AysncTask被设计成Thread和Handler的辅助类,并不能执行过于复杂和耗时的任务,这种任务应该用其他方法这里就不说了。AsyncTask最好用于耗时最多只有几秒钟的操作,比如向网络请求个xml或是json之类的网络操作,或是用在程序的初始化界面等等。

下面说下AsyncTask的使用。其实很简单。

首先你需要定义一个AsyncTask的子类,并且必须重写父类的doInBackground(Params...)方法。另外还有onPostExecute(Result)方法也可重写,这个方法在doInBackground之后被自动调用,所以你可以在这里写一些任务完成的通知代码。

先给一个官方的例子:

private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> {
    protected Long doInBackground(URL... urls) {
        int count = urls.length;
        long totalSize = 0;
        for (int i = 0; i < count; i++) {
            totalSize += Downloader.downloadFile(urls[i]);
            publishProgress((int) ((i / (float) count) * 100));
            // Escape early if cancel() is called
            if (isCancelled()) break;
        }
        return totalSize;
    }

    protected void onProgressUpdate(Integer... progress) {
        setProgressPercent(progress[0]);
    }

    protected void onPostExecute(Long result) {
        showDialog("Downloaded " + result + " bytes");
    }
 }

执行AsyncTask的时候,必须在UI线程中执行,如下语句。

new DownloadFilesTask().execute(url1, url2, url3);

可以看到,在继承AsyncTask的时候,有几个泛型类型,如AsyncTask<URL, Integer, Long>,简单解释下。

  1. 第一个可以指定输入参数的类型,就是new DownloadFilesTask().execute()的参数(最后传到了doInBackground),这里的参数可以不只一个,因为最后到方法里面,收到的是个数组。
  2. 第二个可指定发送进度更新需要的类型,一般都是整型,用在publishProgress(用来在后台进程中发送进度的方法,直接使用的,不用定义)和onProgressUpdate两个方法中。
  3. 第三个是AsyncTask返回结果的数据类型,它设置了doInBackground的返回类型,以及onPostExecute的输入参数类型

当然,如果你什么都不需要,可以都使用Void

private class MyTask extends AsyncTask<Void, Void, Void> { ... }

另外,还有一个可以重写的方法,是onPreExecute(),它在doInBackground之前被调用,所以如果需要的话,你可以重写它然后做一些实例化进度条啊之类的工作。

最后,总结一下:

使用AsyncTask,你要做的是,继承父类,然后重写doInBackground(Params...),在里面实现后台操作,如果有返回结果的话,重写onPostExecute(Result)然后处理后台程序的结果。

如果需要更新进度的话,在onPreExecute()里实例化进度条(也可以不在这),之后在doInBackground(Params...)里面用publishProgress()发布进度值,然后重写onProgressUpdate(Progress...)接收onPreExecute()发布的结果,并添加更新进度条的代码。

查看原文

赞 2 收藏 8 评论 0

er3456qi 发布了文章 · 2015-06-13

Python的迭代器和生成器

先说迭代器,对于stringlistdicttuple等这类容器对象,使用for循环遍历是很方便的。在后台for语句对容器对象调用iter()函数,iter()是python的内置函数。iter()会返回一个定义了next()方法的迭代器对象,它在容器中逐个访问容器内元素,next()也是python的内置函数。在没有后续元素时,next()会抛出一个StopIteration异常,通知for语句循环结束。比如:

>>> s = 'abc'
>>> it = iter(s)
>>> it
<str_iterator object at 0x7f71fefe9d68>
>>> next(it)
'a'
>>> next(it)
'b'
>>> next(it)
'c'
>>> next(it)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

上面说的都是python自带的容器对象,它们都实现了相应的迭代器方法,那如果是自定义类需要遍历怎么办?方法很简单,对这个类AClass,实现一个__iter__(self)方法,使其返回一个带有__next__(self)方法的对象就可以了。如果你在AClass刚好也定义了__next__(self)方法(一般使用迭代器都会定义),那在__iter__里只要返回self就可以。废话少说,先上代码:

class Fib(object):
    def __init__(self, max):
        super(Fib, self).__init__()
        self.max = max

    def __iter__(self):
        self.a = 0
        self.b = 1
        return self

    def __next__(self):
        fib = self.a
        if fib > self.max:
            raise StopIteration
        self.a, self.b = self.b, self.a + self.b
        return fib

def main():
    fib = Fib(100)
    for i in fib:
        print(i)

if __name__ == '__main__':
    main()

简单讲下代码会干什么,定义了一个Fib类,用于生成fibonacci序列。用for遍历时会逐个打印生成的fibonacci数,max是生成的fibonacci序列中数字大小的上限。

在类的实现中,定义了一个__iter__(self)方法,这个方法是在遍历时被iter()调用,返回一个迭代器。因为在遍历的时候,是直接调用的python内置函数iter(),由iter()通过调用__iter__(self)获得对象的迭代器。有了迭代器,就可以逐个遍历元素了。而逐个遍历的时候,也是使用内置的next()函数通过调用对象的__next__(self)方法对迭代器对象进行遍历。所以要实现__iter__(self)__next__(self)。而且因为实现了__next__(self),所以在实现__iter__(self)的时候,直接返回self就可以。

为了更好理解,我再简单重复下上面说的那一段:在循环遍历自定义容器对象时,会使用python内置函数iter()调用遍历对象的__iter__(self)获得一个迭代器,之后再循环对这个迭代器使用next()调用迭代器对象的__next__(self)__iter__只会被调用一次,而__next__会被调用 n 次。

下面说生成器。

生成器(Generator)是创建迭代器的简单而强大的工具。它们写起来就像是正规的函数,只是在需要返回数据的时候使用yield语句。每次next()被调用时,生成器会返回它脱离的位置(它记忆语句最后一次执行的位置和所有的数据值)。以下示例演示了生成器可以很简单的创建出来:

>>> def reverse(data):
...     for index in range(len(data)-1, -1, -1):
...         yield data[index]
... 
>>> for char in reverse('hello'):
...     print(char)
... 
o
l
l
e
h

关于迭代器和生成器的区别,生成器能做到迭代器能做的所有事,而且因为自动创建了__iter__()next()方法,生成器显得特别简洁,而且生成器也是高效的。除了创建和保存程序状态的自动方法,当发生器终结时,还会自动抛出StopIteration异常。一个带有yield的函数就是一个 生成器,它和普通函数不同,生成一个 generator 看起来像函数调用,但不会执行任何函数代码,直到对其调用next()(在 for 循环中会自动调用next())才开始执行。虽然执行流程仍按函数的流程执行,但每执行到一个yield语句就会中断,并返回一个迭代值,下次执行时从yield的下一个语句继续执行。看起来就好像一个函数在正常执行的过程中被yield中断了数次,每次中断都会通过yield返回当前的迭代值(yield暂停一个函数,next()从其暂停处恢复其运行)。

另外对于生成器,python还提供了一个生成器表达式:类似与一个yield值的匿名函数。表达式本身看起来像列表推到, 但不是用方括号而是用圆括号包围起来:

>>> unique_characters = {'E', 'D', 'M', 'O', 'N', 'S', 'R', 'Y'}
>>> gen = (ord(c) for c in unique_characters)
>>> gen
<generator object <genexpr> at 0x7f2be4668678>
>>> for i in gen:
...     print(i)
... 
69
79
83
77
82
78
89
68
>>> 

如果需要,可以将生成器表达式传给tuplelist或是set来迭代所有的值并且返回元组、列表或是集合。在这种情况下,不需要一对额外的括号 ———— 直接将生成器表达式 ord(c) for c in unique_characters传给tuple()等函数就可以了, Python 会推断出它是一个生成器表达式。

最后,为什么要使用生成器?因为效率。使用生成器表达式取代列表解析可以同时节省 cpu 和 内存(ram)。如果你构造一个列表的目的仅仅是传递给别的函数,(比如 传递给tuple()或者set()), 那就用生成器表达式替代吧!

查看原文

赞 3 收藏 25 评论 2

er3456qi 回答了问题 · 2015-04-22

负数的算术右移有什么规律?

你要知道原码和补码的概念,还有符号位。原码的数值部分是无符号的二进制数,正数的符号位是0,负数的符号位是1;补码的话,简单说一下,正数和原码一样,但是负数是原码的数值部分各位取反,末位加一。

计算机里我们看到的和用的数字都是原码,而底层一般都是用补码实现的,例如,-10的原码是[1]1010(假设符号位是一位[]),补码是[1]0110,算数右移一位变成[1]1011(因为是补码,算术右移,首位补符号位),这个数转换成原码是 [1]0101,也就是 -5,再移一位为[1]1101,转换成原码是[1]0011,是 -3.以此类推,至于规律,我也不知道有什么规律,不过移动的位数如果超过了数值所有位数,结果会是-1.

关注 3 回答 2