Android学习指南 — Java知识点汇总,为什么说Flutter让移动开发变得更好

for (int i = 0; i < value.length; i++) {

h = 31 * h + val[i];

}

hash = h;

}

return h;

}

static

======

  • static关键字修饰的方法或者变量不需要依赖于对象来进行访问,只要类被加载了,就可以通过类名去进行访问。

  • 静态变量被所有的对象所共享,在内存中只有一个副本,它当且仅当在类初次加载时会被初始化。

  • 能通过 this 访问静态成员变量吗? 所有的静态方法和静态变量都可以通过对象访问(只要访问权限足够)。

  • static是不允许用来修饰局部变量

final

=====

  • 可以声明成员变量、方法、类以及本地变量

  • final 成员变量必须在声明的时候初始化或者在构造器中初始化,否则就会报编译错误

  • final 变量是只读的

  • final 申明的方法不可以被子类的方法重写

  • final 类通常功能是完整的,不能被继承

  • final 变量可以安全的在多线程环境下进行共享,而不需要额外的同步开销

  • final 关键字提高了性能,JVM 和 Java 应用都会缓存 final 变量,会对方法、变量及类进行优化

  • 方法的内部类访问方法中的局部变量,但必须用 final 修饰才能访问

String、StringBuffer、StringBuilder

=================================

  • String 是 final 类,不能被继承。对于已经存在的 Stirng 对象,修改它的值,就是重新创建一个对象

  • StringBuffer 是一个类似于 String 的字符串缓冲区,使用 append() 方法修改 Stringbuffer 的值,使用 toString() 方法转换为字符串,是线程安全的

  • StringBuilder 用来替代于 StringBuffer,StringBuilder 是非线程安全的,速度更快

异常处理

====

  • Exception、Error 是 Throwable 类的子类

  • Error 类对象由 Java 虚拟机生成并抛出,不可捕捉

  • 不管有没有异常,finally 中的代码都会执行

  • 当 try、catch 中有 return 时,finally 中的代码依然会继续执行

| 常见的Error | | |

| — | — | — |

| OutOfMemoryError | StackOverflowError | NoClassDeffoundError |

| 常见的Exception | | |

| — | — | — |

| 常见的非检查性异常 | | |

| ArithmeticException | ArrayIndexOutOfBoundsException | ClassCastException |

| IllegalArgumentException | IndexOutOfBoundsException | NullPointerException |

| NumberFormatException | SecurityException | UnsupportedOperationException |

| 常见的检查性异常 | | |

| IOException | CloneNotSupportedException | IllegalAccessException |

| NoSuchFieldException | NoSuchMethodException | FileNotFoundException |

内部类

===

  • 非静态内部类没法在外部类的静态方法中实例化。

  • 非静态内部类的方法可以直接访问外部类的所有数据,包括私有的数据。

  • 在静态内部类中调用外部类成员,成员也要求用 static 修饰。

  • 创建静态内部类的对象可以直接通过外部类调用静态内部类的构造器;创建非静态的内部类的对象必须先创建外部类的对象,通过外部类的对象调用内部类的构造器。

匿名内部类


  • 匿名内部类不能定义任何静态成员、方法

  • 匿名内部类中的方法不能是抽象的

  • 匿名内部类必须实现接口或抽象父类的所有抽象方法

  • 匿名内部类不能定义构造器

  • 匿名内部类访问的外部类成员变量或成员方法必须用 final 修饰

多态

==

  • 父类的引用可以指向子类的对象

  • 创建子类对象时,调用的方法为子类重写的方法或者继承的方法

  • 如果我们在子类中编写一个独有的方法,此时就不能通过父类的引用创建的子类对象来调用该方法

抽象和接口

=====

  • 抽象类不能有对象(不能用 new 关键字来创建抽象类的对象)

  • 抽象类中的抽象方法必须在子类中被重写

  • 接口中的所有属性默认为:public static final;

  • 接口中的所有方法默认为:public abstract;

