面试准备2

217 阅读42分钟

final,finally,finalize 的区别

  • final:变量、类、方法的修饰符,被 final 修饰的类不能被继承,变量或方法被 final 修饰 则不能被修改和重写。
  • finally:异常处理时提供 finally 块来执行清除操作,不管有没有异常抛出,此处代码都会 被执行。如果 try 语句块中包含 return 语句,finally 语句块是在 return 之后运行;
  • finalize:Object 类中定义的方法,若子类覆盖了 finalize()方法,在在垃圾收集器将对象 从内存中清除前,会执行该方法,确定对象是否会被回收。

序列化 Serializable 和 Parcelable 的区别

  • 序列化:将一个对象转换成可存储或可传输的状态,序列化后的对象可以在网络上传输,也 可以存储到本地,或实现跨进程传输;
  • Parcelable:与 Serializable 实现的效果相同,也是将一个对象转换成可传输的状态,但它 的实现原理是将一个完整的对象进行分解,分解后的每一部分都是 Intent 所支持的数据类 型,这样实现传递对象的功能。

区别:Serializable 在序列化时会产生大量临时变量,引起频繁 GC。Serializable 本质上使 用了反射,序列化过程慢。Parcelable 不能将数据存储在磁盘上,在外界变化时,它不能 很好的保证数据的持续性。

选择原则:若仅在内存中使用,如 activity\service 间传递对象,优先使用 Parcelable,它 性能高。若是持久化操作,优先使用 Serializable

SharedPreference的commit ,apply区别,SharedPreference是线程安全的吗?

  • apply没有返回值而commit返回boolean表明修改是否提交成功。
  • apply是将修改数据原子提交到内存, 而后异步真正提交到硬件磁盘, 而commit是同步的提交到硬件磁盘,因此,在多个并发的提交commit的时候,他们会等待正在处理的commit保存到磁盘后在操作,从而降低了效率。而apply只是原子的提交到内容,后面有调用apply的函数的将会直接覆盖前面的内存数据,这样从一定程度上提高了很多效率。

代码中可以看到读写操作时都有大量的synchronized,因此它是线程安全的。

WebView秒开的一些优化可以怎么做

  • 1.文件下载耗时:包括html、css、js、图片等
  • 2.页面渲染耗时:页面渲染,解析js、css文件等
  • 3.WebView创建耗时:首次创建WebView耗时大约需要500ms左右,第二次创建耗时大约需要20ms左右

Bitmap Drawable View 三者之间的联系和区别

  • bitmap: 仅仅就是一个位图 你可以理解为一张图片在内存中的映射。 就这么简单。这个很多人都知道
  • view: 这个就是android的核心了,你看到的一切东西都是view 这个很多人也知道。 但是这个理解成都还不够,view最大的作用是2个 一个是draw 也就是canvas的draw方法,还有一个作用 就是测量大小。 要想明白这点。
  • drawable: 他其实本身和bitmap没有关系, 你可以把他理解为是一个绘制工具,和view的第一个作用是一摸一样的,你能用view的canvas 画出来的东西 你用drawable 一样可以画出来, 不一样的是drawable 仅仅能绘制,但是不能测量自己的大小,但是view可以。换句话说 drawable 承担了view的一半作用。

Handler实现两个子线程(Thrae)通信

子线程中创出Handler 需要先调用Looper.prepare()获取消息队列在之后调用Looper.loop();开启Looper的循环(主线程中默认启动looper,子线程需要自己手动开启)

两种线程:Runable与Thread区别详解

(1)适合多个相同程序代码的线程去处理同一资源的情况,把虚拟CPU(线程)同程序的代码,数据有效的分离,较好地体现了面向对象的设计思想

(2)可以避免由于Java的单继承特性带来的局限。我们经常碰到这样一种情况,即当我们要将已经继承了某一个类的子类放入多线程中,由于一个类不能同时有两个父类,所以不能用继承Thread类的方式,那么,这个类就只能采用实现Runnable接口的方式了。

(3)有利于程序的健壮性,代码能够被多个线程共享,代码与数据是独立的。当多个线程的执行代码来自同一个类的实例时,即称它们共享相同的代码。多个线程操作相同的数据,与它们的代码无关。当共享访问相同的对象是,即它们共享相同的数据。当线程被构造时,需要的代码和数据通过一个对象作为构造函数实参传递进去,这个对象就是一个实现了Runnable接口的类的实例。

sendmessage和postmessage的区别

本质上没有区别

哪些情况下的对象会被垃圾回收机制处理掉?

利用可达性分析算法,虚拟机会将一些对象定义为 GC Roots,从 GC Roots 出发沿着引用链 向下寻找,如果某个对象不能通过 GC Roots 寻找到,虚拟机就认为该对象可以被回收掉。

1.1 哪些对象可以被看做是 GC Roots 呢?

  • 1)虚拟机栈(栈帧中的本地变量表)中引用的对象;
  • 2)方法区中的类静态属性引用的对象,常量引用的对象;
  • 3)本地方法栈中 JNI(Native 方法)引用的对象;

1.2 对象不可达,一定会被垃圾收集器回收么? 即使不可达,对象也不一定会被垃圾收集器回收,1)先判断对象是否有必要执行 finalize() 方法,对象必须重写 finalize()方法且没有被运行过。2)若有必要执行,会把对象放到一个 队列中,JVM 会开一个线程去回收它们,这是对象最后一次可以逃逸清理的机会。

Java 的异常体系

Java 中 Throwable 是所有异常和错误的超类,两个直接子类是 Error(错误)和 Exception(异 常): 1)Error 是程序无法处理的错误,由 JVM 产生和抛出,如 OOM、ThreadDeath 等。这些异常 发生时,JVM 一般会选择终止程序。

