1、make和new区别?

make和new都是go的内置函数(builtin包下)。

func new(Type) *Type:内建函数new分配内存。其第一个实参为类型,而非值。其返回值为指向该类型的新分配的零值的指针。
func make(Type, size IntegerType) Type:内建函数make分配并初始化一个类型为切片slice、映射map、或通道channel的对象。其第一个实参为类型,而非值。make的返回类型与其参数相同,而非指向它的指针。其具体结果取决于具体的类型:
切片:size指定了其长度。该切片的容量等于其长度。切片支持第二个整数实参可用来指定不同的容量;它必须不小于其长度,因此 make([]int, 0, 10) 会分配一个长度为0,容量为10的切片。
映射:初始分配的创建取决于size,但产生的映射长度为0。size可以省略,这种情况下就会分配一个小的起始大小。
通道:通道的缓存根据指定的缓存容量初始化。若 size为零或被省略,该信道即为无缓存的。

new 只分配内存;make 只能用于 slice、map 和 channel 的初始化。

2、Go的内存管理?

内存管理会有两个问题:1)内存碎片——提高内存利用率。2)多线程——稳定性,效率问题。

内存划分:

  • arena即所谓的堆区,应用中所需的内存从这里分配,大小为512G,为了方便管理把arena区域划分成一个个的page,每个page为8KB,一共有512GB/8KB个页。
  • spans区域存放span的指针,每个指针对应一个page,所以span区域的大小为(512GB/8KB) * 指针大小8byte = 512M
  • bitmap区域大小也是通过arena计算出来512GB / (指针大小(8 byte) * 8 / 2) = 16G,用于表示arena区域中哪些地址保存了对象, 并且对象中哪些地址包含了指针,主要用于GC。

span是内存管理的基本单位,每个span用来管子特定的size class对象,根据size class,span将若干个页分成块进行管理。

stack:Go语言中,每个goroutine采用动态扩容方式,初始2KB,按需增长,最大1G。此外GC会收缩栈空间。

BTW,增长扩容都是有代价的,需要copy数据到新的stack,所以初始2KB可能有些性能问题。

Cache:从上面我们知道go通过span来分配内存,那在哪里用span?每个P都有mcache,通过mcache管理每个G需要的内存。

alloc是span数组,长度是67 << 1,说明每种size class有2组元素。第一组span对象中包含了指针,叫做scan,表示需要gc scan;第二组没有指针,叫做noscan。提高gc scan性能。

mcache初始没有span,G先从central动态申请span,并缓存在cache。

Central——从central获取span步骤如下: 加锁 —> 从nonempty列表获取一个可用span,并将其从链表中删除 —> 将取出的span放入empty链表 —> 将span返回给线程 —> 解锁 —> 线程将该span缓存进cache。span归还步骤:加锁 —> 将span从empty链表删除 —>  将span加入nonempty列表 —> 将span返回给线程 —> 解锁。

heap:central只管理特定的size class span,所以必然有一个更上层的数据结构,管理所有的sizeclass central,这就是heap。

Go的内存分配核心思想

Go是内置运行时的编程语言(runtime),像这种内置运行时的编程语言通常会抛弃传统的内存分配方式,改为自己管理。这样可以完成类似预分配、内存池等操作,以避开系统调用带来的性能问题,防止每次分配内存都需要系统调用。

Go的内存分配的核心思想可以分为以下几点:

  • 每次从操作系统申请一大块儿的内存,由Go来对这块儿内存做分配,减少系统调用。
  • 内存分配算法采用Google的 TCMalloc算法。算法比较复杂,究其原理可自行查阅。其核心思想就是把内存切分的非常的细小,分为多级管理,以降低锁的粒度。
  • 回收对象内存时,并没有将其真正释放掉,只是放回预先分配的大块内存中,以便复用。只有内存闲置过多的时候,才会尝试归还部分内存给操作系统,降低整体开销。

参考:图解golang内存分配机制 (转) - 孤独信徒 - 博客园

2、调用一个函数,它需要传入一个结构体的话,它传指针还是传值?哪个更好?

函数传递指针不一定比传值效率高。传递指针可以减少底层值的拷贝,可以提高效率,但是如果拷贝的数据量小,由于指针传递会产生逃逸,可能会使用堆,也可能会增加GC的负担,所以传递指针不一定是高效的。

