算法&笔试题
基础
最有效率的方法算出2乘以8
2 << 3,
因为将一个数左移n位,就相当于乘以了2的n次方,那么,一个数乘以8只要将其左移3位即可,而位运算cpu直接支持的,效率最高,所以,2乘以8等於几的最效率的方法是2 << 3。
在Java中,这种差异可能会被JVM的优化所掩盖。JVM会尝试优化代码以提高执行效率,这可能包括将某些算术运算转换为更高效的位运算或其他优化技术。因此,在实际应用中,你可能会发现 2<<3 和 2*3 的执行时间非常接近,甚至在某些情况下完全相同。
short s1 = 1; s1 = s1 + 1;有什么错? short s1 = 1; s1 += 1;有什么错?
对于short s1 = 1; s1 = s1 + 1; 由于s1+1运算时会自动提升表达式的类型,所以结果是int型,再赋值给short类型s1时,编译器将报告需要强制转换类型的错误。ClassCastException
对于short s1 = 1; s1 += 1;由于 += 是java语言规定的运算符,java编译器会对它进行特殊处理,因此可以正确编译。
Math.round(11.5)等于多少? Math.round(-11.5)等于多少?
Math类中提供了三个与取整有关的方法:ceil、floor、round,
这些方法的作用与它们的英文名称的含义相对应,
例如,ceil的英文意义是天花板,该方法就表示向上取整,Math.ceil(11.3)的结果为12,Math.ceil(-11.3)的结果是-11;
floor的英文意义是地板,该方法就表示向下取整,Math.floor(11.6)的结果为11,Math.floor(-11.6)的结果是-12;
最难掌握的是round方法,它表示“四舍五入”,算法为Math.floor(x+0.5),即将原来的数字加上0.5后再向下取整,所以,Math.round(11.5)的结果为12,Math.round(-11.5)的结果为-11。
四舍五入保留小数两位小数
DecimalFormat d1=new DecimalFormat("#.00");
d1.format(3.1456);
String.format("%.2f", 3.1456);
还有其他方法,比较麻烦
java有指针吗
Java与C++的区别在于:Java去除了指针的概念,使用引用,并且Java的内存管理不需要程序员来管理,由Java虚拟机来完成对内存的管理
编写一个函数将一个十六进制数的字符串参数转换成整数返回。
String str = “13abf”;
int len = str.length;
int sum = 0;
for(int i=0;i<len;i++){
char c = str.charAt(len-1-i);
int n = Character.digit(c,16);//将16进制的字符c变为十进制的数值得到该位基数。
sum += n * (1<<(4*i)); //基数x进制的i次方代表该位的数值大小
}
其实,也可以用Integer.parseInt(str,16),但面试官很可能是想考我们的编码基本功。
super.getClass()方法调用
下面程序的输出结果是多少?
import java.util.Date;
public class Test extends Date{
public static void main(String[] args) {
new Test().test();
}
public void test(){
System.out.println(super.getClass().getName());
}
}
很奇怪,结果是Test
在test方法中,直接调用getClass().getName()方法,返回的是Test类名
由于getClass()在Object类中定义成了final,子类不能覆盖该方法,所以,在
test方法中调用getClass().getName()方法,其实就是在调用从父类继承的getClass()方法,等效于调用super.getClass().getName()方法,所以,super.getClass().getName()方法返回的也应该是Test。
如果想得到父类的名称,应该用如下代码:
getClass().getSuperClass().getName();
利用getClass方法可以方便的知道spring注入的实现对象是哪个
如
@Autowired
private TestService testService;
System.out.println(testService.getClass());
打印的是class com.sun.proxy.$Proxy18,说明注入到testService的实例是一个动态代理生成的类的实例
String字符串
下面这条语句一共创建了多少个对象:String s="a"+"b"+"c"+"d":
javac编译可以对字符串常量直接相加的表达式进行优化,相当于直接定义了一个”abcd”的字符串,所以,上面的代码应该只创建了一个String对象。
String s = new String("xyz");创建了几个StringObject
答案:两个或一个都有可能
用new关键字创建字符串对象时,JVM会先检查字符串常量池中时候有存在的对应字符串,如果已经存在,则不会在字符串常量池中创建,如果没有存在,那么就会在字符串常量池中创建一个字符串对象,然后还会去堆内存中创建一份字符串对象,把常量池中的对象内容拷贝到内存中的字符串对象,然后返回堆内存中的字符串对象内存地址。
public class StringDemo{
private static final String MESSAGE="taobao";
public static void main(String [] args) {
String a ="tao"+"bao";
String b="tao";
String c="bao";
System.out.println(a==MESSAGE);
System.out.println((b+c)==MESSAGE);
}
}
//最终结果为true,false; 对于ajvm会优化会直接指向string常量池中创建好的对象。对于b+c变量和变量拼接不会进行优化会产生新的对象,所以不是指同一个地方
public class Test {
private String name = "abc";
public static void main(String[] args) {
Test test = new Test();
Test testB = new Test();
System.out.println(test.name == testB.name);
}
}
返回true,直接赋值的方式,没有使用new关键字,会去字符串常量池中找。
String s1 = "coder";
String s2 = "coder";
String s3 = "coder" + s2;
String s4 = "coder" + "coder";
String s5 = s1 + s2;
System.out.println(s3 == s4);
System.out.println(s3 == s5);
System.out.println(s4 == "codercoder");
答案是false;false; true;
引用类型判断时,==判断的是地址是否相等。如果用常量字符串来定义的(如s1,s2,s4)会存在常量池里面;用变量定义的就不会(如s3,s5)
位运算符
<<表示左移位
>>表示带符号右移位
>>>表示无符号右移
但是没有<<<运算符
native
native关键字表名修饰的方法是由其它非Java语言编写的
import
import是用于导包语句,其前面可以出现package,用来声明包的
方法入参
public class foo {
public static void main(String sgf[]) {
StringBuffer a=new StringBuffer("A");
StringBuffer b=new StringBuffer("B");
operate(a,b);
System.out.println(a+"."+b);
}
static void operate(StringBuffer x,StringBuffer y) {
x.append(y);
y=x;
}
}
最后结果AB.B
这里简单地说,a,b,x,y就是四个指针。y本来指向的是b所指向的对象,但是一个“=”,y就指向了x所指向的目标即是a指向的对象,因此原来b所指向的目标并没有发生任何改变。与y不同的是,x进行的是对象操作,此时此对象在内存中是真正的本质上的改变。
JSON
{company:4399}是否错误
json对象要求属性必须加双引号
cookie
Cookie是Web服务器发送给客户端的一小段信息,客户端请求时,可以读取该信息发送到服务器端
关闭浏览器意味着临时会话ID丢失,但所有与原会话关联的会话数据仍保留在服务器上,直至会话过期
在禁用Cookie时可以使用URL重写技术跟踪会话
java关键字
true、false、null都不是关键字 他们是值;
关键字(Keywords)是Java语言本身定义并保留的具有特殊含义的单词。这些单词在Java中有固定的用途,并且不能用作变量名、方法名、类名或其他标识符。Java的关键字都是小写字母。
Java中的关键字大致可以分为以下几类:
- 访问修饰符:用于设置类、方法或变量(字段)的访问级别。例如:
private、protected、public。 - 控制流关键字:用于控制程序的执行流程。例如:
if、else、switch、case、default、while、do-while、for、break、continue、return。 - 错误处理关键字:用于处理程序中可能出现的异常或错误。例如:
try、catch、finally、throw、throws。 - 类、接口和枚举定义关键字:用于定义Java程序中的类和接口。例如:
class、interface、enum。 - 实例化和引用关键字:用于创建对象实例和引用对象。例如:
new、this、super。 - 基本数据类型关键字:用于声明变量时指定其数据类型。例如:
int、char、double、boolean、byte、short、long、float、void。 - 其他关键字:这些关键字在Java中有特定的用途,但不属于上述分类。例如:
final、abstract、static、import、native、strictfp、synchronized、transient、volatile、assert、instanceof、package、switch、const、goto。
注意:虽然const和goto是Java的关键字,但它们在Java中并没有实际用途
Java标识符由数字,字母和下划线(_),美元符号($)或人民币符号(¥)组成。在Java中是区分大小写的,而且还要求首位不能是数字。最重要的是,Java关键字不能当作Java标识符。
request
request.getAttribute()方法返回request范围内存在的对象,而request.getParameter()方法是获取http提交过来的数据。
Socket
服务器端:ServerSocket提供的实例
ServerSocket server= new ServerSocket(端口号)
客户端:Socket提供的实例
Socket soc=new Socket(ip地址,端口号)
服务器端通过TCP连接对象调用accept()方法创建通信的Socket对象
转码
将ISO8859-1字符串转成GB2312编码
new String("字符串".getBytes("ISO8859-1"),"GB2312")
线程
线程状态转换

