首页 文章 精选 留言 我的

精选列表

搜索[知识资产沉淀],共10000篇文章
优秀的个人博客,低调大师

android,java知识点总结

monkey List ,HashMap ,set ,HashTable ,eventBus ,LitPal ,OkHttp ,Glide ,热修复,线程安全,线程池,6.0以及5.0权限管理,7.0分屏画中画;材料设计语言,蓝牙,wifi,ViewDrapHelper,动画,排序,RandomAccess,Rxjava,注解,设计模式。类设计,项目结构设计。功能设计。泛型数据结构。SqlLiteOpenHelper。内存泄漏。adb命令等等 1、使用aapt进行apk信息的查看 aapt dump dadging 跟上apk路径 (必须要注意要配置aapt 环境变量) 2、特殊字符在string.xml中需要进行转义 `一般需要注意的就是< 、> 、@,?等等可以使用"<( ̄︶ ̄)>" 类似这样进行包装;也可以是使用 ,使用比如颜文字` <!--颜文字 --> <string-array name="emoji_array"> <item>⊙▽⊙</item> <item> ( ؕؔʘ̥̥̥̥ ه ؔؕʘ̥̥̥̥ )? </item> <item>( •̅_•̅ ) </item> <item><MSGCONTENT><![CDATA["<( ̄︶ ̄)>" ]]></MSGCONTENT></item> <item>(๑ ̄ ̫  ̄๑) </item> <item>눈_눈 </item> <item>ᕙ(⇀‸↼‵‵)ᕗ</item> <item> ( ・᷄ ᵌ・᷅ ) </item> <item>(৹ᵒ̴̶̷᷄﹏ᵒ̴̶̷᷅৹) </item> <item>( ˉ ⌓ ˉ ๑) </item> <item> o(〃\'▽\'〃)o </item> <item>₍₍ (̨̡ ‾᷄ᗣ‾᷅ )̧̢ ₎₎ </item> <item>( ¬_¬) </item> <item>( ゚皿゚) </item> <item>(▭-▭) </item> <item>π_π </item> <item>(¬ω¬) </item> <item>(。•ˇ‸ˇ•。)</item> <item>(。・ω・。)ノ</item> <item>ू(ʚ̴̶̷́ .̠ ʚ̴̶̷̥̀ ू)</item> <item>ლ(●ↀωↀ●)ლ</item> <item>(ノ ̄д ̄)ノ</item> <item>⸂⸂⸜(രᴗര๑)⸝⸃⸃</item> <item>-_-||</item> <item>ɿ(。・ɜ・)ɾ</item> <item>p(´⌒`。q)</item> <item>⊙ω⊙</item> <item>ꉂ ೭(˵¯̴͒ꇴ¯̴͒˵)౨”</item> <item>(ღ˘⌣˘ღ)</item> <item>→_→ </item> <item>〜( ̄△ ̄〜)</item> <item>←_←</item> <item>؏؏ᖗ乛◡乛ᖘ؏؏</item> <item>\@_@</item> <item>(^3^)</item> <item>≥﹏≤</item> <item>^ω^</item> <item>^_^</item> <item>\^O^/</item> <item>T_T</item> <item>◑▂◐</item> <item>-_-#</item> </string-array> 3、android EditText 在获取到焦点的时候就会弹出软键盘,然后是不会执行onClick方法的,因为第一次点击如果是没有获取到焦点的话首先会获取焦点,然后下一次点击才会执行onCLick,如果需要首先执行onCLick的话,那么就要在布局中设置 android:clickable="true" android:focusableInTouchMode=“false" 然后点击的时候会执行onClick,然后在里边可以设置 mEditText.setFocusable(true); mEditText.setFocusableInTouchMode(true); mEditText.requestFocus(); mEditText.requestFocusFromTouch(); 然后就可以获取到焦点,然后在做软键盘的弹出 4、EditText 添加onScrollListener 只在5.0以上(不包括5.0)有这个方法,必须要保证最小编译版本在23 5、关于List源码学习: List 是一个接口,它继承了Collection接口,Collection继承了Iterable接口, Iterable里边定义了Iterator,foreach,spliterator这么三个方法Collection继承了以后在里边添加了add ,remove,clear,hashCode,equals,spliterator,retainAll,addAll,removeAll,toArray,containsAll,contains等方法List在继承了以后添加了get,set,indexOf,lastIndexOf,listIterator,subList,replaceAll,sort这几个方法。 我们在使用List的时候都是使用它的实现类ArrayList,LinkedList,Vector。 ArrayList的实现: 在实现了List接口以后我们会重写里边所有的方法,然后实现对应的功能,ArrayList还继承了AbstracList这个抽象类,这个类继承自AbstractCollection,然后实现了List接口(抽象类的主要作用就是实现共有部分功能) 但是大部分功能都是在ArrayList中进行的实现。ArrayList提供了三个构造方法 1、默认的无参构造方法,默认长度为10 public ArrayList() { super(); this.elementData = EMPTY_ELEMENTDATA; } 2、指定集合长度 public ArrayList(int initialCapacity) { super(); if (initialCapacity < 0) throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); this.elementData = new Object[initialCapacity]; } 3、以集合为参数 public ArrayList(Collection<? extends E> c) { elementData = c.toArray(); size = elementData.length; // c.toArray might (incorrectly) not return Object[](see 6260652) if (elementData.getClass() != Object[].class) elementData = Arrays.copyOf(elementData, size, Object[].class); } 其中的elementData是一个object类型的数组,用来存放集合中的元素。集合默认长度为10,如果要添加的元素超过了这个长度就会进行扩容,调用ensureCapacity,ensureExplicitCapacity,ensureExplicitCapacity,grow() private void grow(int minCapacity) { // overflow-conscious code int oldCapacity = elementData.length; int newCapacity = oldCapacity + (oldCapacity >> 1); if (newCapacity - minCapacity < 0) newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); // minCapacity is usually close to size, so this is a win: elementData = Arrays.copyOf(elementData, newCapacity); } 最后是通过Arrays的copyOf方法复制一个新的长度为newCapacity的数组,新的长度就等于老的长度除以2 + 老的长度,然后在进行下边的判断计算最终的长度。 所以从这些流程可以看到ArrayList在数据频繁添加的过程中会有频繁的扩容操作,因为会有数组的copy过程,如果数据量很大的时候会影响性能,最后是能确定他的最终大小,在初始化的时候直接申请。而且ArrayList是基于数组来进行数据元素的存储,所以在插入的过程中都会有元素的移动,插入速度相当慢。查询 6 、adb monkey 首先确保android的adb命令是ok的。然后就是monkey命令的参数说明。adb shell 进入手机命令行然后就可以执行 monkey 看到 monkey的参数说明 shell@PD1624:/ $ monkey usage: monkey [-p ALLOWED_PACKAGE [-p ALLOWED_PACKAGE] ...] [-c MAIN_CATEGORY [-c MAIN_CATEGORY] ...] [--ignore-crashes] [--ignore-timeouts] [--ignore-security-exceptions]//忽略安全检查 [--monitor-native-crashes] [--ignore-native-crashes] //忽略native 异常 [--kill-process-after-error]//发生错误时杀掉进程 [--hprof] //生成hprof文件 [--pct-touch PERCENT] [--pct-motion PERCENT]//该种类型事件的百分比 [--pct-trackball PERCENT]//轨迹球的事件百分比 [--pct-syskeys PERCENT]//系统事件百分比 [--pct-nav PERCENT] [--pct-majornav PERCENT] [--pct-appswitch PERCENT] [--pct-flip PERCENT] [--pct-anyevent PERCENT] [--pct-pinchzoom PERCENT] [--pct-permission PERCENT] [--pkg-blacklist-file PACKAGE_BLACKLIST_FILE]//黑名单 [--pkg-whitelist-file PACKAGE_WHITELIST_FILE]//白名单 [--wait-dbg] [--dbg-no-events] [--setup scriptfile] [-f scriptfile [-f scriptfile] ...] [--port port] [-s SEED] //设置随机种子数,如果俩次seed值相同,那么对于同一事件那么执行的结果完全相同 [-v [-v] ...] 作用:命令行上的每一个-v都将增加反馈信息的详细级别。 Level0(默认-v),除了启动、测试完成和最终结果外只提供较少的信息。 Level1(-v -v),提供了较为详细的测试信息,如逐个发送到Activity的事件信息。 Level2(-v -v -v),提供了更多的设置信息,如测试中选中或未选中的Activity信息。 [--throttle MILLISEC] [--randomize-throttle]//事件的延迟时间(这个时间设置了以后不是特别准备,如果需要确定要跑多久的monkey的话需要计算执行一次时间所花费的时间,然后用时间除以一个事件的时长算出要执行的次数) [--profile-wait MILLISEC] [--device-sleep-time MILLISEC]//设备睡眠事件 [--randomize-script] //随机脚本 [--script-log]//脚本log [--bugreport]//bug报告 [--periodic-bugreport]//定期bug报告 [--permission-target-system] COUNT //数量 >> 使用这个命令输出monkey日志到目标地址的文件中(这个不是monkey命令,是命令统一的输出方式) 从上边可以发现一个monkey命令的完整写法 简单的命令: monkey -v -p com.android.bluetooth 200-v log级别 -p 后边跟包名 200 就是要执行事件的次数 这就是一个简单的monkey测试 monkey -v -p com.guoyi.qinghua --pct-majornav 20 --monitor-native-crashes --ignore-security-exceptions --ignore-crashes --ignore-timeouts --kill-process-after-error -s 220 --throttle 300 500000 >> ../monkey.txt 因为monkey在测试过程中事件的延迟时间(这个时间设置了以后不是特别准确,如果需要确定要跑多久的monkey的话需要计算执行一次时间所花费的时间,然后用时间除以一个事件的时长算出要执行的次数)如果--throttle 1000 ,那么计算次数的方式 count= (时长 单位毫秒)/(一次事件的时间) monkey -v -v -v -p com.xxxx.xxx --throttle 1000 count 7、关于进入手机设置界面 可以通过抓包来获取当前ActivityManager来获取,当前跳转的activity的信息。下边是adb 命令adb logcat | grep ActivityManager 进入系统设置的所有界面都可以通过Intent 只要设置不同的Settings就可以 Intent appIntent = new Intent(); appIntent = new Intent(Settings.ACTION_SETTINGS); context.startActivity(appIntent); 只要替换Settings的值就可以,如果要进入其中一个应用的详情设置了Settings以后的话还需要传递包名 appIntent.setData(Uri.parse("package:" + context.getPackageName())); 但是android系统没有专门的权限管理。如果要进入权限管理必须要对不同系统进行区别,然后根据不同的定制系统来进行处理跳转。//vivo权限管理界面 appIntent.setClassName("com.iqoo.secure", "com.iqoo.secure.safeguard.PurviewTabActivity"); 然后可以通过adb 命令来启动一个页面 db shell am start -n com.android.settings/com.letv.leui.settings.LeUIMainSettings 8、关于EventBus源码学习 EventBus 的学习首先从 EventBus这个类开始。 这个类主要是用来进行EventBus的初始化,注册反注册,发送事件或者移除事件,是整个工具所暴露的可以进行操作的一个工作接口。 EventBus在初始化上边有俩种方式: (1)、通过getDefault() (2)、通过new EventBus() 但是实际创建方式都是通过Builder(建造者模式来进行创建)跟所有的建造者模式一样,都是通过一个私有的构造方法以EventBusBuilder对象为参数来进行对象的创建。 然后调用register来进行注册。在调用register这个方法的时候会传递一个要注册的对象的。然后会通过反射来获取这个对象中的所有的方法。然后会通过方法来找到其中订阅了EventBus的事件的方法。然后通过subscribe这个方法,把这些方法添加到订阅列表中。 public void register(Object subscriber) { Class<?> subscriberClass = subscriber.getClass(); List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass); synchronized (this) { for (SubscriberMethod subscriberMethod : subscriberMethods) { subscribe(subscriber, subscriberMethod); } } } 反注册的流程与一般的订阅者都是一样的,把订阅的方法都从订阅队列进行删除。 剩下的就是事件的发送: 因为整个EventBus的架构就是观察者模式。 所以事件发送其实就是通过EventBus对象对所有订阅了这个事件的订阅者进行通知,然后在他订阅的方法里边就可以进行处理。 public void post(Object event) { PostingThreadState postingState = currentPostingThreadState.get(); List<Object> eventQueue = postingState.eventQueue; eventQueue.add(event); if (!postingState.isPosting) { postingState.isMainThread = Looper.getMainLooper() == Looper.myLooper(); 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 ->postSingleEventForEventType->postToSubscription 然后执行真正的事件发送。 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 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); } } 说是发送,其实是通过反射来调用被订阅的方法。(只要我们能拿到被订阅的方法所在的类的对象,就可以执行他里边的方法)也就是通过EventBus中的invokeSubscriber方法 void invokeSubscriber(PendingPost pendingPost) { Object event = pendingPost.event; Subscription subscription = pendingPost.subscription; PendingPost.releasePendingPost(pendingPost); if (subscription.active) { invokeSubscriber(subscription, event); } } 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); } } 还有些要注意的类,就是HandlerPoster,这个类主要是维护了一个队列,用来管理要发送的事件。他的内在实现其实是通过Handler来进行的。handler就不需要多说了。AsyncPoster 异步Poster,通过实现Runnable,通过ExectorService 进行线程管理。BackgroundPoster后台Poster,通过实现Runnable进行实现,通过ExectorService 进行线程管理。它使用的是Executors.newCachedThreadPool创建的缓存线程池。具体什么事缓存线程池可以看相关的文章。 private final static ExecutorService DEFAULT_EXECUTOR_SERVICE = Executors.newCachedThreadPool(); 总结: **EventBus的整体架构是通过 观察者模式来进行事件的订阅,然后在订阅的事件发生了改变以后通知所有的订阅者。在这个过程中使用到了反射来进行。在订阅的时候通过反射获取到当前对象所定于的方法,在有改变的通过反射执行被订阅的方法,然后就可以处理自己的逻辑。 在发送事件的时候(post)我们会判断当前事件是在什么线程中执行。然后做相应的处理。对与事件处理主要使用了Handler(初始化的时候默认是主线程Handler),使用线程池进行事件的管理。里边用到了多线程的并发处理:使用ThreadLocal ,CopyOrWriteArrayList;synchronized。ThreadLocal 会为每一个线程创建一个副本对象,CopyOrWirteArrayList是通过copy一份数据,然后在Write的时候通过创建一个新的对象,然后对新的对象进行write,然后把新对象赋值给旧对象来保证线程安全。读取的时候通过使用旧的对象来进行,因为读取是不需要关注线程安全问题。synchronized同步锁,只有持有这个方法的所的对象才能进入这个方法,其他的都要等待。** 9、关于RecycleView的使用 使用RecyvleView的方式很简单,跟使用ListView,GridView类似,只是RecycleView可以实现比这俩个控件更多的功能。只要通过LayoutManager进行设置。 RecycleView没有onItemClickListener,需要自己实现。RecycleView 如果有错位的问题,那么肯定也是跟ListView一样因为复用引起的,其实修改原理都类似。个人觉得只要把复用的View里边的参数都还原成最初的状态(如果view成为最原始的状态,那么只要数据没有问题,就不会有错位的事了),然后通过tag来进行判断,然后在更新View即可。 RecycleView的adapter里边强制使用VIewHolder。为了减少重复创建对象的消耗(主要是会影响性能)。 RecycleView 滚动条的设置使用的是View的通用滚动条设置。 android:scrollbars=“horizontal" //设置滚动条的方向 android:scrollbarAlwaysDrawHorizontalTrack=“true" //设置总是绘制横向滚动条 android:scrollbarThumbHorizontal=“@drawable/scrollbar_line”//横向滚动条 android:scrollbarTrackHorizontal=“@drawable/scrollbar_line_track”//横向滚动条的轨道 android:paddingBottom="20dp" android:scrollbarStyle=“outsideInset" //设置显示的位置 android:fadeScrollbars=“false" //是否一直显示 如果要设置不一样粗细的滚动条与轨道,可以通过使用不同粗细的进度图片来实现,也可以通过使用shape来自定义滚动条与轨道,自定义的时候滚动条的高度要比轨道的高,这样横向滚动条与轨道就会不一样粗细。 10、fresco是一个很强大的图片加载库,不同的功能都有相对应的库模块来处理。 11、android中点击电源键关闭屏幕的话。 如果现在屏幕是竖屏没有任何影响,如果是横屏,会销毁activity,然后在启动这个Activity 。会导致activity数据的丢失。如果只是数据可以通过onSaveInstance进行保存,但是如果正在进行通话或者长连接的功能,会导致功能中断。解决方式: 1、使屏幕常亮 2、设置activity的 android:configChanges=“orientation|keyboardHidden|screenSize" 12、jni 编译 : (1)首先项目的sdk地址 创建native方法,然后点击studio的make project生成class文件 (2)然后切换到项目package目录下 (例如 com.xxx.xxx.xx) 调用 javaH -d ../jni “包名 + 类名” 然后会在jni文件夹下生成.h头文件。然后创建c或c++文件,进行代码的编写,可以直接复制头文件到c或c++文件中。提供的头文件里边的参数需要自定义名称。是一个空的实现。(3)配置app.gradle文件 ndk { moduleName"jnilib" ldLibs "log", "z", "m" abiFilters "armeabi", "armeabi-v7a", "x86" } 如果不知道studio支持的ndk可编译版本可以在gradle.properties添加android.useDeprecatedNdk=true//可使用过时版本 (如果使用studio编译以后运行出现dlopen failed: cannot locate symbol "__aeabi_memcpy" referenced by “/data/app/com.example.chenpengfei.uninstallreceiver-2/lib/arm/libjnilib.so”,那么是因为sdk版本的问题,23及以下,或者更新studio到最新版本) 13、LinkedList源码学习: 关于LinkedList 类的功能来说,也是一个集合容器类,实现了List接口,但是他内部的数据结构跟ArrayList不一样,使用的是链表。而实现链表主要是通过Node这个类。 他继承了AbstractSequentialList,而这个类其实是继承了AbstractList。他与ArrayList不一样的就是他实现了Deque接口。 因为它是基于Node实现的列表,那么需要关注的就是Node这个类 private static class Node<E> { E item; Node<E> next; Node<E> prev; Node(Node<E> prev, E element, Node<E> next) { this.item = element; this.next = next; this.prev = prev; } } 这个类会保存它上一个节点node,跟下一个节点node。 如果是有俩个节点那么a 节点的next指向b节点 ,pre也指向b,b的pre指向a,next也指向a,这样就形成了一个闭环。实现了双向链表。 如果是三个的话 a 节点的next 指向b ,pre指向c,b 节点的next 指向c,pre指向a,而c节点的next指向a,pre指向b。这样也是一个闭环。 所以LinkedList存储数据的时候特别快。直接把节点的指向改变了就可以了。但是存储的时候也分几种情况:如果是直接添加的话不管是添加到首部还是尾部都直接修改节点指向,但是如果是指定下标来进行添加的话,会折半查找到下边的node节点,然后在该节点处修改节点指向。速度会慢点。但是查询的时候就需要类似上边的折半以后进行查找,然后返回该节点。速度就会比ArrayList慢很多。毕竟人家是实现了RendemAsscess接口,而且内部结构是array,查询没的比啊。 14、Vector源码学习 通过学习ArrayList 与LinkedList以后在学习Vector发现没有太多要注意的地方。因为Vector是跟ArrayList基本上完全相同的,只是在某些地方有点区别。Vector是线程安全的,在很多方法上都加了线程锁,synchronize,实现同步。内部结构完全就是ArrayList的结构,只是在容量扩展的时候这是Vecctor的容量扩展 private void grow(int minCapacity) { // overflow-conscious code int oldCapacity = elementData.length; int newCapacity = oldCapacity + ((capacityIncrement > 0) ? capacityIncrement : oldCapacity); if (newCapacity - minCapacity < 0) newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); elementData = Arrays.copyOf(elementData, newCapacity); } 这是ArrayList的容量扩展 private void grow(int minCapacity) { // overflow-conscious code int oldCapacity = elementData.length; int newCapacity = oldCapacity + (oldCapacity >> 1); if (newCapacity - minCapacity < 0) newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); // minCapacity is usually close to size, so this is a win: elementData = Arrays.copyOf(elementData, newCapacity); } 可以发现在进行容量扩展的时候 newCapacity的值在取值的时候有点区别ArrayList是直接扩展为原来容量的3/2,但是Vector因为有 一个容量扩充的参数capacityIncrement,如果不为0,那么每次扩展是在原来的基础上增加capacityIncrement的长度,否则就是原来的2倍。 因为跟ArrayList很相似,学习的过程中发现比较一下这几个实现了List接口的类更有用: ArrayList、Vecotr、LinkedList的区别(源码角度): 区别: 1、 ArrayList、Vector使用的是数组的数据结构来进行数据的存储,实现了RandomAccess接口提供快速随机访问。 而LinkedList是通过链表这样的数据接口来存储数据,他实现了Deque接口,在随机访问方面性能很差,因为需要遍历这个链表中的节点(虽然是折半以后的遍历,但是一样很慢) 2、 容量扩展对于LinkedList来说是没有任何关系的,因为它不需要。但是对于ArrayList于Vector来说这是很重要的点。因为他们的初始容量都是10,除非你指定了他的容量。但是在把数据进行添加的过程中,他们会检查容量是否足够存放数据,如果不够就要进行容量的扩展。容量的扩展是通过创建一个新的容量的数组,然后指向上一个数组对象,创建的方式是调用一个系统的arrayCopy方法。数据量很大的话对与性能有很大影响,使用的时候根据需要可以指定初始容量。这就是他们之间的又一个不同点,插入效率,很明显LinkedList很快,但是Vector与ArrayList很慢 Android 虚拟现实开发 开发者中心地址:https://vr.google.com/daydream/developers/ 按需要下载自己需要的sdk已经开发工具 15、HashMap源码学习 HashMap是一个存储键值对的容器类,通过学习他的源码来熟悉他的实现。首先说下HashMap 1.8之前,最基本的实现是通过维护一个Entry[]的数组已经单链表来实现的数据存储。 就是当我们在创建一个HashMap以后,通过调用put方法,会进行很多判断,最后把我们要存放的数据添加到Entry[]数组中。 public V put(K key, V value) { if (key == null) return putForNullKey(value); // 得到key的哈希码 int hash = hash(key); // 通过哈希码计算出bucketIndex int i = indexFor(hash, table.length); // 取出bucketIndex位置上的元素,并循环单链表,判断key是否已存在 for (Entry<K,V> e = table[i]; e != null; e = e.next) { Object k; // 哈希码相同并且对象相同时 if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { // 新值替换旧值,并返回旧值 V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } // key不存在时,加入新元素 modCount++; addEntry(hash, key, value, i); return null; } 但是在1.8以后,为了实现快速查找,添加了红黑树。里边维护的数组变为了Node[]数组,一个实现Map.Entry接口的类。这应该是1.8改动最大的地方。 1 public V put(K key, V value) { 2 // 对key的hashCode()做hash 3 return putVal(hash(key), key, value, false, true); 4 } 5 6 final V putVal(int hash, K key, V value, boolean onlyIfAbsent, 7 boolean evict) { 8 Node<K,V>[] tab; Node<K,V> p; int n, i; 9 // 步骤①:tab为空则创建 10 if ((tab = table) == null || (n = tab.length) == 0) 11 n = (tab = resize()).length; 12 // 步骤②:计算index,并对null做处理 13 if ((p = tab[i = (n - 1) & hash]) == null) 14 tab[i] = newNode(hash, key, value, null); 15 else { 16 Node<K,V> e; K k; 17 // 步骤③:节点key存在,直接覆盖value 18 if (p.hash == hash && 19 ((k = p.key) == key || (key != null && key.equals(k)))) 20 e = p; 21 // 步骤④:判断该链为红黑树 22 else if (p instanceof TreeNode) 23 e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); 24 // 步骤⑤:该链为链表 25 else { 26 for (int binCount = 0; ; ++binCount) { 27 if ((e = p.next) == null) { 28 p.next = newNode(hash, key,value,null); //链表长度大于8转换为红黑树进行处理 29 if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st 30 treeifyBin(tab, hash); 31 break; 32 } // key已经存在直接覆盖value 33 if (e.hash == hash && 34 ((k = e.key) == key || (key != null && key.equals(k)))) break; 36 p = e; 37 } 38 } 39 40 if (e != null) { // existing mapping for key 41 V oldValue = e.value; 42 if (!onlyIfAbsent || oldValue == null) 43 e.value = value; 44 afterNodeAccess(e); 45 return oldValue; 46 } 47 } 48 ++modCount; 49 // 步骤⑥:超过最大容量 就扩容 50 if (++size > threshold) 51 resize(); 52 afterNodeInsertion(evict); 53 return null; 54 } 跟所有的容器类一样,这个类里边也定义size, modcout,loadFactor(负载因子),threshold(判断是否需要扩展的界限 他的值等于初始容量乘以 负载因子),默认的初始容量是 1<< 4 , Hash主要是通过散列表来进行存储,通过计算key的hash值,然后计算要存放的下标,然后放到数组中对应的位置。如果计算出来的hash相同,那么比较key是否相等,如果相等,那么久覆盖旧值,返回旧值,如果不相同,那么创建Node对象,然后与该位置中的Node链表进行连接,使Node链表的最新的节点是新创建的Node对象。 HashMap的扩容是扩容为 原来的2倍。 因为HashMap是通过散列表来进行存储,所以会有空间的浪费,也就是用空间换取时间。查找的时候通过计算key的hash值,然后计算在数组中的下标,可以快速的进行查找。HashMap中使用链表主要是为了解决碰撞问题,也就是会有相同的Hash值的问题。 16、HashSet源码: HashSet用来存放单一数据,不在是键值对,而且存储到其中的数据没有顺序,但是他可以保证数据不会重复(前提是你要重新hashCode 以及equals方法)。因为HashSet的底层实现是通过HashMap来进行实现的。通过他的构造方法可以看到: public HashSet(int initialCapacity) { map = new HashMap<>(initialCapacity); } public HashSet(int initialCapacity, float loadFactor) { map = new HashMap<>(initialCapacity, loadFactor); } public HashSet(Collection<? extends E> c) { map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16)); addAll(c); } public HashSet() { map = new HashMap<>(); } 如果对HashMap源码了解的话很简单就可以看懂。 HashSet提供了add方法进行数据的添加,也提供了容器类基本的功能。比如删除,是否包含,是否为空等等。使用HashSet的时候遍历数据是通过HashMap的KeySet这个类进行的。 final class KeySet extends AbstractSet<K> { public final int size() { return size; } public final void clear() { HashMap.this.clear(); } public final Iterator<K> iterator() { return new KeyIterator(); } public final boolean contains(Object o) { return containsKey(o); } public final boolean remove(Object key) { return removeNode(hash(key), key, null, false, true) != null; } public final Spliterator<K> spliterator() { return new KeySpliterator<>(HashMap.this, 0, -1, 0, 0); } public final void forEach(Consumer<? super K> action) { Node<K,V>[] tab; if (action == null) throw new NullPointerException(); if (size > 0 && (tab = table) != null) { int mc = modCount; for (int i = 0; i < tab.length; ++i) { for (Node<K,V> e = tab[i]; e != null; e = e.next) action.accept(e.key); } if (modCount != mc) throw new ConcurrentModificationException(); } } } 从这个可以看出,我们可以是用ForEach,或者迭代器。 HashSet实现数据不重复就是重写了HashCode以及Equals方法:是在他所继承的AbstractSet这个类中书写的: 使用HashSet的时候要注意他不是线程安全的,需要重新要存放数据的HashCode 与Equals方法。HashSet没有办法实现快速存取,如果需要如果需要获取到某一个位置的数据可以先调用 toArray方法转换为数组,然后进行查找.返回的数据顺序与插入的顺序相反。 HashMap的keySet的效率比entrySet效率低,因为keySet是通过迭代key来进行迭代,entrySet通过迭代Node对象来进行,不需要通过key进行value的查找,所以速度更快。 17、Popwindow源码学习: 关于popWindow的源码其中最主要的是show 以及dismiss的原理;做过悬浮窗开发的人肯定知道,如果我们要创建一个悬浮在当前Activity或者这个android系统上边的话,需要使用到的一个类肯定是WindowManager,通过WindowManager的addView的方法添加到界面上去,如果只是添加到当前Activity的话 18、Toast源码学习 也是通过WindowManager来进行实现的。toast主要是通过TN这个类来进行最终的显示以及隐藏,如果要自己来控制显示以及隐藏可以通过反射来获取Toast中的mTN对象,然后通过这个对象用反射来调用show以及dismiss。Toast中TN源码: private static class TN extends ITransientNotification.Stub { final Runnable mShow = new Runnable() { @Override public void run() { handleShow(); } }; final Runnable mHide = new Runnable() { @Override public void run() { handleHide(); // Don't do this in handleHide() because it is also invoked by handleShow() mNextView = null; } }; private final WindowManager.LayoutParams mParams = new WindowManager.LayoutParams(); final Handler mHandler = new Handler(); int mGravity; int mX, mY; float mHorizontalMargin; float mVerticalMargin; View mView; View mNextView; WindowManager mWM; TN() { // XXX This should be changed to use a Dialog, with a Theme.Toast // defined that sets up the layout params appropriately. final WindowManager.LayoutParams params = mParams; params.height = WindowManager.LayoutParams.WRAP_CONTENT; params.width = WindowManager.LayoutParams.WRAP_CONTENT; params.format = PixelFormat.TRANSLUCENT; params.windowAnimations = com.android.internal.R.style.Animation_Toast; params.type = WindowManager.LayoutParams.TYPE_TOAST; params.setTitle("Toast"); params.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; } /** * schedule handleShow into the right thread */ @Override public void show() { if (localLOGV) Log.v(TAG, "SHOW: " + this); mHandler.post(mShow); } /** * schedule handleHide into the right thread */ @Override public void hide() { if (localLOGV) Log.v(TAG, "HIDE: " + this); mHandler.post(mHide); } public void handleShow() { if (localLOGV) Log.v(TAG, "HANDLE SHOW: " + this + " mView=" + mView + " mNextView=" + mNextView); if (mView != mNextView) { // remove the old view if necessary handleHide(); mView = mNextView; Context context = mView.getContext().getApplicationContext(); String packageName = mView.getContext().getOpPackageName(); if (context == null) { context = mView.getContext(); } mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE); // We can resolve the Gravity here by using the Locale for getting // the layout direction final Configuration config = mView.getContext().getResources().getConfiguration(); final int gravity = Gravity.getAbsoluteGravity(mGravity, config.getLayoutDirection()); mParams.gravity = gravity; if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.FILL_HORIZONTAL) { mParams.horizontalWeight = 1.0f; } if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.FILL_VERTICAL) { mParams.verticalWeight = 1.0f; } mParams.x = mX; mParams.y = mY; mParams.verticalMargin = mVerticalMargin; mParams.horizontalMargin = mHorizontalMargin; mParams.packageName = packageName; if (mView.getParent() != null) { if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this); mWM.removeView(mView); } if (localLOGV) Log.v(TAG, "ADD! " + mView + " in " + this); mWM.addView(mView, mParams); trySendAccessibilityEvent(); } } private void trySendAccessibilityEvent() { AccessibilityManager accessibilityManager = AccessibilityManager.getInstance(mView.getContext()); if (!accessibilityManager.isEnabled()) { return; } // treat toasts as notifications since they are used to // announce a transient piece of information to the user AccessibilityEvent event = AccessibilityEvent.obtain( AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED); event.setClassName(getClass().getName()); event.setPackageName(mView.getContext().getPackageName()); mView.dispatchPopulateAccessibilityEvent(event); accessibilityManager.sendAccessibilityEvent(event); } public void handleHide() { if (localLOGV) Log.v(TAG, "HANDLE HIDE: " + this + " mView=" + mView); if (mView != null) { // note: checking parent() just to make sure the view has // been added... i have seen cases where we get here when // the view isn't yet added, so let's try not to crash. if (mView.getParent() != null) { if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this); mWM.removeView(mView); } mView = null; } } } 19、反射学习 关于java的反射:首先反射就是在程序运行的过程中可以动态的修改其状态以及行为的一种能力。现在我们使用反射大部分是通过修改某一个类的其中一个对象的引用或者对其值的修改,达到修改对象的行为。如果要对类中的方法或者实现进行修改就需要进行字节码注入操作,不在反射的学习中。 关于反射主要是通过Class这个类以及java.lang.reflect这个包下边Method,Field等等的来实现的。 20、android切换到桌面 Intent mHomeIntent; mHomeIntent = new Intent(Intent.ACTION_MAIN, null); mHomeIntent.addCategory(Intent.CATEGORY_HOME); mHomeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) startActivity(mHomeIntent); 21、关于String ,StringBuffer,StringBuilder的比较 String 类是一个final类型的类,它是不能被修改的,也就是说 String s = “aaaaaa” s = s + “bbbbb” 因为String 是不能被修改的,所以上边的在执行的时候是重新创建一个String 对象,然后 赋值给s 。所以如果在使用的时候是对String 对象来进行操作,是十分的耗时的,但是如果是直接对字符串进行操作,那么速度很快,都是jvm来进行操作的。 StringBuilder、StringBuffer是可变字符串对象,我们所有对字符串的操作都在这个对象上边,如果是对字符串对象进行操作的话,速度上要比String快上很多很多。 他们俩个都是继承了AbstractStringBuilder这个抽象类,在这个类中实现了大部分的功能,比如getValue,setLength,append ,reverse,等等,这个类还实现了CharSequence接口, 所以StringBuffer与StringBuilder都是在AbstractStringBuilder的基础上又进行了扩展,他们都实现了相同的三个接口java.io.Serializable, Appendable, CharSequence。 而他们俩个的区别就在于线程安全: StringBuffer:线程安全 ,是因为它里边很多方法都是上锁的,通过synchronized来进行修饰的,所以在多线程中操作时是不会引起对象的因多线程操作而发生的数据不一致问题。StringBuilder:非线程安全,与StringBuffer正好相反,所有的方法都么有加锁。但是如果在非多线程操作中,速度比StringBuffer要快。 22、6.0多进程无法安装总是提示安装失败。应用之间交互也需要设置关联启动 23、 5.0以后获取正在运行的程序只能获取到当前应用。而后台正在运行的服务以及应用都只能通过获取服务然后通过service或者process来进行判断。因为6.0以后应用如果设置多进程是无法安装的。获取方式: /** * 方法描述:判断某一Service或者应用是否正在运行 * * @param context 上下文 * @param serviceName Service的全路径: 包名 + service的类名 (如果是判断应用是否安装直接传递包名) * @return true 表示正在运行,false 表示没有运行 */ public static boolean isServiceRunning(Context context, String serviceName) { ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); List<ActivityManager.RunningServiceInfo> runningServiceInfos = am.getRunningServices(200); if (runningServiceInfos.size() <= 0) { return false; } for (ActivityManager.RunningServiceInfo serviceInfo : runningServiceInfos) { Log.e("TAG", serviceInfo.service.getClassName()); if (serviceInfo.process.equals(serviceName)) { return true; } } return false; } 24、android aidl进程间通信要注意的点; 现在在做一个sdk用来让讯飞可以与我们公司的应用进行开发。因为是要在进程间通信,所以肯定会使用Aidl来进行。但是要注意的几点就是:(1)进程间通信的话最好是不要把提供服务的service设置为独立的进程,要保证跟应用在同一个进程(2)还有就是进程间通信必 25、查看当前应用cpu内、存使用情况 adb shelltop -m 15 -s cpu //查询当前cpu占用前十的应用 dumpsys meminfo //查看手机上所有的内存信息dumpsys battery//查看电量信息dumpsys meminfo “要查看的应用的包名” //查询某一个应用的包名 top -d 1 | grep com.guoyi.qinghua //查询某一个应用的cpu使用情况 26、关于android 的应用程序cpu占用率问题 1、图片加载(图片加载使用好第三方框架,尽量不要使用Bitmap.create的方法) android动画对于cpu的消耗还是很大的,如果是可以通过自定实现的动画使用自定义动画,如过是复杂动画,能用ObjectAnimator实现就不要用ValueAnimator来进行实。不要使用动画来更新自定义的进度条或者是会根据时间间隔变化的View。可以用Timer或者Handler来替换。可以减小图片的大小。2、线程 (线程确保执行完成,或者可以手动控制)3、布局加载(布局尽量减少布局层数)4、网络(在进程网络访问的时候要先判断网状态)5、定位(降低定位的时间间隔)6、直播软件的编解码(在使用七牛播放器的时候,设置硬解码cpu的占有率比软解码低一半,如果cpu占用太高切换硬解码是一个好的选择) 27、android logo 尺寸 DENSITY SIZE LOCATION RATIO SCREEN MARGINXXXHDPI 192×192 drawable-xxxhdpi 4 640 DPI 12 to 16 pixelsXXHDPI 144×144 drawable-xxhdpi 3 480 DPI 8 to 12 pixelsXHDPI 96×96 drawable-xhdpi 2 320 DPI 6 to 8 pixelsHDPI 72×72 drawable-hdpi 1.5 240 DPI 4 to 6 pixelsMDPI 48×48 drawable-mdpi 1 160 DPI 3 to 4 pixelsMDPI 48×48 drawable (Cupcake) 1 160 DPI 3 to 4 pixelsLDPI 36×36 drawable-ldpi 0.75 120 DPI 2 to 3 pixelsNA 512×512 Google Play NA NA As required 28、ython环境配置以后在使用中出现的问题 python代码需要在开头插入 #!/usr/bin/python用来生名python支持,然后通过import可以导入需要的类。如果出现 -bash: ./**.py: /usr/bin/python^M: bad interpreter: No such file or directory那么应该是不同系统编码格式引起的:在windows系统中编辑的.sh .py文件可能有不可见字符,所以在linux系统下执行会报以上异常信息。一般是因为windows行结尾和linux行结尾标识不同造成的。 解决: 1)在windows下转换: 利用一些编辑器如UltraEdit或EditPlus等工具先将脚本编码转换,再放到Linux中执行。转换方式如下(UltraEdit):File-->Conversions-->DOS->UNIX即可。 2)linux下直接替换: sed -i 's/^M//g' filename (注意^M 在linux 下写法 按^M 是回车换行符,输入方法是按住CTRL+v,松开v,按m) 3)也可在Linux中转换: 首先要确保文件有可执行权限 #sh>chmod a+x filename 然后修改文件格式 #sh>vi filename 利用如下命令查看文件格式 :set ff 或 :set fileformat 可以看到如下信息 fileformat=dos 或 fileformat=unix 利用如下命令修改文件格式 :set ff=unix 或 :set fileformat=unix :wq (存盘退出) 或者使用:wq! 进行保存 最后再执行文件 #sh>./filename 如果配置了全局的python环境变量的话可以直接使用xxxx.py来执行py文件,或者要通过./xxxx.py 29、Bitmap.createBitmap中参数的理解 x的值不能小于0,width 不能小于0;x+width 的值不能小于0,也不能大于原bitmap的宽度,y的值与height与上边规则相同,而通过这个方法创建的btimap是通过x与y来控制要截取的bitmap的起始位置,最后的width与height用来确定从x与y的位置截取开始到什么时候结束。 30、如果.9图片不正规可能导致android studio编译出现多线程问题 31、studio中svn的使用 studio中使用svn可以更新指定文件,也可以提交指定文件。svn指定版本更新会把指定版本以下的代码都更新下来,不会更新指定版本以上的代码svn 中revert 复原代码,只能复原没有提交的文件,如果提交时没有办法复原 32、RecycleView 的scrollToPosition方法,如果剩下的item个数不够一屏幕的话无法滚动。 但是可以使用 scrollBy(int x, int y) 滚动屏幕的宽度的距离就可以。 33、内存泄漏的理解,如何防止内存泄漏(以及内存泄漏会引起的问题) 关于内存泄漏其实最难的是定位问题出现。一个良好的代码习惯,能避免掉很多内存泄漏,但是也不能规避过所有的。以前都是在内存泄漏以后使用工具进行内存分析,找到内存泄漏的地方,然后进行修改,现在发现一个内存泄漏检测比较简单的一个工具leakcanary,可以去gitHub上直接查看源码,然后简单集成下就可以了。内存泄漏:内存泄漏就是一个对象被别的对象引用,导致他应该被释放回收的时候,没有办法释放,对内存的持续占用,会导致android应用在给别的功能分配内存的时候,可能没有办法分配给这个功能那么多的内存,然后gc开始查找可以释放的内存,但是因为内存泄漏了(也就是那么没有办法释放的对象都是强引用),gc查找以后也不能释放出足够这个功能所需要占用的内存的时候,那么就会出现内存泄漏。内存泄漏的解决办法:可以加大android系统给应用分配的内存上限,但是这个是治标不治本,最主要还是要修改内存泄漏的根本原因。一般发生内存泄漏的地方有(1)匿名内部类的使用(因为匿名内部类一般都会持有当前对象的引用,那么在内部类对象没有释放的时候,那么他所持有的引用也不会消失,就会导致内存泄漏)如果可以在Application中添加的回调监听,一定不要放在activity中,因为那样的话,在回调没有回调的时候,引用也会一直持有;(2)在开发中发现 集合类很容易引起内存泄漏,一般尽量不要把集合类创建为static类型,否则的话他里边的对象都不会得到释放,除非是从集合中移除。可以参考的文章:http://blog.csdn.net/u012808234/article/details/74942491 34、屏幕适配是android一直以来都很坚挺的问题呢, 因为android的开源性,各个厂商的定制,等等,导致android手机的分辨率是千奇百怪,但是我们适配一般也只适配主流的分辨率,不是所有。那么在适配的过程中如何用最小的成本来适配最多屏幕,就是我们开发需要考虑的问题。android 中推荐使用match_parent,warp_parent, xxxdl ,xxxsp,主要就是为了适配。 dp(dip或者叫dpi) 代表的是单位英寸内的像素点个数,他是跟像素无关,跟屏幕密度有关的。android中屏幕密度越大也就是单位英寸内的像素点越多。屏幕显示的更清晰。密度的计算方式:1920x1080 5英寸手机(对角线的长度,也就是手机左上角跟右下角的长度)√(1920^2+1080^2)=2202.9071 2202.9/5=468.7021(ppi)≈469ppi如果在480px 跟240px上 如果密度相同,那么1dp所占的大小也是相等的。这就是跟像素无关160ppi 1dp 1px 320ppi 1dp 2px480ppi 1dp 3px密度因子以160为基础,也就是160 密度因子是1,密度因子= 密度/160 dp转px px = dp ️密度因子 知道了这个,但是如果只是简单的设置dp还是不能够适配大多数屏幕:适配方式: 1、布局适配,根据不同分辨率创建不同的布局 优点是简单明了,只要根据不同的屏幕创建不同的布局即可,但是会导致的问题就是,布局文件的增多,如果里边对于图片的使用很多的话,那么会导致app打包以后的大小变的很大,要适配大部分屏幕,工作量比较大 2、布局的时候通过比例来进行适配,比如一个按钮占屏幕的几分之几 优点就是一套布局可以适配所有的屏幕,但是不是所有的布局都能用比例来适配 3、使用一套布局,然后创建不同分辨率的dimens.xml (创建不同分辨率的values比如values_480x1280) 4、一些动画或者效果,客户端如果可以实现的话,而且不是特别难的话,最好是自定义,动态的适配分辨率 5、在代码中动态修改控件的宽度等等参数 35、开发工程中编程语言的学习以及开发工具: 36 、多项目依赖同一个module,实现module的统一管理 现在做android的基本上都在用studio进行开发,如果开发一个项目还好,但是如果是多个项目,而且他们还有同样在使用的Module,那么就要维护很多套不同项目的,但是缺相同的Moudle,所以使用多项目依赖同样的Moudule可以很有效的减小代码的维护成本,减小开发时间。 方式: 首先创建一个项目,该项目包含所有公有的Module 打开要添加公有Module的项目,通过强制引用添加所需要的Module打开项目的setting.gradle如下配置:include ':app', ':framework' include ':cuslibrary' ,’:qhlibrary' //添加项目所要包含的Moduleproject(':cuslibrary').projectDir = new File(‘../AllModule/cuslibrary') //导入该路径下的Moudleproject(':qhlibrary').projectDir = new File(‘../AllModule/qhlibrary’) //导入该路径下的Moudle 然后在该项目的app文件夹的根目录下边的build.gradle中添加Module的依赖 compile project(':cuslibrary') compile project(‘:qhlibrary') 保证不会有相同的文件或者类库的引用,然后运行ok 37、在使用svn的时候如果add 了一个不进行版本管理的文件会出现 E200009的错误 解决方式:svn st | grep ^? | awk '{print $2}' | xargs svn add

优秀的个人博客,低调大师

Zigbee联盟基础知识普及

做智能家居,还要了解ZigBee联盟,不是做好产品就好吗,与ZigBee联盟有什么关系?在具体说明这个问题之前,我们不妨先来了解一下ZigBee。 ZigBee实际是一种短距离、低功耗的无线通信技术,名称来源于 ZigZag——一种蜜蜂的肢体语言。当蜜蜂新发现一片花丛后会用特殊舞蹈来告知同伴发现的食物种类及位置等信息,是蜜蜂群体间一种简单、高效的传递信息方式,因此ZigBee 也被成为“紫蜂协议”。 这样解释可能还是难以让人明白。简单说,ZigBee是一种类似于WiFi和蓝牙的通信协议,能够让设备与设备之间实现无线通信。而不同的是,虽然都是无线通信技术,ZigBee在智能家居领域却有着独特优势,如功耗低(发射功率仅为1mW)、连接设备多(理论上可达六万多个)、安全性高(AES-128加密算法)等等。 考虑到智能家居系统实质上为电子产品,涉及的设备又比较多,且与用户家庭安全息息相关,ZigBee技术在智能家居行业得到广泛关注也就不足为奇了。事实上,ZigBee目前已成为智能家居领域的一种主流无线技术,被广泛应用于各类家庭智能化产品。 ZigBee是一种实现智能硬件设备连接和数据交换的无线技术,那么ZigBee联盟又是干什么的呢?接下来,就进入正题,说一说ZigBee联盟。 ZigBee联盟(ZigBee Alliance)是为消费、商业和工业应用领域创建开放式全球物联网标准的非营利性组织,在全球拥有成员公司400多家,覆盖芯片供应商,设备制造商,电视、电信运营商,认证机构,大型零售集团等产业链的各个环节。联盟董事会成员包括恩智浦半导体、康卡斯特有线、飞思卡尔半导体、艾创、克罗格、兰吉尔、罗格朗、飞利浦、施耐德、芯科、德州仪器、物联传感(亚洲区唯一董事会成员)、SmartThings(三星子公司)等13家全球知名企业。 说得简单些,ZigBee联盟是制定、优化和完善ZigBee技术标准的非营利组织,组织成员主要为世界各地的知名公司,当然,其中也有不少是来自中国。显而易见,Zigbee联盟在ZigBee技术的逐步完善过程中扮演重要角色,在智能家居乃至整个物联网领域具有重大影响。而混智能家居圈,需要了解ZigBee联盟,也就不难理解了。 那么都是哪些公司有资格成为ZigBee联盟成员呢?是不是只要加入了ZigBee联盟就能参与标准制定和完善了呢? ZigBee联盟没有对申请加入的企业作严格的限定,没有设立过高的门槛。正如上面所说,ZigBee联盟成员覆盖了多个环节,并非局限于某一方面。不过,申请加入的企业还是需要有一定的实力的,绝非随随便便就能加入。需要指出的是,加入ZigBee联盟后,成员的地位有一定的差别。 近日,WiFi联盟(WiFi Alliance)宣布推出全新的会员资格类别(实施级会员),以支持更多企业推出WiFi CERTIFIED认证产品。事实上,ZigBee联盟一直都在实行三级会员资格体系,不同级别会员所拥有的权力范围不相同。 ZigBee联盟成员分为三个级别,分别是促进者(Promoters),参与者(Participants)和采纳者(Adopters)。促进者是联盟中最有影响力的级别,可获得董事会席位;参与者则可以参与细则的制订、技术委员会候选人资格和参加研发工作;而采纳者只能得到最终通过的文书并使用完成的ZigBee规范。 比如,在今年9月23—25日举办的2015上海国际智能建筑展上,一些ZigBee联盟采纳者成员称“一直在积极的参与标准化制定和实施”是不准确的,因为“标准化制定”是“参与者”以上等级会员才能够参与的,采纳者并无权参与。再如,ZigBee联盟前不久宣布,家装零售商Lowe’s(劳氏)以“参与者”身份加入ZigBee联盟,Lowe’s就可以参与细则的制订、有资格成为技术指导委员会的候选人以及参加研发工作。 当然,无论是什么等级的会员,成为ZigBee联盟成员对企业在智能家居领域发展都是有益的。而对于智能家居企业来说,至少有机会获得并且能够影响新兴的ZigBee规范,增强产品和产业的竞争力。 作者:何妍 来源:51CTO

优秀的个人博客,低调大师

Spark知识体系完整解读

Spark简介 Spark是整个BDAS的核心组件,是一个大数据分布式编程框架,不仅实现了MapReduce的算子map 函数和reduce函数及计算模型,还提供更为丰富的算子,如filter、join、groupByKey等。是一个用来实现快速而同用的集群计算的平台。 Spark将分布式数据抽象为弹性分布式数据集(RDD),实现了应用任务调度、RPC、序列化和压缩,并为运行在其上的上层组件提供API。其底层采用Scala这种函数式语言书写而成,并且所提供的API深度借鉴Scala函数式的编程思想,提供与Scala类似的编程接口 Sparkon Yarn 从用户提交作业到作业运行结束整个运行期间的过程分析。 一、客户端进行操作 根据yarnConf来初始化yarnClient,并启动yarnClient 创建客户端Application,并获取Application的ID,进一步判断集群中的资源是否满足executor和ApplicationMaster申请的资源,如果不满足则抛出IllegalArgumentException; 设置资源、环境变量:其中包括了设置Application的Staging目录、准备本地资源(jar文件、log4j.properties)、设置Application其中的环境变量、创建Container启动的Context等; 设置Application提交的Context,包括设置应用的名字、队列、AM的申请的Container、标记该作业的类型为Spark; 申请Memory,并最终通过yarnClient.submitApplication向ResourceManager提交该Application。 当作业提交到YARN上之后,客户端就没事了,甚至在终端关掉那个进程也没事,因为整个作业运行在YARN集群上进行,运行的结果将会保存到HDFS或者日志中。 二、提交到YARN集群,YARN操作 运行ApplicationMaster的run方法; 设置好相关的环境变量。 创建amClient,并启动; 在Spark UI启动之前设置Spark UI的AmIpFilter; 在startUserClass函数专门启动了一个线程(名称为Driver的线程)来启动用户提交的Application,也就是启动了Driver。在Driver中将会初始化SparkContext; 等待SparkContext初始化完成,最多等待spark.yarn.applicationMaster.waitTries次数(默认为10),如果等待了的次数超过了配置的,程序将会退出;否则用SparkContext初始化yarnAllocator; 当SparkContext、Driver初始化完成的时候,通过amClient向ResourceManager注册ApplicationMaster 分配并启动Executeors。在启动Executeors之前,先要通过yarnAllocator获取到numExecutors个Container,然后在Container中启动Executeors。 那么这个Application将失败,将Application Status标明为FAILED,并将关闭SparkContext。其实,启动Executeors是通过ExecutorRunnable实现的,而ExecutorRunnable内部是启动CoarseGrainedExecutorBackend的。 最后,Task将在CoarseGrainedExecutorBackend里面运行,然后运行状况会通过Akka通知CoarseGrainedScheduler,直到作业运行完成。 Spark节点的概念 一、Spark驱动器是执行程序中的main()方法的进程。它执行用户编写的用来创建SparkContext(初始化)、创建RDD,以及运行RDD的转化操作和行动操作的代码。 驱动器节点driver的职责: 把用户程序转为任务task(driver) Spark驱动器程序负责把用户程序转化为多个物理执行单元,这些单元也被称之为任务task(详解见备注) 为执行器节点调度任务(executor) 有了物理计划之后,Spark驱动器在各个执行器节点进程间协调任务的调度。Spark驱动器程序会根据当前的执行器节点,把所有任务基于数据所在位置分配给合适的执行器进程。当执行任务时,执行器进程会把缓存的数据存储起来,而驱动器进程同样会跟踪这些缓存数据的位置,并利用这些位置信息来调度以后的任务,以尽量减少数据的网络传输。(就是所谓的移动计算,而不移动数据)。 二、执行器节点 作用: 负责运行组成Spark应用的任务,并将结果返回给驱动器进程; 通过自身的块管理器(blockManager)为用户程序中要求缓存的RDD提供内存式存储。RDD是直接缓存在执行器进程内的,因此任务可以在运行时充分利用缓存数据加快运算。 驱动器的职责: 所有的Spark程序都遵循同样的结构:程序从输入数据创建一系列RDD,再使用转化操作派生成新的RDD,最后使用行动操作手机或存储结果RDD,Spark程序其实是隐式地创建出了一个由操作组成的逻辑上的有向无环图DAG。当驱动器程序执行时,它会把这个逻辑图转为物理执行计划。 这样 Spark就把逻辑计划转为一系列步骤(stage),而每个步骤又由多个任务组成。这些任务会被打包送到集群中。 Spark初始化 每个Spark应用都由一个驱动器程序来发起集群上的各种并行操作。驱动器程序包含应用的main函数,并且定义了集群上的分布式数据集,以及对该分布式数据集应用了相关操作。 驱动器程序通过一个SparkContext对象来访问spark,这个对象代表对计算集群的一个连接。(比如在sparkshell启动时已经自动创建了一个SparkContext对象,是一个叫做SC的变量。(下图,查看变量sc) 一旦创建了sparkContext,就可以用它来创建RDD。比如调用sc.textFile()来创建一个代表文本中各行文本的RDD。(比如vallinesRDD = sc.textFile(“yangsy.text”),val spark = linesRDD.filter(line=>line.contains(“spark”),spark.count()) 执行这些操作,驱动器程序一般要管理多个执行器,就是我们所说的executor节点。 在初始化SparkContext的同时,加载sparkConf对象来加载集群的配置,从而创建sparkContext对象。 从源码中可以看到,在启动thriftserver时,调用了spark- daemon.sh文件,该文件源码如左图,加载spark_home下的conf中的文件。 (在执行后台代码时,需要首先创建conf对象,加载相应参数, val sparkConf = newSparkConf().setMaster("local").setAppName("cocapp").set("spark.executor.memory","1g"), val sc: SparkContext = new SparkContext(sparkConf)) RDD工作原理: RDD(Resilient DistributedDatasets)[1] ,弹性分布式数据集,是分布式内存的一个抽象概念,RDD提供了一种高度受限的共享内存模型,即RDD是只读的记录分区的集合,只能通过在其他RDD执行确定的转换操作(如map、join和group by)而创建,然而这些限制使得实现容错的开销很低。对开发者而言,RDD可以看作是Spark的一个对象,它本身运行于内存中,如读文件是一个RDD,对文件计算是一个RDD,结果集也是一个RDD ,不同的分片、数据之间的依赖、key-value类型的map数据都可以看做RDD。 主要分为三部分:创建RDD对象,DAG调度器创建执行计划,Task调度器分配任务并调度Worker开始运行。 SparkContext(RDD相关操作)→通过(提交作业)→(遍历RDD拆分stage→生成作业)DAGScheduler→通过(提交任务集)→任务调度管理(TaskScheduler)→通过(按照资源获取任务)→任务调度管理(TaskSetManager) Transformation返回值还是一个RDD。它使用了链式调用的设计模式,对一个RDD进行计算后,变换成另外一个RDD,然后这个RDD又可以进行另外一次转换。这个过程是分布式的。 Action返回值不是一个RDD。它要么是一个Scala的普通集合,要么是一个值,要么是空,最终或返回到Driver程序,或把RDD写入到文件系统中 转换(Transformations)(如:map, filter, groupBy, join等),Transformations操作是Lazy的,也就是说从一个RDD转换生成另一个RDD的操作不是马上执行,Spark在遇到Transformations操作时只会记录需要这样的操作,并不会去执行,需要等到有Actions操作的时候才会真正启动计算过程进行计算。 操作(Actions)(如:count, collect, save等),Actions操作会返回结果或把RDD数据写到存储系统中。Actions是触发Spark启动计算的动因。 它们本质区别是:Transformation返回值还是一个RDD。它使用了链式调用的设计模式,对一个RDD进行计算后,变换成另外一个RDD,然后这个RDD又可以进行另外一次转换。这个过程是分布式的。Action返回值不是一个RDD。它要么是一个Scala的普通集合,要么是一个值,要么是空,最终或返回到Driver程序,或把RDD写入到文件系统中。关于这两个动作,在Spark开发指南中会有就进一步的详细介绍,它们是基于Spark开发的核心。 RDD基础 Spark中的RDD就是一个不可变的分布式对象集合。每个RDD都被分为多个分区,这些分区运行在集群的不同节点上。创建RDD的方法有两种:一种是读取一个外部数据集;一种是在群东程序里分发驱动器程序中的对象集合,不如刚才的示例,读取文本文件作为一个字符串的RDD的示例。 创建出来后,RDD支持两种类型的操作:转化操作和行动操作 转化操作会由一个RDD生成一个新的RDD。(比如刚才的根据谓词筛选) 行动操作会对RDD计算出一个结果,并把结果返回到驱动器程序中,或把结果存储到外部存储系统(比如HDFS)中。比如first()操作就是一个行动操作,会返回RDD的第一个元素。 注:转化操作与行动操作的区别在于Spark计算RDD的方式不同。虽然你可以在任何时候定义一个新的RDD,但Spark只会惰性计算这些RDD。它们只有第一个在一个行动操作中用到时,才会真正的计算。之所以这样设计,是因为比如刚才调用sc.textFile(...)时就把文件中的所有行都读取并存储起来,就会消耗很多存储空间,而我们马上又要筛选掉其中的很多数据。 这里还需要注意的一点是,spark会在你每次对它们进行行动操作时重新计算。如果想在多个行动操作中重用同一个RDD,那么可以使用RDD.persist()或RDD.collect()让Spark把这个RDD缓存下来。(可以是内存,也可以是磁盘) Spark会使用谱系图来记录这些不同RDD之间的依赖关系,Spark需要用这些信息来按需计算每个RDD,也可以依靠谱系图在持久化的RDD丢失部分数据时用来恢复所丢失的数据。(如下图,过滤errorsRDD与warningsRDD,最终调用union()函数) RDD计算方式 RDD的宽窄依赖 窄依赖 (narrowdependencies) 和宽依赖 (widedependencies) 。窄依赖是指 父 RDD 的每个分区都只被子 RDD 的一个分区所使用 。相应的,那么宽依赖就是指父 RDD 的分区被多个子 RDD 的分区所依赖。例如, map 就是一种窄依赖,而 join 则会导致宽依赖 这种划分有两个用处。首先,窄依赖支持在一个结点上管道化执行。例如基于一对一的关系,可以在 filter 之后执行 map 。其次,窄依赖支持更高效的故障还原。因为对于窄依赖,只有丢失的父 RDD 的分区需要重新计算。而对于宽依赖,一个结点的故障可能导致来自所有父 RDD 的分区丢失,因此就需要完全重新执行。因此对于宽依赖,Spark 会在持有各个父分区的结点上,将中间数据持久化来简化故障还原,就像 MapReduce 会持久化 map 的输出一样。 SparkExample 步骤 1 :创建 RDD 。上面的例子除去最后一个 collect 是个动作,不会创建 RDD 之外,前面四个转换都会创建出新的 RDD 。因此第一步就是创建好所有 RDD( 内部的五项信息 ) 。 步骤 2 :创建执行计划。Spark 会尽可能地管道化,并基于是否要重新组织数据来划分 阶段 (stage) ,例如本例中的 groupBy() 转换就会将整个执行计划划分成两阶段执行。最终会产生一个 DAG(directedacyclic graph ,有向无环图 ) 作为逻辑执行计划。 步骤 3 :调度任务。将各阶段划分成不同的 任务 (task) ,每个任务都是数据和计算的合体。在进行下一阶段前,当前阶段的所有任务都要执行完成。因为下一阶段的第一个转换一定是重新组织数据的,所以必须等当前阶段所有结果数据都计算出来了才能继续。 假设本例中的 hdfs://names 下有四个文件块,那么 HadoopRDD 中 partitions 就会有四个分区对应这四个块数据,同时 preferedLocations 会指明这四个块的最佳位置。现在,就可以创建出四个任务,并调度到合适的集群结点上。 Spark数据分区 Spark的特性是对数据集在节点间的分区进行控制。在分布式系统中,通讯的代价是巨大的,控制数据分布以获得最少的网络传输可以极大地提升整体性能。Spark程序可以通过控制RDD分区方式来减少通讯的开销。 Spark中所有的键值对RDD都可以进行分区。确保同一组的键出现在同一个节点上。比如,使用哈希分区将一个RDD分成了100个分区,此时键的哈希值对100取模的结果相同的记录会被放在一个节点上。 (可使用partitionBy(newHashPartitioner(100)).persist()来构造100个分区) Spark中的许多操作都引入了将数据根据键跨界点进行混洗的过程。(比如:join(),leftOuterJoin(),groupByKey(),reducebyKey()等)对于像reduceByKey()这样只作用于单个RDD的操作,运行在未分区的RDD上的时候会导致每个键的所有对应值都在每台机器上进行本地计算。 SparkSQL的shuffle过程 Spark SQL的核心是把已有的RDD,带上Schema信息,然后注册成类似sql里的”Table”,对其进行sql查询。这里面主要分两部分,一是生成SchemaRD,二是执行查询。 如果是spark-hive项目,那么读取metadata信息作为Schema、读取hdfs上数据的过程交给Hive完成,然后根据这俩部分生成SchemaRDD,在HiveContext下进行hql()查询。 SparkSQL结构化数据 首先说一下ApacheHive,Hive可以在HDFS内或者在其他存储系统上存储多种格式的表。SparkSQL可以读取Hive支持的任何表。要把Spark SQL连接已有的hive上,需要提供Hive的配置文件。hive-site.xml文件复制到spark的conf文件夹下。再创建出HiveContext对象(sparksql的入口),然后就可以使用HQL来对表进行查询,并以由行足证的RDD的形式拿到返回的数据。 创建Hivecontext并查询数据 importorg.apache.spark.sql.hive.HiveContext valhiveCtx = new org.apache.spark.sql.hive.HiveContext(sc) valrows = hiveCtx.sql(“SELECT name,age FROM users”) valfitstRow – rows.first() println(fitstRow.getSgtring(0)) //字段0是name字段 通过jdbc连接外部数据源更新与加载 Class.forName("com.mysql.jdbc.Driver") val conn =DriverManager.getConnection(mySQLUrl) val stat1 =conn.createStatement() stat1.execute("UPDATE CI_LABEL_INFO set DATA_STATUS_ID = 2 , DATA_DATE ='" + dataDate +"' where LABEL_ID in ("+allCreatedLabels.mkString(",")+")") stat1.close() //加载外部数据源数据到内存 valDIM_COC_INDEX_MODEL_TABLE_CONF =sqlContext.jdbc(mySQLUrl,"DIM_COC_INDEX_MODEL_TABLE_CONF").cache() val targets =DIM_COC_INDEX_MODEL_TABLE_CONF.filter("TABLE_DATA_CYCLE ="+TABLE_DATA_CYCLE).collect SparkSQL解析 首先说下传统数据库的解析,传统数据库的解析过程是按Rusult、Data Source、Operation的次序来解析的。传统数据库先将读入的SQL语句进行解析,分辨出SQL语句中哪些词是关键字(如select,from,where),哪些是表达式,哪些是Projection,哪些是Data Source等等。进一步判断SQL语句是否规范,不规范就报错,规范则按照下一步过程绑定(Bind)。过程绑定是将SQL语句和数据库的数据字典(列,表,视图等)进行绑定,如果相关的Projection、Data Source等都存在,就表示这个SQL语句是可以执行的。在执行过程中,有时候甚至不需要读取物理表就可以返回结果,比如重新运行刚运行过的SQL语句,直接从数据库的缓冲池中获取返回结果。在数据库解析的过程中SQL语句时,将会把SQL语句转化成一个树形结构来进行处理,会形成一个或含有多个节点(TreeNode)的Tree,然后再后续的处理政对该Tree进行一系列的操作。 Spark SQL对SQL语句的处理和关系数据库对SQL语句的解析采用了类似的方法,首先会将SQL语句进行解析,然后形成一个Tree,后续如绑定、优化等处理过程都是对Tree的操作,而操作方法是采用Rule,通过模式匹配,对不同类型的节点采用不同的操作。SparkSQL有两个分支,sqlContext和hiveContext。sqlContext现在只支持SQL语法解析器(Catalyst),hiveContext支持SQL语法和HiveContext语法解析器。 本文作者:杨思义 来源:51CTO

优秀的个人博客,低调大师

知识分享:详解Hadoop核心架构

通过对Hadoop分布式计算平台最核心的分布式文件系统HDFS、MapReduce处理过程,以及数据仓库工具Hive和分布式数据库Hbase的介绍,基本涵盖了Hadoop分布式平台的所有技术核心。 通过这一阶段的调研总结,从内部机理的角度详细分析,HDFS、MapReduce、Hbase、Hive是如何运行,以及基于Hadoop数据仓库的构建和分布式数据库内部具体实现。如有不足,后续及时修改。 HDFS的体系架构 整个Hadoop的体系结构主要是通过HDFS来实现对分布式存储的底层支持,并通过MR来实现对分布式并行任务处理的程序支持。 HDFS采用主从(Master/Slave)结构模型,一个HDFS集群是由一个NameNode和若干个DataNode组成的(在最新的Hadoop2.2版本已经实现多个NameNode的配置-这也是一些大公司通过修改hadoop源代码实现的功能,在最新的版本中就已经实现了)。NameNode作为主服务器,管理文件系统命名空间和客户端对文件的访问操作。DataNode管理存储的数据。HDFS支持文件形式的数据。 从内部来看,文件被分成若干个数据块,这若干个数据块存放在一组DataNode上。NameNode执行文件系统的命名空间,如打开、关闭、重命名文件或目录等,也负责数据块到具体DataNode的映射。DataNode负责处理文件系统客户端的文件读写,并在NameNode的统一调度下进行数据库的创建、删除和复制工作。NameNode是所有HDFS元数据的管理者,用户数据永远不会经过NameNode。 如图:HDFS体系结构图 图中涉及三个角色:NameNode、DataNode、Client。NameNode是管理者,DataNode是文件存储者、Client是需要获取分布式文件系统的应用程序。 文件写入: 1) Client向NameNode发起文件写入的请求。 2) NameNode根据文件大小和文件块配置情况,返回给Client它管理的DataNode的信息。 3) Client将文件划分为多个block,根据DataNode的地址,按顺序将block写入DataNode块中。 文件读取: 1) Client向NameNode发起读取文件的请求。 2) NameNode返回文件存储的DataNode信息。 3) Client读取文件信息。 HDFS作为分布式文件系统在数据管理方面可借鉴点: 文件块的放置:一个Block会有三份备份,一份在NameNode指定的DateNode上,一份放在与指定的DataNode不在同一台机器的DataNode上,一根在于指定的DataNode在同一Rack上的DataNode上。备份的目的是为了数据安全,采用这种方式是为了考虑到同一Rack失败的情况,以及不同数据拷贝带来的性能的问题。 MapReduce体系架构 MR框架是由一个单独运行在主节点上的JobTracker和运行在每个集群从节点上的TaskTracker共同组成。主节点负责调度构成一个作业的所有任务,这些任务分布在不同的不同的从节点上。主节点监视它们的执行情况,并重新执行之前失败的任务。从节点仅负责由主节点指派的任务。当一个Job被提交时,JobTracker接受到提交作业和配置信息之后,就会将配置信息等分发给从节点,同时调度任务并监控TaskTracker的执行。JobTracker可以运行于集群中的任意一台计算机上。TaskTracker负责执行任务,它必须运行在DataNode上,DataNode既是数据存储节点,也是计算节点。JobTracker将map任务和reduce任务分发给空闲的TaskTracker,这些任务并行运行,并监控任务运行的情况。如果JobTracker出了故障,JobTracker会把任务转交给另一个空闲的TaskTracker重新运行。 HDFS和MR共同组成Hadoop分布式系统体系结构的核心。HDFS在集群上实现了分布式文件系统,MR在集群上实现了分布式计算和任务处理。HDFS在MR任务处理过程中提供了文件操作和存储等支持,MR在HDFS的基础上实现了任务的分发、跟踪、执行等工作,并收集结果,二者相互作用,完成分布式集群的主要任务。 Hadoop上的并行应用程序开发是基于MR编程框架。MR编程模型原理:利用一个输入的key-value对集合来产生一个输出的key-value对集合。MR库通过Map和Reduce两个函数来实现这个框架。用户自定义的map函数接受一个输入的key-value对,然后产生一个中间的key-value对的集合。MR把所有具有相同的key值的value结合在一起,然后传递个reduce函数。Reduce函数接受key和相关的value结合,reduce函数合并这些value值,形成一个较小的value集合。通常我们通过一个迭代器把中间的value值提供给reduce函数(迭代器的作用就是收集这些value值),这样就可以处理无法全部放在内存中的大量的value值集合了。 说明:(第三幅图为同伴自己画的) 流程简而言之,大数据集被分成众多小的数据集块,若干个数据集被分在集群中的一个节点进行处理并产生中间结果。单节点上的任务,map函数一行行读取数据获得数据的(k1,v1),数据进入缓存,通过map函数执行map(基于key-value)排序(框架会对map的输出进行排序)执行后输入(k2,v2)。每一台机器都执行同样的操作。不同机器上的(k2,v2)通过merge排序的过程(shuffle的过程可以理解成reduce前的一个过程),最后reduce合并得到,(k3,v3),输出到HDFS文件中。 谈到reduce,在reduce之前,可以先对中间数据进行数据合并(Combine),即将中间有相同的key的<key,value>对合并。Combine的过程与reduce的过程类似,但Combine是作为map任务的一部分,在执行完map函数后仅接着执行。Combine能减少中间结果key-value对的数目,从而降低网络流量。 Map任务的中间结果在做完Combine和Partition后,以文件的形式存于本地磁盘上。中间结果文件的位置会通知主控JobTracker,JobTracker再通知reduce任务到哪一个DataNode上去取中间结果。所有的map任务产生的中间结果均按其key值按hash函数划分成R份,R个reduce任务各自负责一段key区间。每个reduce需要向许多个map任务节点取的落在其负责的key区间内的中间结果,然后执行reduce函数,最后形成一个最终结果。有R个reduce任务,就会有R个最终结果,很多情况下这R个最终结果并不需要合并成一个最终结果,因为这R个最终结果可以作为另一个计算任务的输入,开始另一个并行计算任务。这就形成了上面图中多个输出数据片段(HDFS副本)。 Hbase数据管理 Hbase就是Hadoop database。与传统的mysql、oracle究竟有什么差别。即列式数据与行式数据由什么区别。NoSql数据库与传统关系型数据由什么区别: Hbase VS Oracle 1、 Hbase适合大量插入同时又有读的情况。输入一个Key获取一个value或输入一些key获得一些value。 2、 Hbase的瓶颈是硬盘传输速度。Hbase的操作,它可以往数据里面insert,也可以update一些数据,但update的实际上也是insert,只是插入一个新的时间戳的一行。Delete数据,也是insert,只是insert一行带有delete标记的一行。Hbase的所有操作都是追加插入操作。Hbase是一种日志集数据库。它的存储方式,像是日志文件一样。它是批量大量的往硬盘中写,通常都是以文件形式的读写。这个读写速度,就取决于硬盘与机器之间的传输有多快。而Oracle的瓶颈是硬盘寻道时间。它经常的操作时随机读写。要update一个数据,先要在硬盘中找到这个block,然后把它读入内存,在内存中的缓存中修改,过段时间再回写回去。由于你寻找的block不同,这就存在一个随机的读。硬盘的寻道时间主要由转速来决定的。而寻道时间,技术基本没有改变,这就形成了寻道时间瓶颈。 3、 Hbase中数据可以保存许多不同时间戳的版本(即同一数据可以复制许多不同的版本,准许数据冗余,也是优势)。数据按时间排序,因此Hbase特别适合寻找按照时间排序寻找Top n的场景。找出某个人最近浏览的消息,最近写的N篇博客,N种行为等等,因此Hbase在互联网应用非常多。 4、 Hbase的局限。只能做很简单的Key-value查询。它适合有高速插入,同时又有大量读的操作场景。而这种场景又很极端,并不是每一个公司都有这种需求。在一些公司,就是普通的OLTP(联机事务处理)随机读写。在这种情况下,Oracle的可靠性,系统的负责程度又比Hbase低一些。而且Hbase局限还在于它只有主键索引,因此在建模的时候就遇到了问题。比如,在一张表中,很多的列我都想做某种条件的查询。但却只能在主键上建快速查询。所以说,不能笼统的说那种技术有优势。 5、 Oracle是行式数据库,而Hbase是列式数据库。列式数据库的优势在于数据分析这种场景。数据分析与传统的OLTP的区别。数据分析,经常是以某个列作为查询条件,返回的结果也经常是某一些列,不是全部的列。在这种情况下,行式数据库反应的性能就很低效。 行式数据库:Oracle为例,数据文件的基本组成单位:块/页。块中数据是按照一行行写入的。这就存在一个问题,当我们要读一个块中的某些列的时候,不能只读这些列,必须把这个块整个的读入内存中,再把这些列的内容读出来。换句话就是:为了读表中的某些列,必须要把整个表的行全部读完,才能读到这些列。这就是行数据库最糟糕的地方。 列式数据库:是以列作为元素存储的。同一个列的元素会挤在一个块。当要读某些列,只需要把相关的列块读到内存中,这样读的IO量就会少很多。通常,同一个列的数据元素通常格式都是相近的。这就意味着,当数据格式相近的时候,数据就可以做大幅度的压缩。所以,列式数据库在数据压缩方面有很大的优势,压缩不仅节省了存储空间,同时也节省了IO。(这一点,可利用在当数据达到百万、千万级别以后,数据查询之间的优化,提高性能,示场景而定) Hive数据管理 Hive是建立在Hadoop上的数据仓库基础架构。它提供了一系列的工具,用来进行数据提取、转换、加载,这是一种可以存储、查询和分析存储在Hadoop中的大规模数据机制。可以把Hadoop下结构化数据文件映射为一张成Hive中的表,并提供类sql查询功能,除了不支持更新、索引和事务,sql其它功能都支持。可以将sql语句转换为MapReduce任务进行运行,作为sql到MapReduce的映射器。提供shell、JDBC/ODBC、Thrift、Web等接口。优点:成本低可以通过类sql语句快速实现简单的MapReduce统计。作为一个数据仓库,Hive的数据管理按照使用层次可以从元数据存储、数据存储和数据交换三个方面介绍。 (1)元数据存储 Hive将元数据存储在RDBMS中,有三种方式可以连接到数据库: ·内嵌模式:元数据保持在内嵌数据库的Derby,一般用于单元测试,只允许一个会话连接 ·多用户模式:在本地安装Mysql,把元数据放到Mysql内 ·远程模式:元数据放置在远程的Mysql数据库 (2)数据存储 首先,Hive没有专门的数据存储格式,也没有为数据建立索引,用于可以非常自由的组织Hive中的表,只需要在创建表的时候告诉Hive数据中的列分隔符和行分隔符,这就可以解析数据了。 其次,Hive中所有的数据都存储在HDFS中,Hive中包含4中数据模型:Tabel、ExternalTable、Partition、Bucket。 Table:类似与传统数据库中的Table,每一个Table在Hive中都有一个相应的目录来存储数据。例如:一个表zz,它在HDFS中的路径为:/wh/zz,其中wh是在hive-site.xml中由$指定的数据仓库的目录,所有的Table数据(不含External Table)都保存在这个目录中。 Partition:类似于传统数据库中划分列的索引。在Hive中,表中的一个Partition对应于表下的一个目录,所有的Partition数据都存储在对应的目录中。例如:zz表中包含ds和city两个Partition,则对应于ds=20140214,city=beijing的HDFS子目录为:/wh/zz/ds=20140214/city=Beijing; Buckets:对指定列计算的hash,根据hash值切分数据,目的是为了便于并行,每一个Buckets对应一个文件。将user列分数至32个Bucket上,首先对user列的值计算hash,比如,对应hash=0的HDFS目录为:/wh/zz/ds=20140214/city=Beijing/part-00000;对应hash=20的,目录为:/wh/zz/ds=20140214/city=Beijing/part-00020。 ExternalTable指向已存在HDFS中的数据,可创建Partition。和Table在元数据组织结构相同,在实际存储上有较大差异。Table创建和数据加载过程,可以用统一语句实现,实际数据被转移到数据仓库目录中,之后对数据的访问将会直接在数据仓库的目录中完成。删除表时,表中的数据和元数据都会删除。ExternalTable只有一个过程,因为加载数据和创建表是同时完成。世界数据是存储在Location后面指定的HDFS路径中的,并不会移动到数据仓库中。 (3)数据交换 ·用户接口:包括客户端、Web界面和数据库接口 ·元数据存储:通常是存储在关系数据库中的,如Mysql,Derby等 ·Hadoop:用HDFS进行存储,利用MapReduce进行计算。 关键点:Hive将元数据存储在数据库中,如Mysql、Derby中。Hive中的元数据包括表的名字、表的列和分区及其属性、表的属性(是否为外部表)、表数据所在的目录等。 Hive的数据存储在HDFS中,大部分的查询由MapReduce完成。 总结: 通过对Hadoop分布式计算平台最核心的分布式文件系统HDFS、MapReduce处理过程,以及数据仓库工具Hive和分布式数据库Hbase的介绍。基本涵盖了Hadoop分布式平台的所有技术核心。从体系架构到数据定义到数据存储再到数据处理,从宏观到微观的系统介绍,为Hadoop平台上大规模的数据存储和任务处理打下基础。 本文作者:佚名 来源:51CTO