所有对象默认都在栈中,当有下列情况时,对象逃逸到堆中:

  1. 对象作为函数的返回值返回,包括指针和值类型
  2. 对象申请的空间过大
  3. 对象申请空间未知
  4. 闭包

3、指针逃逸分析的作用?

在编译原理中,分析指针动态范围的方法称之为逃逸分析。通俗来讲,当一个对象的指针被多个方法或线程引用时,我们称这个指针发生了“逃逸”。

  • 逃逸分析的好处是为了减少gc的压力,不逃逸的对象分配在栈上,当函数返回时就回收了资源,不需要gc标记清除。
  • 逃逸分析完后可以确定哪些变量可以分配在栈上,栈的分配比堆快,性能好(逃逸的局部变量会在堆上分配 ,而没有发生逃逸的则有编译器在栈上分配)。
  • 同步消除,如果你定义的对象的方法上有同步锁,但在运行时,却只有一个线程在访问,此时逃逸分析后的机器码,会去掉同步锁运行。

逃逸总结:

  • 栈上分配内存比在堆中分配内存有更高的效率。

  • 栈上分配的内存不需要GC处理。

  • 堆上分配的内存使用完毕会交给GC处理。

  • 逃逸分析目的是决定内分配地址是栈还是堆。

  • 逃逸分析在编译阶段完成。

4、Linux操作系统中线程有哪几种模型?

Linux多线程模型 - 知乎

5、Go垃圾回收,stop the world。

Stop the world:停止程序意味着停止所有正在运行的 goroutine。

  1. 抢占所有正在运行的 goroutine。一旦 goroutine 被抢占,它们将在安全点停止。同时,P 处理器将(正在运行的代码或在空闲列表)被标记为已停止,以不运行任何代码。
  2. Go 调度程序将运行,将每个 M 与其 P 各自分离,并将其放入空闲列表中。关于在每个M上运行的 goroutine,它们将在全局队列GRQ中等待。
  3. 一旦世界停止了,只有唯一活动的 goroutine 才能安全地运行,并在工作完成后启动整个世界。

6、PMG模型。

P:processor ,代表一个逻辑处理器,也就是执行代码的上下文环境。

M:machine ,代表一个内核线程(OS线程),这个线程是操作系统来处理的,操作系统负责把它放置到一个 core 上去执行。

G:goroutine ,代表一个并发的代码片段。

简而言之,P 在 M 上执行 G 。

LRQ:Local RunQueue,本地等待运行的 G,存的数量有限,不超过 256 个。如果队列满了,则会把本地队列中一半的 G 移动到全局队列。

GRQ:Global RunQueue,存放等待运行的 G,LRQ都满了的时候,会放入GRQ。其中,LRQ 不加锁,GRQ加锁。

Network poller:同步调度。

线程状态:

  • 等待(Waiting):线程停止并等待被唤醒。可能发生的原因有,等待硬件(硬盘、网络),操作系统(系统调用) 或者是同步调用(atomic,mutexes)。这些情况是导致性能问题的根源。
  • 可运行(Runnable):线程想要占用内核上的cpu时间来执行分配给线程的指令。如果你有许多线程想要cpu时间,线程必须要等一段时间才能取到cpu时间。随着更多线程争用cpu时间,线程分配的cpu时间会更短。这种情况下的调度延时也会造成性能问题。
  • 执行中(Executing):此时线程已经置于内核中,并且正在执行它的机器指令。应用程序的相关内容正在被处理。

线程的工作类型:

  • CPU密集型(cpu-bound):这种工作下,线程永远不会被置换到等待(waiting)状态。这种一般是进行持续性的cpu计算工作。比如计算Pi这种的就是cpu密集型工作。

  • IO密集型(io-bound):这种工作会让线程进入到等待(waiting)状态。这种情况线程会持续的请求资源(比如网络资源)或者是对操作系统进行系统调用。线程需要访问数据库的情况就是IO密集型工作。同步事件(例如mutexes、atomic)。

参考链接:理解golang调度之一 :操作系统调度 - 掘金

7、线程有继承关系吗?——没有,只有组合。

package mainimport("fmt"
)type Human struct{name string
}func(h Human)Dowork(){fmt.Println(h.name + "work")
}func(h Human)Happy(){fmt.Println(h.name + "happy")
}type Father interface{Dowork()
}type Son interface{FatherHappy()
}func main(){human1 := Human{name:"张三丰"}human2 := Human{name:"张三"}father := human1son := human2father.Dowork()son.Happy()son.Dowork()
}