线程从运行状态变为就绪状态通常发生在以下几种情况:
- 时间片用完:当线程正在运行时,它会被分配一个时间片来执行。当这个时间片用完时,线程的状态就会从运行状态变为就绪状态,等待再次被调度执行。
- 其他线程优先级更高:在Java中,线程有优先级的概念。如果当前运行的线程优先级较低,而有一个优先级更高的线程处于就绪状态,那么JVM可能会中断当前线程的执行,让优先级更高的线程运行。在这种情况下,当前线程的状态会从运行状态变为就绪状态。
- 线程主动让出CPU:Java提供了
Thread.yield()方法,允许线程主动放弃当前占用的CPU资源,使线程从运行状态变为就绪状态。这样可以让其他线程有机会得到执行。
join方法:
join方法通常用于在多线程环境中协调线程的执行顺序,以确保某个线程的执行完成后再执行其他线程。例如,当主线程需要等待子线程完成某个任务后才能继续执行时,就可以使用join方法来实现。调用join方法的线程会等待被调用的线程完成执行。比如在线程B中调用了线程A的Join()方法,直到线程A执行完毕后,才会继续执行线程B。
yield方法用于提示线程调度器,当前线程愿意放弃当前的CPU使用权,使得其他具有相同优先级的线程有机会执行。调用yield方法并不保证当前线程一定会让出CPU,仅仅是一个提示,具体是否让出CPU,还取决于线程调度器的决策。
yield方法通常用于在多线程环境中,希望让其他线程有更多的执行机会,以平衡各个线程的执行。yield方法不会阻塞当前线程,而是将当前线程从运行状态转为就绪状态,等待线程调度器重新安排。
join()底层就是调用wait()方法的,wait()释放锁资源,故join也释放锁资源
yield方法不会释放锁。
InterruptedException
Thread.sleep() 和 Object.wait(),都可以抛出 InterruptedException。这个异常是不能忽略的,因为它是一个检查异常(checked exception)
当一个线程因为抢先机制而停止运行,它可能被放在可运行队列的前面。是对的
数组
数组使用equals方法:
当你使用equals方法比较两个数组对象时,你实际上是在比较这两个数组对象的引用是否相同(同一个内存地址),而不是比较它们的内容是否相同。可以使用Arrays.equals来比较内容。
类、接口
is-a继承(x is-a y x集成y ) like-a接口 has-a关联(x has-a y x中有y的引用) use-a 依赖
接口没有构造方法,所以不能实例化,抽象类有构造方法,但是不是用来实例化的,是用来初始化的。
类实现对象:两同两小一大:参数列表和方法名相同,返回值和异常相同或者是子类,权限相同或者更大。
为了降低模块的耦合性,应优先选用接口,尽量少用抽象类
接口的方法默认是public abstract
public类的类名和文件名要完全相同(包括大小写)
类的实例化:静态代码块--》非静态代码块--》 构造函数
public class MemberVariableInitialization {
// 静态变量
static int staticVar = initializeStaticVar();
// 实例变量
int instanceVar = initializeInstanceVar();
// 静态初始化块
static {
System.out.println("Static initialization block executed.");
}
// 实例初始化块
{
System.out.println("Instance initialization block executed.");
}
// 构造函数
public MemberVariableInitialization() {
System.out.println("Constructor executed.");
}
// 静态变量初始化方法
static int initializeStaticVar() {
System.out.println("Static variable initialized.");
return 42;
}
// 实例变量初始化方法
int initializeInstanceVar() {
System.out.println("Instance variable initialized.");
return 13;
}
public static void main(String[] args) {
MemberVariableInitialization obj = new MemberVariableInitialization(); // 创建实例
}
}
输出的结果为:
Static variable initialized. // 静态变量初始化
Static initialization block executed. // 静态初始化块执行
Instance variable initialized. // 实例变量初始化
Instance initialization block executed. // 实例初始化块执行
Constructor executed. // 构造函数执行
抽象类可以有构造方法,接口不能有构造方法。
构造方法没有返回类型,也不能定义为void,在方法名前面不声明方法类型。
通过反射创建实例,会调用构造方法。使用java.lang.Class或java.lang.reflect.Constructor的newInstance()方法;反序列化和clone不会调用构造方法
局部变量能否和成员变量重名:可以,局部变量可以与成员变量重名,这时可用“this”来指向成员变量
可以把任何一种数据类型的变量赋给Object类型的变量,八大基础数据类型会自动装箱后赋值给Object,所以编译运行都不会报错
接口特性:
1.7接口定义抽象方法
1.8接口可以定义default方法和static方法;1.9接口中可以定义私有方法。
接口方法默认是public abstract的,且实现该接口的类中对应的方法的可见性不能小于接口方法的可见性。
equal方法:
没有重写equal()方法的话,equal()方法和“==”的作用是一样的,在String里equals比较的才是值,因为String重写了equals方法
不会初始化子类的几种 :
public class P {
public static int abc = 123;
static{
System.out.println("P is init");
}
}
public class S extends P {
static{
System.out.println("S is init");
}
}
public class Test {
public static void main(String[] args) {
System.out.println(S.abc);
}
}
结果为:P is init<br />123
public class A implements B{
public static void main(String args[]){
int i;
A a1=new A();
i =a1.k;
System.out.println("i="+i);
}
}
interface B{
int k=10;
}
i=10。
class Test {
public static void hello() {
System.out.println("hello");
}
}
public class MyApplication {
public static void main(String[] args) {
// TODO Auto-generated method stub
Test test=null;
test.hello();
}
}
集合类
对于随机访问get和set,ArrayList觉得优于LinkedList,因为LinkedList要移动指针。
ArrayList是实现了基于动态数组的数据结构(动态指的可以扩容),LinkedList基于链表的数据结构。
ArrayList的空间浪费主要体现在在list列表的结尾预留一定的容量空间(直接申请固定大小空间,有的空间可能会用不到),而LinkedList的空间花费则体现在它的每一个元素都需要消耗相当的空间(存指针)。
解决冲突主要有三种方法:定址法,拉链法,再散列法。hashMap是拉链法(冲突就放到链表或红黑树,)localHost中额LocalHashMap是定址法(冲突就看下个位置冲突不冲突,适合数据量少的)
基本数据类型和包装类
基本数据类型 包装类 byte Byte boolean Boolean short Short char Character int Integer long Long float Float double Double
包装类的final特点,
public class Test2
{
public void add(Byte b)
{
b = b++;
}
public void test()
{
Byte a = 127;
Byte b = 127;
add(++a);
System.out.print(a + " ");
add(b);
System.out.print(b + "");
}
}
包装类的值是final修饰不可变的,无论是++b还是b++都创建了一个新对象,那么作为参数传递时,形参和实参不会指向一个地址。也就是说,即使add() 方法中不是b = b++;这样没有意义的代码,传入参数的值也不会改变。
byte类型取值范围为-128~127,在a变量进行++a运算时,会导致a变为-128,这是因为整数在内存中使用的是补码的形式表示,最高位是符号位,0表示正数,1表示负数,加一导致补码进位,符号位变为1.
因此,本题正确输出为-128 127
@Test
public void test1(){
Integer a=1;
test2(a);
System.out.println(a);
System.out.println(System.identityHashCode(a));
}
public void test2(Integer a){
System.out.println(System.identityHashCode(a));
a=a+1;//Integer是final类型,这里改变值是生成了一个新的对象
System.out.println(System.identityHashCode(a));
}
2104457164
1521118594
1
2104457164
int类型与Integer类型比较时, 先将Integer拆箱, 再比较值。
Integer a1=127,a2=127;
Integer b1=200,b2=200;
Integer a1=127实际上执行了Integer.valueOf(127);
对于–128到127(默认是127)之间的值,Integer.valueOf(int i) 返回的是缓存的Integer对象(并不是新建对象),变量所指向的是同一个对象,a1==a2为true;分开定义也是一样(Integer a1=127;Integer a2=127)
由于超出自动装箱的范围b1和b2是不同的对象,b1==b2为false;Integer两个对象==比较的是引用地址
输入123456.234,输出:一十二万三千四百五十六点二三四
思想
技术储备:
Integer.parseInt(s.charAt(i)+"");//将指定数值型字符串转为数值
|
public class DigitTest {
String[] digits={"零","一","二","三","四","五","六","七","八","九"}; String[] radices={"","十","百","千"}; String[] bigRadices={"","万","亿"};
public String test(String s){ String[] parts = s.split("\\."); String integral=null; String decimal=""; integral = parts[0]; if(parts.length>1){ decimal = parts[1]; } int length = integral.length(); int zeroCount = 0; String out=""; for(int i=0;i<length-1;i++){ int p=length-i-1; int a= Integer.parseInt(s.charAt(i)+""); int quotient = p / 4; int modulus = p % 4; if(a==0){ zeroCount++; }else{ if(zeroCount>0){ out+=digits[0]; } zeroCount = 0; out += digits[a] + radices[modulus]; } if (modulus == 0 && zeroCount < 4) { out += bigRadices[quotient]; zeroCount = 0; } } if (decimal != "") { out +="点"; int len = decimal.length(); for (int i = 0; i < len; i++) { int d = Integer.parseInt(decimal.charAt(i)+""); if (d != 0) { out += digits[d] ; } else if(i!=len-1){ out +=digits[0]; } } } return out; }
@Test public void test1(){ test("211111111.034"); } } |
反射
反射相关的类在long包,反射带来的效率问题主要是动态解析类,JVM没法对反射代码优化。
它允许程序在运行时检查类、接口、字段和方法的信息,并且可以动态地创建和调用对象。
-
获取类名:
java`Class<?> clazz = MyClass.class;
String className = clazz.getName();
System.out.println("Class Name: " + className);`
-
获取类的所有字段:
java`Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
System.out.println("Field Name: " + field.getName());
}`
获取静态属性
- 使用
Class对象的getDeclaredField或getField方法获取静态属性的Field对象。注意,如果属性是私有的,你需要调用setAccessible(true)来允许访问。 - 使用
Field对象的get方法获取静态属性的值。对于静态属性,get方法的第一个参数是null,因为静态属性属于类本身,而不是类的实例。
// 获取类的Class对象
Class<?> clazz = MyClass.class;
// 获取静态属性的Field对象
Field field = clazz.getDeclaredField("MY_STATIC_FIELD");
// 如果字段是私有的,设置可访问
field.setAccessible(true);
// 获取静态属性的值
Object value = field.get(null); // 对于静态字段,第一个参数是null
也可以使用实例对象的类对象获取
User user=new User(); return user. getClass(). getField ("name").get (user);
User user=new User(): return user. getClass(). getDeclaredField ("name"). get (user);
-
获取类的所有方法:
java`Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
System.out.println("Method Name: " + method.getName());
}`
-
调用方法
假设我们有一个类MyClass,它有一个名为myMethod的公共方法,该方法接受一个字符串参数并返回一个字符串。
public class MyClass {
public String myMethod(String input) {
return "Hello, " + input;
}
}
Class<?> clazz = MyClass.class;
Method method = clazz.getDeclaredMethod("myMethod", String.class); // 获取方法,注意参数类型
method.setAccessible(true); // 如果方法是私有的,需要设置为可访问
MyClass obj = new MyClass();
Object result = method.invoke(obj, "World"); // 调用方法,传入对象实例和参数
System.out.println("Method Result: " + result); // 输出:Method Result: Hello, World
getDeclaredField方法:getDeclaredField方法用于获取类自身声明的字段,无论这些字段是public、private、protected还是默认(无修饰符)的。- 它不会获取父类中的字段,只考虑当前类声明的字段。
- 如果需要访问的字段是私有的,那么在使用
getDeclaredField方法获取到Field对象后,通常需要调用setAccessible(true)来绕过Java的访问控制检查,从而能够读取或修改该字段的值。
getField方法:getField方法用于获取类的公共(public)字段,包括从父类中继承的公共字段。- 它只能访问公共字段,不能访问非公共字段(如
private或protected字段)。 - 使用
getField方法时,不需要调用setAccessible(true),因为公共字段本来就是可以被任意访问的。
静态方法
在静态方法中调用本类的静态方法时可直接调用
静态方法不能直接调用实例方法和对象,但可以通过在静态方法中创建类的实例的方式间接调用。
静态代码块
public class Test{
static{
int x=5;
}
static int x,y;
public static void main(String args[]){
x--;
myMethod( );
System.out.println(x+y+ ++x);
}
public static void myMethod( ){
y=x++ + ++x;
}
}
静态语句块中x为局部变量,不影响静态变量x的值 2.x和y为静态变量,默认初始值为0,属于当前类,其值得改变会影响整个类运行。 3.java中自增操作非原子性的 main方法中: 执行x--后 x=-1 调用myMethod方法,x执行x++结果为-1(后++),但x=0,++x结果1,x=1 ,则y=0 x+y+ ++x,先执行x+y,结果为1,执行++x结果为2,得到最终结果为3
内存分配
class A {
private String a = “aa”;
public boolean methodB() {
String b = “bb”;
final String c = “cc”;
}
}
a是成员变量在堆区,b和c是局部变量在栈区
垃圾回收
垃圾回收跟方法是否执行完没有关系,没有被引用了或设置为null了就可以被回收
垃圾回收时不一定执行finallize方法,如果是等待清理队列中如果又被调用,则不会执行finallize方法
强引用不回收,软引用内存不足时回收,弱引用JVMGC时回收,虚引用随时会被回收。
垃圾回收在jvm中优先级相当相当低
重载重写
1. 静态方法,不存在重写,重写只对可见的实例方法有效。静态方法只有隐藏。
2. 重载是根据形参的静态类型确定调用的方法版本,重写是根据调用者在运行期的实际类型来确定调用的方法版本。
public class Demo {
public static void main(String[] args) {
Collection<?>[] collections =
{new HashSet<String>(), new ArrayList<String>(), new HashMap<String, String>().values()};
Super subToSuper = new Sub();
for(Collection<?> collection: collections) {
System.out.println(subToSuper.getType(collection));
}
}
abstract static class Super {
public static String getType(Collection<?> collection) {
return “Super:collection”;
}
public static String getType(List<?> list) {
return “Super:list”;
}
public String getType(ArrayList<?> list) {
return “Super:arrayList”;
}
public static String getType(Set<?> set) {
return “Super:set”;
}
public String getType(HashSet<?> set) {
return “Super:hashSet”;
}
}
static class Sub extends Super {
public static String getType(Collection<?> collection) {
return "Sub"; }
}
}
最终结果Super:collection Super:collection Super:collection
IO流
使用ObjectOutputStream和ObjectInputStream可以将对象进行传输
声明为static和transient类型的成员数据不能被串行化(序列化)。因为static代表类的状态, transient代表对象的临时数据。
字符流,那么一般是reader和writer结尾;字节流一版都是Stream结尾
File类是对文件整体或者文件属性操作的类,例如创建文件、删除文件、查看文件是否存在等功能,不能操作文件内容;文件内容是用IO流操作的
Main方法
main方法本身必须是public和static的,main方法所在的类不要求是public的
jsp
application对象是共享的,多个用户共享一个,以此实现数据共享和通信
在开发servlet继承HttpServlet时如何处理父类的service方法,重载doGet()之类的doXxx()方法
ServletConfig接口默认是哪里实现的:GenericServlet
HttpServletRequest. getParameter获取的参数,由客户端浏览器和Web容器配置共同决定编码
Servlet生命周期分成3个阶段:
1)初始化阶段:调用init方法
2)响应客户请求:调用service
3)终止:调用destory方法
instanceof
instanceof是java的二元运算符,用来判断他左边的对象是否为右面类(接口,抽象类,父类)的实例
注释
注释信息不会被编译
锁
CopyOnWriteArrayList适用于写少读多的并发场景,对读操作不加锁,对写操作,先复制一份新的集合,在新的集合上面修改,然后将新集合赋值给旧的引用。
ReadWriteLock即为读写锁,他要求写与写之间互斥,读与写之间互斥, 读与读之间可以并发执行。在读多写少的情况下可以提高效率 ,ConcurrentHashMap是同步的HashMap,读写都加锁
一个容器类数据结构,读写平均,使用锁机制保证线程安全。如果要综合提高该数据结构的访问性能,最好的办法是分区段加锁,只在影响读写的地方加锁
finnal
class Car extends Vehicle
{
public static void main (String[] args)
{
new Car(). run();
}
private final void run()
{
System. out. println ("Car");
}
}
class Vehicle
{
private final void run()
{
System. out. println("Vehicle");
}
}
可以通过编译,并执行成功,输出Car;因为是private的对子类不可见,所以虽然方法是final的子类中也可以定义一样的方法。
设计模式
java数据库连接库JDBC用到哪种设计模式:桥接模式。JDBC提供两套接口,一个面向数据库厂商,一个面向JDBC使用者。定义 :将抽象部分与它的实现部分分离,使它们都可以独立地变化。
单例模式
饿汉式:类加载的时候创建实例;懒汉式:使用实例时进行创建实例;
资源利用率:懒汉式资源利用率更高,因为使用饿汉式如果这个实例没有被用,这个实例还是会占用资源
线程安全:饿汉式是线程安全的,因为虚拟机保证只会装载一次,在装载类的时候不会发生并发问题。而懒汉式如果不加同步则可能导致线程安全问题,懒汉式单例模式通常需要使用同步机制,如双重检查锁定等,来确保线程安全。
饿汉式
public class Single{
private Single(){} //私有化构造方法。
private static Single s = new Single(); //创建私有并静态的本类对象。
public static Single getInstance(){ //定义公有并静态的方法,返回该对象。
return s;
}
}
懒汉式:
public class Singleton {
// 使用volatile关键字确保instance在多线程环境下的可见性
private static volatile Singleton instance;
// 私有构造方法,防止外部通过new Singleton()创建对象
private Singleton() {
// 私有构造方法,防止被实例化
}
// 提供静态方法获取Singleton实例
public static Singleton getInstance() {
if (instance == null) { // 第一次检查
synchronized (Singleton.class) {
if (instance == null) { // 第二次检查
instance = new Singleton();
}
}
}
return instance;
}
}
为什么进行两次的null检查:
第一次检查 instance 是否为 null 是在非同步块中进行的。这一步的目的是为了减少不必要的同步开销。已经不是 null,那么就没有必要进入同步块,第二次检查 instance 是否为 null 是在同步块内部进行的。这一步是确保线程安全的关键。尽管第一次检查可以避免不必要的同步,但如果没有第二次检查,就可能会遇到并发问题。需要注意的是,双重检查锁定在Java中有效的一个前提是 instance 变量必须是 volatile 的。volatile 关键字确保了 instance 变量的可见性和禁止指令重排,从而保证了双重检查锁定的正确性。
内部类方式:
public class Single {
private static class LazyLoader{
private static final Single instance = new Single ();
}
private Single(){}
public static Single getInstance(){
return LazyLoader.instance;
}
}
内部类
在Java中,可以将一个类定义在另一个类里面或者一个方法里边,这样的类称为内部类,广泛意义上的内部类一般包括四种:成员内部类,局部内部类,匿名内部类,静态内部类 。
1.成员内部类
(1)该类像是外部类的一个成员,可以无条件的访问外部类的所有成员属性和成员方法(包括private成员和静态成员);
(2)成员内部类拥有与外部类同名的成员变量时,会发生隐藏现象,即默认情况下访问的是成员内部类中的成员。如果要访问外部类中的成员,需要以下形式访问:【外部类.this.成员变量 或 外部类.this.成员方法】;
(3)在外部类中如果要访问成员内部类的成员,必须先创建一个成员内部类的对象,再通过指向这个对象的引用来访问;
(4)成员内部类是依附外部类而存在的,也就是说,如果要创建成员内部类的对象,前提是必须存在一个外部类的对象;
(5)内部类可以拥有private访问权限、protected访问权限、public访问权限及包访问权限。如果成员内部类用private修饰,则只能在外部类的内部访问;如果用public修饰,则任何地方都能访问;如果用protected修饰,则只能在同一个包下或者继承外部类的情况下访问;如果是默认访问权限,则只能在同一个包下访问。外部类只能被public和包访问两种权限修饰。
2.局部内部类
(1)局部内部类是定义在一个方法或者一个作用域里面的类,它和成员内部类的区别在于局部内部类的访问仅限于方法内或者该作用域内;
(2)局部内部类就像是方法里面的一个局部变量一样,是不能有public、protected、private以及static修饰符的。
对于局部内部类,只有在方法的局部变量被标记为final或局部变量是effctively final的,内部类才能使用它们,(局部内部类对于使用到的方法中的参数,会在类中生成相应的变量。而变量之间通过引用或者值传递(八大基本类型)。而在我们的逻辑中,局部内部类和方法中的参数是同一个参数。可如果在方法中或者局部内部类的方法中改变了变量的引用指向或者值(八大基本类型),相应的内部类或者方法中该变量的引用和值(八大基本类型)却并没有改变,会导致逻辑与实际情况脱节。 为了解决这个问题才使用final修饰符让变量无法改变)
3.匿名内部类
(1)一般使用匿名内部类的方法来编写事件监听代码;
(2)匿名内部类是不能有访问修饰符和static修饰符的;
(3)匿名内部类是唯一一种没有构造器的类;(构造器必须有名字,它没有名字)
(4)匿名内部类用于继承其他类或是实现接口,并不需要增加额外的方法,只是对继承方法的实现或是重写。
4.内部静态类
(1)静态内部类是不需要依赖于外部类的,这点和类的静态成员属性有点类似;
(2)不能使用外部类的非static成员变量或者方法。
变量
class HasStatic{
private static int x = 100;
public static void main(String args[ ]){
HasStatic hs1 = new HasStatic();
hs1.x++;
HasStatic hs2 = new HasStatic();
hs2.x++;
hs1=new HasStatic();
hs1.x++;
HasStatic.x--;
System.out.println( "x=" +x);
}
}
修饰符为 static 所以x为类变量,即对于所有的实例来说,他们访问的x为同一个x
数组
数组复制效率排行System.arraycopy>clone>Arrays.copyOf>for循环
算法题
斐波那契数列实现
在数学上,斐波纳契数列以如下被以递归的方法定义:F(0)=0,F(1)=1,F(n)=F(n-1)+F(n-2)(n≥2,n∈N*)
(就是从第三个数开始,其值是前面两个数之和)
实现一:基于递归形式实现
**
* 返回斐波那契数第n个值,n从0开始
* 实现方式,基于递归实现
*/
public static int getFib(int n){
if(n < 0){
return -1;
}else if(n == 0){
return 0;
}else if(n == 1 || n ==2){
return 1;
}else{
return getFib(n - 1) + getFib(n - 2);
}
}
递归是最简单的实现方式,但递归有很多的问题,在n的值非常大时,会占用很多的内存空间,既然该数列定义F(n)=F(n-1)+F(n-2)(n≥2,n∈N*), 那么我们可以从头到尾进行计算,先计算前面的值,然后逐步算出第n个值。
实例二:基于变量的实现:
/**
* 返回斐波那契数第n个值,n从0开始
* 实现方式,基于变量实现,循环控制次数
*/
public static int getFib2(int n){
if(n < 0){
return -1;
}else if(n == 0){
return 0;
}else if (n == 1 || n == 2){
return 1;
}else{
int c = 0, a = 1, b = 1;
for(int i = 3; i <= n; i++){
c = a + b;
a = b;
b = c;
}
return c;
}
}
从上面的实现中我们定义了3个变量a、b、c其中c=a+b,然后逐步进行计算从而得到下标为n的值。
既然我们可以定义变量进行存储,那么同样我们还可以定义一个数组,该数组的每一个元素即一个斐波那契数列的值,这样我们不仅能得到第n个值,还能获取整个斐波那契数列。
/**
* 返回斐波那契数第n个值,n从0开始
* 实现方式,基于数组实现
* @param n
* @return
*/
public static int getFib3(int n){
if(n < 0){
return -1;
}else if(n == 0){
return 0;
}else if (n == 1 || n == 2){
return 1;
}else{
int[] fibAry = new int[n + 1];
fibAry[0] = 0;
fibAry[1] = fibAry[2] = 1;
for(int i = 3; i <= n; i++){
fibAry[i] = fibAry[i - 1] + fibAry[i - 2];
}
return fibAry[n];
}
}
排序
实现阶乘n!
含义:f(n)=所有小于或等于 n 的正整数的乘积
/**
* 使用循环方式计算n的阶乘
*/
private long test(int n) {
if(n<1){
return -1;
}
long num = 1;
for (int i = 1; i <= n; i++) {
num *= i;
}
return num;
}
/**
* 使用递归方式计算n的阶乘
*/
public long test(int n) {
if(n<1){
return -1;
}
if(n==1){
return 1;
}
return n*test(n-1);
}
阶乘求和
阶乘:1+2!+3!+4!+5!+……
/**
* 使用循环方式计算n的阶乘
*/
private long test(int n) {
long sum=0,num=1; //sum用于加和,num作为每一个数阶乘后的结果
for(int i=1;i<=n;i++) {
num*=i; //num始终保留上一次阶乘的结果,所以只需要乘i
sum+=num; //每次阶乘后相加
}
return sum;
}
两个栈实现队列
栈是后进先出,队列是先进先出
方法:push是添加,pop移除顶部元素并返回
思想:数据都放入stack1,取数据时取stack2中数据,stack2中没有数据时把stack1中数据放入stack2再取出
public class StackQueue {
private Stack<Integer> stack1;
private Stack<Integer> stack2;
public StackQueue(){
stack1 = new Stack<Integer>();
stack2 = new Stack<Integer>();
}
public void push(int node){
stack1.push(node);
}
public int pop(){
if(stack2.empty()){
while(!stack1.empty())
stack2.push(stack1.pop());
}
return stack2.pop();
}
// 检查队列是否为空
public boolean isEmpty() {
return stack1.isEmpty() && stack2.isEmpty();
}
}
用两个队列实现一个栈
思想:放入都放一个队列,取出就把有数据的队列数据放入另一个队列剩一个数据弹出
public class QueueStack {
private Queue<Integer> queue1;
private Queue<Integer> queue2;
public QueueStack(){
queue1 = new ArrayDeque<Integer>();
queue2 = new ArrayDeque<Integer>();
}
/*
* 向栈中压入数据 (思想那个有数据就放那个)
*/
public void push(Integer element){
//两个队列都为空时,优先考虑 queue1
if(queue1.isEmpty() && queue2.isEmpty()){
queue1.add(element);
return;
}
//如果queue1为空,queue2有数据,直接放入queue2
if(queue1.isEmpty()){
queue2.add(element);
return;
}
//如果queue2为空,queue1有数据,直接放入queue1中
if(queue2.isEmpty()){
queue1.add(element);
return;
}
}
/*
* 从栈中弹出一个数据 (思想那个有数据就倒出剩一个弹出)
*/
public Integer pop(){
//如果两个栈都为空,则没有元素可以弹出,异常
if(queue1.isEmpty() && queue2.isEmpty()){
return null;
}
//如果queue1中没有元素,queue2中有元素,将其queue2中的元素依次放入queue1中,直到最后一个元素,弹出即可
if(queue1.isEmpty()){
while(queue2.size() > 1){
queue1.add(queue2.poll());
}
return queue2.poll();
}
//如果queue2中没有元素,queue1中有元素,将其queue1中的元素依次放入queue2中,直到最后一个元素,弹出即可
if(queue2.isEmpty()){
while(queue1.size() > 1){
queue2.add(queue1.poll());
}
return queue1.poll();
}
return (Integer)null;
}
}
实现二分查找
算法思想:又叫折半查找,要求待查找的序列有序。每次取中间位置的值与待查关键字比较,如果中间位置的值比待查关键字大,则在前半部分循环这个查找的过程,如果中间位置的值比待查关键字小,则在后半部分循环这个查找的过程。直到查找到了为止,否则序列中没有待查的关键字。
传入一个有序数组和一个数,判断这个数是第几个数,没有的话返回-1
实现:
1.非递归代码
public static int biSearch(int []array,int a){
int lo=0;
int hi=array.length-1;
int mid;
while(lo<=hi){
mid=(lo+hi)/2;
if(array[mid]==a){
return mid+1;
}else if(array[mid]<a){
lo=mid+1;
}else{
hi=mid-1;
}
}
return -1;
}
- 递归实现
public static int sort(int []array,int a,int lo,int hi){
if(lo<=hi){
int mid=(lo+hi)/2;
if(a==array[mid]){
return mid+1;
}
else if(a>array[mid]){
return sort(array,a,mid+1,hi);
}else{
return sort(array,a,lo,mid-1);
}
}
return -1;
}
反转链表
定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点。
输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL
//思路:先定位到最后一个元素作为head,然后反向变换next的指向
public Node reverseList(Node head){
if(head==null||head.next==null){
return head;
}
Node result = reverseList(head.next);
head.next.next=head;
head.next=null;
return result;
}
class Node<E>{
E item;
Node next;
}
各种排序
常用的排序有:
插入排序 冒泡排序 选择排序 快速排序 堆排序 归并排序 基数排序 希尔排序
排序复杂度:

稳定:复杂度不变
稳定的排序算法是:冒泡排序,直接插入排序,归并排序,基数排序,二叉树排序,计数排序。
不稳定的排序算法:选择排序,快速排序,堆排序,希尔排序。
排序实现
从小到大排序
插入排序
思想:
从低到高依次处理每个位置上的数,如果要插入到前面,被插位置后面的排好序的后移一位
public class InsertionSort {
public static void sort(int[] arr) {
for (int i = 1; i < arr.length; i++) {
int key = arr[i];
int j = i - 1;
while (j >= 0 && arr[j] > key) {
arr[j + 1] = arr[j];
j = j - 1;
}
arr[j + 1] = key;
}
}
}
快速排序
1、基本思想:选择一个基准元素,通常选择第一个元素或者最后一个元素,通过一趟扫描,将待排序列分成两部分,一部分比基准元素小,一部分大于等于基准元素,此时基准元素在其排好序后的正确位置,然后再用同样的方法递归地排序划分的两部分。
冒泡排序
基本思想:自上而下对相邻的两个数依次进行比较和调整,让较大的数往数组后面走。即:每当两相邻的数比较后发现它们的排序与排序要求相反时,就将它们互换;内层循环走一遍最大数就到最后了(所以j<a.length-i-1)。a[j]与a[j+1]比较
冒泡排序实现:遇到比自己小的就换