2 )Exception 是 程 序 本 身 可 以 处 理 的 异 常 ,又 分 为 运 行 时 异 常 ( RuntimeException ( 也 叫 CheckedEception)和非运行时异常(不检查异常 Unchecked Exception)。运行时异常有 NullPointerException\IndexOutOfBoundsException 等,这些异常一般是由程序逻辑错误引起 的,应尽可能避免。非运行时异常有 IOException\SQLException\FileNotFoundException 以及 由用户自定义的 Exception 异常等。

说说你对 Java 反射的理解

反射的作用:开发过程中,经常会遇到某个类的某个成员变量、方法或属性是私有的,或只 对系统应用开放,这里就可以利用 java 的反射机制通过反射来获取所需的私有成员或是方 法。

说说你对 Java 注解的理解

注解是通过@interface 关键字来进行定义的,形式和接口差不多,只是前面多了一个@ 为保证注解正常工作: 需要使用元注解 @Retention @Documented @Target @Inherited @Repeatable 五种

  • @Retention 说明注解的存活时间

    • RetentionPolicy.SOURCE 注解只在源码阶段保留,
    • RetentionPolicy.CLASS 注解只保留到编译进行的时候,并不会 被 加 载 到 J V M 中
    • R e t e n t i o n P o l i c y . R U N T I M E 可 以 留 到 程 序 运 行 的 时 候 ,它 会 被 加 载 进 入 到 J V M 中,所以在程序运行时可以获取到它们。
  • @Documented 注解中的元素包含到 javadoc 中去

  • @Target 限定注解的应用场景

    • ElementType.FIELD 给属性进行注解
    • ElementType.LOCAL_VARIABLE 可以给局部变量进行注解
    • ElementType.METHOD 可以给方法 进行注解
    • ElementType.PACKAGE 可以给一个包进行注解
    • ElementType.TYPE 可以给一个类型 进行注解,如类、接口、枚举
  • @Inherited 若一个超类被@Inherited 注解过的注解进行注解,它的子类没有被任何注解应用 的话,该子类就可继承超类的注解;

泛型中 extends 和 super 的区别

  • extends 限定参数类型的上界,参数类型必须是 T 或 T 的子类型,但对于 List<? extends T>,不能通过 add()来加入元素,因为不知道<? extends T>是 T 的哪一种子类;
  • super 限定参数类型的下界,参数类型必须是 T 或 T 的父类型,不能能过 get()获取

kotlin系列之let、with、run、apply、also函数的使用

image.png

cloud.tencent.com/developer/a… youkmi.blog.csdn.net/article/det…

kotlin中var、val、const val 中新增关键字vararg区别

  • var是变量,可对其进行读取和修改(读写)
  • val是常量,只能对其进行读取,而不能修改(读)
  • const val定义的【常量 】 const只能用来修饰val,并且Const 修饰的val只能定义在top level或者在objects中。top level指的是在最外面的类的外面,表示该常量属于整个文件,而非某个类。object可以指最外面的object和companion object。
  • 中新增关键字vararg,例如:"vararg args:String?" 替代Java中的写法"String...args"

kotlin-高阶函数

由于函数可以当做特殊变量,如果把A函数作为B函数的输入参数,此时,因为B函数的输入参数内嵌了A函数,故而B函数被称作为高阶函数,对应的A函数则为高阶函数的函数参数,又称函数变量。

        //4.3.5 高阶函数
        /**
         * 由于函数可以当做特殊变量,如果把A函数作为B函数的输入参数,
         * 此时,因为B函数的输入参数内嵌了A函数,故而B函数被称作为高阶函数
         */
        //maxCustom()是高阶函数,这里的greater函数就像是个变量
        //greater函数有两个输入参数,返回布尔类型的输出参数
        //如果第一个参数大于第二个参数,就认为greater返回true,否则返回false
        fun <T> maxCustom(array: Array<T>, greater: (T, T) -> Boolean): T? {
            var max: T? = null
            for (item in array) {
                if (max == null || greater(item, max))
                    max = item
            }
            return max
        }

   //调用
        var string_array: Array<String> = arrayOf("Hao", "do", "you", "do", "I'm", "fine")
        btn_default_fun8.setOnClickListener {
            btn_default_fun8.text = when (count % 4) {
                //string_array.max()返回的时you
                0 -> "字符串数组的默认最大值为${string_array.max()}"
                //string_array.max()对应的高阶函数是maxCustom同时也是泛型函数,所以要在函数名称后面加上<String>
                1 -> "字符串数组按长度比较的最大值为${maxCustom<String>(string_array, { a, b -> a.length > b.length })}"
                //因为系统可以根据string_array判断泛型函数采用了String类型,故而函数名称后面的<String>也可以省略掉
                2 -> "字符串数组的默认最大值(使用高阶函数)为${maxCustom(string_array, { a, b -> a > b })}"
                else -> "字符串数组按去掉空格在比较长度的最大值为${maxCustom(string_array, { a, b -> a.trim().length > b.trim().length })}"
            }
            count++
        }

kotlin-扩展函数

扩展函数允许开发者给系统类补写新的方法,而无须另外编写额外的工具类。

  /**
         * 概念:系统自带的类提供的方法无法满足日常开发需求,于是乎开发者往往编写了很多工具类,由于工具类繁多,难以管理。
         * Kotlin推出了扩展函数的概念,扩展函数允许开发者给系统类补写新的方法,而无需编写额外的工具类。
         */
        //例如:给数组Array新增交换的方法。
        fun <T> Array<T>.swap(pos1: Int, pos2: Int) {
            //this表示数组变量自身
            val temp = this[pos1]
            this[pos1] = this[pos2]
            this[pos2] = temp

        }

        val array: Array<Double> = arrayOf(1.0, 2.0, 3.0, 4.0, 5.0)
        btn_default_fun9.setOnClickListener {
            //下标为0和3的两个数组元素进行交换
            array.swap(0, 3)
            btn_default_fun9.text = setArrayStr<Double>(array)
        }

kotlin-扩展高阶函数

  /**
     * 扩展高阶函数
     * 扩展:扩展类的方法
     * 高阶:将函数作为变量传递
     */
    fun <T> Array<T>.maxCustomize(greater: (T, T) -> Boolean): T? {
        var max: T? = null
        for (item in this) {
            if (max == null || greater(item, max)) {
                max = item
            }
        }
        return max
    }

       //扩展高阶函数
        //扩展函数+高阶函数

        btn_default_fun10.setOnClickListener {
            btn_default_fun10.text = when (count % 4) {
                0 -> "字符串数组的默认最大值为${string_array.max()}"
                1 -> "字符串数组按长度比较的最大值为${string_array.maxCustomize<String>({ a, b -> a.length > b.length })}"
                2 -> "字符串数组的默认最大值(使用高阶函数)为${string_array.maxCustomize({ a, b -> a > b })}"
                else -> "字符串数组按去掉空格在比较长度的最大值为${string_array.maxCustomize({ a, b -> a.trim().length > b.trim().length })}"
            }
            count++
        }

kotlin-内联函数

通过关键字inline修饰都函数 优点:

  • 优化Lambda开销 通过inline修饰的函数,其函数体代码被调用的Lambda代码都粘贴到了相应调用的位置
fun main() {
    payFoo {
        println("write kotlin...")
    }
}
fun payFoo(block: () -> Unit) {
    println("before block")
    block()
    println("end block")
}

--------inline修改后----------
fun main() {
    payFoo {
        println("write kotlin...")
    }
}
inline fun payFoo(block: () -> Unit) {
    println("before block")
    block()
    println("end block")
}
  • 非局部返回 Kotlin中内联函数除了优化Lambda开销之外,还带来了非局部返回和具体化参数类型。

首先看常见的局部返回的例子

fun main() {
    payFoo()
}
 
fun localReturn() {
    return
}
 
fun payFoo() {
    println("before local return")
    localReturn()
    println("after local return")
    return
}
 
before local return
after local return

从上面代码可以发现,localReturn执行之后,其函数体中的return只会在该函数的局部生效,所以localReturn()之后的println函数依旧生效。

通过内联函数修改

fun main() {
    payFoo { return }
}
 
inline fun payFoo(returning: () -> Unit) {
    println("before local return")
    returning()
    println("after local return")
    return
}
 
before local return

Lambda的return执行之后直接让foo函数退出了执行。因为内联函数payFoo的函数体及参数Lambda会直接替代具体的调用,所以实际产生的代码中,return相当于是直接暴露在main函数中的,所以returning之后的代码就不会执行了,这就是非局部返回。

  • 具体化参数类型 内联函数可以帮助我们实现具体化参数类型,Kotlin与Java一样,由于运行时的类型擦除,我们不能直接获取一个参数的类型。然而,由于内联函数会直接在字节码中生成相应的函数体实现,这时候反而可以获得参数的具体类型。 使用reified修饰符来实现这一效果。
inline fun <reified T : Activity> Activity.startActivity() {
    startActivity(Intent(this, T::class.java))
}

内联函数不是万能的,以下情况避免使用内联函数:

1.由于JVM对普通函数已经能够根据实际情况智能地判断是否进行内联优化,所以我们并不需要对其使用Kotlin的inline语法,那只会让字节码变得更加复杂。

2.尽量避免对具有大量函数体的函数进行内联,这样会导致过多的字节码数量。

3.一旦一个函数被定义为内联函数,便不能获取闭包类的私有成员,除非你把它们声明为internal。

kotlin-伴生对象

Kotlin取消了关键字static,也就无法直接声明静态成员。为了弥补这方面的功能缺陷,Kotlin引入 了伴生对象的概念.

class WildAnimalCompanion(var name: String, val sex: Int = 0) {
    var sexName: String

    init {
        sexName = if (sex == 0) "公" else "母"
    }

    fun getDesc(tag: String): String {
        return "欢迎来到$tag:这只${name}是${sexName}的"
    }
    //在类加载的时候就运行伴生对象的代码块,其作用相当于Java里面的static{...}代码块

    companion object WildAnimal {
        fun judgeSex(sexName: String): Int {
            var sex: Int = when (sexName) {
                "公", "雄" -> 0
                "母", "雌" -> 1
                else -> -1
            }
            return sex
        }
    }
}


--------

        val sexArray: Array<String> = arrayOf("公", "母", "雄", "雌")
        btn_simple_class7.setOnClickListener {
            var sexName: String = sexArray[count++ % 4]
            //伴生对象的WildAnimal名称可以省略
//            btn_simple_class7.text="${sexName} 对应的类型是${WildAnimalCompanion.WildAnimal.judgeSex(sexName)}"
            btn_simple_class7.text = "$sexName 对应的类型是${WildAnimalCompanion.judgeSex(sexName)}"
        }

kotlin--类的修饰符

  • open作为修饰符 表示该类可以被继承
  • public ->对所有人开放。kotlin的类,函数、变量不加开放性修饰的话默认就是public类型
  • internal ->只对本模块内部开放,这是kotlin新增的关键字。对于app开发来说,本模块便是指app自身
  • protected ->只对自己跟子类开放
  • private ->只对自己开放,即私有

注意:open 跟private 是对立的关系 不能同时出现

  • 抽象类用abstract关键字修饰,abstract方法默认就是open类型

kotlin--几种特殊的类

嵌套类

class Tree(var treeName: String) {


    //在类的内部再定一个类,这个新类称作嵌套类
    //不能访问外部类成员,如treename

    class Flower(var flowerName: String) {
        fun getName(): String {
            return "这是一朵${flowerName}"
        }
    }
}

注意:

  • 调用嵌套类时,得在嵌套类的类名前面添加外部类的类名,相当于把这个嵌套类作为外部类的静态对象使用
  • 不能访问外部类成员

内部类 既然Kotlin限制了嵌套类不能访问外部类的成员,Kotin另外增加了关键字inner表示内部,把inner加在嵌套类的class前面,这个内部类比起嵌套类的好处是能够访问外部类的成员。

class Tree(var treeName: String) {
    //嵌套类加上inner前缀,就成为内部类
    inner class Fruit(var fruitName: String) {
        fun getName(): String {
            return "这是${treeName}长出的$fruitName"
        }
    }
}

泛型类

  • 定义格式跟Java相似,一样在类名后面补充形如,<A,B>这样的表达式
  • 外部调用模板类构造函数的时候,要在类名后面补充“<参数类型>”,从而动态指定实际的参数类型
class River<T>(var name: String, var length: T) {
    fun getInfo(): String {
        var unit: String = when (length) {
            is String -> "米"
            is Number -> "m"
            else -> ""
        }
        return "${name}的长度是$length$unit"
    }
}
--------
btn_simple_class19.setOnClickListener {
            var river = when (count++ % 4) {
                //泛型类声明对象时,要在模板类的后面加上“<参数类型>”
                0 -> River<Int>("小溪", 100)
                //如果编译器根据输入参数就能知晓参数类型,也可以直接省略“<参数类型>"
                1 -> River("瀑布", 99.9f)
                1 -> River<Double>("瀑布", 55.5)
                else -> River("大河", "一千")
            }
            btn_simple_class19.text = river.getInfo()
        }

泛型擦除

  • 如果泛型没有设置上界约束,那么将泛型转化成 Object 类型
  • 如果泛型设置了上界约束,那么将泛型转化成该上界约束

协变/逆变

  • 协变 带 extends 限定了上界的通配符类型使得泛型参数类型是协变的,即如果 A 是 B 的子类,那么 Generic 就是 Generic的子类型
    private static <T> void copyAll(List<T> to, List<? extends T> from) {
        to.addAll(from);
    }

    private static <T> void copyAll(List<? super T> to, List<T> from) {
        to.addAll(from);
    }

out & in

fun <T> copyAll(to: MutableList<in T>, from: MutableList<T>) {
    to.addAll(from)
}

fun <T> copyAll(to: MutableList<T>, from: MutableList<out T>) {
    to.addAll(from)
}

  • in 关键字就相当于 Java 中的<? super T>,其作用就是限制了 to 只能用于接收值而不能向其取值,这样就避免了从 to 取出值然后向 from 赋值这种不安全的行为了,即实现了泛型逆变

  • out 关键字就相当于 Java 中的<? extends T>,其作用就是限制了 from 不能用于接收值而只能向其取值,这样就避免了从 to 取出值然后向 from 赋值这种不安全的行为了,即实现了泛型协变

reified & inline 上文讲了,由于类型擦除,Java 和 Kotlin 的泛型类型实参都会在编译阶段被擦除。而在 Kotlin 中存在一个额外手段可以来避免这个问题,即内联函数

用关键字 inline 标记的函数就称为内联函数,再用 reified 关键字修饰内联函数中的泛型形参,编译器在进行编译的时候便会将内联函数的字节码插入到每一个调用的地方,当中就包括泛型的类型实参。而内联函数的类型形参能够被实化,就意味着我们可以在运行时引用实际的类型实参了

例如,我们可以写出以下这样的一个内联函数,用于判断一个对象是否是指定类型

fun main() {
    println(1.isInstanceOf<String>())
    println("string".isInstanceOf<Int>())
}

inline fun <reified T> Any.isInstanceOf(): Boolean {
    return this is T
}

单例模式(懒汉式和饿汉式区别)

  • 饿汉氏 简单来说就是空间换时间,因为上来就实例化一个对象,占用了内存,天生线程安全

class Singleton{
	//私有的构造函数,保证外类不能实例化本类
	private Singleton(){}
	//自己创建一个类的实例化
	private static Singleton singleton = new Singleton();
	//创建一个get方法,返回一个实例s
	public static Singleton getInstance(){
		return singleton;
	}
  • 懒汉氏 简单的来说就是时间换空间,与饿汉式正好相反 线程不安全

浏览器输入URL会发生什么?

  • 首先先解析URL得到里面的参数,将域名和请求的资源分开,然后再将这些信息封装成一段HTTP请求报文发送出去。
  • DNS域名解析获取IP地址
  • 通过DNS解析拿到目标IP地址后,就可以将HTTP的传输工作委托给操作系统的协议栈了。
  • 进行三次握手建立连接,保证双方的一个可靠传输。
  • 使用ARP协议找到MAC地址实现两点传输
  • 服务器响应请求

HTTP与HTTPS有什么区别?

HTTPS是一种通过计算机网络进行安全通信的传输协议。HTTPS经由HTTP进行通信,但利用SSL/TLS来加密数据包。HTTPS开发的主要目的,是提供对网站服务器的身份 认证,保护交换数据的隐私与完整性。

HTPPS和HTTP的概念:

HTTPS(全称:Hypertext Transfer Protocol over Secure Socket Layer),是以安全为目标的HTTP通道,简单讲是HTTP的安全版。即HTTP下加入SSL层,HTTPS的安全基础是SSL,因此加密的详细内容就需要SSL。 它是一个URI scheme(抽象标识符体系),句法类同http:体系。用于安全的HTTP数据传输。https:URL表明它使用了HTTP,但HTTPS存在不同于HTTP的默认端口及一个加密/身份验证层(在HTTP与TCP之间)。这个系统的最初研发由网景公司进行,提供了身份验证与加密通讯方法,现在它被广泛用于万维网上安全敏感的通讯,例如交易支付方面。

超文本传输协议 (HTTP-Hypertext transfer protocol) 是一种详细规定了浏览器和万维网服务器之间互相通信的规则,通过因特网传送万维网文档的数据传送协议。

https协议需要到ca申请证书,一般免费证书很少,需要交费。http是超文本传输协议,信息是明文传输,https 则是具有安全性的ssl加密传输协议http和https使用的是完全不同的连接方式用的端口也不一样,前者是80,后者是443。http的连接很简单,是无状态的HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议 要比http协议安全HTTPS解决的问题:1 . 信任主机的问题. 采用https 的server 必须从CA 申请一个用于证明服务器用途类型的证书. 改证书只有用于对应的server 的时候,客户度才信任次主机2 . 防止通讯过程中的数据的泄密和被窜改

如下图所示,可以很明显的看出两个的区别:

image.png

https 为什么是安全的

对称加密 就是客户端和服务器共用同一个密钥,该密钥可以用于加密一段内容,同时也可以用于解密这段内容。对称加密的优点是加解密效率高,但是在安全性方面可能存在一些问题,因为密钥存放在客户端有被窃取的风险。对称加密的代表算法有:AES、DES 等。

非对称加密 公钥和私钥。公钥通常存放在客户端,私钥通常存放在服务器。使用公钥加密的数据只有用私钥才能解密,反过来使用私钥加密的数据也只有用公钥才能解密。非对称加密的优点是安全性更高, 非对称加密的代表算法有:RSA、ElGamal 等。

对称加密的优点是加解密效率高,而我们在网络上传输数据是非常讲究效率的,因此https这里很明显应该使用对称加密。我们需要将非对称加密引入进来,协助解决无法安全创建对称加密密钥的问题‘

CA 机构。

首先,我们作为一个网站的管理员需要向CA机构进行申请,将自己的公钥提交给CA机构。CA机构则会使用我们提交的公钥,再加上一系列其他的信息,如网站域名、有效时长等,来制作证书。

证书制作完成后,CA机构会使用自己的私钥对其加密,并将加密后的数据返回给我们,我们只需要将获得的加密数据配置到网站服务器上即可。 然后,每当有浏览器请求我们的网站时,首先会将这段加密数据返回给浏览器,此时浏览器会用CA机构的公钥来对这段数据解密。

原文链接:blog.csdn.net/guolin_blog…

https是怎么抓包的

  • 下载证书
  • 配置https安全文件 Android 7.0系统中才做了这项安全升级。默认情况下,我们无法对各个App的https请求进行抓包,如果你是想要对自己App的https请求抓包的话,那么可以这样做。
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <base-config cleartextTrafficPermitted="true">
        <trust-anchors>
            <certificates src="user"/>
            <certificates src="system"/>
        </trust-anchors>
    </base-config>
</network-security-config>

https协议确实是非常安全的,但同时,用https协议传输的数据也确实是可以被抓包的,它们两者之间并不冲突。https协议是结合了非对称加密和对称加密一起工作,从而保证数据传输的安全性的。

HTTPS 如何防范中间人攻击?

https加密原理。

Http的request和response的协议组成

客户端发送一个HTTP请求到服务器的请求消息包括以下格式:

请求行(request line)、请求头部(header)、空行和请求数据四个部分组成。

image.png

请求行以一个方法符号开头,以空格分开,后面跟着请求的URI和协议的版本。

Get请求例子

GET /562f25980001b1b106000338.jpg HTTP/1.1
Host    img.mukewang.com
User-Agent  Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36
Accept  image/webp,image/*,*/*;q=0.8
Referer http://www.imooc.com/
Accept-Encoding gzip, deflate, sdch
Accept-Language zh-CN,zh;q=0.8

第一部分:请求行,用来说明请求类型,要访问的资源以及所使用的HTTP版本. GET说明请求类型为GET,[/562f25980001b1b106000338.jpg]为要访问的资源,该行的最后一部分说明使用的是HTTP1.1版本。 第二部分:请求头部,紧接着请求行(即第一行)之后的部分,用来说明服务器要使用的附加信息 从第二行起为请求头部,HOST将指出请求的目的地.User-Agent,服务器端和客户端脚本都能访问它,它是浏览器类型检测逻辑的重要基础.该信息由你的浏览器来定义,并且在每个请求中自动发送等等 第三部分:空行,请求头部后面的空行是必须的 即使第四部分的请求数据为空,也必须有空行。 第四部分:请求数据也叫主体,可以添加任意的其他数据。 这个例子的请求数据为空。

POST请求例子

POST / HTTP1.1
Host:www.wrox.com
User-Agent:Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727; .NET CLR 3.0.04506.648; .NET CLR 3.5.21022)
Content-Type:application/x-www-form-urlencoded
Content-Length:40
Connection: Keep-Alive

name=Professional%20Ajax&publisher=Wiley

第一部分:请求行,第一行明了是post请求,以及http1.1版本。

第二部分:请求头部,第二行至第六行。

第三部分:空行,第七行的空行。

第四部分:请求数据,第八行。

Request组成

一般情况下,服务器接收并处理客户端发过来的请求后会返回一个HTTP的响应消息。 HTTP响应也由四个部分组成,分别是:状态行、消息报头、空行和响应正文。

image.png 第一部分:状态行,由HTTP协议版本号, 状态码, 状态消息 三部分组成。

第一行为状态行,(HTTP/1.1)表明HTTP版本为1.1版本,状态码为200,状态消息为(ok)

第二部分:消息报头,用来说明客户端要使用的一些附加信息

第二行和第三行为消息报头, Date:生成响应的日期和时间;Content-Type:指定了MIME类型的HTML(text/html),编码类型是UTF-8

第三部分:空行,消息报头后面的空行是必须的

第四部分:响应正文,服务器返回给客户端的文本信息。

空行后面的html部分为响应正文

https 缓存

HTTP的缓存机制也是依赖于请求和响应header里的参数类实现的,最终响应式从缓存中取,还是从服务端重新拉取,HTTP的缓存机制的流程如下所示

image.png

http三次握手跟四次挥手

  1. 三次握手:
  • 第一次握手 客户端向服务器发送一个连接请求的报文
  • 第二次握手 服务器接收到了报文段后,若同意建立连接,则向客户端发回连接确认的报文
  • 第三次握手 客户端收到确认报文段后,向服务器再次发出连接确认报文段

为什么TCP建立连接需三次握手?

防止服务器端因接收了早已失效的连接请求报文,从而一直等待客户端请求,最终导致形成死锁、浪费资源

  1. 4次挥手
  • 第一次挥手 客户端向服务器发送一个连接释放的报文
  • 第二次挥手 服务器接收到了释放报文段后,则向客户端发送表示同意释报文
  • 第三次挥手 无服务器无需在向客户端发送数据,则发出释放连接的报文
  • 第三次挥手 客户端收到连接释放报文后,则向服务器发送连接释放确认的报文

为什么TCP释放连接需四次挥手? 为了保证通信双方都能通知对方 需释放 & 断开连接

Http长连接。

Http1.0是短连接,HTTP1.1默认是长连接,也就是默认Connection的值就是keep-alive。但是长连接实质是指的TCP连接,而不是HTTP连接。TCP连接是一个双向的通道,它是可以保持一段时间不关闭的,因此TCP连接才有真正的长连接和短连接这一说。

Http1.1为什么要用使用tcp长连接?

长连接是指的TCP连接,也就是说复用的是TCP连接。即长连接情况下,多个HTTP请求可以复用同一个TCP连接,这就节省了很多TCP连接建立和断开的消耗。

此外,长连接并不是永久连接的。如果一段时间内(具体的时间长短,是可以在header当中进行设置的,也就是所谓的超时时间),这个连接没有HTTP请求发出的话,那么这个长连接就会被断掉。

UDP和TCP的区别是什么?

  • TCP是面向有连接型,UDP是面向无连接型;
  • TCP是一对一传输,UDP支持一对一、一对多、多对一和多对多的交互通信;
  • TCP是面向字节流的,即把应用层传来的报文看成字节流,将字节流拆分成大小不等的数据块,并添加TCP首部;
  • UDP是面向报文的,对应用层传下来的报文不拆分也不合并,仅添加UDP首部;
  • TCP支持传输可靠性的多种措施,包括保证包的传输顺序、重发机制、流量控制和拥塞控制;
  • UDP仅提供最基本的数据传输能力。

有哪些响应码,分别都代表什么意思?

1** 信息,服务器收到请求,需要请求者继续执行操作

2** 成功,操作被成功接收并处理

3** 重定向,需要进一步的操作以完成请求

4** 客户端错误,请求包含语法错误或无法完成请求

5** 服务器错误,服务器在处理请求的过程中发生了错误

socket断线重连怎么实现,心跳机制又是怎样实现?

建立socket连接

建立Socket连接至少需要一对套接字,其中一个运行于客户端,称为ClientSocket ,另一个运行于服务器端,称为ServerSocket 。

套接字之间的连接过程分为三个步骤:服务器监听,客户端请求,连接确认。

  • 服务器监听:服务器端套接字并不定位具体的客户端套接字,而是处于等待连接的状态,实时监控网络状态,等待客户端的连接请求。
  • 客户端请求:指客户端的套接字提出连接请求,要连接的目标是服务器端的套接字。为此,客户端的套接字必须首先描述它要连接的服务器的套接字,指出服务器端- - 套接字的地址和端口号,然后就向服务器端套接字提出连接请求。 连接确认:当服务器端套接字监听到或者说接收到客户端套接字的连接请求时,就响应客户端套接字的请求,建立一个新的线程,把服务器端套接字的描述发 给客户端,一旦客户端确认了此描述,双方就正式建立连接。而服务器端套接字继续处于监听状态,继续接收其他客户端套接字的连接请求。
SOCKET连接与TCP连接

创建Socket连接时,可以指定使用的传输层协议,Socket可以支持不同的传输层协议(TCP或UDP),当使用TCP协议进行连接时,该Socket连接就是一个TCP连接。

Socket连接与HTTP连接

由于通常情况下Socket连接就是TCP连接,因此Socket连接一旦建立,通信双方即可开始相互发送数据内容,直到双方连接断开。但在实际网 络应用中,客户端到服务器之间的通信往往需要穿越多个中间节点,例如路由器、网关、防火墙等,大部分防火墙默认会关闭长时间处于非活跃状态的连接而导致 Socket 连接断连,因此需要通过轮询告诉网络,该连接处于活跃状态。

而HTTP连接使用的是“请求—响应”的方式,不仅在请求时需要先建立连接,而且需要客户端向服务器发出请求后,服务器端才能回复数据。

很多情况下,需要服务器端主动向客户端推送数据,保持客户端与服务器数据的实时与同步。此时若双方建立的是Socket连接,服务器就可以直接将数 据传送给客户端;若双方建立的是HTTP连接,则服务器需要等到客户端发送一次请求后才能将数据传回给客户端,因此,客户端定时向服务器端发送连接请求, 不仅可以保持在线,同时也是在“询问”服务器是否有新的数据,如果有就将数据传给客户端。TCP(Transmission Control Protocol) 传输控制协议

socket断线重连实现

正常连接断开客户端会给服务端发送一个fin包,服务端收到fin包后才会知道连接断开。 而断网断电时客户端无法发送fin包给服务端,所以服务端没办法检测到客户端已经短线。 为了缓解这个问题,服务端需要有个心跳逻辑,就是服务端检测到某个客户端多久没发送任何数据过来就认为客户端已经断开, 这需要客户端定时向服务端发送心跳数据维持连接。

心跳机制实现

长连接的实现:心跳机制,应用层协议大多都有HeartBeat机制,通常是客户端每隔一小段时间向服务器发送一个数据包,通知服务器自己仍然在线。并传输一些可能必要的数据。使用心跳包的典型协议是IM,比如QQ/MSN/飞信等协议

1、在TCP的机制里面,本身是存在有心跳包的机制的,也就是TCP的选项:SO_KEEPALIVE。 系统默认是设置的2小时的心跳频率。但是它检查不到机器断电、网线拔出、防火墙这些断线。 而且逻辑层处理断线可能也不是那么好处理。一般,如果只是用于保活还是可以的。通过使用TCP的KeepAlive机制(修改那个time参数),可以让连接每隔一小段时间就产生一些ack包,以降低被踢掉的风险,当然,这样的代价是额外的网络和CPU负担。

2、应用层心跳机制实现。

Cookie与Session的作用和原理。

  • Session是在服务端保存的一个数据结构,用来跟踪用户的状态,这个数据可以保存在集群、数据库、文件中。
  • Cookie是客户端保存用户信息的一种机制,用来记录用户的一些信息,也是实现Session的一种方式。
Session:

由于HTTP协议是无状态的协议,所以服务端需要记录用户的状态时,就需要用某种机制来识具体的用户,这个机制就是Session.典型的场景比如购物车,当你点击下单按钮时,由于HTTP协议无状态,所以并不知道是哪个用户操作的,所以服务端要为特定的用户创建了特定的Session,用用于标识这个用户,并且跟踪用户,这样才知道购物车里面有几本书。这个Session是保存在服务端的,有一个唯一标识。在服务端保存Session的方法很多,内存、数据库、文件都有。集群的时候也要考虑Session的转移,在大型的网站,一般会有专门的Session服务器集群,用来保存用户会话,这个时候 Session 信息都是放在内存的。

具体到Web中的Session指的就是用户在浏览某个网站时,从进入网站到浏览器关闭所经过的这段时间,也就是用户浏览这个网站所花费的时间。因此从上述的定义中我们可以看到,Session实际上是一个特定的时间概念。

当客户端访问服务器时,服务器根据需求设置Session,将会话信息保存在服务器上,同时将标示Session的SessionId传递给客户端浏览器,

浏览器将这个SessionId保存在内存中,我们称之为无过期时间的Cookie。浏览器关闭后,这个Cookie就会被清掉,它不会存在于用户的Cookie临时文件。

以后浏览器每次请求都会额外加上这个参数值,服务器会根据这个SessionId,就能取得客户端的数据信息。

如果客户端浏览器意外关闭,服务器保存的Session数据不是立即释放,此时数据还会存在,只要我们知道那个SessionId,就可以继续通过请求获得此Session的信息,因为此时后台的Session还存在,当然我们可以设置一个Session超时时间,一旦超过规定时间没有客户端请求时,服务器就会清除对应SessionId的Session信息。

Cookie

Cookie是由服务器端生成,发送给User-Agent(一般是web浏览器),浏览器会将Cookie的key/value保存到某个目录下的文本文件内,下次请求同一网站时就发送该Cookie给服务器(前提是浏览器设置为启用Cookie)。Cookie名称和值可以由服务器端开发自己定义,对于JSP而言也可以直接写入Sessionid,这样服务器可以知道该用户是否合法用户以及是否需要重新登录等。

Android 各个版本适配问题

10,11版本 作用域存储: 那么到底什么是作用域存储呢?简单来讲,就是Android系统对SD卡的使用做了很大的限制。从Android 10开始,每个应用程序只能有权在自己的外置存储空间关联目录下读取和创建文件,获取该关联目录的代码是:context.getExternalFilesDir()。关联目录对应的路径大致如下:

/storage/emulated/0/Android/data/<包名>/files

需要适配的点场景:

  • 获取相册中的图片 :们只能借助MediaStore API获取到图片的Uri
  • 将图片添加到相册 :
  • 下载文件到Download目录:那么该如何解决呢?主要有以下两种方式。
    • 就是更改文件的下载目录
    • 适配10MediaStore中新增了一种Downloads集合,专门用于执行文件下载操作。
  • 使用文件选择器: 如果我们要读取SD卡上非图片、音频、视频类的文件,比如说打开一个PDF文件,这个时候就不能再使用MediaStore API了,而是要使用文件选择器。
const val PICK_FILE = 1

private fun pickFile() {
    val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
    intent.addCategory(Intent.CATEGORY_OPENABLE)
    intent.type = "*/*"
    startActivityForResult(intent, PICK_FILE)
}

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    super.onActivityResult(requestCode, resultCode, data)
    when (requestCode) {
        PICK_FILE -> {
            if (resultCode == Activity.RESULT_OK && data != null) {
                val uri = data.data
                if (uri != null) {
                    val inputStream = contentResolver.openInputStream(uri)
					// 执行文件读取操作
                }
            }
        }
    }
}

  • 第三方SDK不支持作用域存储怎么办?
    • 在那之前我们先不要将targetSdkVersion指定到29,或者先在AndroidManifest文件中配置一下requestLegacyExternalStorage属性。
    • 就是我们自己编写一个文件复制功能,将Uri对象所对应的文件复制到应用程序的关联目录下,然后再将关联目录下这个文件的绝对路径传递给第三方SDK,这样就可以完美进行适配了

