0%

EventBus我们都很熟悉了,它主要是通过注解+观察者模式+反射实现的.接下来我们通过它的源码来分析分析.
我们在使用EventBus时,先是注册,将当前类注册事件,EventBus.getDefault().register(this),然后在定义一个有@Subscriber注解的方法来接受分发者传来的事件.好,首先我们来看看register()的实现.

register方法实现

以下是register的方法实现.

1
2
3
4
5
6
7
8
9
10
11
public void register(Object subscriber) {
Class<?> subscriberClass = subscriber.getClass();
// 获取订阅者中有Subscriber注解的方法
List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
synchronized (this) {
for (SubscriberMethod subscriberMethod : subscriberMethods) {
// 实现订阅
subscribe(subscriber, subscriberMethod);
}
}
}

我们看到register方法中,首先是通过调用subscriberMethodFinder中的findSubscriberMethods方法来获取有Subscriber注解的方法,那么接下来我们在看看这个方法的实现.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
if (subscriberMethods != null) {
return subscriberMethods;
}

if (ignoreGeneratedIndex) {
subscriberMethods = findUsingReflection(subscriberClass);
} else {
subscriberMethods = findUsingInfo(subscriberClass);
}
if (subscriberMethods.isEmpty()) {
throw new EventBusException("Subscriber " + subscriberClass
+ " and its super classes have no public methods with the @Subscribe annotation");
} else {
METHOD_CACHE.put(subscriberClass, subscriberMethods);
return subscriberMethods;
}
}

这一步主要是先在METHOD_CACHE中寻找是否有对应subscriberClass类的@Subscriber方法,如果有则返回该值,否则根据ignoreGeneratedIndex的值去按不同的方式的去查找具有Subscriber注解的方法.如果该类中没有Subscriber注解的方法,则将会抛出EventBusException异常,否则将该类和具有Subscriber注解的方法加入到METHOD_CACHE的键值对中.

ignoreGeneratedIndex为true时,是通过反射去查找具有Subscriber注解的方法,接下来我们先看看该方法.

  1. findSubscriberMethods

1.1 findUsingReflection

1
2
3
4
5
6
7
8
9
private List<SubscriberMethod> findUsingReflection(Class<?> subscriberClass) {
FindState findState = prepareFindState();
findState.initForSubscriber(subscriberClass);
while (findState.clazz != null) {
findUsingReflectionInSingleClass(findState);
findState.moveToSuperclass();
}
return getMethodsAndRelease(findState);
}

先从FindState数组中获取一个FindState对象,在调用initForSubscriber方法,initForSubscriber方法中没有什么其他操作,就是简单赋值,接下来是个循环直到findStateclazz的属性为null为止,在循环体中调用了findUsingReflectionInSingleClass方法,那我们再看看该方法是用来做什么的.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
private void findUsingReflectionInSingleClass(FindState findState) {
Method[] methods;
try {
// This is faster than getMethods, especially when subscribers are fat classes like Activities
// findState的clazz属性就是初始化时被赋的值,也就是调用register方法所在的类
// 使用getDeclaredMethods是获取该类声明的方法(包括public,private,protected,friendly)不包//括父类和接口实现的方法.
methods = findState.clazz.getDeclaredMethods();
} catch (Throwable th) {
// Workaround for java.lang.NoClassDefFoundError, see https://github.com/greenrobot/EventBus/issues/149
methods = findState.clazz.getMethods();
findState.skipSuperClasses = true;
}
for (Method method : methods) {
int modifiers = method.getModifiers();
// 判断该方法是否是public
if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {
Class<?>[] parameterTypes = method.getParameterTypes();
if (parameterTypes.length == 1) {
Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
if (subscribeAnnotation != null) {
// 获取有Subscriber注解的方法的参数的类型
Class<?> eventType = parameterTypes[0];
if (findState.checkAdd(method, eventType)) {
ThreadMode threadMode = subscribeAnnotation.threadMode();
// 将注解中的参数封装为SubscriberMethod对象,加入到subscriberMethods中
findState.subscriberMethods.add(new SubscriberMethod(method, eventType, threadMode,
subscribeAnnotation.priority(), subscribeAnnotation.sticky()));
}
}
} else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {
// 如果有Subscriber注解的方法的参数不是一个则抛出异常
String methodName = method.getDeclaringClass().getName() + "." + method.getName();
throw new EventBusException("@Subscribe method " + methodName +
"must have exactly 1 parameter but has " + parameterTypes.length);
}
} else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {
// 如果该方法中有Subscriber注解但是该方法不是public的则抛出异常
String methodName = method.getDeclaringClass().getName() + "." + method.getName();
throw new EventBusException(methodName +
" is a illegal @Subscribe method: must be public, non-static, and non-abstract");
}
}
}