集合框架

====

  • List接口存储一组不唯一,有序(插入顺序)的对象, Set接口存储一组唯一,无序的对象。

HashMap


结构图

  • JDK 1.7 HashMap 结构图

  • JDK 1.8 HashMap 结构图

HashMap 的工作原理

HashMap 基于 hashing 原理,我们通过 put() 和 get() 方法储存和获取对象。当我们将键值对传递给 put() 方法时,它调用键对象的 hashCode() 方法来计算 hashcode,让后找到 bucket 位置来储存 Entry 对象。当两个对象的 hashcode 相同时,它们的 bucket 位置相同,‘碰撞’会发生。因为 HashMap 使用链表存储对象,这个 Entry 会存储在链表中,当获取对象时,通过键对象的 equals() 方法找到正确的键值对,然后返回值对象。

如果 HashMap 的大小超过了负载因子(load factor)定义的容量,怎么办?

默认的负载因子大小为 0.75,也就是说,当一个 map 填满了 75% 的 bucket 时候,和其它集合类(如 ArrayList 等)一样,将会创建原来 HashMap 大小的两倍的 bucket 数组,来重新调整 map 的大小,并将原来的对象放入新的 bucket 数组中。这个过程叫作 rehashing,因为它调用 hash 方法找到新的 bucket 位置。

为什么 String, Interger 这样的 wrapper 类适合作为键?

因为 String 是不可变的,也是 final 的,而且已经重写了 equals() 和 hashCode() 方法了。其他的 wrapper 类也有这个特点。不可变性是必要的,因为为了要计算 hashCode(),就要防止键值改变,如果键值在放入时和获取时返回不同的 hashcode 的话,那么就不能从 HashMap 中找到你想要的对象。不可变性还有其他的优点如线程安全。如果你可以仅仅通过将某个 field 声明成 final 就能保证 hashCode 是不变的,那么请这么做吧。因为获取对象的时候要用到 equals() 和 hashCode() 方法,那么键对象正确的重写这两个方法是非常重要的。如果两个不相等的对象返回不同的 hashcode 的话,那么碰撞的几率就会小些,这样就能提高 HashMap 的性能。

HashMap 与 HashTable 对比

HashMap 是非 synchronized 的,性能更好,HashMap 可以接受为 null 的 key-value,而 Hashtable 是线程安全的,比 HashMap 要慢,不接受 null 的 key-value。

HashMap.java

public class HashMap<K,V> extends AbstractMap<K,V>

implements Map<K,V>, Cloneable, Serializable {

···

public V put(K key, V value) {

return putVal(hash(key), key, value, false, true);

}

···

public V get(Object key) {

Node<K,V> e;

return (e = getNode(hash(key), key)) == null ? null : e.value;

}

···

}

HashTable.java

public class Hashtable<K,V>

extends Dictionary<K,V>

implements Map<K,V>, Cloneable, java.io.Serializable {

···

public synchronized V put(K key, V value) {

// Make sure the value is not null

if (value == null) {

throw new NullPointerException();

}

···

addEntry(hash, key, value, index);

return null;

}

···

public synchronized V get(Object key) {

HashtableEntry<?,?> tab[] = table;

int hash = key.hashCode();

int index = (hash & 0x7FFFFFFF) % tab.length;

for (HashtableEntry<?,?> e = tab[index] ; e != null ; e = e.next) {

if ((e.hash == hash) && e.key.equals(key)) {

return (V)e.value;

}

}

return null;

}

···

}

ConcurrentHashMap


Base 1.7

ConcurrentHashMap 最外层不是一个大的数组,而是一个 Segment 的数组。每个 Segment 包含一个与 HashMap 数据结构差不多的链表数组。

正在上传…重新上传取消​