9.0版本适配 问题原因: Android P 限制了明文流量的网络请求,非加密的流量请求都会被系统禁止掉 解决方案: 1:在资源文件新建xml目录,新建文件network_security_config.xml

<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <base-config cleartextTrafficPermitted="true" />
</network-security-config>

2:清单文件配置:

<application
    android:networkSecurityConfig="@xml/network_security_config">
    <!--Android 9.0加的-->
    <uses-library
        android:name="org.apache.http.legacy"
        android:required="false" />
</application>

8.0版本适配 Android 8.0中,为了更好的管制通知的提醒,不想一些不重要的通知打扰用户,新增了通知渠道,用户可以根据渠道来屏蔽一些不想要的通知。

7.0适配 应用间共享文件

6.0适配 权限适配

volatile关键字在Android中到底有什么用?

  • volatile这个关键字的其中一个重要作用就是解决可见性问题,即保证当一个线程修改了某个变量之后,该变量对于另外一个线程是立即可见的。
  • volatile关键字还有另外一个重要的作用,就是禁止指令重排

sychronized关键字

三种应用方式

  • 修饰实例方法 作用于当前实例加锁,进入同步代码前要获得当前实例的锁
  • 修饰静态方法 作用于当前类对象加锁,进入同步代码前要获得当前类对象的锁
  • 修饰代码块 指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁

synchronized底层语义原理 理解Java对象头与Monitor

  • 在JVM中,对象在内存中的布局分为三块区域:对象头、实例数据和对齐填充
    • Java头对象,它实现synchronized的锁对象的基础,这点我们重点分析它,一般而言,synchronized使用的锁对象是存储在Java对象头里的,jvm中采用2个字来存储对象头(如果对象是数组则会分配3个字,多出来的1个字记录的是数组长度),其主要结构是由Mark Word 和 Class Metadata Address 组成
    • Mark Word 存储对象的hashCode、锁信息或分代年龄或GC标志等信息
    • Class Metadata Address 类型指针指向对象的类元数据,JVM通过这个指针确定该对象是哪个类的实例。

synchronized代码块底层原理

从字节码中可知同步语句块的实现使用的是monitorenter 和 monitorexit 指令,其中monitorenter指令指向同步代码块的开始位置,monitorexit指令则指明同步代码块的结束位置,当执行monitorenter指令时,当前线程将试图获取 objectref(即对象锁) 所对应的 monitor 的持有权,当 objectref 的 monitor 的进入计数器为 0,那线程可以成功取得 monitor,并将计数器值设置为 1,取锁成功。如果当前线程已经拥有 objectref 的 monitor 的持有权,那它可以重入这个 monitor (关于重入性稍后会分析),重入时计数器的值也会加 1。倘若其他线程已经拥有 objectref 的 monitor 的所有权,那当前线程将被阻塞,直到正在执行线程执行完毕,即monitorexit指令被执行,执行线程将释放 monitor(锁)并设置计数器值为0 ,其他线程将有机会持有 monitor 。值得注意的是编译器将会确保无论方法通过何种方式完成,方法中调用过的每条 monitorenter 指令都有执行其对应 monitorexit 指令,而无论这个方法是正常结束还是异常结束。为了保证在方法异常完成时 monitorenter 和 monitorexit 指令依然可以正确配对执行,编译器会自动产生一个异常处理器,这个异常处理器声明可处理所有的异常,它的目的就是用来执行 monitorexit 指令。从字节码中也可以看出多了一个monitorexit指令,它就是异常结束时被执行的释放monitor 的指令。