该方法稍微有一些长,但是逻辑比较清晰,我画了张流程图,大家看一下.

1.2 findUsingInfo

这个方法目前没研究明白,等研究透彻再给补上吧.

  1. subscriber

上面通过反射获取到该类及其父类中的含有Subscriber注解的方法.接下来要做的事情就是将这些方法进行订阅.如何订阅则查看subscriber方法源码的实现.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
// eventType我们通过前面的操作知道是具有Subscriber的方法的参数参数类型,
// 即我们编写的MessageEvent等传递对象.
Class<?> eventType = subscriberMethod.eventType;
// 将订阅的类与具有Subscriber注解的方法封装为Subscription对象.
Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
// eventType表示方法中的参数的类型
CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
if (subscriptions == null) {
subscriptions = new CopyOnWriteArrayList<>();
subscriptionsByEventType.put(eventType, subscriptions);
} else {
if (subscriptions.contains(newSubscription)) {
throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "
+ eventType);
}
}

int size = subscriptions.size();
for (int i = 0; i <= size; i++) {
// 这块不理解??
if (i == size || subscriberMethod.priority > subscriptions.get(i).subscriberMethod.priority) {
subscriptions.add(i, newSubscription);
break;
}
}

List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
if (subscribedEvents == null) {
subscribedEvents = new ArrayList<>();
typesBySubscriber.put(subscriber, subscribedEvents);
}
subscribedEvents.add(eventType);

// 这个方法是否是sticky方法
if (subscriberMethod.sticky) {
if (eventInheritance) {
// Existing sticky events of all subclasses of eventType have to be considered.
// Note: Iterating over all events may be inefficient with lots of sticky events,
// thus data structure should be changed to allow a more efficient lookup
// (e.g. an additional map storing sub classes of super classes: Class -> List<Class>).
Set<Map.Entry<Class<?>, Object>> entries = stickyEvents.entrySet();
for (Map.Entry<Class<?>, Object> entry : entries) {
Class<?> candidateEventType = entry.getKey();
if (eventType.isAssignableFrom(candidateEventType)) {
Object stickyEvent = entry.getValue();
checkPostStickyEventToSubscription(newSubscription, stickyEvent);
}
}
} else {
Object stickyEvent = stickyEvents.get(eventType);
checkPostStickyEventToSubscription(newSubscription, stickyEvent);
}
}
}

上面的方法就是按有@Subscriber注解的方法的参数类型作为key(即开发声明的MessageEvent),将订阅的class和method封装为Subscripution的list作为value,存储在subscriptionsByEventType中.
还将订阅的class作为可以,有@Subscriber注解的方法的参数类型作为value,存储在typesBySubscriber.
sticky先不分析,至此,register方法的流程已经梳理完毕.

post方法

接下来我们得从另外一个类中调用EventBus.getDefault().post(AnyEvent)方法来通知订阅者们(即注册了事件并且有Subscriber注解的方法).我们来看看这个post方法.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public void post(Object event) {
// currentPostingThreadState是个ThreadLocal它保证在一个线程中有唯一的值.
PostingThreadState postingState = currentPostingThreadState.get();
List<Object> eventQueue = postingState.eventQueue;
// 把要通知的事件加入到事件队列中
eventQueue.add(event);
// isPosting是默认为false的
if (!postingState.isPosting) {
postingState.isMainThread = isMainThread();
postingState.isPosting = true;
if (postingState.canceled) {
throw new EventBusException("Internal error. Abort state was not reset");
}
try {
// 这里是依次从事件队列中拿出第一个元素来分发消息直到事件队列为空为止.
while (!eventQueue.isEmpty()) {
postSingleEvent(eventQueue.remove(0), postingState);
}
} finally {
postingState.isPosting = false;
postingState.isMainThread = false;
}
}
}