8、什么是token认证方式?

在web应用的开发过程中,我们往往还会使用另外一种认证方式进行身份验证,那就是:Token认证。基于Token的身份验证是无状态,不需要将用户信息服务存在服务器或者session中。

基于Token认证的身份验证主要过程是:客户端在发送请求前,首先向服务器发起请求,服务器返回一个生成的token给客户端。客户端将token保存下来,用于后续每次请求时,携带着token参数。服务端在进行处理请求之前,会首先对token进行验证,只有token验证成功了,才会处理并返回相关的数据。

客户端的token存在cookies里。

9、go语言反射原理?

Go反射的实现和interface和unsafe.Pointer密切相关。

先看interface的底层实现:Go的interface是由两种类型来实现的: ifaceeface

无函数的eface结构:一共有两个属性构成,一个是类型信息 _type,一个是数据信息。其中, _type可以认为是Go语言中所有类型的公共描述,Go语言中几乎所有的数据结构都可以抽象成 _type,是所有类型的表现,可以说是万能类型, data是指向具体数据的指针。

有函数的iface结构:itab是确定唯一的包含方法的interface的具体结构类型,data是指向具体方法集的指针。

Go语言中,每个变量都有唯一个静态类型(static interface type),这个类型是编译阶段就可以确定的。有的变量可能除了静态类型之外,还会有动态混合类型(dynamic concrete type)。

Go的反射法则:

  • 给定一个数据对象,可以将数据对象转化为反射对象Type和Value。
  • 给定的反射对象,可以转化为某种类型的数据对象。即法则一的逆向。
  • 通过反射对象,可以修改原数据中的内容。

Go的反射原理:在运行时,能够动态知道给定数据对象的类型和结构,并有机会修改它!

10、select有哪些特性?

select是Go语言中的一个控制语句,只用来操作的channel的读写操作(I/O操作)。

备注:golang 的 select 本质上,就是监听 IO 操作,当 IO 操作发生时,触发相应的动作。也是常用的多路复用的一种,例如poll, epoll。

select 的特性:

  1. 如果只有一个 case 语句评估通过,那么就执行这个case里的语句
  2. 如果有多个 case 语句评估通过,那么通过伪随机的方式随机选一个
  3. 如果 default 外的 case 语句都没有通过评估,那么执行 default 里的语句
  4. 如果没有 default,那么 代码块会被阻塞,直到有一个 case 通过评估;否则一直阻塞

应用场景:

  1. 实现非阻塞读写操作。——如果 default 外的 case 语句都没有通过评估,那么执行 default 里的语句
  2. 为请求设置超时时间。
  3. 调度协程,控制其他协程的退出或者完成。

底层实现是创建select —> 注册case —> 执行case —> 释放select。结构体select如下(runtime/select.go),

type hselect struct {tcase     uint16   // total count of scase[]      总的case数目ncase     uint16   // currently filled scase[]    目前已经注册的case数目     pollorder *uint16  // case poll order             【超重要】 轮询的case序号lockorder *uint16  // channel lock order          【超重要】chan的锁定顺序// case 数组,为了节省一个指针的 8 个字节搞成这样的结构// 实际上要访问后面的值,还是需要进行指针移动// 指针移动使用 runtime 内部的 add 函数scase     [1]scase // one per case (in order of appearance)  【超重要】保存当前case操作的chan (按照轮询顺序)
}/**
select 中每一个case的定义
*/
type scase struct {elem        unsafe.Pointer // data element                           数据指针c           *hchan         // chan                                   当前case所对应的chan引用pc          uintptr        // return pc (for race detector / msan)   和汇编中的pc同义,表示 程序计数器,用于指示当前将要执行的下一条机器指令的内存地址kind        uint16         // 通道的类型receivedp   *bool // pointer to received bool, if anyreleasetime int64
}

参考链接:【我的架构师之路】- golang源码分析之select的底层实现_GavinXujiacan的博客-CSDN博客