相邻位置的元素比较,前面的大就换位子,直到一次遍历完成,沉下去一个最大数。然后开始第二遍沉下次大数。
每i值变换一次沉下去一个大数。
核心方法:
private void sort(int[] array){
//对array进行冒泡排序
//冒泡排序:比较相邻元素,比前面小就换位,遍历一次找出一个最大值
int len=array.length;
for(int i=0;i<len;i++){
for(int j=0;j<len-i-1;j++){
if(array[j]>array[j+1]){
int temp=array[j];
array[j]=array[j+1];
array[j+1]=temp;
}
}
}
}
选择排序
思想:每趟从待排序的记录序列中选择关键字最小的记录放置到已排序表的最前位置,直到全部排完。内层循环走一遍最小数就在最前面了(所以内层j=i+1);
简单的选择排序
1、基本思想:在要排序的一组数中,选出最小的一个数与第一个位置的数交换;然后在剩下的数当中再找最小的与第二个位置的数交换,如此循环到倒数第二个数和最后一个数比较为止。a[j]与min比较
选择排序实现:遇到最小的才换
SelectionSort

找到最小数来与第一个位置的数交换,然后找次最小数跟第二个位置交换。用变量min记住目前最小数跟其他值比较到最后,min就是最小。n记住最小数的索引,到最后也好交换两个数的位置。
核心方法:
private void sort(int[] array){
//对array进行选择排序
//选择排序:每次选择出最小的数放到前面,遍历一次找出一个最小值
int len=array.length;
for(int i=0;i<len;i++){
int min=array[i];
int minindex=i;
for(int j=i+1;j<len;j++){//找出最小值
if(array[j]<min){
min = array[j];
minindex=j;
}
}
//互换位置
array[minindex]=array[i];
array[i]=min;
}
}
快速排序
其他
使用redis实现分布式锁
使用set方法实现

浙公网安备 33010602011771号