这里主要步骤就是将要推送的事件加入到队列中,然后循环队列每次获取队列中的第一个元素来进行事件推送,直到事件队列为空为止.接下来我们看看postSingleEvent这个函数是干了什么?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
Class<?> eventClass = event.getClass();
boolean subscriptionFound = false;
if (eventInheritance) {
// 查询具有该通知事件的所有类
List<Class<?>> eventTypes = lookupAllEventTypes(eventClass);
int countTypes = eventTypes.size();
for (int h = 0; h < countTypes; h++) {
Class<?> clazz = eventTypes.get(h);
// 通知每个类(具有该通知消息的类)
subscriptionFound |= postSingleEventForEventType(event, postingState, clazz);
}
} else {
subscriptionFound = postSingleEventForEventType(event, postingState, eventClass);
}
if (!subscriptionFound) {
if (logNoSubscriberMessages) {
logger.log(Level.FINE, "No subscribers registered for event " + eventClass);
}
if (sendNoSubscriberEvent && eventClass != NoSubscriberEvent.class &&
eventClass != SubscriberExceptionEvent.class) {
post(new NoSubscriberEvent(this, event));
}
}
}

我们看到第一步是通过调用lookupAllEventTypes方法,获取所有注册该消息的类,然后遍历所有的类依次调用postSingleEventForEventType方法.我们看看postSingleEventForEventType的实现.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {
CopyOnWriteArrayList<Subscription> subscriptions;
synchronized (this) {
subscriptions = subscriptionsByEventType.get(eventClass);
}
if (subscriptions != null && !subscriptions.isEmpty()) {
for (Subscription subscription : subscriptions) {
postingState.event = event;
postingState.subscription = subscription;
boolean aborted = false;
try {
postToSubscription(subscription, event, postingState.isMainThread);
aborted = postingState.canceled;
} finally {
postingState.event = null;
postingState.subscription = null;
postingState.canceled = false;
}
if (aborted) {
break;
}
}
return true;
}
return false;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
switch (subscription.subscriberMethod.threadMode) {
case POSTING:
invokeSubscriber(subscription, event);
break;
case MAIN:
if (isMainThread) {
invokeSubscriber(subscription, event);
} else {
mainThreadPoster.enqueue(subscription, event);
}
break;
case MAIN_ORDERED:
if (mainThreadPoster != null) {
mainThreadPoster.enqueue(subscription, event);
} else {
// temporary: technically not correct as poster not decoupled from subscriber
invokeSubscriber(subscription, event);
}
break;
case BACKGROUND:
if (isMainThread) {
backgroundPoster.enqueue(subscription, event);
} else {
invokeSubscriber(subscription, event);
}
break;
case ASYNC:
asyncPoster.enqueue(subscription, event);
break;
default:
throw new IllegalStateException("Unknown thread mode: " + subscription.subscriberMethod.threadMode);
}
}
1
2
3
4
5
6
7
8
9
void invokeSubscriber(Subscription subscription, Object event) {
try {
subscription.subscriberMethod.method.invoke(subscription.subscriber, event);
} catch (InvocationTargetException e) {
handleSubscriberException(subscription, event, e.getCause());
} catch (IllegalAccessException e) {
throw new IllegalStateException("Unexpected exception", e);
}
}

通过反射调用具有Subscriber注解的方法,至此分析完毕.

view事件流程分析

view的事件流程我们先从activity说起,当一个view被点击时,其实事件消息是从activity的dispatchTouchEvent开始进行传递,我们走进activity的dispatchTouchEvent看看究竟是怎么实现的.

1
2
3
4
5
6
7
8
9
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}

我们看到activity的dispatchTouchEvent是调用了Window的superDispatchTouchEvent方法,那我们在看看Window的superDispatchTouchEvent,我们都知道Window是个抽象类,并且仅有一个子类即PhoneWindow,我们边看看PhoneWindow的
那我们在看看Window的superDispatchTouchEvent方法.在API26的源码中无法找到PhoneWindow的源码,因此这块代码借鉴Android艺术开发探索的View事件机制部分代码.

Abstract base class for a top-level window look and behavior policy. An instance of this class should be used as the top-level view added to the window manager. It provides standard UI policies such as a background, title area, default key processing, etc.