优秀的个人博客,低调大师

IT关键词,面试知识问与答

二叉树遍历的三种方式? 遍历是指依次访问⼆叉树中的每个元素。有三种遍历⽅法,分别是前序遍历、 中序遍历和后序遍历。它们是按照访问根节点和⼦节点的先后顺序命名的。 • 前序遍历:先访问根节点,然后访问左⼦树,最后访问右⼦树; • 中序遍历:先访问左⼦树,然后访问根节点,最后访问右⼦树; • 后序遍历:先访问左⼦树,然后访问右⼦树,最后访问根节点。 php中单引号和双引号有什么区别? 单引号内部的变量不会执行 双引号会执行 如 $name = 'hello'; echo "the $name"; 会输出 the hello 而如果是单引号 $name = 'hello'; echo 'the $name'; 会输出 the $name 主要区别就是这个,其他的无所谓,看你自己喜欢了 时序图 1.什么是时序图 2.如何看懂时序图 3.时序图的作用 4.如何绘制时序图 分布式 一个业务分拆多个子业务,部署在不同的服务器上。 分布式是指将不同的业务分布在不同的地方。 而集群指的是将几台服务器集中在一起,实现同一业务。 集群 同一个业务,部署在多个服务器上。 小饭店原来只有一个厨师,切菜洗菜备料炒菜全干。后来客人多了,厨房一个厨师忙不过来,又请了个厨师,两个厨师都能炒一样的菜,这两个厨师的关系是集群。为了让厨师专心炒菜,把菜做到极致,又请了个配菜师负责切菜,备菜,备料,厨师和配菜师的关系是分布式,一个配菜师也忙不过来了,又请了个配菜师,两个配菜师关系是集群。 生产环境 开发->测试->上线,其中生产环境也就是通常说的真实环境。 消息队列 消息队列(Message Queue,简称MQ),从字面意思上看,本质是个队列,FIFO先入先出,只不过队列中存放的内容是message而已。 socket 我们经常把socket翻译为套接字,socket是在应用层和传输层之间的一个抽象层,它把TCP/IP层复杂的操作抽象为几个简单的接口供应用层调用已实现进程在网络中通信。 User-Agent 告诉HTTP服务器, 客户端使用的操作系统和浏览器的名称和版本。 索引 索引就像一本书的目录,目录越详细越容易定位具体页码。索引也是越详细就越容易定位具体数据。 索引越详细意味着索引自身的数据越多。 –最好的情况是如果那个列具有unique属性,那每一条数据就会有一个索引记录。 –最差的情况是所有的数据都一样,索引只有一条,这一条索引对应了所有的数据。 算法时间复杂度 一个算法花费的时间与算法中语句的执行次数成正比例,哪个算法中语句执行次数多,它花费时间就多。一个算法中的语句执行次数称为语句频度或时间频度。记为T(n)。 常数阶O(1),对数阶O(log2n)(以2为底n的对数),线性阶O(n),线性对数阶O(nlog2n),平方阶O(n^2),立方阶O(n^3),...,k次方阶O(n^k),指数阶O(2^n)。随着问题规模n的不断增大,上述时间复杂度不断增大,算法的执行效率越低。 设每条语句执行一次所需的时间均是单位时间,一个算法的时间耗费就是该算法中所有语句的频度之和。 算法空间复杂度 与时间复杂度类似,空间复杂度是指算法在计算机内执行时所需存储空间的度量。 1.算法程序所占的空间; 2.输入的初始数据所占的存储空间; 3.算法执行过程中所需要的额外空间。 log2n LOG2(N) 相当于2的多少次方(立方)等于N 例:LOG2(8)=3 相当于,2的3次方等于8 OpenStack OpenStack是一个开源的云计算管理平台项目,由几个主要的组件组合起来完成具体工作。OpenStack支持几乎所有类型的云环境,项目目标是提供实施简单、可大规模扩展、丰富、标准统一的云计算管理平台。OpenStack通过各种互补的服务提供了基础设施即服务(IaaS)的解决方案,每个服务提供API以进行集成。 经纬度 东西经,南北纬。 一纬度=110.94公里,北纬35度至49度=?公里南北。一经度=85.276公里,东经73度至96度=?公里东西。 DNS DNS (Domain Name System 的缩写)的作用非常简单,就是根据域名查出IP地址。你可以把它想象成一本巨大的电话本。 举例来说,如果你要访问域名math.stackexchange.com,首先要通过DNS查出它的IP地址是151.101.129.69。 当我们通过在浏览器的地址栏中键入域名来访问某个网站时,浏览器将首先查找本地的DNS缓存是否拥有该域名所对应的IP地址。如果有,那么浏览器将尝试直接使用该IP地址访问该网站的内容。如果本地DNS缓存中没有该域名所对应的IP地址,那么它将向DNS发送一个请求,以获得该域名所对应的IP并添加到本地DNS缓存中。 组合和聚合 比如A类中包含B类的一个引用b,当A类的一个对象消亡时,b这个引用所指向的对象也同时消亡(没有任何一个引用指向它,成了垃圾对象),这种情况叫做组合,反之b所指向的对象还会有另外的引用指向它,这种情况叫聚合。 十六进制AF转为十进制 0、1、2、3、4、5、6、7、8、9、A、B、C、D、E、F A是10,F是15。转为十进制,10*16+15*1 = 175 框架与库 库是解决某个问题而拼凑出来的一大堆函数与类的集合。 框架则是一个半成品的应用,直接给出一个骨架。写框架能提升自己的架构能力。 剖析别人框架的特点,写出属于自己的框架。 rem 与 px的区别 px:各个终端统一大小,简单明了,未尝不可! rem:大屏幕显示大字体,小屏幕显示小字体,渐进增强视觉感。 冒泡的过程 事件冒泡的过程是:a --> div --> body 。a冒泡到div冒泡到body。 include、require的区别 incluce在用到时加载 require在一开始就加载 _once后缀表示已加载的不加载 include引入文件的时候,如果碰到错误,会给出提示,并继续运行下边的代码。 require引入文件的时候,如果碰到错误,会给出提示,并停止运行下边的代码。 dos系统与微软的关系 DOS=disk operating system, 磁盘操作系统 MS=MICRO SOFT 美国微软公司 MS-DOS是微软公司的磁盘操作系统 DOS(磁盘操作系统)除了微软的外,还有其他公司的产品。如DR-DOS、PC-DOS、ROM-DOS、FreeDOS等,但MS-DOS是最多人使用也是最实用的一种,不过现在基本上见到不其他公司的产品了,都被微软垄断了。 1980年,西雅图电脑产品公司(Seattle Computer Products)的一名24岁的程序员蒂姆·帕特森(Tim Paterson)花费了四个月时间编写出了86-DOS操作系统。1981年7月,微软以五万美元的代价向西雅图公司购得本产品的全部版权,并将它更名为MS-DOS。 最基本的MS-DOS系统由一个基于MBR的BOOT引导程序和三个文件模块组成。这三个模块是输入输出模块(IO.SYS)、文件管理模块(MSDOS.SYS)及命令解释模块。除此之外,微软还在零售的MS-DOS系统包中加入了若干标准的外部程序(即外部命令),这才与内部命令(即由解释执行的命令)一同构建起一个在磁盘操作时代相对完备的人机交互环境。 本文转自TBHacker博客园博客,原文链接:http://www.cnblogs.com/jiqing9006/p/5844568.html,如需转载请自行联系原作者

优秀的个人博客,低调大师

技术经验如何变成内容资产?先看懂4种文章收入模式

很多人写了几篇文章没看到收入,就会怀疑:靠写东西,是不是只属于大号?答案没那么简单。文章确实可能带来收入,但平台并不是按字数给钱。有人靠阅读产生广告分成,有人卖付费内容,有人的文章经过编辑选中获得稿酬,还有人先积累读者,再得到合作机会。模式不同,写法也完全不同。这件事适合什么人?不是只有文笔特别好的人,而是那些在工作、生活或学习中解决过具体问题,愿意把过程讲明白的人。一篇真正有价值的内容,通常先回答一个问题,再考虑能不能变现。

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

用户登录
用户注册