锁的分类4个

  1. 无锁状态
  2. 偏向锁
  3. 轻量级锁
  4. 重量级锁

Java虚拟机对synchronized的优化

  • 偏向锁

偏向锁是Java 6之后加入的新锁,它是一种针对加锁操作的优化手段,经过研究发现,在大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,因此为了减少同一线程获取锁(会涉及到一些CAS操作,耗时)的代价而引入偏向锁。偏向锁的核心思想是,如果一个线程获得了锁,那么锁就进入偏向模式,此时Mark Word 的结构也变为偏向锁结构,当这个线程再次请求锁时,无需再做任何同步操作,即获取锁的过程,这样就省去了大量有关锁申请的操作,从而也就提供程序的性能。所以,对于没有锁竞争的场合,偏向锁有很好的优化效果,毕竟极有可能连续多次是同一个线程申请相同的锁。但是对于锁竞争比较激烈的场合,偏向锁就失效了,因为这样场合极有可能每次申请锁的线程都是不相同的,因此这种场合下不应该使用偏向锁,否则会得不偿失,需要注意的是,偏向锁失败后,并不会立即膨胀为重量级锁,而是先升级为轻量级锁

  • 轻量级锁

倘若偏向锁失败,虚拟机并不会立即升级为重量级锁,它还会尝试使用一种称为轻量级锁的优化手段(1.6之后加入的),此时Mark Word 的结构也变为轻量级锁的结构。轻量级锁能够提升程序性能的依据是“对绝大部分的锁,在整个同步周期内都不存在竞争”,注意这是经验数据。需要了解的是,轻量级锁所适应的场景是线程交替执行同步块的场合,如果存在同一时间访问同一锁的场合,就会导致轻量级锁膨胀为重量级锁

  • 自旋锁