select和poll、epoll区别:

  • select:select具有O(n)的无差别轮询复杂度。
  • poll:本质上和select没有区别。它将用户传入的数组拷贝到内核空间,然后查询每个fd(file descriptor,文件描述符是内核为了高效管理已被打开的文件所创建的索引)对应的设备状态, 但是它没有最大连接数的限制,原因是它是基于链表来存储的。
  • epoll:epoll可以理解为event poll,不同于忙轮询和无差别轮询,epoll会把哪个流发生了怎样的I/O事件通知我们。所以我们说epoll实际上是事件驱动(每个事件关联上fd)的,此时我们对这些流的操作都是有意义的。(复杂度降低到了O(1))

参考链接:select、poll、epoll之间的区别(搜狗面试) - aspirant - 博客园

11、互斥体和饥饿问题?

参考链接:https://www.jianshu.com/p/9f4376fbbe5c

12、多核情况下如何保证Cache的数据一致性?

MESI缓存一致性协议,每个缓存行都用2个bit表示四种状态,修改状态Modified,独占状态Exclusive,共享状态Shared,失效状态Invalid。

  • 修改状态:代表该缓存行中的内容被修改了,并且该缓存行只被缓存在该CPU中。这个状态的缓存行中的数据和内存中的不一样,在未来的某个时刻它会被写入到内存中。
  • 独占状态:缓存行对应内存中的内容只被该CPU缓存,其他CPU没有缓存该缓存对应内存行中的内容。
  • 共享状态:缓存行中的数据不止存在本地CPU缓存中,还存在别的CPU的缓存中。这个状态的数据和内存中的数据是一致的。当有一个CPU修改该缓存行对应的内存的内容时会使该缓存行变成 失效状态。
  • 失效状态:该缓存行中的内容时无效的。

比如说,当CPU的某个内核Core加载某一个数据到Cache1里时,这个缓存行的状态就是独占状态,然后内核对数据做了修改,这里缓存行的状态就是修改状态。如果有另外一个内核读相同的数据时,Cache1监测到另外一个内核读取了数据,这时候缓存行的状态就变成了共享状态。在共享状态下,如果自己内核对数据做修改,那自己的缓存行就变成修改状态,其他所有缓存行变成失效状态。

13、实现一个dog、cat、fish循环输出程序。三个通道,利用dogCh->catCh->fishCh->dogCh->...->fishch控制顺序,WaitGroup等待这组的三个线程结束。

package mainimport("fmt""sync""sync/atomic"
)const num = 10func main(){var wg sync.WaitGroupvar dogCount uint64var catCount uint64var fishCount uint64dogCh := make(chan struct{}, 1)catCh := make(chan struct{}, 1)fishCh := make(chan struct{}, 1)wg.Add(3)go dog(&wg, dogCount, dogCh, catCh)go cat(&wg, catCount, catCh, fishCh)go fish(&wg, fishCount, fishCh, dogCh)dogCh <- struct{}{}wg.Wait()
}func dog(wg *sync.WaitGroup, count uint64, dogCh chan struct{}, catCh chan struct{}){for{if count>=uint64(num) {wg.Done()return}<- dogChfmt.Println("dog")atomic.AddUint64(&count, 1)catCh <- struct{}{}}
}func cat(wg *sync.WaitGroup, count uint64, catCh chan struct{}, fishCh chan struct{}){for{if count>=uint64(num) {wg.Done()return}<- catChfmt.Println("cat")atomic.AddUint64(&count, 1)fishCh <- struct{}{}}
}func fish(wg *sync.WaitGroup, count uint64, fishCh chan struct{}, dogCh chan struct{}){for{if count>=uint64(num) {wg.Done()return}<- fishChfmt.Println("fish")atomic.AddUint64(&count, 1)dogCh <- struct{}{}}
}

14、golang中channel底层如何实现?

结构体为hchan

type hchan struct {qcount   uint           // total data in the queuedataqsiz uint           // size of the circular queuebuf      unsafe.Pointer // 是有缓冲的channel所特有的结构,用来存储缓存数据。是个循环链表elemsize uint16closed   uint32elemtype *_type // element typesendx    uint   // 用于记录buf这个循环链表中的发送indexrecvx    uint   // 用于记录buf这个循环链表中的接收indexrecvq    waitq  // 接收(<-channel)的goroutine抽象出来的结构体(sudog)的队列。是个双向链表sendq    waitq  // 发送(channel <- xxx)的goroutine抽象出来的结构体(sudog)的队列。lock mutex      // lock是个互斥锁
}

  1. chan是分配在堆上的
  2. 创建channel实际上就是在内存中实例化了一个hchan的结构体,并返回一个ch指针,我们使用过程中channel在函数之间的传递都是用的这个指针(channel本身就是一个指针),使用 make 内建函数返回的就是指针。