The only existing implementation of this abstract class is android.view.PhoneWindow, which you should instantiate when needing a Window.

1
2
3
public boolean superDispatchTouchEvent(MotionEvent event){
return mDecor.superDispatchTouchEvent(event);
}

我们看到事件又传递到了DecorView中,我们知道DecorView是继承自FrameLayout,而我们在调用setContentView时是将View直接添加到了DecorView的第一个位置.好了不多说了,DecorView将事件又传递到了setContentView方法中的最外层的ViewGroup中,我们便看看ViewGroup中的dispatchTouchEvent方法.下面伪代码显示:

1
2
3
4
5
6
7
8
9
10
11
12
public boolean dispatchTouchEvent(MotionEvent event) {
boolean consume = false;
if (onInterceptTouchEvent(event)){
consume = onTouchEvent(event);
}else{
// View[] childs = getChildren();
// for (View child : childs) {
consume = child.dispatchTouchEvent(event);
// }
}
return consume;
}

可以看到先是判断拦截,如果没有拦截,则将事件分发给子View进行处理.

总结

android事件传递伪代码. 这个是ViewGroup中事件分发的伪代码.

1
2
3
4
5
6
7
8
9
10
11
12
public boolean dispatchTouchEvent(MotionEvent event) {
boolean consume = false;
if (onInterceptTouchEvent(event)){
consume = onTouchEvent(event);
}else{
// View[] childs = getChildren();
// for (View child : childs) {
consume = child.dispatchTouchEvent(event);
// }
}
return consume;
}

view滑动冲突处理

  1. 外部拦截法

外部拦截发主要是通过父容器进行拦截,调用父view的onInterceptTouchEvent()方法,来通过不同的手势操作进行对子view事件的拦截.以下是代码实现.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public boolean onInterceptTouchEvent(MotionEvent event) {
boolean intercept = false;
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
intercept = false;
break;
case MotionEvent.ACTION_MOVE:
if(calOffset()){
intercept = true;
}else{
intercept = false;
}
break;
case MotionEvent.ACTION_UP:
intercept = false;
break;
}
return intercept;
}

其中,calOffset是开发者计算,什么时候需要拦截,什么时候不需要拦截.

  1. 内部拦截法

内部拦截法是指把所有事件都传给子view处理,如果子view需要消费,就消费掉,否则,由父view处理.
具体伪代码实现.

子view的dispatchTouchEvent

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public boolean dispatchTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
switch(event.getAction()) {
case MotionEvent.ACTION_DOWN:
// parent view 不拦截
parent.rquestDisallowInterceptTouchEvent(true);
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
parent.rquestDisallowInterceptTouchEvent(true);
break;
case MotionEvent.ACTION_MOVE:
if(calOffest()) {
parent.rquestDisallowInterceptTouchEvent(false);
}
break;
}
return super.dispatchTouchEvent(event);
}

在依据开发者写的calOffset方法,判断是否应该拦截.requestDisallowInterceptTouchEvent()方法的意思是:是否要取消父view的拦截,true为取消拦截,false反之.

参考资料

Android开发艺术探索

Handler大家都知道可以来用线程间的通信,从一个worker thread处理完任务以后,可以通过handler发送message,切回到主线程来进行更新UI.

大家都知道Activity的入口在ActivityThread的main方法中.我们看看main函数中的核心实现.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public static void main(String[] args) {

...

Looper.prepareMainLooper();

ActivityThread thread = new ActivityThread();
thread.attach(false);

if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}

if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}

// End of event ActivityThreadMain.
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
Looper.loop();

...

}

Read more »

android View的执行流程分析.初始的入口是从ViewRootImpl的performTraversals()方法开始执行,接下来开始分析这个方法.由于这个方法有上千行代码,咱们是粗略的分析一下其中的measure(),layout(),draw()方法.

当每次调用requestLayout()是就会调用performTraversals()方法来执行绘制流程.

  1. Measure阶段

perfromTraversal()中调用performMeasure()方法,在调用performMeasure()之前,先是粗略的计算了子view的大小,代码如下:

1
2
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);

可以看到计算子View大小的方法是getRootMeasureSpec()方法,好,我们再看看这个方法的实现,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) {

case ViewGroup.LayoutParams.MATCH_PARENT:
// Window can't resize. Force root view to be windowSize.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT:
// Window can resize. Set max size for root view.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default:
// Window wants to be an exact size. Force root view to be that size.
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}
Read more »