轻量级锁失败后,虚拟机为了避免线程真实地在操作系统层面挂起,还会进行一项称为自旋锁的优化手段。这是基于在大多数情况下,线程持有锁的时间都不会太长,如果直接挂起操作系统层面的线程可能会得不偿失,毕竟操作系统实现线程之间的切换时需要从用户态转换到核心态,这个状态之间的转换需要相对比较长的时间,时间成本相对较高,因此自旋锁会假设在不久将来,当前的线程可以获得锁,因此虚拟机会让当前想要获取锁的线程做几个空循环(这也是称为自旋的原因),一般不会太久,可能是50个循环或100循环,在经过若干次循环后,如果得到锁,就顺利进入临界区。如果还不能获得锁,那就会将线程在操作系统层面挂起,这就是自旋锁的优化方式,这种方式确实也是可以提升效率的。最后没办法也就只能升级为重量级锁了。

  • 锁消除

消除锁是虚拟机另外一种锁的优化,这种优化更彻底,Java虚拟机在JIT编译时(可以简单理解为当某段代码即将第一次被执行时进行编译,又称即时编译),通过对运行上下文的扫描,去除不可能存在共享资源竞争的锁,通过这种方式消除没有必要的锁,可以节省毫无意义的请求锁时间,如下StringBuffer的append是一个同步方法,但是在add方法中的StringBuffer属于一个局部变量,并且不会被其他线程所使用,因此StringBuffer不可能存在共享资源竞争的情景,JVM会自动将其锁消除。