在读写某个 Key 时,先取该 Key 的哈希值。并将哈希值的高 N 位对 Segment 个数取模从而得到该 Key 应该属于哪个Segment,接着如同操作 HashMap 一样操作这个 Segment。

Segment 继承自 ReentrantLock,可以很方便的对每一个 Segmen 上锁。

对于读操作,获取 Key 所在的 Segment 时,需要保证可见性。具体实现上可以使用volatile关键字,也可使用锁。但使用锁开销太大,而使用volatile时每次写操作都会让所有CPU内缓存无效,也有一定开销。ConcurrentHashMap 使用如下方法保证可见性,取得最新的Segment:

Segment<K,V> s = (Segment<K,V>)UNSAFE.getObjectVolatile(segments, u)

获取 Segment 中的 HashEntry 时也使用了类似方法:

HashEntry<K,V> e = (HashEntry<K,V>) UNSAFE.getObjectVolatile

(tab, ((long)(((tab.length - 1) & h)) << TSHIFT) + TBASE)

对于写操作,并不要求同时获取所有 Segment 的锁,因为那样相当于锁住了整个Map。它会先获取该 Key-Value 对所在的 Segment 的锁,获取成功后就可以像操作一个普通的 HashMap 一样操作该 Segment,并保证该 Segment 的安全性。同时由于其它 Segment 的锁并未被获取,因此理论上可支持 concurrencyLevel(等于Segment的个数)个线程安全的并发读写。

获取锁时,并不直接使用 lock 来获取,因为该方法获取锁失败时会挂起。事实上,它使用了自旋锁,如果 tryLock 获取锁失败,说明锁被其它线程占用,此时通过循环再次以 tryLock 的方式申请锁。如果在循环过程中该 Key 所对应的链表头被修改,则重置 retry 次数。如果 retry 次数超过一定值,则使用 lock 方法申请锁。

这里使用自旋锁是因为自旋锁的效率比较高,但是它消耗 CPU 资源比较多,因此在自旋次数超过阈值时切换为互斥锁。

Base 1.8

1.7 已经解决了并发问题,并且能支持 N 个 Segment 这么多次数的并发,但依然存在 HashMap 在 1.7 版本中的问题:查询遍历链表效率太低。因此 1.8 做了一些数据结构上的调整。

其中抛弃了原有的 Segment 分段锁,而采用了 CAS + synchronized 来保证并发安全性。

ConcurrentHashMap.java