因为mysql5.6以后对password增加了安全措施,5.6以后的版本中user表中就没有password这个字段.网上好多mysql更改密码教程都没写mysql对应的版本号,所以,让好多人走了弯路,因此,我在这里记录一下.

为了少走弯路,我先说一下的环境,我环境是:操作系统->Ubuntu16.0.4LTS , mysql版本->5.7.20

如果你忘记了mysql的密码,从这里开始重新设置root密码.

  1. 首先关闭mysql服务

可以通过一下命令查看你的mysql服务是否开启

1
$ ps -ef | grep mysql

可以看到我的mysql正在启动

1
mysql    11245     1  0 12:02 ?        00:00:00 /usr/sbin/mysqld

使用mysql命令来停止服务

1
$ service mysql stop
  1. 使用无密码来进入到安全模式
1
$ mysqld_safe --skip-grant-tables --skip-networking &

在这里你可能有失败的可能,如果你出现了如下错误:

1
2
2018-01-22T03:29:12.303840Z mysqld_safe Logging to '/var/log/mysql/error.log'.
2018-01-22T03:29:12.305724Z mysqld_safe Directory '/var/run/mysqld' for UNIX socket file don't exists.

你可以使用以下命令,先创建好文件目录,在执行安全模式的命令.

1
2
3
$ sudo mkdir -p /var/run/mysqld
$ sudo chown mysql:mysql /var/run/mysqld
$ mysqld_safe --skip-grant-tables --skip-networking &

如果成功了,会有以下打印:

1
2018-01-22T03:40:26.921362Z mysqld_safe mysqld from pid file /var/run/mysqld/mysqld.pid ended

接下来我们就可以不使用密码进入到root帐号了,密码为空,直接按回车.

1
mysql -uroot -p
  1. 重新设置root帐号的密码

重新设置root帐号的密码命令:

1
mysql> update mysql.user set authentication_string=PASSWORD("u new password") where User='root' and Host='localhost';

特别提醒注意的一点是,新版的mysql数据库下的user表中已经没有Password字段了,也就是mysql5.6以后已经没password字段了,加密密码存在了authentication_string字段中了

接下来保存退出,

1
2
mysql> flush privileges;
mysql> quit;

然后重启mysql服务,使用新设置的密码进行登录,

‘’’
$ service mysql restart
$ mysql -uroot -p
‘’’

如果你叒出现了错误,比如以下错误:

1
ERROR 1524 (HY000): Plugin 'auth_socket' is not loaded

你需要在安全模式进入mysql后,输入以下命令,主要是增加了第3条命令:

1
2
3
4
5
6
mysql> use mysql;
mysql> update user set authentication_string=PASSWORD("u new password") where User='root';
mysql> update user set plugin="mysql_native_password" where User='root'; # THIS LINE

mysql> flush privileges;
mysql> quit;

输入完以后你再重启mysql服务,再使用新设置的密码,进行登录,

1
2
$ service mysql restart
$ mysql -uroot -p

终于登录进来了,按照网上的教程真是坎坷,因此,在此记录一下,防止下次再次掉坑.

Reference:
Mysql5.7忘记root密码及mysql5.7修改root密码的方法
Mysql更改密码官方手册
mysqld_safe Directory ‘/var/run/mysqld’ for UNIX socket file don’t exists
MySQL fails on: mysql “ERROR 1524 (HY000): Plugin ‘auth_socket’ is not loaded”

  • 在实现一个对象的equals方法时,需要实现hashCode方法.Java中认为如果两个对象值相等,那么他们的hashCode必须一致,equals也必须相等.为什么要这麽做呢?因为:在Java中collection中HashMap,HashTable等,有Hash表的数据结构中,他们的key值的索引要使用对象的hashCode方法去进行Hash,如果他们的hashCode不一样,会被hash在不同的索引处,会被认为是不同的对象.当hashCode一致时,才比较他们的equals方法.所以,我们得出了结论,hashCode方法主要是为了存储在Hash表中定位索引值,因为在Java中Map是一个非常常用的数据结构,使用他的子类一般会使用HashMap,因此,在Java中实现equals方法时,也实现hashCode的方法就成了一种规矩.