volatile关键字

  • volatile这个关键字的其中一个重要作用就是解决可见性问题,即保证当一个线程修改了某个变量之后,该变量对于另外一个线程是立即可见的。
  • volatile关键字还有另外一个重要的作用,就是禁止指令重排, CPU在执行代码时,其实并不一定会严格按照我们编写的顺序去执行,而是可能会考虑一些效率方面的原因,对那些先后顺序无关紧要的代码进行重新排序,这个操作就被称为指令重排。
  • android 上的应用 对于多线程场景的变量基本会使用volatile关键字

guolin.blog.csdn.net/article/det…

红黑树

它或者是一个空数,或者是具有以下性质的二叉查找数 特性

  • 节点非红即黑
  • 根结点是黑色
  • 所有null结点称为叶子节点,且默认是黑色
  • 所有红节点的子节点都为黑色
  • 从任一节点到其叶子节点的所有路径上都包含相同数目的黑节点

image.png

对平衡树的改进.任意一个节点,他的左右子树的层次最多不超过一倍

AVL树(平衡二叉树)

平衡二叉树是一种二叉排序树,其中每一个节点的左子数和右子数的高度差不多等于1

image.png

平衡因子

二叉树上节点的左子树深度减去右子树深度的值称为平衡因子BF(Balance Factor)