final V putVal(K key, V value, boolean onlyIfAbsent) {

if (key == null || value == null) throw new NullPointerException();

int hash = spread(key.hashCode());

int binCount = 0;

for (Node<K,V>[] tab = table;😉 {

Node<K,V> f; int n, i, fh;

if (tab == null || (n = tab.length) == 0)

tab = initTable();

else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {

if (casTabAt(tab, i, null,

new Node<K,V>(hash, key, value, null)))

break; // no lock when adding to empty bin

}

else if ((fh = f.hash) == MOVED)

tab = helpTransfer(tab, f);

else {

V oldVal = null;

synchronized (f) {

if (tabAt(tab, i) == f) {

if (fh >= 0) {

binCount = 1;

···

}

else if (f instanceof TreeBin) {

···

}

else if (f instanceof ReservationNode)

throw new IllegalStateException(“Recursive update”);

}

}

···

}

addCount(1L, binCount);

return null;

}

ArrayList


ArrayList 本质上是一个动态数组,第一次添加元素时,数组大小将变化为 DEFAULT_CAPACITY 10,不断添加元素后,会进行扩容。删除元素时,会按照位置关系把数组元素整体(复制)移动一遍。 ArrayList.java

public class ArrayList extends AbstractList

implements List, RandomAccess, Cloneable, java.io.Serializable

···

// 增加元素

public boolean add(E e) {

ensureCapacityInternal(size + 1); // Increments modCount!!

elementData[size++] = e;

return true;

}

···

// 删除元素

public E remove(int index) {

if (index >= size)

throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

modCount++;

E oldValue = (E) elementData[index];

int numMoved = size - index - 1;

if (numMoved > 0)

System.arraycopy(elementData, index+1, elementData, index,

numMoved);

elementData[–size] = null; // clear to let GC do its work

return oldValue;

}

···

// 查找元素

public E get(int index) {

if (index >= size)

throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

return (E) elementData[index];

}

···

}

LinkedList


LinkedList 本质上是一个双向链表的存储结构。

LinkedList.java

public class LinkedList

extends AbstractSequentialList

implements List, Deque, Cloneable, java.io.Serializable

{

····

private static class Node {

E item;

Node next;

Node prev;

Node(Node prev, E element, Node next) {

this.item = element;

this.next = next;

this.prev = prev;

}

}

···

// 增加元素

void linkLast(E e) {

final Node l = last;

final Node newNode = new Node<>(l, e, null);

last = newNode;

if (l == null)

first = newNode;

else

l.next = newNode;

size++;

modCount++;

}

···

// 删除元素

E unlink(Node x) {

final E element = x.item;

final Node next = x.next;

final Node prev = x.prev;

if (prev == null) {

first = next;

} else {

prev.next = next;

x.prev = null;

}

if (next == null) {

last = prev;

} else {

next.prev = prev;

x.next = null;

}

x.item = null;

size–;

modCount++;

return element;

}

···

// 查找元素

Node node(int index) {

// assert isElementIndex(index);

if (index < (size >> 1)) {

Node x = first;

for (int i = 0; i < index; i++)

x = x.next;

return x;

} else {

Node x = last;

for (int i = size - 1; i > index; i–)

x = x.prev;

return x;

}

}

···

}

对于元素查询来说,ArrayList 优于 LinkedList,因为 LinkedList 要移动指针。对于新增和删除操作,LinedList 比较占优势,因为 ArrayList 要移动数据。

CopyOnWriteArrayList


CopyOnWriteArrayList 是线程安全容器(相对于 ArrayList),增加删除等写操作通过加锁的形式保证数据一致性,通过复制新集合的方式解决遍历迭代的问题。

CopyOnWriteArrayList.java

public class CopyOnWriteArrayList

implements List, RandomAccess, Cloneable, java.io.Serializable {

final transient Object lock = new Object();

···

// 增加元素

public boolean add(E e) {

synchronized (lock) {

Object[] elements = getArray();

int len = elements.length;

Object[] newElements = Arrays.copyOf(elements, len + 1);

newElements[len] = e;

setArray(newElements);

return true;

}

}

···

// 删除元素

public E remove(int index) {

synchronized (lock) {

Object[] elements = getArray();

int len = elements.length;

E oldValue = get(elements, index);

int numMoved = len - index - 1;

if (numMoved == 0)

setArray(Arrays.copyOf(elements, len - 1));

else {

Object[] newElements = new Object[len - 1];

System.arraycopy(elements, 0, newElements, 0, index);

System.arraycopy(elements, index + 1, newElements, index,

numMoved);

setArray(newElements);

}

return oldValue;

}

}

···

// 查找元素

private E get(Object[] a, int index) {

return (E) a[index];

}

}

反射

==

try {

Class cls = Class.forName(“com.jasonwu.Test”);

//获取构造方法

Constructor[] publicConstructors = cls.getConstructors();

//获取全部构造方法

Constructor[] declaredConstructors = cls.getDeclaredConstructors();

//获取公开方法

Method[] methods = cls.getMethods();

//获取全部方法

Method[] declaredMethods = cls.getDeclaredMethods();

//获取公开属性

Field[] publicFields = cls.getFields();

//获取全部属性

Field[] declaredFields = cls.getDeclaredFields();

Object clsObject = cls.newInstance();

Method method = cls.getDeclaredMethod(“getModule1Functionality”);

Object object = method.invoke(null);

} catch (ClassNotFoundException e) {

e.printStackTrace();

} catch (IllegalAccessException e) {

e.printStackTrace();

} catch (InstantiationException e) {

e.printStackTrace();

} catch (NoSuchMethodException e) {

e.printStackTrace();

} catch (InvocationTargetException e) {

e.printStackTrace();

}

单例

==

饿汉式


public class CustomManager {

private Context mContext;

private static final Object mLock = new Object();

private static CustomManager mInstance;

public static CustomManager getInstance(Context context) {

synchronized (mLock) {

if (mInstance == null) {

mInstance = new CustomManager(context);

}

return mInstance;

}

}

private CustomManager(Context context) {

this.mContext = context.getApplicationContext();

}

}

双重检查模式


public class CustomManager {

private Context mContext;

private volatile static CustomManager mInstance;

public static CustomManager getInstance(Context context) {

// 避免非必要加锁

if (mInstance == null) {

synchronized (CustomManger.class) {

if (mInstance == null) {

mInstacne = new CustomManager(context);

}

}

}

return mInstacne;

}

private CustomManager(Context context) {

this.mContext = context.getApplicationContext();

}

}

静态内部类模式


public class CustomManager{

private CustomManager(){}

private static class CustomManagerHolder {

private static final CustomManager INSTANCE = new CustomManager();

}

public static CustomManager getInstance() {

return CustomManagerHolder.INSTANCE;

}

}

静态内部类的原理是: 当 SingleTon 第一次被加载时,并不需要去加载 SingleTonHoler,只有当 getInstance() 方法第一次被调用时,才会去初始化 INSTANCE,这种方法不仅能确保线程安全,也能保证单例的唯一性,同时也延迟了单例的实例化。getInstance 方法并没有多次去 new 对象,取的都是同一个 INSTANCE 对象。

虚拟机会保证一个类的 <clinit>() 方法在多线程环境中被正确地加锁、同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的 <clinit>() 方法,其他线程都需要阻塞等待,直到活动线程执行 <clinit>() 方法完毕

缺点在于无法传递参数,如Context等

线程

==

线程是进程中可独立执行的最小单位,也是 CPU 资源(时间片)分配的基本单位。同一个进程中的线程可以共享进程中的资源,如内存空间和文件句柄。

属性

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
img

资源分享

  • 最新大厂面试专题

这个题库内容是比较多的,除了一些流行的热门技术面试题,如Kotlin,数据库,Java虚拟机面试题,数组,Framework ,混合跨平台开发,等

  • 对应导图的Android高级工程师进阶系统学习视频
    最近热门的,NDK,热修复,MVVM,源码等一系列系统学习视频都有!

资源(时间片)分配的基本单位。同一个进程中的线程可以共享进程中的资源,如内存空间和文件句柄。

属性

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-Ko7OWF0j-1711736075126)]
[外链图片转存中…(img-xcD3qcD3-1711736075126)]
[外链图片转存中…(img-gSyPFRw3-1711736075127)]
[外链图片转存中…(img-Uu7tWgNi-1711736075127)]
[外链图片转存中…(img-aWHzsEXn-1711736075127)]
[外链图片转存中…(img-SZKSmOCu-1711736075128)]
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
[外链图片转存中…(img-Le8xrqed-1711736075128)]

资源分享

  • 最新大厂面试专题

这个题库内容是比较多的,除了一些流行的热门技术面试题,如Kotlin,数据库,Java虚拟机面试题,数组,Framework ,混合跨平台开发,等

[外链图片转存中…(img-rdyTipjW-1711736075128)]

  • 对应导图的Android高级工程师进阶系统学习视频
    最近热门的,NDK,热修复,MVVM,源码等一系列系统学习视频都有!

[外链图片转存中…(img-s0t2YXUo-1711736075129)]

本文已被CODING开源项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》收录

  • 9
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值