四:查找链表的倒数第 K 个节点
为了能够只遍历一次就能找到倒数第k个节点,可以定义两个指针:
(1)第一个指针从链表的头指针开始遍历向前走k-1,第二个指针保持不动;
(2)从第k步开始,第二个指针也开始从链表的头指针开始遍历;
(3)由于两个指针的距离保持在k-1,当第一个(走在前面的)指针到达链表的尾结点时,第二个指针(走 在后面的)指针正好是倒数第k个结点。
注意:剑指 offer 中有提到,有提到,有提到,,,(ps:忘了)
六:进程在内存中是如何分区的
进程内存区域:
一:代码区:代码块是用来存放可执行文件的操作指令(存放函数的二进制代码),也就是说它是可执行程序在内存中的镜像。代码段需要防止在运行时被非法修改,所以只准许读取操作,而不允许写入(修改操作)----它是不可写的。
二:全局(静态)区包含下面两个区:
(1)数据区:数据段用来存放可执行文件中已经初始化全局变量,换句话说就是存放程序静态分配的变量和全局变量。
(2)BSS区:BSS段包含了程序中未初始化全局变量。
三:常量区:常量存储区,这是一块比较特殊的区域,存放的是常量。
四:堆区:堆是由程序员分配和释放,用于存放进程运行中被动态分配的内存段,它大小并不固定,可以动态扩张或缩减。当进程调用 alloc 等函数分配内存时,新分配的内存就被动态添加到堆上;当利用release释放内存的时候,被释放的内存从堆中被剔除。
五:栈区:由编译器自动分配并释放,用户存放程序临时创建的局部变量,存放函数的参数值,局部变量等。也就是说我们函数括弧“{ }”中定义的变量(不包括static声明的变量)。除此意外在函数被调用的时候,其参数也会被压入发起调用的进程栈中,并且待到调用结束后,函数的返回值也会被存放回栈中。由于栈的先进后出特点,所以栈特别方便用来保存。是一个临时数据寄存,交换的内存区。
九:HashMap 实现原理,hash值是如何映射到数组下标的?
HashMap基于哈希表的 Map 接口的实现。此实现提供所有可选的映射操作,并允许使用 null 值和 null 键。(除了非同步和允许使用 null 之外,HashMap 类与 Hashtable 大致相同。)此类不保证映射的顺序,特别是它不保证该顺序恒久不变。
既然是一个数组,就有下标,HashMap的数组下标是如何实现的呢,源码是这么写的:
// hash码函数
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
// table下标计算
n = tab.length;
Node p = tab[i = (n - 1) & hash];
即数组的下标 index 是 (length-1) 与 key 的hash值的 & (与) 运算。
十一:handle如果我想取消发送的消息怎么办?
tips: 只有延时消息才可以被取消,即时消息无法被取消。
handler发消息方式:
发送即时消息:
Message msg = new Message();
msg.what = 100;
handler.sendMessage(msg);
延时5秒发送消息:
Message msg = new Message();
msg.what = 100;
handler.sendMessageDelayed(msg, 5000);
当执行handler.sengMessageDelayed后,需要取消消息的发送,可以执行handler.removeMessages(100).
removeMessages会将handler对应message queue里的消息清空,如果带了int参数则是对应的消息清空。队列里面没有消息则handler会不工作,但不表示handler会停止。当队列中有新的消息进来以后handler还是会处理。
十三:如何保证数据在多线程下的访问安全?
解决办法:(1)同步代码块
(2)同步方法
(3)Lock 对象锁(本身是一个接口,使用其子类)
public class MoneyRunnableImp implements Runnable {
private int sumMoney = 1000;
@Override
public void run() {
while (true) {
/**
* 同步代码块实现数据安全:
*
* 这里面的this就是一把锁,使用这个类创建的线程使用同一把锁
*
*/
synchronized (this) {
if (sumMoney > 0) {
/**
* sumMoney = sumMoney - 1; 放在前面就不会有问题
*/
// sumMoney = sumMoney - 1;
System.out.println(Thread.currentThread().getName() + "获得一元钱");
if (Thread.currentThread().getName().equals("张三")) {
Data.zsMoney++;
} else if (Thread.currentThread().getName().equals("李四")) {
Data.lsMoney++;
} else {
Data.wwMoney++;
}
/**
* sumMoney = sumMoney - 1; 放在后面就会出现数据安全问题(线程安全问题)
*/
sumMoney = sumMoney - 1;
} else {
System.out.println("钱分完了:");
System.out.println("张三获得了:" + Data.zsMoney);
System.out.println("李四获得了:" + Data.lsMoney);
System.out.println("王五获得了:" + Data.wwMoney);
System.out.println("他们一共获得了:" + (Data.zsMoney + Data.wwMoney + Data.lsMoney));
try {
// 防止数据刷的过快,休眠一段时间
Thread.sleep(4000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
}
就是在进入同步代码块之前,必须要拿到this这个锁,当时当其他线程正常执行时,即便丢失cpu执行权,也不释放this这个锁,所以,其他线程无法执行,必须等待该线程执行完同步代码块,把锁释放了,其他的线程才可以拿着这个锁进入同步代码块。
问题来了上述代码中synchronized中的同步锁是谁?
对于非static方法同步锁就是this
对于static方法,我们使用当前方法所在类的字节码对象(当前类名.class)
tips : tips :
Java中的对象锁和类锁:java的对象锁和类锁在锁的概念上基本上和内置锁是一致的,但是,两个锁实际是有很大的区别的,对象锁是用于对象实例方法,或者一个对象实例上的,类锁是用于类的静态方法或者一个类的class对象上的。我们知道,类的对象实例可以有很多个,但是每个类只有一个class对象,所以不同对象实例的对象锁是互不干扰的,但是每个类只有一个类锁。
synchronized 关键字主要有以下几种用法:
- 非静态方法的同步;
- 静态方法的同步;
- 代码块。
对象锁
非静态方法使用 synchronized 修饰的写法,修饰实例方法时,锁定的是当前对象:
public synchronized void test(){
// TODO
}
代码块使用 synchronized 修饰的写法,使用代码块,如果传入的参数是 this,那么锁定的也是当前的对象:
public void test(){
synchronized (this) {
// TODO
}
下面通过例子来说明对象锁:
定义一个类,方法如下,将 count 自减,从 5 到 0:
public class TestSynchronized {
public synchronized void minus() {
int count = 5;
for (int i = 0; i < 5; i++) {
count--;
System.out.println(Thread.currentThread().getName() + " - " + count);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
}
}
}
}
---------------------
测试方法调用如下:
public class Run {
public static void main(String[] args) {
final TestSynchronized test = new TestSynchronized();
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
test.minus();
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
test.minus();
}
});
thread1.start();
thread2.start();
}
}
---------------------
两个线程 thread1 和 thread2,同时访问对象的方法,由于该方法是 synchronized 关键字修饰的,那么这两个线程都需要获得该对象锁,一个获得后另一个线程必须等待。所以我们可以猜测运行结果应该是,一个线程执行完毕后,另一个线程才开始执行,运行例子,输出打印结果如下:
Thread-0 - 4
Thread-0 - 3
Thread-0 - 2
Thread-0 - 1
Thread-0 - 0
Thread-1 - 4
Thread-1 - 3
Thread-1 - 2
Thread-1 - 1
Thread-1 - 0
---------------------
本例对于对象锁进行了基础的解释。但是对象锁的范围是怎样的,对象的某个同步方法被一个线程访问后,其他线程能不能访问该对象的其他同步方法,以及是否可以访问对象的其他非同步方法呢,下面对两种进行验证:
对两个同步方法两个线程的验证:
修改类如下,加入 minus2() 方法,和 minus() 方法一样:
package com.test.run;
public class TestSynchronized {
public synchronized void minus() {
int count = 5;
for (int i = 0; i < 5; i++) {
count--;
System.out.println(Thread.currentThread().getName() + " - " + count);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
}
}
}
public synchronized void minus2() {
int count = 5;
for (int i = 0; i < 5; i++) {
count--;
System.out.println(Thread.currentThread().getName() + " - " + count);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
}
}
}
}
测试调用如下,两个线程访问不同的方法:
public class Run {
public static void main(String[] args) {
final TestSynchronized test = new TestSynchronized();
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
test.minus();
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
test.minus2();
}
});
thread1.start();
thread2.start();
}
}
输出结果如下:
Thread-0 - 4
Thread-0 - 3
Thread-0 - 2
Thread-0 - 1
Thread-0 - 0
Thread-1 - 4
Thread-1 - 3
Thread-1 - 2
Thread-1 - 1
Thread-1 - 0
可以看到,某个线程得到了对象锁之后,该对象的其他同步方法是锁定的,其他线程是无法访问的。
下面看是否能访问非同步方法:
public class TestSynchronized {
public synchronized void minus() {
int count = 5;
for (int i = 0; i < 5; i++) {
count--;
System.out.println(Thread.currentThread().getName() + " - " + count);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
}
}
}
public void minus2() {
int count = 5;
for (int i = 0; i < 5; i++) {
count--;
System.out.println(Thread.currentThread().getName() + " - " + count);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
}
}
}
}
测试调用的类不变:
public class Run2 {
public static void main(String[] args) {
final TestSynchronized test = new TestSynchronized();
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
test.minus();
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
test.minus2();
}
});
thread1.start();
thread2.start();
}
}
运行结果:
Thread-1 - 4
Thread-0 - 4
Thread-1 - 3
Thread-0 - 3
Thread-1 - 2
Thread-0 - 2
Thread-1 - 1
Thread-0 - 1
Thread-1 - 0
Thread-0 - 0
可以看到,结果是交替的,说明线程是交替执行的,说明如果某个线程得到了对象锁,但是另一个线程还是可以访问没有进行同步的方法或者代码。进行了同步的方法(加锁方法)和没有进行同步的方法(普通方法)是互不影响的,一个线程进入了同步方法,得到了对象锁,其他线程还是可以访问那些没有同步的方法(普通方法)。当获取到与对象关联的内置锁时,并不能阻止其他线程访问该对象,当某个线程获得对象的锁之后,只能阻止其他线程获得同一个锁。
类锁
类锁需要 synchronized 来修饰静态 static 方法,写法如下:
public static synchronized void test(){
// TODO
}
或者使用代码块,需要引用当前的类:
public static void test(){
synchronized (TestSynchronized.class) {
// TODO
}
}
举例:
public class TestSynchronized {
public static synchronized void minus() {
int count = 5;
for (int i = 0; i < 5; i++) {
count--;
System.out.println(Thread.currentThread().getName() + " - " + count);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
}
}
}
}
测试类:
public class Run {
public static void main(String[] args) {
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
TestSynchronized.minus();
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
TestSynchronized.minus();
}
});
thread1.start();
thread2.start();
}
}
输出结果:
Thread-0 - 4
Thread-0 - 3
Thread-0 - 2
Thread-0 - 1
Thread-0 - 0
Thread-1 - 4
Thread-1 - 3
Thread-1 - 2
Thread-1 - 1
Thread-1 - 0
可以看到,类锁和对象锁其实是一样的,由于静态方法是类所有对象共用的,所以进行同步后,该静态方法的锁也是所有对象唯一的。每次只能有一个线程来访问对象的该非静态同步方法。
类锁的作用和对象锁类似,但是作用范围是否和对象锁一致呢
public class TestSynchronized {
public static synchronized void minus() {
int count = 5;
for (int i = 0; i < 5; i++) {
count--;
System.out.println(Thread.currentThread().getName() + " - " + count);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
}
}
}
public synchronized void minus2() {
int count = 5;
for (int i = 0; i < 5; i++) {
count--;
System.out.println(Thread.currentThread().getName() + " - " + count);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
}
}
}
}
测试调用如下:
public class Run {
public static void main(String[] args) {
final TestSynchronized test = new TestSynchronized();
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
TestSynchronized.minus();
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
test.minus2();
}
});
thread1.start();
thread2.start();
}
}
运行结果:
Thread-1 - 4
Thread-0 - 4
Thread-0 - 3
Thread-1 - 3
Thread-0 - 2
Thread-1 - 2
Thread-0 - 1
Thread-1 - 1
Thread-1 - 0
Thread-0 - 0
可以看到两个线程是交替进行的,也就是说类锁和对象锁是不一样的锁,是互相独立的
十五:如果两个函数方法名和参数相同只有返回值不同,是重载吗?
重写的原则:
1,重写方法的方法名称,列表参数必须和原方法相同,返回类型可以相同也可以是原类型的子类型(从 Java SE5开始支持)
2,重写方法不能比原方法的访问性差(权限不允许缩小)
3,重写方法不能比原方法抛出更多的异常
4,被重写的方法不能是 final 类型,因为 fianl 修饰的方法是无法被重写的
5,被重写的方法不能为 private,否则在其子类中只是重新定义了一个方法,并没有对其进行重写。
6,被重写的方法不能为 static,如果父类中的方法是静态的,而子类中的方法不是静态的,但是两个方法除了这一点外其他都满足重写条件,就会发生编译错误;反之亦然。即使父类和子类中的方法都是静态的,并且满足重写条件,但是任然不会发生重写,因为静态方法是在编译的时候把静态方法和类的引用类型进行匹配。
7,重写是发生在运行时,因为编译期间编译器不知道并且没办法确定该去调用哪个方法,JVM会在代码运行时做出决定。
方法重载的原则:
1,方法名称必须相同。
2,参数列表必须不同(个数不同或类型不同,参数类型排列顺序不同等)。
3,方法的返回类型可以相同也可以不同。
4,仅仅返回类型不同不足以成为方法的重载
5,重载是发生在编译时,因为编译器可以根据参数的类型来选择使用哪个方法。
重写和重载的不同:
1,方法重写要求参数列表必须一致,而方法重载要求参数列表必须不一致。
2,方法重写要求返回类型必须一致(或为其子类型),方法重载对此没有要求。
3,方法重写只能用于子类重写父类的方法,方法重载用于同一个类中的所有方法。
4,方法重写对方法的访问权限和抛出的异常有特殊的要求,而方法重载在这方面没有任何限制。
5,父类的一个方法只能被子类重写一次,而一个方法可以在所有的类中可以被重载多次。
6,重载是编译时多态,重写是运行时多态。
十六:static 的函数和内部类或者变量什么时候执行?
https://www.cnblogs.com/maohuidong/p/7843807.html
答:static是在类被初始化的时候执行的,tips:静态内部类不持有外部类的引用。
注意:静态内部类和非静态内部类一样,都是在被调用时才会被加载
静态内部类其实和外部类的静态变量,静态方法一样,只要被调用了都会让外部类的被加载。不过当只调用外部类的静态变量,静态方法时,是不会让静态内部类的被加载
为什么静态内部类可以不传入引用呢?
因为其本质就是针对外部类的内部类,而不是对象的内部类,不必用this来调用。
首先,从静态的概念出发理解,静态修饰过后的一切物件都只与类相关,不与对象引用相关
As we known,静态变量,静态方法,静态块等都是类级别的属性,而不是单纯的对象属性。他们在类第一次被使用时被加载(记住,是一次使用,不一定是实例化)。我们可以简单得用 类名.变量 或者 类名.方法来调用它们。与调用没有被static 修饰过变量和方法不同的是:一般变量和方法是用当前对象的引用(即this)来调用的,静态的方法和变量则不需要。从一个角度上来说,它们是共享给所有对象的,不是一个角度私有。这点上,静态内部类也是一样的。
静态内部类的加载过程:
静态内部类的加载不需要依附外部类,在使用时才加载。不过在加载静态内部类的过程中也会加载外部类。