HashMap的key值的hash算法

1
2
3
4
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

计算元素放置的位置的hash算法是:

1
(capacity - 1) & hash

使用该算法原因是因为与运算,位运算等在计算机中运算计较高效,为什么??查资料作证.

Read more »

the fuck

当你命令打错时,直接敲fuck会更正命令,这个插件使用python写的,有时间的话,看看他的代码实现.

etree

用树状图的形式显示当前目录下的结构

1
tree

显示效果如下:

1
2
3
4
5
6
7
8
9
10
├── parse_html
│   ├── Bs4Test.py
│   ├── demo.html
│   └── LxmlTest.py
├── proxy
│   ├── CrawlIp.py
│   └── ProxyTest.py
└── README.md

2 directories, 6 files

遇到好玩的在往后加…

因为公司的业务变更,最近让我搞起了python爬虫,现在是下班时间,所以把最近的东西整理整理.先来总结一下爬虫的html解析部分,这块也比较简单.

python爬虫的html解析器我目前使用了lxml和BeautifulSoup两种,接下来讲讲他们的使用方法.

lxml

  1. lxml安装

    首先是安装,默认已经有了python环境和已安装pip,安装lxml的命令.

    1
    $ pip install lxml
  2. lxml使用

    安装好以后,就可以使用lxml来进行xpath解析了,还不知道xpath??传送们:xpath介绍

    2.1 lxml读取文件

    使用etree.parse(‘filename’)来读取文件,代码如下:

    1
    2
    from lxml import etree
    selector = etree.parse('demo.html')

    2.2 lxml 读取html文本

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
        html = '''
    <html lang="en">
    <head>
    <meta charset="UTF-8"/>
    <title>Html parse demo</title>
    </head>
    <body>
    <div class="main">
    <div class="first">
    <img src="http://www.sinaimg.cn/dy/slidenews/21_img/2017_27/41065_5741488_547160.jpg" width="200px" height="200px" alt="美女图片"/>
    <p>这是文字</p>
    </div>
    <ul class="first">
    <li><a href="http://www.baidu.com">百度</a></li>
    <li><a href="http://www.zhihu.com">知乎</a></li>
    <li><a href="http://www.google.com">google</a></li>
    <li><a href="http://www.qq.com">QQ</a></li>
    </ul>
    </div>

    </body>
    </html>
    '''
    html_selector = etree.HTML(html)
    print_element(html_selector)

    2.3 获取class为main的div标签

    1
    2
    main_div = selector.xpath('//div[@class="main"]')
    print_element(main_div[0])

    输出结果

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <div class="main">
    <div class="first">
    <img src="http://www.sinaimg.cn/dy/slidenews/21_img/2017_27/41065_5741488_547160.jpg" width="200px" height="200px" alt="&#32654;&#22899;&#22270;&#29255;"/>
    <p>&#36825;&#26159;&#25991;&#23383;</p>
    </div>
    <ul class="first">
    <li><a href="http://www.baidu.com">&#30334;&#24230;</a></li>
    <li><a href="http://www.zhihu.com">&#30693;&#20046;</a></li>
    <li><a href="http://www.google.com">google</a></li>
    <li><a href="http://www.qq.com">QQ</a></li>
    </ul>
    </div>

    2.4 读取main_div中的div/p中的文字信息

    1
    2
    text = main_div[0].findtext('div/p')
    print(text)

    查找element中的元素使用find(),查找某个标签中的文字使用findtext(),查找element中某个元素的所有标签findall()

    输出结果

    1
    这是文字

    2.5 获取main_div中的所有li标签中a标签中的href属性

    1
    2
    3
    4
    5
    6
    7
    lis = main_div[0].findall('ul/li')
    for li in lis:
    # print_element(li)
    link = li.find('a')
    # 获取a标签中href的属性
    url = link.get('href')
    print(url)

    输出结果:

    1
    2
    3
    4
    http://www.baidu.com
    http://www.zhihu.com
    http://www.google.com
    http://www.qq.com

    2.6 在循环中获取li的a标签的文字

    1
    2
    link_text = link.xpath('string(.)')
    print(link_text)

    输出结果:

    1
    2
    3
    4
    百度
    知乎
    google
    QQ

    2.7 lxml xpath解析的完整例子

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    from lxml import etree
    def print_element(element):
    string = etree.tostring(element,pretty_print=True).decode('utf-8')
    print(string)
    # 打开html文件
    selector = etree.parse('demo.html')
    # print_element(selector)
    # 获取class为main的div标签
    main_div = selector.xpath('//div[@class="main"]')
    print_element(main_div[0])
    # 读取main_div中的div/p中的文字信息
    text = main_div[0].findtext('div/p')
    print(text)
    # 获取main_div中的所有li标签
    lis = main_div[0].findall('ul/li')
    for li in lis:
    # print_element(li)
    link = li.find('a')
    # 获取a标签中href的属性
    url = link.get('href')
    print(url)