goroutine 是一种用户级线程,由 Go 运行时系统来创建和管理,而不是操作系统。相较于操作系统的线程更加轻量级。一个 OS thread 对应对个 goroutine。这便是 Go 语言的 M:N 调度模型。

channel的使用及原理:

  • 使用锁保证并发安全
  • 采用 FIFO 先进先出策略存储
  • 以 sudog 结构体类型存储阻塞的 goroutine 作为队列节点
  • 通过 gopark,goready 来呼叫(calls into)运行时调度器

参考链接:Go Chanel 使用与原理 一 - Go语言中文网 - Golang中文社区

参考链接:深入详解Go的channel底层实现原理【图解】 - 腾讯云开发者社区-腾讯云

15、进程通信方式有哪7种?

  • 1)管道——半双工,父子进程关系;2)命名管道FIFO——半双工;3)消息队列;4)共享内存——IPC(Inter-Process Communication)方式,两个进程通过页表将虚拟地址映射到物理地址时,物理地址中共同的内存区即共享内存,使用信号量实现同步与互斥;5)信号量——计数器,主要作为进程间和同一进程不同线程的同步;6)套接字;7)信号Signal

16、Mutex互斥锁底层原理?

Mutex互斥锁结构体有两个成员变量:状态(标志锁是否被持有)和信号量(阻塞和唤醒goroutine)。它有两种模式:正常模式(非公平):进入等待状态的goroutine会进入等待队列,在获取锁的时候按先进先出的顺序获取,但当唤醒一个goroutine时它不会立即获取锁而是和新来的goroutine竞争,通常是新来的更容易获得锁,因为它已经运行在CPU,所以刚被唤醒的goroutine大概率获取不到锁。饥饿模式(公平):当在正常模式等待的goroutine超过1ms没获取到锁,Mutex被切换成饥饿模式,饥饿模式下锁直接交给等待队列最前面的goroutine,新来的goroutine不会参与竞争锁也不会在那自旋,而是直接添加到队列尾部。当拥有锁的goroutine发现它是队列里的最后一个或者等待时间小于1ms又会切换回正常模式。

参考链接:mutex | 学习笔记

其他:

  • GIN框架,beego框架需要了解,前者路由速度快,后者功能全。Go语言WEB框架(Gin)详解
  • web服务中caddy类似于Apache,Nginx,是一个高性能的Http/2和反向代理服务器,天然支持HTTPS。
  • 数据库ORM框架,gorm或beego的orm框架。
  • 包管理工具,类似Maven——govendor、godep、glide。
  • 热重启:endless——通过监听syscall信号量实现优雅重启。
  • log记录——github.com/Sirupsen/logrus
  • 定时任务github.com/robfig/cron
  • 帮助文档生成工具——github.com/urfave/cli
  • 配置文件读取——github.com/spf13/viper
  • 内存管理——支持Redis内存存储:github.com/coocood/freecache
  • redis依赖——github.com/garyburd/redigo
  • go垃圾回收stop the world,耗时太长无法接受。
  • gRPC框架?gRPC微服务框架 - 顽强的allin - 博客园;
  • gRPC拦截器:grpc 拦截器: 参数校验、日志上报、panic拦截 等等逻辑的另一种姿态 - C/C++教程 - 找一找教程网
  • gateway怎么做?
  • proto文件怎么管理?
  • 微服务框架go micro?
  • 其他面试题:golang 面试题(从基础到高级)_weixin_34128839的博客-CSDN博客

