JAVA 面试 尚硅谷Java面试题 定西 2024-07-23 2024-09-09 再找不到工作怎么办
基础篇 i++热身 只要记住i++
和++i
的区别即可,i++
就是先把i的原值进行操作,操作完后进行加,所以输出的是2,然后i的值加了1 *,所以第三行i++
的值还是2,而++i
则表示先给i+1,然后进行操作,所以会先改变i的值 ,当然如果是单独的i++
或者++i
是一样的.
1 2 3 4 5 6 7 8 9 10 11 12 13 public class IPlusPlus { public static void main (String[] args) { int i = 1 ; System.out.println("i: " + i); System.out.println("++i: " + ++i); System.out.println("i++: " + i++); System.out.println("i: " + i); System.out.println("--i: " + --i); System.out.println("i--: " + i--); System.out.println("i: " + i); } }
你知道服务可用性多少个9是什么意思 可用性级别,9越多,可用性越高
Arrays.asList()
把数组转换成集合大坑使用Arrays.asList()
得到的ArrayList是继承自AbstractList
,实现了List接口,它重写的add(),remove()
等修改List结构的方法,并直接抛出异常
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public static <T> List<T> asList (T... a) { return new ArrayList <>(a); } private static class ArrayList <E> extends AbstractList <E> implements RandomAccess , java.io.Serializable public void add (int index, E element) { throw new UnsupportedOperationException (); }
对于集合和数组之间的转换,还有一个list.toArray(),将集合转换为数组,这样的转换生成的数组是一个新的数组,和原集合没有关系,所以修改集合的值,并不会影响数组的值,反之亦然.
但是如果实在想改:
1 2 3 4 5 6 7 public static void test () { Integer[] array = new Integer []{1 , 2 , 3 , 4 , 5 }; List<Integer> list = new ArrayList <>(Arrays.asList(array)); list.add(6 ); list.forEach(System.out::println); }
遍历集合时remove或add操作注意事项 遍历迭代时remove掉一个元素,会导致程序抛出异常:java.util.ConcurrentModificationException
不要在foreach循环里进行元素的remove/add操作,remove元素请使用iterator方式,如果并发操作,需要对iterator对象加锁
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class IteratorRemoveDemo { public static void main (String[] args) { List<Integer> list = new ArrayList <>(); list.add(11 ); list.add(12 ); list.add(13 ); list.add(14 ); list.add(15 ); Iterator<Integer> iterator = list.iterator(); while (iterator.hasNext()) { Integer value = iterator.next(); if (value == 12 ) { iterator.remove(); } } list.forEach(System.out::println); } }
源码:
1 2 3 4 final void checkForComodification () { if (modCount != expectedModCount) throw new ConcurrentModificationException (); }
hashcode冲突案例
java中什么是hashcode?它属于哪个类的方法? hashcode是一个对象方法,用于返回对象的散列码,这个方法是Object
类中的
题外题:说出Object的5个常用方法:
equals
hashCode
toString
wait
notify
哈希冲突是什么意思,你写一个冲突的案例看看
哈希冲突是指不同对象得到的哈希值相同
一般来说10万以上才会起冲突
案例:使用HashSet来存放对象的hashcode,判断集合中是否有这个hashcode,有了就是重复了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 package com.zzmr;import java.util.HashSet;public class HashConflictDemo { static class Book { int id; } public static void main (String[] args) { HashSet<Integer> hashSet = new HashSet <>(); for (int i = 1 ; i <= 11 * 10000 ; i++) { int bookHashCode = new Book ().hashCode(); if (hashSet.contains(bookHashCode)) { System.out.println("发生了hash冲突,在: " + i + "值是: " + bookHashCode); } else { hashSet.add(bookHashCode); } } System.out.println(hashSet.size()); } private static void m1 () { System.out.println("AA" .hashCode()); System.out.println("BB" .hashCode()); System.out.println("+==+" ); System.out.println("Aa" .hashCode()); System.out.println("BB" .hashCode()); System.out.println("+==+" ); System.out.println("柳柴" .hashCode()); System.out.println("柴柕" .hashCode()); System.out.println("+==+" ); } }
整型包装类Integer Integer的构造方法从java8以后有变动,在java17中,Integer的构造方法已经被弃用了
推荐使用的是Integer.valueOf(xxx)
Integer在使用时,数值如何比较
阿里手册:所有整型包装类对象之间值
的比较,全部使用equals
方法比较 对于Integer var = ?
在-128至127之间的赋值,Integer对象是在IntegerCache.cache产生,会复用已有对象,这个区间内的Integer值可以直接使用==进行判断,但是这个区间之外的所有数据,都会在堆上产生,并不会复用已有的对象,这是一个大坑,推荐使用equals
进行判断
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class IntegerBugDemo { public static void main (String[] args) { Integer a = Integer.valueOf(600 ); Integer b = Integer.valueOf(600 ); int c = 600 ; System.out.println(a == b); System.out.println(a.equals(b)); System.out.println(a == c); System.out.println("===============" ); Integer x = Integer.valueOf(99 ); Integer y = Integer.valueOf(99 ); System.out.println(x == y); System.out.println(x.equals(y)); } }
源码:
BigDecimal的坑 金额处理使用什么类型?精度如何处理?
BigDecimal
用于对超过16位有效位的数进行精确运算
由BigDecimal
创建的对象不能使用传统的+-*/
等算术运算符直接对其对象进行数学运算,而必须调用其相应的方法,方法中的参数也必须是BigDecimal的对象,构造器是类的特殊方法,专门用来创建对象,特别是带有参数的对象
阿里手册
BigDecimal的等值比较应使用compareTo()
方法,而不是equals()
方法,因为compareTo会忽略精度比较,equals比较1.0和1.00时,得到的结果为false
禁止使用构造方法BigDecimal(double)
的方式把double值转化为BigDecimal
对象-存在精度损失风险
浮点数之间 的等值判断,基本数据类型不能使用==
进行比较,包装数据类型不能使用equals
进行判断
除法商的结果,需要指定精度,BigDecimal的8种RoundingMode
舍入模式:https://my.oschina.net/u/3644969/blog/4927776
,记住ROUND_HALF_UP
,即四舍五入
科学计数
总结就是,如果要创建BigDecimal
对象,最好使用new BigDecimal(String)
的构造方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 public class BigDecimalDemo { public static void main (String[] args) { m5(); } private static void m5 () { BigDecimal b1 = new BigDecimal ("113213121312.431228491323" ); System.out.println(b1); System.out.println(b1.toPlainString()); } private static void m4 () { BigDecimal b1 = new BigDecimal ("0.2" ); BigDecimal b2 = new BigDecimal ("0.3" ); System.out.println(b1.divide(b2, 2 , RoundingMode.HALF_UP)); } private static void m1 () { BigDecimal bigDecimal1 = new BigDecimal (0.1 ); BigDecimal bigDecimal2 = new BigDecimal ("0.1" ); System.out.println(bigDecimal1); System.out.println(bigDecimal2); BigDecimal bigDecimal3 = BigDecimal.valueOf(0.3 ); System.out.println(bigDecimal3); System.out.println(bigDecimal3.subtract(bigDecimal2)); } private static void m2 () { double d1 = 0.03 ; double d2 = 0.01 ; System.out.println(d1 - d2); } private static void m3 () { BigDecimal b1 = new BigDecimal ("0.10" ); BigDecimal b2 = new BigDecimal ("0.1" ); System.out.println(b1.equals(b2)); System.out.println(b1 == b2); System.out.println(b1.compareTo(b2)); } }
编码最佳实践
ArithmeticUtils.java下载
如何去除list中的重复元素 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 public class ListDemo { public static void main (String[] args) { List<Integer> list = new ArrayList <>(Arrays.asList(92 , 20 , 99 , 1 , 2 , 99 , 3 , 4 , 4 , 5 )); System.out.println(list); } private static void m5 (List<Integer> list) { List<Integer> newList = new ArrayList <>(list); for (int i = 0 ; i < newList.size() - 1 ; i++) { for (int j = newList.size() - 1 ; j > i; j--) { if (newList.get(i).equals(newList.get(j))) { newList.remove(i); } } } System.out.println("去重后: " + newList); } private static void m4 (List<Integer> list) { List<Integer> newList = new ArrayList <>(list); for (Integer i : list) { if (newList.indexOf(i) != newList.lastIndexOf(i)) { newList.remove(newList.indexOf(i)); } } System.out.println("去重后: " + newList); } private static void m3 (List<Integer> list) { List<Integer> newList = new ArrayList <>(); newList = list.stream().distinct().collect(Collectors.toList()); System.out.println("去重后: " + newList); } private static void m2 (List<Integer> list) { List<Integer> newList = new ArrayList <>(); for (Integer i : list) { if (!newList.contains(i)) { newList.add(i); } } System.out.println("去重后: " + newList); } private static void m1 (List<Integer> list) { Set<Integer> set = new HashSet <>(list); System.out.println("去重后: " + set); } }
equals
和==
对比
==
可用于基本数据类型和引用数据类型的比较,若为基本,则比较值,若为引用,则比较地址
equals
是Object的方法,只能用于比较引用类型,但究竟是如何比的,要看是否被重写过,而Object默认的equals如下,也是直接使用==比较,比较地址1 2 3 public boolean equals (Object obj) { return (this == obj); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 public class EqualsDemo { public static void main (String[] args) { String s1 = new String ("abc" ); String s2 = new String ("abc" ); System.out.println(s1 == s2); System.out.println(s1.equals(s2)); Set<String> set01 = new HashSet <>(); set01.add(s1); set01.add(s2); System.out.println(set01.size()); System.out.println(s1.hashCode() + "\t" + s2.hashCode()); System.out.println("=====================" ); Person p1 = new Person ("abc" ); Person p2 = new Person ("abc" ); System.out.println(p1 == p2); System.out.println(p1.equals(p2)); Set<Person> set02 = new HashSet <>(); set02.add(p1); set02.add(p2); System.out.println(set02.size()); System.out.println(p1.hashCode() + "\t" + p2.hashCode()); } }
传值还是传引用
基本类型(如 int)是按值传递的, 因此方法内部的修改不会影响原始变量。
对象引用(如 Person)是按引用传递的, 因此方法内部对对象的修改会影响原始对象。
不可变对象(如 String)也是按引用传递的, 但方法内部的重新赋值不会影响方法外的原始引用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 public class ValueOrRef { public void changeValue1 (int age) { age = 30 ; } public void changeValue2 (Person person) { person.setPersonName("xxx" ); } public void changeValue3 (String str) { str = "xxx" ; } public static void main (String[] args) { ValueOrRef test = new ValueOrRef (); int age = 20 ; test.changeValue1(age); System.out.println("age----" + age); Person person = new Person ("abc" ); test.changeValue2(person); System.out.println("personName----" + person.getPersonName()); String str = "abc" ; test.changeValue3(str); System.out.println("String----" + str); } }
深拷贝+浅拷贝 对象拷贝 就是将一个对象的属性拷贝到另一个有着相同类型的对象中去
浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享一块内存,类似一个分支
拷贝基本类型:拷贝的就是基本类型的值
拷贝引用类型:拷贝的就是内存地址,因此如果其中一个对象改变了值,就会影响到另一个对象,即为多个引用指向同一个对象
深拷贝会另外创造一个一摸一样的对象新对象跟原对象不共享内存,修改新对象不会改到原对象,深拷贝拷贝了原对象的所有值,所以即使原对象的值发生变化时,拷贝对象的值也不会改变
深拷贝的作用:
避免共享引用
线程安全
Cloneable接口,和序列化Serializable
接口一样,都是空的
1 2 public interface Cloneable {}
如果某个类没有实现Cloneable
接口直接使用clone方法,则会抛出异常,实现接口后,可使用clone来实现浅拷贝
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 @Data @AllArgsConstructor @NoArgsConstructor public class ShallowDeepDemo implements Cloneable { private String name; private Integer age; public static void main (String[] args) throws CloneNotSupportedException { Emp emp = new Emp ("z3" , 15 , "雷军" , "CEO" ); System.out.println("原始对象: " + emp.getBoss().getTitle() + " " + emp.getAge()); Emp emp2 = (Emp) emp.clone(); System.out.println("拷贝对象: " + emp2.getBoss().getTitle() + " " + emp.getAge()); System.out.println(); emp2.getBoss().setTitle("CTO" ); emp2.setAge(20 ); System.out.println("----emp2拷贝对象修改Title=CTO,age=20,是否会影响原始对象" ); System.out.println("原始对象: " + emp.getBoss().getTitle() + " " + emp.getAge()); System.out.println("拷贝对象: " + emp2.getBoss().getTitle() + " " + emp.getAge()); } } @Data @AllArgsConstructor @NoArgsConstructor class Boss implements Cloneable { private String bossName; private String title; @Override protected Object clone () throws CloneNotSupportedException { return super .clone(); } } @Data @NoArgsConstructor @AllArgsConstructor class Emp implements Cloneable { private String empName; private Integer age; private Boss boss; public Emp (String empName, Integer age, String bossName, String title) { this .empName = empName; this .age = age; this .boss = new Boss (bossName, title); } @Override protected Object clone () throws CloneNotSupportedException { return new Emp (empName, age, boss.getBossName(), boss.getTitle()); } }
IDEA工具 如何使用Debug并说出3个常用的Debug快捷键
Trace current stream chain
断点的四种类别
Line Breakpoint
单行断点
Method Breakpoint
接口方法断点
Field Watchpoint
变量断点
Exception Breakpoint
异常断点
Test 单元测试应该是全自动执行的,并且是非交互式的,测试用例通常是被定期执行的,执行过程必须完全自动化才有意义,输出结果需要人工检查的测试不是一个好的单元测试,不能使用System.out()
来进行人肉验证,单元测试必须使用assert来验证.
好的单元测试应该遵守AIR原则
简单的断言:
1 2 3 4 5 6 7 8 9 10 @Test void add () { CalcDemo calcDemo = new CalcDemo (); int retValue1 = calcDemo.add(2 , 2 ); int retValue2 = calcDemo.add(2 , 3 ); assertEquals(4 , retValue1); assertEquals(5 , retValue2); }
有关覆盖率
还有就是BeforeEach和AfterEach以及BeforeAll和AfterAll
BeforeAll:所有@Test测试方法调用前执行一次,在测试类没有实例化之前就已被加载,需要static修饰
@AfterAll:所有测试方法调用后执行一次,在测试类没有实例化之前就已被加载,需用static修饰
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 class CalcDemoTestV2 { CalcDemo calcDemo = null ; static StringBuffer stringBuffer = null ; @BeforeAll static void m1 () { stringBuffer = new StringBuffer ("abc" ); System.out.println("===================: " + stringBuffer.length()); } @AfterAll static void m2 () { System.out.println("===================: " + stringBuffer.append(" ,end" ).toString()); } @BeforeEach void setUp () { System.out.println("---come in BeforeEach" ); calcDemo = new CalcDemo (); } @AfterEach void tearDown () { System.out.println("---come in AfterEach" ); calcDemo = null ; } @Test void add () { Assertions.assertEquals(5 , calcDemo.add(1 , 4 )); assertEquals(5 , calcDemo.add(2 , 3 )); } @Test void sub () { assertEquals(5 , calcDemo.sub(10 , 5 )); } }
输出:
1 2 3 4 5 6 ===================: 3 ---come in BeforeEach ---come in AfterEach ---come in BeforeEach ---come in AfterEach ===================: abc ,end
LeetCode算法题 概念 一个算法的好坏是通过时间复杂度 与空间复杂度 来衡量的,简单来说,所花时间与占用内存便是衡量一个算法好坏的标准
常见的时间复杂度
常数阶O(1):hashMap的get
对数阶O(logN):二分查找
线性阶O(N):单层for循环
线性对数阶O(nlogN)1 2 3 4 5 6 7 8 private static void nlogn (int n) { int count = 1 ; for (int i = 0 ; i < n; i++) { while (count <= n) { count = count * 2 ; } } }
平方阶:双层for循环,但是循环条件应该一样,如果循环条件不一样,应该是O(n*m)
如何刷?先从母题开始刷
双指针母题:
双指针技巧
只要数组有序,就应该想到双指针技巧
双指针技巧主要分为两类,左右指针和快慢指针
左右指针,可能想向,可能相背,多用于数组
前后指针-slow/fast
左右指针口诀
结果比目标,小了要变大,左针右移
结果比目标,大了要变小,右针左移
快慢指针口诀
快慢相等值不变,慢针不动快针走
快慢不等值,我是题型一,慢针向前一步走,快针赋值给慢针,快针向前一步走
快慢不等值,我是题型二,快针赋值给慢针,慢针向前一步走,快针向前一步走
1两数之和 这道题,起点亦是终点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 public class TwoNums { public static void main (String[] args) { int [] nums = new int []{2 , 5 , 5 , 11 }; int [] result = twoSum2(nums, 10 ); for (int i : result) { System.out.println(i); } } public static int [] twoSum1(int [] nums, int target) { for (int i = 0 ; i < nums.length; i++) { for (int j = i + 1 ; j < nums.length; j++) { if (nums[i] + nums[j] == target) { return new int []{i, j}; } } } return null ; } public static int [] twoSum2(int [] nums, int target) { Map<Integer, Integer> map = new HashMap <>(nums.length); for (int i = 0 ; i < nums.length; i++) { int param = target - nums[i]; if (map.containsKey(param)) { return new int []{map.get(param), i}; } map.put(nums[i], i); } return null ; } }
总结就是判断map中是否有,有就可以返回结果了,没有就是将这个数的值作为map的键,下标作为map的值存入map
如果有两个数字相同,比如5,5,target是10,此时,当num[i]
第一次等于5时,此时map中不可能有5,所以将这个数以及下标存入map,而当nums[i]
又一次等于5时,此时的5和map之前存的5就是target了,所以返回结果return new int[]{map.get(param),i}
344反转字符串 只在原数组上进行操作,不能创建新的数组,所以就可以交换首尾指针
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public static void reverse (char [] s) { int left = 0 ; int right = s.length - 1 ; while (left < right) { char temp = s[left]; s[left] = s[right]; s[right] = temp; left++; right--; } }
167两数之和-输入有序数组 和之前的题区别在于,这里是递增的数组,那么就可以使用双指针来解决这个问题
两数相加,大了就让右指针左移,小了就让左指针右移,相等就输出,就这么简单
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public static int [] twoSum(int [] nums, int target) { int left = 0 ; int right = nums.length - 1 ; while (left < right) { if (nums[left] + nums[right] == target) { return new int []{left + 1 , right + 1 }; } else if (nums[left] + nums[right] > target) { right--; } else if (nums[left] + nums[right] < target) { left++; } } return null ; }
704二分查找 中间下标就是两下标相加除2,如果这个中间值大了target,那么就让right等于middle-1
,如果这个中间值小于target,那么就让left等于middle+1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public static int search (int [] arraySorted, int target) { int left = 0 ; int right = arraySorted.length - 1 ; while (left <= right) { int middle = (left + right) / 2 ; if (arraySorted[middle] == target) { return middle; } else if (arraySorted[middle] < target) { left = middle + 1 ; } else if (arraySorted[middle] > target) { right = middle - 1 ; } } return -1 ; }
26删除有序数组的重复项 快慢指针:
快慢相等值不变,慢针不动快针走
快慢不等值,我是题型一,慢针向前一步走,快针赋值给慢针,快针向前一步走
快指针如果和慢指针的值相等,那么就把快指针++,然后再判断,如果值不相等了,就把慢指针++,然后把快指针的值赋值给慢指针右移后的位置,以此类推,直到快指针遍历整个数组
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class RemoveNums { public static int removeDuplicates (int [] nums) { int slow = 0 ; int fast = 0 ; while (fast < nums.length) { if (nums[fast] != nums[slow]) { slow++; nums[slow] = nums[fast]; } fast++; } return slow + 1 ; } public static void main (String[] args) { int [] arr = new int []{1 , 2 , 2 , 3 , 4 , 4 , 5 }; int length = removeDuplicates(arr); System.out.println(length); } }
283移动零 给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序
输入 0 1 0 3 12 输出 1 3 12 0 0
就是判断快指针的值是否是0,如果不是0,就交换,然后快慢都往前 如果是0,那么就快指针往前,慢指针不变
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public static void moveZeros (int [] nums) { int slow = 0 ; int fast = 0 ; while (fast < nums.length) { if (nums[fast] != 0 ) { int tmp = nums[slow]; nums[slow] = nums[fast]; nums[fast] = tmp; slow++; } fast++; } }
移除数据 快慢指针,3 2 2 3,移除2,则为3 3
首先判断快指针是否不等于要移除的值,如果不等于,就交换
Tmp明天要用的 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 CREATE TABLE users ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR (100 ), email VARCHAR (100 ), age INT ); INSERT INTO users (name, email, age) VALUES ('Jane Doe' , 'jane.doe@example.com' , 25 );SELECT * FROM users;UPDATE users SET age = 26 WHERE name = 'Jane Doe' ;SELECT * FROM users WHERE name = 'Jane Doe' ;DELETE FROM users WHERE name = 'Jane Doe' ;SELECT * FROM users;DROP TABLE IF EXISTS users;
插入排序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 public class InsertionSort { public static void main (String[] args) { int [] array = {12 , 11 , 13 , 5 , 6 }; insertionSort(array); printArray(array); } public static void insertionSort (int [] array) { int n = array.length; for (int i = 1 ; i < n; ++i) { int key = array[i]; int j = i - 1 ; while (j >= 0 && array[j] > key) { array[j + 1 ] = array[j]; j = j - 1 ; } array[j + 1 ] = key; } } public static void printArray (int [] array) { int n = array.length; for (int i = 0 ; i < n; ++i) { System.out.print(array[i] + " " ); } System.out.println(); } }
一道面试时问道的题:
给定一个无序数组,再给定一个值,要求查出数组中与这个值最接近的值的下标
具体思路:
先定义一个最小差值,这个最小差值的初始值定义为Integer.Max
遍历数组
计算数组每一项与目标值的差值,这个差值应该用Math.abs()
绝对值来操作
判断当前差值和最小差值谁比较小
如果当前差值比较小,就把当前差值的值赋值给最小差值
记录当前数组的位置
就是这么简单,当时竟然紧张了,愣是想不起来
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 public class GetApproach { public static void getData (int [] arr, int target) { int minDiff = Integer.MAX_VALUE; int closest = 0 ; int closestIndex = -1 ; for (int i = 0 ; i < arr.length; i++) { int diff = Math.abs(arr[i] - target); if (diff < minDiff) { minDiff = diff; closest = arr[i]; closestIndex = i; } } System.out.println("下标为: " + closestIndex); System.out.println("最接近的值为: " + closest); System.out.println("差值为: " + minDiff); } public static void main (String args[]) { int [] arr = new int []{0 , 8 , 1 , 2 , 3 }; getData(arr, -1 ); } }
Mysql 有关索引的建立规则
禁止在更新十分频繁,区分度不高的属性上建立索引
建立组合索引,必须把区分度高的字段放在前面
结论
区分度最高,重复率最低
尽可能满足上述2个条件的字段建索引,效果最好
Innodb的行锁到底锁了什么 首先说一下mysql锁级别
表锁
开销小,加锁快;不会出现死锁;锁定力度大,发生锁冲突的概率最高,并发度最低
行级锁
开销大,加锁慢;会出现死锁;锁定力度最小,发生锁冲突的概率最低,并发度也最高
页面锁(不要求)
开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般
行锁会变成表锁?
默认的事务级别是可以避免脏读的,就是无法读到其他事务没有提交的数据
什么时候会变?当where
语句的字段没有命中索引时,没有使用索引,就会导致行锁变成表锁
InnoDB的行锁,是通过锁住索引来实现的 ,如果加锁查询的时候,没有使用倒索引,会将整个聚簇索引都锁住,相当于锁表了命中索引锁行,没有命中锁表,问题会扩大化
什么是回表 回表是指数据库根据索引(非主键)找到了指定的记录所在行后,还需要根据主键再次倒数据块里获取数据的操作
举例:在字段age上建立了索引,然后根据age查询,这就使用到了索引,那么根据这个索引查到的是该行的id值,再使用id主键索引搜索的过程,就成为回表
B+树中,所有数据记录节点都是按照键值大小顺序存放在同一层的叶子节点上,而非叶子节点只存储key值信息,这样可以大大加大每个节点存储的key值数量,降低B+树的高度
如果一张表数据量级是千万级别以上的,要给这张表添加索引,你需要怎么做 腾笼换鸟
建新表+建索引+导数据+废旧表
给表添加索引时,是会对表加锁的,如果不慎操作可能出现生产事故,比如过程中发生了数据修改,则可能会读取到不一致或错误的数据
先创建一张跟原表A数据结构相同的新表B
在新表B添加需要加上的新索引
把原表A数据导到新表B
rename新表B为原表的表名A,原表A换别的表名
having和where的区别 用的地方不一样
where可以用在select和update等语句中
having只能用在select中
执行顺序不一样
where的搜索条件是在分组之前
having的搜索条件是在分组之后