BeautifulSoup

Beautiful Soup简介:

Beautiful Soup 是一个可以从HTML或XML文件中提取数据的Python库.它能够通过你喜欢的转换器实现惯用的文档导航,查找,修改文档的方式.Beautiful Soup会帮你节省数小时甚至数天的工作时间.

  1. BeautifulSoup安装

    1
    $ pip install beautifulsoup4
  2. BeautifulSoup使用

    2.1 创建beautifulsoup对象

    1
    soup = BeautifulSoup(html,'lxml')

    2.2 打开本地html文件

    1
    soup = BeautifulSoup(open('demo.html'),'lxml')

    2.3 beautifulSoup格式化输出

    1
    print(soup.prettify())

    2.4 查找class为sister的a标签

    1
    2
    3
    4
    5
    # 方式1
    links = soup.find_all('a',{'class':'sister'})
    # 方式2
    links = soup.find_all('a',class_='sister')

    2.5 获取a标签中的href属性

    1
    a = link.get('href')

    2.6 获取a标签中的文字

    1
    print(link.get_text())

    2.7 完整的例子

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    from bs4 import BeautifulSoup

    html = '''
    <html>
    <head>
    <title>
    The Dormouse's story
    </title>
    </head>
    <body>
    <p class="title">
    <b>
    The Dormouse's story
    </b>
    <span class="first second">
    Hello World
    </span>
    </p>
    <div class="test">
    <!-- 这是注释 -->
    </div>
    <p class="story">
    Once upon a time there were three little sisters; and their names were
    <a class="sister" href="http://example.com/elsie" id="link1">
    Elsie
    </a>
    ,
    <a class="sister" href="http://example.com/lacie" id="link2">
    Lacie
    </a>
    and
    <a class="sister" href="http://example.com/tillie" id="link2">
    Tillie
    </a>
    ; and they lived at the bottom of a well.
    </p>
    <p class="story">
    ...
    </p>
    </body>
    </html>
    '''

    # soup = BeautifulSoup(open('demo.html'),'lxml')
    # print(soup)

    soup = BeautifulSoup(html,'lxml')
    # print(soup)

    # # 查找class为sister的a标签
    # links = soup.find_all('a',{'class':'sister'})
    # links = soup.find_all('a',class_='sister')
    # # 获取a标签中的href属性
    # for link in links:
    # # print(link)
    # a = link.get('href')
    # print(a)
    # print(link.get_text())

    # span = soup.find('span',{'class':'first'})
    # print(span)

    # print(soup.a.string)

    # print(soup.prettify())

    以上是基于lxml,BeautifulSoup最基本的解析html步骤,掌握以上就可解析大部分网页了,以后有补充的再加进来.

参考资料:
xpath介绍
Python爬虫利器三之Xpath语法与lxml库的用法
lxml官方网站
BeautifulSoup官网
Python爬虫利器二之Beautiful Soup的用法

大家都已经对okhttp很熟悉了吧,今天我把我学习okhttp源码的过程记录一下.
okhttp是一个轻量级的网络请求框架,可用于java和android,支持同步请求和异步请求两种方式.

同步GET请求源码分析

1
2
3
4
5
6
7
8
9
10
//实例okhttpclient
OkhttpClient client = new OkhttpClient.Builder().build();
//实例Request
Request request = new Request.Builder().url('http://www.baidu.com').build();
//发送请求
new Thread(
public void run(){
Response response = client.newCall(request).execute();
}
).start();

以上是简单的OkHttp同步GET请求方式,接下来咱们来分析一下源码.

Read more »