Golang面试题整理相关推荐

  1. 计算机组成算术流水线,计算机组成系统结构试题整理.doc

    计算机组成系统结构试题整理 选择题(50分,每题2分,正确答案可能不只一个,可单选或复选) (CPU周期.机器周期)是内存读取一条指令字的最短时间. (多线程.多核)技术体现了计算机并行处理中的空间并 ...

  2. 2010 .NET面试题整理之基础篇

    2010 .NET面试题整理之基础篇 zhuan 开篇语:对于已有工作经验的朋友,也许面试题已显得不怎么重要,但是如果你应聘的还仅仅是个普通的程序员,相信在很多的公司都还是会先拿出一套面试题,可能对整 ...

  3. android笔试题整理

    笔试题整理 今天接到消息,说下个星期三.会陆陆续续的有公司来学校找暑假实习生.还没准备好啊,这就来啦?麻蛋 我慌的要死啊~ 1.Math.round(11.5)等于多少(). Math.round(- ...

  4. Java经典面试题整理及答案详解(八)

    简介: Java经典面试题第八节来啦!本节面试题包含了进程.线程.Object类.虚拟内存等相关内容,希望大家多多练习,早日拿下心仪offer- 了解更多: Java经典面试题整理及答案详解(一) J ...

  5. Java经典面试题整理及答案详解(三)

    简介: 以下是某同学面试时,面试官问到的问题,关于面试题答案可以参考以下内容- 上一篇:Java经典面试题整理及答案详解(二) Java面试真题第三弹接住!相信通过前两节的学习,大家对于Java多少有 ...

  6. 【资料整理】一些英语面试题整理

    [求职英语]一些英语面试题整理 SkySeraph Nov 3th 2011  HQU Email:zgzhaobo@gmail.com    QQ:452728574 Latest Modified ...

  7. 史上最全 BAT 大厂面试题整理

    转载自 史上最全 BAT 大厂面试题整理!(速度收藏) 主要分为以下几部分: (1)java面试题 (2)Android面试题 (3)高端技术面试题 (4)非技术性问题&HR问题汇总 1 ja ...

  8. 常见的面试题整理 -python

    常见的面试题整理 在这里插入代码片 #二分查找def binarySearch(alist, item):first=0;last=len(alist)-1;while first <= las ...

  9. [转载] Spring面试题整理

    参考链接: Java中的动态方法Dispatch和运行时多态 Spring面试题整理 2018年03月07日 21:11:46 hrbeuwhw 阅读数:49116 Spring 概述 1. 什么是s ...

  10. Spring Boot 面试题整理

    Spring Boot 面试题整理 2018年08月12日 22:32:35 Time_sg 阅读数 19380 版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文 ...

最新文章

  1. arm linux嵌入式网络控制系统,基于ARMLinux的嵌入式网络控制系统的研究与设计
  2. 通过修改程序解决Vista/Win7/Win8下应用程序兼容性问题
  3. 玩转虚拟化VMWare之一: VMWare ESXi 5.0和vSphere Client安装和配置
  4. spring 的配置 beanpropertyname属性
  5. LazyInitializationException的四种解决方案–第1部分
  6. poj 1006 java_POJ 1006 Java:中国剩余定理
  7. 台灯的内置和外置是什么意思_两款米家台灯:1S/Lite对比简评
  8. 巴蒂尔7个三分火箭破网 麦蒂战表兄取缺姚后首胜
  9. VK Cup 2012 Qualification Round 2 C. String Manipulation 1.0 线段树 or 树状数组+二分
  10. 股票python量化交易015-计算累积收益率
  11. 使用Python编写简单的端口扫描程序
  12. 百度地图开放平台天气预报查询API
  13. ssm基于微信小程序的学习资料销售平台+ssm+uinapp+Mysql+计算机毕业设计
  14. ios 去掉底部状态栏_iOS 隐藏顶部状态栏
  15. allegro加泪滴方法
  16. 11 款超赞的 MySQL 图形化工具,好用!
  17. 理性看待“视觉中国版权问题”,强化知识产权保护意识
  18. GreeNC:植物lncRNA数据库
  19. R语言(The R Programming Language)
  20. Matlab2012b licence失效解决办法

热门文章

  1. WEB漏洞攻防 -根据不同数据库类型之间的差异性进行注入
  2. 总结获得【酷我音乐】歌曲URL地址
  3. 剪辑师的基本素养--了解四种特写类型
  4. 域名由谁管理?申请域名注册服务机构要具备什么条件?
  5. 2022年软件测试——精选金融银行面试真题
  6. HR不排斥的三大跳槽理由
  7. Error in Summary.factor ‘min’ not meaningful for factors
  8. Android 4.4 PM机制系列(四) APK安装需要空间分析
  9. Android 获取/设置:窝蜂移动数据网络状态
  10. 普及1080i和1080p的区别