前言:有些概念经常容易忘,有些题目过段时间又不会写。
数据结构概念类
已知完全二叉树的结点数,求叶子节点数
这个要明确:总结点数为奇数,则n1为0,为偶数,则n1为1
其次:顶点个数关系与边的关系构成两条约束
n = n0 + n1 + n2
n-1 = 2n2 + n1
编程算法类
排序-任取数组中的一个数,将它放置在数组的最后一个位置,是整体有序
注:总个数-不需要移动的操作数
// "19 5 9 255"
public static int sort (String inData) {
// write code here
String[] s = inData.split(" ");
int[] arr = new int[s.length];
for (int i = 0; i < s.length; i++) {
arr[i] = Integer.parseInt(s[i]);
}
int[] sortArr = Arrays.copyOf(arr, arr.length);
Arrays.sort(sortArr);
int count = 0; // count表示不需要移位操作数
for (int i = 0; i < arr.length; i++) {
if(sortArr[count] == arr[i]){
count++;
}
}
return arr.length - count;
}
10. 正则表达式匹配
10. 正则表达式匹配
注:隔段时间又不会了,现在递归都写不来了
class Solution {
public boolean isMatch(String s, String p) {
int sLen = s.length();
int pLen = p.length();
boolean[][] dp = new boolean[sLen+1][pLen+1]; // dp[i][j]表示s的前i个字符与p的前j个字符的匹配结果
dp[0][0] = true; // 两个空字符串认为匹配成功
for(int i = 0; i <= sLen; i++){
for(int j = 1; j <= pLen; j++){
if(p.charAt(j-1) == '*'){
// 这里默认*不会出现在第一个字符,即走到这j>=2
dp[i][j] = dp[i][j-2]; // 不匹配
if(matches(s, p, i, j-1)){
dp[i][j] = dp[i][j] || dp[i-1][j]; // 模糊匹配一次,为什么不需要模糊匹配多次,因为dp涵盖了前面多次匹配的情况
}
}else{
if(matches(s, p, i, j)){
dp[i][j] = dp[i-1][j-1]; // 匹配一个字符
}
}
//System.out.println(i + " " + Arrays.toString(dp[i]));
}
}
return dp[sLen][pLen];
}
public boolean matches(String s, String p, int i, int j) {
if(i == 0){
return false; // 无法匹配相等
}
if(p.charAt(j-1) == '.'){
return true; // 模糊匹配单个字符
}
return s.charAt(i-1) == p.charAt(j-1); // 比较s第i个字符和p第j个字符是否相等
}
}
多线程
注:还是存在些问题
三个线程循环的按顺序打印
public class Main {
private static final int max = 72; // 计数到75就退出
private static volatile int pos = 1; // 计数标志
public static void main(String[] args) {
final int count = 5; // 单线程中循环次数
String[] abc = {"1", "2", "3"};
for (int i = 0; i < abc.length; i++) {
final String current = abc[i];
final String next = abc[(i + 1) % abc.length];
new Thread(new Runnable() {
public void run() {
// 注:若循环打印几轮,则无需while(true),只需要将下面for提到外面即可
while (true) {
synchronized (current) {
// 等待信号
try {
current.wait();
} catch (InterruptedException e) {
}
// 具体执行逻辑
for (int j = 0; j < count; j++) {
System.out.println("thread" + current + ":" + pos++);
// 退出标志
if(pos > max)
return;
//System.out.print(current);
}
// 给下个线程发信号
synchronized (next) {
next.notify();
}
}
}
}
}).start();
}
// 主线程给第一个线程发信号,线程因某个对象到等待池中,又可以用这个对象来唤醒这个线程
synchronized (abc[0]) {
abc[0].notify();
}
}
}
剑指 Offer 43. 1~n整数中1出现的次数
力扣-剑指 Offer 43. 1~n整数中1出现的次数
leetcode题解,两道掌握
private static int focusOnBit(int n) {
// 总结每一位上的为0、或1或其他
if (n <= 0)
return 0;
int count = 0;
int factor = 1;
while (n / factor != 0) {
int low = n % factor;
int cur = (n/factor)%10;
int high = n/(factor*10);
if(cur == 0){
count += high *factor;
}else if(cur == 1){
count += high * factor + low + 1;
}else{
count += (high+1)*factor;
}
factor *= 10;
}
return count;
}
public static int fuck(int n) {
// 关心高位为1或其他(大于1)
if (n <= 0) {
return 0;
}
String str = String.valueOf(n);
int high = str.charAt(0) - '0';
int pow = (int) Math.pow(10, str.length() - 1);
int last = n - high * pow;
if (high == 1) {
return fuck(pow - 1) + last + 1 + fuck(last);
} else {
return high * fuck(pow - 1) + pow + fuck(last);
}
}
将数字按中文读法翻译
public class Main {
public static String convertChinese(long n) {
if (n <= 0)
return "零";
String[] numArr = {"零", "一", "二", "三", "四", "五", "六", "七", "八", "九"};
String[] unit = {"", "十", "百", "千"};
String[] bigUnit = {"万", "亿", "兆", "京"};
String str = Long.toString(n);
StringBuilder sb = new StringBuilder();
int len = str.length();
int j = (len-1) % 4; // 判断是当前数字在千百十个哪个位上
int zeroCnt = 0;// 辅助判断是否输出零
for (int i = 0; i < len; i++) {
j = (j+4) % 4; // 因为这里采用递减,所以取模先加个4
int num = Integer.parseInt(str.charAt(i) + "");
if (num != 0) {
sb.append(numArr[num]);
sb.append(unit[j]);// 只有非零才加上千百十
} else if(num == 0) {
// 若到当前位置已经出现一个或多个零,且最后一个出现零的位置不在个位上,则读一个零
zeroCnt++;
if(str.charAt(i+1) != '0' && zeroCnt > 0 && j != 0){
sb.append(numArr[0]);
zeroCnt = 0;
}
}
if (len - i == 5) {
sb.append(bigUnit[0]);
}
if (len - i == 9) {
sb.append(bigUnit[1]);
}
if (len - i == 13) {
sb.append(bigUnit[2]);
}
if (len - i == 17) {
sb.append(bigUnit[3]);
}
j--;
}
return sb.toString();
}
public static void main(String[] args) {
//long n = 100 0870 7985 6203 0506L;
// 一百京零八百七十兆七千九百八十五亿六千二百零三万零五百零六
long n = 1000870798562030506L;
String s = convertChinese(n);
System.out.println(s);
}
}
10. 正则表达式匹配
class Solution {
public boolean isMatch(String s, String p) {
boolean[][] dp = new boolean[s.length()+1][p.length()+1]; // dp[i][j]表示s的前i个字符与p的前j个字符能否匹配
dp[0][0] = true;
for(int i = 0; i <= s.length(); i++){
for(int j = 1; j <= p.length(); j++){
if(p.charAt(j-1) == '*'){ // 判断p的第j个字符是否为*
dp[i][j] = dp[i][j-2]; // 先匹配0次
if(match(s, p, i, j-1)){
// 尝试匹配1、或n次
dp[i][j] = dp[i][j] || dp[i-1][j-2] || dp[i-1][j];
}
}else if( match(s, p, i, j)){
dp[i][j] = dp[i-1][j-1];
}
}
}
return dp[s.length()][p.length()];
}
// 判断p的第j个字符是否为.或者与s的第i个字符相同
private boolean match(String s, String p, int i, int j){
if(i < 1)
return false;
if(p.charAt(j-1) == '.')
return true;
return s.charAt(i-1) == p.charAt(j-1);
}
}
// 有限状态机真精妙,不做掌握
class Solution {
public boolean isMatch(String s, String p) {
// S串当前下标位置,刚开始为0
int index = 0;
// 下一个状态集合
// 这里状态用p串的下标表示,代表下一次可以从p的这些下标开始匹配
Set<Integer> nextState = new HashSet<>();
// 由于是刚开始匹配,这时候p下一个下标是0
nextMatch(p, 0, nextState);
// nextState不为空时,表示还有合法的下一个状态,匹配继续
while (!nextState.isEmpty()){
// 当前状态就是上一次的nextState
Set<Integer> nowState = nextState;
// 创建新的nextState
nextState = new HashSet<>();
// 测试s[index]和集合里的状态是否有匹配
for (int state : nowState){
// 如果同时到达s和p串末尾,匹配成功
if (state >= p.length() && index >= s.length()){
return true;
}
// 仅仅p到达末尾还不行
else if (state >= p.length()){
continue;
}
// s和p都未到达末尾
else if (index < s.length()){
// 这里是匹配上的情况
if (p.charAt(state) == '.' || s.charAt(index) == p.charAt(state)){
// 如果p串的下一个字符是'*',当前状态可以匹配任意多次,所以下一个状态还是当前
if (state+1 < p.length() && p.charAt(state+1) == '*'){
nextMatch(p, state, nextState);
}
// 否则,下一个状态就是state+1
else {
nextMatch(p, state+1, nextState);
}
}
}
}
index++;
}
// 此时,nextState为空,代表没有合法的下一个状态了,匹配失败
return false;
}
// p:正则表达式
// state:下一个状态
// nextState:下一个状态集合,无重复
private void nextMatch(String p, int state, Set<Integer> nextState){
// 首先加上下一个状态到状态集中
nextState.add(state);
// 这里是判断下一个字符是'*'的情况,由于此时匹配次数可以是0,所以state+2也是合法的下一个状态
if (state+1 < p.length() && p.charAt(state+1) == '*'){
nextMatch(p, state+2, nextState);
}
}
}
54. 螺旋矩阵
注:此题两种方法必须掌握,左上和右下两个点限制矩形范围
// 最初循环思路
class Solution {
public List<Integer> spiralOrder(int[][] matrix) {
ArrayList<Integer> list = new ArrayList<>();
if (matrix == null || matrix.length == 0 || matrix[0].length == 0) {
return list;
}
int top_x = 0, top_y = 0;
int bottom_x = matrix.length-1, bottom_y = matrix[0].length-1;
while (top_x <= bottom_x && top_y <= bottom_y) {
// 向右走
for (int j = top_y; j <= bottom_y; j++) {
list.add(matrix[top_x][j]);
}
// 往下走
for (int i = top_x + 1; i <= bottom_x; i++) {
list.add(matrix[i][bottom_y]);
}
if (top_x < bottom_x) {
// 往左走
for (int j = bottom_y - 1; j >= top_y; j--) {
list.add(matrix[bottom_x][j]);
}
}
if (top_y < bottom_y) {
// 往上走
for (int i = bottom_x - 1; i > top_x; i--) {
list.add(matrix[i][top_y]);
}
}
top_x++;
top_y++;
bottom_x--;
bottom_y--;
}
return list;
}
}
// 访问方向数组,一个for循环
class Solution {
public List<Integer> spiralOrder(int[][] matrix) {
List<Integer> order = new ArrayList<Integer>();
if (matrix == null || matrix.length == 0 || matrix[0].length == 0) {
return order;
}
int rows = matrix.length, cols = matrix[0].length;
boolean[][] visited = new boolean[rows][cols];
int total = rows * cols; // 总个数
int row = 0, col = 0; // 起始位置
int[][] directions = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}}; // 方向数组,4个方向
int directionIndex = 0;
for (int i = 0; i < total; i++) {
order.add(matrix[row][col]); // 添加值
visited[row][col] = true; // 访问
int nextRow = row + directions[directionIndex][0]; // 下一行
int nextCol = col + directions[directionIndex][1]; // 下一列
if (nextRow < 0 || nextRow >= rows || nextCol < 0 || nextCol >= cols
|| visited[nextRow][nextCol]) {
directionIndex = (directionIndex + 1) % 4; // 超界或已访问过就转换方向
}
row += directions[directionIndex][0]; // 下一个位置
col += directions[directionIndex][1];
}
return order;
}
}
根据前序中序或者中序后序构建二叉树
import java.util.*;
class TreeNode {
char val;
TreeNode left;
TreeNode right;
TreeNode(char val) {
this.val = val;
}
}
public class Main {
public static void main(String[] args) throws IOException {
Scanner sc = new Scanner(System.in);
while (sc.hasNext()) {
String inorder = sc.next();
String postOrder = sc.next();
int len = inorder.length() - 1;
System.out.println("*****************");
TreeNode root = constructByInPost(inorder, postOrder, 0, len, 0, len);
StringBuilder ans = new StringBuilder();
printByPre(root, ans);
String preOrder = ans.toString();
System.out.println(preOrder);
System.out.println("*****************");
TreeNode treeNode = constructByPreIn(preOrder, inorder, 0, len, 0, len);
ans = new StringBuilder();
printByPost(treeNode, ans);
System.out.println(ans.toString());
}
}
private static void printByPre(TreeNode node, StringBuilder ans) {
if (node == null)
return;
ans.append(node.val);
// ans += node.val; 血泪教训,以后不能使用这个,传不回来值
printByPre(node.left, ans);
printByPre(node.right, ans);
}
private static void printByPost(TreeNode node, StringBuilder ans) {
if (node == null)
return;
printByPost(node.left, ans);
printByPost(node.right, ans);
ans.append(node.val);
}
// bdac dbca
// adbc
// 根据中序和后序构建二叉树,打印先序
private static TreeNode constructByInPost(String inorder, String postOrder, int l1, int r1, int l2, int r2) {
if (l1 > r1 || l2 > r2)
return null;
TreeNode root = new TreeNode(postOrder.charAt(r2));
int mid = l1;
while (root.val != inorder.charAt(mid) && mid <= r1) {
mid++;
}
int count = mid - l1; //这个要记住,移动距离
root.left = constructByInPost(inorder, postOrder, l1, mid - 1, l2, l2 + count - 1);
root.right = constructByInPost(inorder, postOrder, mid + 1, r1, l2 + count, r2 - 1);
return root;
}
// adbc bdac
// dbca
// 根据前序和中序构建二叉树,打印后序
private static TreeNode constructByPreIn(String preOrder, String inOrder, int l1, int r1, int l2, int r2) {
if (l1 > r1 || l2 > r2)
return null;
TreeNode root = new TreeNode(preOrder.charAt(l1));
int mid = l2;
while (root.val != inOrder.charAt(mid) && mid <= r2) {
mid++;
}
int count = mid - l2; //这个要记住,移动距离
root.left = constructByPreIn(preOrder, inOrder, l1 + 1, l1 + count, l2, mid - 1);
root.right = constructByPreIn(preOrder, inOrder, l1 + count + 1, r1, mid + 1, r2);
return root;
}
}
402. 移掉K位数字-从数字串中移除K位数字使剩余数字最小
注:没办法,笔试碰到两次了,第一次从字符串中找到降序位置删除不能ac,第二次哎。
leetcode-402. 移掉K位数字
详解如何从数字串中移除K位数字使剩余数字最小
注:这种方法较巧妙,但不容易分析
有趣的算法题之移除 k 位数字后使剩下的数字最小
注:这种方法需要数组作为栈,不熟悉可能写起来反而麻烦
综合了以上的方法,下述方法时间上比前两种慢一倍,但直观易懂
注:需要掌握LinkedList作为栈的方式,addLast()、pollLast()、peekLast()
class Solution {
public String removeKdigits(String num, int k) {
if(num == null || k >= num.length()){
return "0";
}
LinkedList<Character> stack = new LinkedList<>();
int rmNum = 0;
for(int index = 0; index < num.length(); index++){
char c = num.charAt(index);
while(!stack.isEmpty() && stack.peekLast() > c && rmNum < k){
stack.pollLast(); // 若当前元素小于栈顶元素,则出栈
rmNum++;
}
stack.addLast(c);
}
while (rmNum < k){
stack.pollLast(); // 若最终移除位数不够,说明后续为升序,则去掉后几位使得满足移除个数要求
rmNum++;
}
StringBuilder sb = new StringBuilder();
while(!stack.isEmpty()){
sb.append(stack.pollFirst());
}
int offset = 0; // 找出前面先导不为0的位置
while(offset < sb.length() && sb.charAt(offset) == '0'){
offset++;
}
String ans = sb.substring(offset); // 取点先导为0的元素
return ans.length() == 0 ? "0" : ans; // 若剩余为空串,则直接返回0,否则返回截取字符串
}
}
框架概念类
slfj和logback
注:
slf4j是java的一个日志门面,定义了日志框架一些通用的api,log4j和logback是具体的日志框架。
这些日志框架可以单独使用,也可以绑定slfj一起使用,
绑定使用,调用slfj的api来输出日志信息,具体使用与底层日志框架无关,但需要底层框架的配置文件
1、slfj :simple logging facade for java 简单日志门面(外观)
把不同的日志系统的实现进行了具体的抽象,提供了统一的日志使用接口
2、sflj 只是一个接口,那么实际使用时,必须要结合具体的日志系统来使用
3、log4j是日志框架,通过配置文件,控制日志输出目的地,格式,级别等
4、logback同样是由log4j的作者设计完成的,拥有更好的特性,用来取代log4j的一个日志框架,是slf4j的原生实现
(即直接实现了slf4j的接口,不需要转换,而log4j并没有直接实现,所以就需要一个适配器)
mysql语法类
哪些语句有table
注:
添加索引Alter table xxx add,主键索引、唯一索引、全文索引均不需要索引名
当然添加也可以采用,CREATE INDEX index_name ON table_name (column_list)
删除索引Alter table drop index xxx,若删除主键索引不需要索引名,直接primary key
当然删除也可以采用, DROP INDEX index_name ON table_name
-- 1.添加PRIMARY KEY(主键索引)
ALTER TABLE `table_name` ADD PRIMARY KEY (`column`) ;
-- 2.添加UNIQUE(唯一索引)
ALTER TABLE `table_name` ADD UNIQUE (`column`);
-- 添加INDEX(普通索引)
ALTER TABLE `table_name` ADD INDEX index_name (`column`);
-- 添加FULLTEXT(全文索引)
ALTER TABLE `table_name` ADD FULLTEXT (`column`);
-- 联合索引
ALTER TABLE `table_name` ADD INDEX index_name (`column1`, `column2`, `column3`);
创建视图create view as select xxx
javaSE概念类
JAVA中4种引用类型
强引用-发生gc不会被回收
弱引用-有用不必须,下次内存溢出回收
软引用-有用不必须,下次gc回收
虚引用-
Throwable\Error\Exception
注:都是类,都实现了Serializable接口,且后两个继承了Throwable
Throwable里面有线程执行堆栈的快照StackTraceElement[],printStackTrace打印堆栈跟踪信息
Java-5个异常关键字-try-catch-finally-throw-throws
ClassNotFoundException是受检异常
ClassNotFoundException是受检查的异常,是Exception的子类。
当jvm尝试加载一个特定的类型,而在ClassPath路径下没有发现这个类的Class文件时,抛出的异常
反射以及获取反射的三种方法
运行中,通过任意类,拿到所有属性和方法;通过任意对象,能够调用它的方法和属性
拿到类对象
通过对象.getClass()
通过全限定类名-Class.forName()
通过类名.class属性
enum类原理
自定义枚举类不能再继承
内部类
注:内部类里面还可以有内部类
四大内部类:
静态内部类-成员内部类-局部内部类-匿名内部类
注:
1、成员内部类和静态内部类的实例化稍微有点区别,多个new
2、局部内部类不能加访问修饰符,有形参要加final
3、匿名内部类
匿名内部类补充:
3.1、匿名内部类必须继承抽象类或实现一个接口
3.2、匿名内部类不能是抽象的,必须实现继承的抽象类或实现的接口的所有的抽象方法
3.2、匿名内部类中不能定义任何静态,如静态成员和静态方法
匿名内部类
理解1:匿名内部类有点像局部内部类,也是在某个大的方法中。若想使用所在方法的形参,参数需要加final
理解2:相当于不用特意去写一个类去实现接口的方法后再实例化,直接在实例化的时候就实现接口的方法。
理解3:当接口作为参数放在方法体中,用new 接口的方式来实例独享
优点:
1、内部类可以访问外部类的数据
2、内部类不为其他外部类所见,封装性
3、克服单继承缺陷
4、匿名内部类方便定义回调??
场景:hashMap里面就有
java值传递
即便传递的是一个引用,也是引用的副本
关键在于,方法不能修改传递给它的任何参数变量的内容
IO模型
BIO-同步阻塞
注:
1、数据的读取、写入必须阻塞在一个线程内等待其完成
2、伪BIO-引入了线程池
NIO-同步非阻塞-select-channel-buffer-基于通道-面向缓冲
AIO-异步非阻塞-基于实践和回调机制
面向对象的六大设计原则
注:开单依里接迪特
开放封闭
单一职责
依赖倒置
里氏替换
接口隔离
迪米特–最少知道原则
创建对象的三种方式
new
反射
反序列化
基本数据类型和包装类的初始化问题
1、从小范围的基本数据类型转化为大范围的基本数据类型可以,
比如long可接int, double可接float以及int甚至long,因为表示范围更大
2、包装类初始化只能是对应的基本数据类型
double t = 3.7f;
Double t1 = 3.7f; // failure
double a = 3;
Double a1 = 3; // failure
double b = 3L;
Double b1 = 3L; // failure
Double c = 3.;
IO模型
区别:同步/异步是从行为角度描述事物的,而阻塞和非阻塞描述的当前事物的状态(等待调用结果时的状态)
同步 :两个任务相互依赖,并且一个任务必须以依赖于另一任务的某种方式执行
异步: 两个任务完全独立的,一方的执行不需要等待另外一方的执行
阻塞: 阻塞就是发起一个请求,调用者一直等待请求结果返回
非阻塞: 非阻塞就是发起一个请求,调用者不用一直等着结果返回,可以先去干其他事情
注:把Java中的BIO、NIO和AIO理解为是Java对操作系统的各种IO模型的封装(linux底层有5种IO模型)
BIO (Blocking I/O)
同步阻塞I/O模式,数据的读取写入必须阻塞在一个线程内等待其完成
后续:伪异步IO–引入线程池,将客户端的socket封装成task任务投递给线程池
NIO(non-blocking IO)
同步非阻塞IO模式,选择器-通道-缓冲区
注:
IO流是阻塞的,NIO流是不阻塞的
IO面向流,NIO面向缓冲区
通道是双向的,可读可写。流是单向的
AIO(asynchronous IO)
异步非阻塞,基于事件和回调机制
IO操作类
1、基于字节操作的 I/O 接口:InputStream 和 OutputStream
2、基于字符操作的 I/O 接口:Writer 和 Reader
3、基于磁盘操作的 I/O 接口:File
4、基于网络操作的 I/O 接口:Socket
枚举类型
枚举类型的基本实现原理:
通过关键字enum,创建枚举类型,在编译后是一个类而且该类继承自java.lang.Enum类,
因此创建枚举类型时不能再继承类,可以实现接口
注:
1、最常用,枚举常量
2、支持switch多条件判断
3、若枚举类中添加自定义方法或构造方法,需先创建枚举实例,且最后一个枚举实例后有;分号。
构造器方法必须私有
4、java.util.EnumSet和java.util.EnumMap是两个枚举集合
5、带抽象方法的枚举
附、枚举类型对象之间的值比较,是可以使用==直接来比较值,不是必须使用equals方法
Java volatile关键字
1、内存模型概念
主存------工作内存
1.1、Java内存模型规定所有的变量都是存在主存当中(类似于前面说的物理内存),
每个线程都有自己的工作内存(类似于前面的高速缓存)
1.2、线程对变量的所有操作都必须在工作内存中进行,而不能直接对主存进行操作,
且每个线程不能访问其他线程的工作内存。
1.3、执行线程必须先在自己的工作线程中对变量所在的缓存行进行赋值操作,
然后再写入主存当中,而不是直接写入主存当中
2、并发编程的三个特性
原子性--
Java内存模型只保证了基本读取和赋值是原子性操作,
如果要实现更大范围操作的原子性,可以通过synchronized和Lock来实现,变量级别的原子性也可以使用原子类
可见性--
对于可见性,Java提供了volatile关键字来保证可见性
当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,
当有其他线程需要读取时,它会去内存中读取新值
普通的共享变量不能保证可见性,因为普通共享变量被修改之后,
什么时候被写入主存是不确定的,当其他线程去读取时,
此时内存中可能还是原来的旧值,因此无法保证可见性
另外,通过synchronized和Lock也能够保证可见性,
synchronized和Lock能保证同一时刻只有一个线程获取锁然后执行同步代码,
并且 在释放锁之前 会将对变量的修改刷新到主存当中。因此可以保证可见性
有序性--
happens-before原则(先行发生原则),保证先天的有序性,即满足此类规则,不会重排
而不满足此类规则,jvm 可以随意的进行重排
举例:
如一个是 初始化操作 + 设置条件, 另一个循环检查条件。
前一个 不满足程序次序规则,但存在语义上的依赖,需要禁止重排
总而言之,言而总之,volatile使得线程的工作内存的变量值变得不可靠,写时强制刷新到主存,读时去主存中取
使用volatile的几个场景:1、状态标记量+循环检测 2、双重校验锁实现单例
顺带回忆下synchronized 关键字和 volatile 关键字的区别
Java—重写与重载的区别
- 重写:2同3限制
- 方法名、参数列表相同。返回值、访问修饰符、抛出异常有限制。
- 返回值限制:
- 1、父类返回空,子类返回空
- 2、父类返回基本数据类型,子类返回同样的基本数据类型
- 3、父类返回引用类型,子类返回的类型 <= 父类
- 访问修饰符:
- 1、父类修饰符为private,不是重写。final不可变更不可行。
- 2、父类修饰符不可为static
- 3、子类访问修饰符要 >= 父类
- 抛出异常:
- 1、子类抛出异常类型 <= 父类
- 2、父类不声明抛出异常,则子类只能抛出运行时异常,不得抛出受检异常
- 重载:1同1不同
- 方法名相同、参数列表不同,前提是一个类中
public class Father {
void say() {
System.out.println("father...");
}
}
class Son extends Father{
@Override
protected void say() throws RuntimeException {
System.out.println();
}
}
class Son1 extends Father{
// 异常反例,不通过
@Override
public void say() throws FileNotFoundException {
System.out.println();
}
}
Java—抽象类和接口区别
1、总体,设计层面,抽象类是对类的抽象,是一种模板。而接口是对行为的抽象,是规范。
2、都不可实例化,但抽象类具有构造函数,抽象类可以有非抽象方法。
3、接口的方法默认访问修饰符public,变量默认public static final
4、抽象类单继承,接口多实现,且接口还可以多继承来实现扩展
线程状态到底是6种还是5种
一文搞懂线程世界级难题——线程状态到底是6种还是5种!!!
Java线程状态中BLOCKED和WAITING有什么区别?
JAVA是6种,新建、运行、阻塞、等待、超时等待(限时等待)、终止
线程池
降低资源消耗
提高响应速度
统一管理
Executors创建线程池对象,主要4个,分别使用什么场景??
Executors 返回线程池对象的弊端如下:
Fixed ThreadPool 和 Single ThreadExecutor :
允许请求的队列长度为 Integer.MAX_VALUE ,可能堆积大量的请求,从而导致OOM。
Cached ThreadPool 和 Scheduled ThreadPool :
允许创建的线程数量为 Integer.MAX_VALUE ,可能会创建大量线程,从而导致OOM。
自定义线程池对象ThreadPoolExecutor,掌握三个重要参数
垃圾回收器
注意:
1、注意上图连线,连线表示配合使用,这个在具体配置中很重要
2、上述大多是新生代+老年代之间连线,但是多了个CMS与serial old的连线,表示CMS后剩余内存不足,会临时采用serial old
新生代:
serial收集器
0、复制
1、串行收集器
2、单线程的、简单高效,缺点是回收时停止其他工作线程
3、应用程序需暂停
parNew收集器
0、复制
1、serial的多线程版本
2、应用程序需暂停
parallel scavenge收集器
0、复制
1、多线程
2、高吞吐量、高效率利用cpu、尽快完成运算、后台交互要求不高
3、吞吐量就是 CPU 中用于运行用户代码的时间与 CPU 总消耗时间的比值
4、应用程序需暂停
老生代:
serial old收集器
0、标记整理
1、serial的老年代版本
2、单线程
3、应用程序需暂停
parallel old收集器
0、标记整理
1、parallel scavenge的老年代版本
2、多线程
3、高吞吐量
4、应用程序需暂停
CMS收集器
0、标记清除
1、并发收集、最短停顿
2、标记清除导致大量内存碎片
补充:CMS 使用的是标记-清除的算法实现的,所以在 gc 的时候回产生大量的内存碎片,当剩余内存不能满足程序运行要求时,系统将会出现 Concurrent Mode Failure,临时 CMS 会采用 Serial Old 回收器进行垃圾清除,此时的性能将会被降低。
整个堆:
G1收集器
0、整体上标记整理、局部复制
1、多线程
2、整个堆
3、高吞吐量、低停顿
给定具体运行场景、条件,合理配置垃圾回收器
///待补充
java具体实践类
获取用键盘输入常用的两种方法
注:非常重要的是,next和nextLine会导致读取字符或字符串问题,不要混合用
比如:
sc.nextInt() 不读取到回车,会把回车留到流里面,所以后面的sc.nextLine()会读取到一个空行,从而导致出现问题!
next()、nextLine()、nextInt()的区别和使用方法
// 方法 1:通过 Scanner
Scanner input = new Scanner(System.in);
String s = input.nextLine();
input.close();
// 方法 2:通过 BufferedReader
BufferedReader input = new BufferedReader(new InputStreamReader(System.in));
String s = input.readLine();