JAVA 基础 -- 基础不牢,地动山摇
子类应该比 父类更为 开放 (public protected default private)
子类方法不能比父类抛出更高异常( 可以为父类方法异常本身或者其异常的子类)
关于类加载和初始化相关的案例
总的顺序是:先父类后子类,先静态后动态,属性和代码块的初始化遵循正常的出场顺序无论是静态还是动态,但是他们总是先于构造器执行。但是还是需要通过题目的学习来加深我们的理解。
package ooptest;
public class StaticDemo6 {
public static void main(String[] args) {
new SB();
}
}
class SA {
D d;
static {
System.out.println("A 1"); // 1.先从父类的静态开始
}
{
System.out.println("A 2"); // 5.此处开始new对象(非静态相关)
d = new D();// 6.顺序执行
}
public SA() {
System.out.println("A 3"); // 10.此时调用完了自己的非静态代码块来到了构造器
}
}
class SB extends SA {
static C c = new C(); // 2.调用完了父类的静态相关来到子类的静态相关
static {
System.out.println("B 1"); // 4.接着按照顺序来调用自己的静态代码块 ,到此子类的所有静态都执行完毕接下来将会执行非静态相关
}
{
System.out.println("B 2"); // 11.父类的构造器调用完成调用子类的非静态块
}
public SB() {
System.out.println("B 3"); // 12.调用完了自己的非静态块调用自己的构造方法
}
}
class C {
public C() {
System.out.println("C"); // 3.C没有父类与静态直接调用自己的构造器 // 8.
}
}
class D extends C {// 7. 来到了D但是D有自己的父类所以到达C类
public D() {
System.out.println("D");// 9.调用完了父类的构造器会来到子类的构造器
}
}
package com.ghc.mmall.concurrency.test;
class ClassA {
public static ClassA classa = new ClassA();
static{
System.out.println("ClassA的静态代码块");
}
public ClassA(){
System.out.println("ClassA的构造方法");
}
{
System.out.println("ClassA的构造代码块");
}
}
class ClassB extends ClassA{
static{
System.out.println("ClassB的静态代码块");
}
public ClassB(){
System.out.println("ClassB的构造方法");
}
{
System.out.println("ClassB的构造代码块");
}
public static ClassC classc = new ClassC();
public void excute(){
System.out.println("执行方法");
}
}
class ClassC {
public ClassC(){
System.out.println("ClassC的构造方法");
}
}
public class Test {
static {
System.out.println("Test的静态代码块");
}
public static void main(String[] args) {
System.out.println("执行main方法");
ClassB b = new ClassB();
b.excute();
}
}
可以单步调试 看输出打印结果
Test的静态代码块
执行main方法
ClassA的构造代码块
ClassA的构造方法
ClassA的静态代码块
ClassB的静态代码块
ClassC的构造方法
ClassA的构造代码块
ClassA的构造方法
ClassB的构造代码块
ClassB的构造方法
执行方法
深度加载知识
无论如何类的加载都
①先进行解析(也就是声明静态变量但是不去初始化),也就是将静态变量放入方法区并且标记,标记一个值0。相当于只定义没有赋值。
②当所有的解析都过去的时候才进行初始化,初始化就是按照出场顺序来执行静态代码块和检查静态变量那里是否赋值值,如果有值得话那么就赋值,没有的话那么就将标记值赋值给静态变量。
注意:标记状态的值相当于无值它不可以直接参加运算但是可以间接的使用标记的值。类名调用。
class Demo {
public static void main(String[] args) {
System.out.println(Demo.i);
}
static {
i = 7; // 1
}
static int i; // 2
}
结果是 7
如果没有定义环境变量classpath,java启动jvm后,会在当前目录下查找要运行的类文件;
如果指定了classpath,那么会在指定的目录下查找要运行的类文件。
还会在当前目录找吗?两种情况:
1):如果classpath的值结尾处有分号,在具体路径中没有找到运行的类,会默认在当前目录再找一次。
2):如果classpath的值结果出没有分号,在具体的路径中没有找到运行的类,不会再当前目录找。
一般不指定分号,如果没有在指定目录下找到要运行的类文件,就报错,这样可以调试程序。
**级别从低到高为:**byte,char,short(这三个平级)–>int–>float–>long–>double
Java语言规范建议按以下顺序列出修饰符:
1. Annotations 2. public 3. protected 4. private 5. abstract 6. static 7. final 8. transient 9. volatile 10. synchronized 11. native 12. strictfp
// 短路与非短路 验证
public class Hello {
public static boolean compare(int n1, int n2){
System.out.println(n1+" --> "+n2);
return n1>n2;
}
public static void main(String [] args){
System.out.println(compare(2,3) & compare(4,3)); // 非短路
System.out.println(compare(2,3) && compare(4,3)); // 短路
}
}
产生某个区间内的 随机数
package com.ghc.test;
import java.security.SecureRandom;
import java.util.Random;
import java.util.logging.Logger;
import java.util.logging.Level;
public class Test {
private static final Logger logger = Logger.getGlobal();
public static void main(String [] args){
logger.info("start log info...");
double res = getRand(1,10);
double res2 = getRand2(11,20);
System.out.println(res2);
double secureRes = getSecureRand(20,30);
System.out.println(secureRes);
}
public static double getRand(long min, long max){
long gap = max - min;
return (long)(Math.random() * gap + min);
}
public static double getRand2(int min, int max){
final Random random = new Random();// 默认种子值为 时间戳
int randInt = random.nextInt(max);
double res = randInt<min?randInt+min:randInt;
return res;
}
public static double getSecureRand(int min, int max){
SecureRandom secureRandom = new SecureRandom();
int randInt = secureRandom.nextInt(max);
int res = randInt<min?randInt+min:randInt;
return res;
}
}
字符串
String s = new String(new char[]{'h','e','l','l'});
System.out.println(s.hashCode()); // 3198781
String s2 = s.toUpperCase();
System.out.println(s2.hashCode()); // 2213693
System.out.println(s == s2); // false
System.out.println(s.equalsIgnoreCase(s2)); //true
从表面上看,两个字符串用==和equals()比较都为true,但实际上那只是Java编译器在编译期,
会自动把所有相同的字符串当作一个对象放入常量池,自然s1和s2的引用就是相同的。
String 的 getBytes 方法 等同于 python 的 encoding , 而 new String("中文","UTF-8") 就是 decode 了
java 的 日志最常用的 还是 org.apache.commons.logging
在 开发阶段不需要引入 Log4j , 尽管 Log4j 是最流行的日志框架,但是使用 commons logging 能够非常容易地切换日志框架
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
只有当我们要对 log4j 的接口进行扩展时候才需要引入 log4j ,否则 使用 commons logging 足矣
import org.apache.log4j.Logger;
泛型
泛型一般应用于 集合类型中,如果我们不使用泛型则会变成 Object ,在源码编写时候需要强转,虽然编译后泛型也会被擦除,但是在源码编写时后我们不需要强转
E — Element,常用在java Collection里,如: List<E>,Iterator<E>,Set<E>
K,V — Key,Value,代表Map的键值对
N — Number,数字
T — Type,类型,如String,Integer等等
// 因为泛型在编译后就会被擦除, 所以无法被直接实例化
class GenericsA<T>
{
T t = new T(); // Error
}
type erasure 泛型擦拭 ,所有泛型最终在编译器内都认为是 Object ,所以基本数据类型不能用作泛型,编译后的字节码中不包含任何泛型信息是 Object 然后 (String) obj 这样强转
所以我们也不能用 p isinstanceof Pair<Integer>.class 或者 用 new T() 这样的方式来编码。
扔掉引用意味着 安全
package com.ghc.test;
import org.junit.Test;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
public class Pair <T extends Number> {
private T first;
private T last;
Pair(T first, T last){
this.first = first;
this.last = last;
}
public Pair(){super();}
public void setFirst(T first){
this.first = first;
}
public void setLast(T last){
this.last = last;
}
public T getFirst(){
return this.first;
}
public T getLast(){
return this.last;
}
public static <K extends Number> Pair createPair(K k1, K k2){
return new Pair<K>(k1,k2);
}
@Override
public String toString(){
return "Pair("+this.getFirst()+","+this.getLast();
}
@Test
public void test(){
Pair<Integer> integerPair = new Pair<>(1,2);
Pair<Float> floatPair = new Pair<>(1f,2f);
System.out.println(integerPair.getFirst());
System.out.println(integerPair.getClass() == Pair.class);
System.out.println(floatPair.getClass() == Pair.class);
// 反射 获取 父类泛型
Class<IntegerPair> clazz = IntegerPair.class;
Type t = clazz.getGenericSuperclass();
if(t instanceof ParameterizedType){
ParameterizedType parameterizedType = (ParameterizedType) t;
Type [] types = parameterizedType.getActualTypeArguments();
for(Type type:types){
System.out.println(type.getTypeName());
}
}
String[] results = getSuperClassGenericType(IntegerPair.class);
for(String res:results){
System.out.println(res);
}
}
public static<C> String [] getSuperClassGenericType(Class<C> clazz){
Type superType = clazz.getGenericSuperclass();
List<String> typesStrList = new ArrayList<>(1);
if(superType instanceof ParameterizedType){
Type[] types = ((ParameterizedType) superType).getActualTypeArguments();
for(Type type:types){
typesStrList.add(type.getTypeName());
}
}
String [] results = new String[typesStrList.size()];
return typesStrList.toArray(results);
}
}
class IntegerPair extends Pair<Integer>{
}
// 调用时候 修改方法签名 ? extends Number
class PairHelper{
static int add(Pair<? extends Number> p){
// 在 get 时候 可以变得灵活易用
Number first = p.getFirst();
Number last = p.getLast();
// p.setFirst(first); extends 上界通配符 允许调用 get 方法,却不允许 调用 set 方法 除了 null
return first.intValue() + last.intValue();
}
/* static int add(Pair<? super Integer> p){
// 这样 泛型擦除后 就会 是同样的函数了
}*/
static void set_super(Pair<? super Integer> p){
// 在 set 时候 可以 变得灵活易用
//Integer integer = p.getFirst(); // 下边界的 泛型 无法使用 get 仅仅可以用 Object 接收 可以使用 set 具体原因看函数签名
Object integer = p.getFirst();
/*Incompatible types.
Required:
java.lang.Integer
Found:
capture<? super java.lang.Integer>*/
p.setFirst(Integer.valueOf(2));// Integer 的超类肯定可以接收一个 Integer 对象
}
}
// 最后来一个综合例子 就可以理解 了
class MyCollections{
public static <T> void copy(List<? extends T> src , List<? super T> dest){
for(T t:src){
dest.add(t);
}
}
}
枚举类型 本质是 enum class
public enum WeekDayEnum {
MONDAY("monday",1),
TUESDAY("tuesday",2),
WEDNESDAY("Wednesday",3),
THURSDAY("thursday",4),
FRIDAY("friday",5);
private String dayName;
private int id;
// 枚举类构造方法必须声明为 private
private WeekDayEnum(String dayName, int id){
this.dayName = dayName;
this.id = id;
}
public String getDayName(){
return this.dayName;
}
}
WeekDayEnum enum1 = WeekDayEnum.WEDNESDAY;
switch(enum1){
case MONDAY:
case TUESDAY:
case WEDNESDAY:
case THURSDAY:
case FRIDAY:
System.out.println("weekday -> "+enum1.getDayName());
break;
default:System.out.println("weekend");
}
利用 name() 方法获取常量定义的字符串 而不要使用 toString 方法因为 toString 可以被复写
JAVA 注解全接触
注解准备知识:看到一个注解的源码
@Target 使用注解时的作用的目标
ElementType.ANNOTATION_TYPE 可以【给一个注解进行注解】
ElementType.CONSTRUCTOR 可以给构造方法进行注解
ElementType.FIELD 可以给属性进行注解
ElementType.LOCAL_VARIABLE 可以给局部变量进行注解
ElementType.METHOD 可以给方法进行注解
ElementType.PACKAGE 可以给一个包进行注解
ElementType.PARAMETER 可以给一个方法内的参数进行注解
ElementType.TYPE 可以给一个类型进行注解,比如类、接口、枚举
@Retention 注解生效的作用域
RetentionPolicy.SOURCE 注解只在源码阶段保留,在编译器进行编译时它将被丢弃忽视。
RetentionPolicy.CLASS 注解只被保留到编译进行的时候,它并不会被加载到 JVM 中。
RetentionPolicy.RUNTIME 注解可以保留到程序运行的时候,它会被加载进入到 JVM 中,所以在程序运行时可以获取到它们。
@Documented 这个元注解肯定是和文档有关。它的作用是能够将注解中的元素包含到 Javadoc 中去
@Inherited 表明该注解如果注解了父类,那么子类就会继承这些注解(即使子类没有被任何注解)
@Repeatable 表明该注解可以在同一目标上多次使用,就像可以贴多个标签
@AliasFor 注解属性的别名 (给注解的这个属性赋值就等于给AliasFor的这个属性赋值)
注解可以有多个属性,没有指明key的,会【默认给注解属性的value()赋值】
自定义注解
Table 注解
import java.lang.annotation.*;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Table {
String value();
}
Column 注解
import java.lang.annotation.*;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Column {
String value();
}
Filter 类里 使用注解
package com.ghc.hbase.api;
@Table("user")
public class Filter {
@Column("id")
private int id;
@Column("userName")
private String userName;
@Column("nickName")
private String nickName;
@Column("age")
private int age;
@Column("city")
private String city;
@Column("email")
private String email;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getNickName() {
return nickName;
}
public void setNickName(String nickName) {
this.nickName = nickName;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}
实现 反射 orm
package com.ghc.hbase.api;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class Test {
public static void main(String [] args) throws Exception{
Filter filter1 = new Filter();
filter1.setAge(18);
filter1.setCity("ny");
filter1.setEmail("test@gmail.com");
filter1.setId(666);
filter1.setUserName("Frank");
filter1.setNickName("Rocket");
System.out.println(query(filter1));
}
public static String query(Filter filter) throws Exception{
Class clazz = filter.getClass();
Table table = (Table)clazz.getAnnotation(Table.class);
String tableName = table.value();
StringBuilder sb = new StringBuilder();
sb.append("select * from ")
.append(tableName)
.append(" where 1=1 ");
Field [] fields = clazz.getDeclaredFields();
Method [] methods = clazz.getDeclaredMethods();
for(Field field:fields){
Object obj = null;
Column column = (Column)field.getAnnotation(Column.class);
for(Method method:methods){
// System.out.println(field.getName() + "---> "+ method.getName());
String fieldName = field.getName();
if(("get"+ fieldName).equalsIgnoreCase(method.getName())){
obj = method.invoke(filter,null) ;
String value = obj instanceof String?"\""+obj+"\"":String.valueOf(obj);
sb.append(" and "+column.value()+"="+value);
}
}
}
return sb.toString();
}
}
java concurrence 开始, 但不是结束
package com.ghc.concurrence.ConcurrentPractice;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
public class ThreadPoolPractice {
private static int threadTotal = 200;
private static int clientTotal = 5000;
private static int count = 0;
public static void main(String [] args){
ExecutorService executorService = Executors.newCachedThreadPool();
final Semaphore semaphore = new Semaphore(threadTotal);
for(int i=0;i<clientTotal;i++){
executorService.execute(()->{
try{
semaphore.acquire();
add();
semaphore.release();
}catch(Exception e){
e.printStackTrace();
}
});
}
executorService.shutdown();
System.out.println(count);
}
private static void add(){
count++;
}
}
package com.ghc.concurrence.ConcurrentPractice;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import java.util.HashMap;
public class MapExample {
private static Map<Integer, Integer> map = new HashMap<Integer,Integer>();
private static int threadNum = 200;
private static int clientNum = 5000;
public static void main(String[] args){
ExecutorService exec = Executors.newCachedThreadPool();
final Semaphore semaphore = new Semaphore(threadNum);
for(int index = 0;index < clientNum; index++){
final int threadNum = index;
exec.execute(()->{
try {
semaphore.acquire();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
func(threadNum);
semaphore.release();
});
}
exec.shutdown();
System.out.println(map.size());
}
public static void func(int threadNum){
map.put(threadNum, threadNum);
}
}
编码解码
String info = "中文";
byte[] infoBytes = info.getBytes(StandardCharsets.UTF_8);
String decodeInfo = new String(infoBytes,StandardCharsets.UTF_8);
System.out.println("are they equals ?: "+Objects.equals(info,decodeInfo));
类型提升问题
byte a = 127;
byte b = 127;
a = a+b; // 编译不通过, + 操作有会提升为 int 需要 强转
a = (byte) (a+b);
a+=b;
System.out.println(a) ; // -2
log4j2.xml
<?xml version="1.0" encoding="UTF-8"?>
<!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL -->
<!--Configuration后面的status,这个用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,你会看到log4j2内部各种详细输出-->
<!--monitorInterval:Log4j能够自动检测修改配置 文件和重新配置本身,设置间隔秒数-->
<configuration status="WARN" monitorInterval="30">
<!--先定义所有的appender-->
<appenders>
<!--这个输出控制台的配置-->
<console name="Console" target="SYSTEM_OUT">
<!--输出日志的格式-->
<PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"/>
</console>
<!--文件会打印出所有信息,这个log每次运行程序会自动清空,由append属性决定,这个也挺有用的,适合临时测试用-->
<File name="log" fileName="log/test.log" append="false">
<PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %class{36} %L %M - %msg%xEx%n"/>
</File>
<!-- 这个会打印出所有的info及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档-->
<RollingFile name="RollingFileInfo" fileName="${sys:user.home}/logs/info.log"
filePattern="${sys:user.home}/logs/$${date:yyyy-MM}/info-%d{yyyy-MM-dd}-%i.log">
<!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
<ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"/>
<Policies>
<TimeBasedTriggeringPolicy/>
<SizeBasedTriggeringPolicy size="100 MB"/>
</Policies>
</RollingFile>
<RollingFile name="RollingFileWarn" fileName="${sys:user.home}/logs/warn.log"
filePattern="${sys:user.home}/logs/$${date:yyyy-MM}/warn-%d{yyyy-MM-dd}-%i.log">
<ThresholdFilter level="warn" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"/>
<Policies>
<TimeBasedTriggeringPolicy/>
<SizeBasedTriggeringPolicy size="100 MB"/>
</Policies>
<!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件,这里设置了20 -->
<DefaultRolloverStrategy max="20"/>
</RollingFile>
<RollingFile name="RollingFileError" fileName="${sys:user.home}/logs/error.log"
filePattern="${sys:user.home}/logs/$${date:yyyy-MM}/error-%d{yyyy-MM-dd}-%i.log">
<ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"/>
<Policies>
<TimeBasedTriggeringPolicy/>
<SizeBasedTriggeringPolicy size="100 MB"/>
</Policies>
</RollingFile>
</appenders>
<!--然后定义logger,只有定义了logger并引入的appender,appender才会生效-->
<loggers>
<!--过滤掉spring和mybatis的一些无用的DEBUG信息-->
<logger name="org.springframework" level="INFO"></logger>
<logger name="org.mybatis" level="INFO"></logger>
<root level="all">
<appender-ref ref="Console"/>
<appender-ref ref="RollingFileInfo"/>
<appender-ref ref="RollingFileWarn"/>
<appender-ref ref="RollingFileError"/>
</root>
</loggers>
</configuration>
23)Java 中 ++ 操作符是线程安全的吗?
不是线程安全的操作。它涉及到多个指令,如读取变量值,增加,然后存储回内存,这个过程可能会出现多个线程交差。
clone 是 Object 类的 本地方法
Number number = new Float(123);
Integer integer = (Integer) number;
// Exception in thread "main" java.lang.ClassCastException: java.lang.Float cannot be cast to java.lang.Integer
我们能在 Switch 中使用 String 吗?
从 Java 7 开始,我们可以在 switch case 中使用字符串,但这仅仅是一个语法糖。内部实现在 switch 中使用字符串的 hash code。
32)Serial 与 Parallel GC之间的不同之处?
Serial 与 Parallel 在GC执行的时候都会引起 stop-the-world。它们之间主要不同 serial 收集器是默认的复制收集器,执行 GC 的时候只有一个线程,而 parallel 收集器使用多个 GC 线程来执行。
38)32 位 JVM 和 64 位 JVM 的最大堆内存分别是多数?
理论上说上 32 位的 JVM 堆内存可以到达 2^32,即 4GB,但实际上会比这个小很多。不同操作系统之间不同,如 Windows 系统大约 1.5 GB,Solaris 大约 3GB。64 位 JVM允许指定最大的堆内存,理论上可以达到 2^64,这是一个非常大的数字,实际上你可以指定堆内存大小到 100GB。甚至有的 JVM,如 Azul,堆内存到 1000G 都是可能的。
Runtime runtime = Runtime.getRuntime();
Long freeMemory = runtime.freeMemory();
Long totalMemory = runtime.totalMemory();
Long maxMemory = runtime.maxMemory();
System.out.println("freeMemory: "+freeMemory/(1024*1024)+"MB");
System.out.println("totalMemory: "+totalMemory/(1024*1024)+"MB");
System.out.println("maxMemory: "+maxMemory/(1024*1024)+"MB");
/* freeMemory: 240MB
totalMemory: 245MB
maxMemory: 3625MB*/
41)你能保证 GC 执行吗?
不能,虽然你可以调用 System.gc() 或者 Runtime.getRuntime().gc(),但是没有办法保证 GC 的执行。
42)怎么获取 Java 程序使用的内存?堆使用的百分比?
可以通过 java.lang.Runtime 类中与内存相关方法来获取剩余的内存,总内存及最大堆内存。通过这些方法你也可以获取到堆使用的百分比及堆内存的剩余空间。Runtime.freeMemory() 方法返回剩余空间的字节数,Runtime.totalMemory() 方法总内存的字节数,Runtime.maxMemory() 返回最大内存的字节数。
43)Java 中堆和栈有什么区别?
JVM 中堆和栈属于不同的内存区域,使用目的也不同。栈常用于保存方法帧和局部变量,而对象总是在堆上分配。栈通常都比堆小,也不会在多个线程之间共享,而堆被整个 JVM 的所有线程共享。 Difference between stack and heap memory in Java
finally 中 不建议 抛出异常,因为 finally 中如果有异常出现,那么 finally 总是优先于 try 代码块中的 代码执行 ,这时候 try 中的 异常 如果没有做 特殊处理是不会被 捕获到的,因为
finally 先抛了一个异常,那么 我们就很可能丢失异常, 这时候 可以在 finally 外部定义一个 Super。。。。 addSusp。。。, 但仍然不建议这么做。
47)Java 中的编译期常量是什么?使用它又什么风险?
公共静态不可变(public static final )变量也就是我们所说的编译期常量,这里的 public 可选的。实际上这些变量在编译时会被替换掉,因为编译器知道这些变量的值,并且知道这些变量在运行时不能改变。这种方式存在的一个问题是你使用了一个内部的或第三方库中的公有编译时常量,但是这个值后面被其他人改变了,但是你的客户端仍然在使用老的值,甚至你已经部署了一个新的jar。为了避免这种情况,当你在更新依赖 JAR 文件时,确保重新编译你的程序。
// 打印 数组 Arrays.toString(arr) or Arrays.deepToString(arr)
Person p1 = new Person("a","f",18);
Person p2 = new Person("b","m",19);
Person p3 = new Person("c","f",20);
Person [] p = new Person[]{p1,p2,p3};
System.out.println(Arrays.toString(p));
Java 中的 TreeMap 是使用红黑树实现的。
HashTable 线程安全不可以用 null 做key , 而 HashMap 没有同步策略可以用 null 做 key ,同样需要 覆写 equals hashCode 方法
HashMap<String, Person> personHashMap = new HashMap<>();
personHashMap.put(null,new Person("a","f",18));
personHashMap.put(null,new Person("b","m",20));
System.out.println(personHashMap); // {null=b|20|m}
List<Person> personList = new ArrayList<Person>();
personList.add(new Person("a","f",18));
personList.add(new Person("b","m",18));
personList.add(new Person("c","f",18));
Iterator<Person> it = personList.iterator();
while(it.hasNext()){
Person p = it.next();
if("c".equals(p.getName())){
System.out.println("start to remove "+p.getName());
personList.remove(p); // 这是错误的 会出现 Exception in thread "main" java.util.ConcurrentModificationException
it.remove(); // 这是对的
}
}
System.out.println(personList);
59)我们能自己写一个容器类,然后使用 for-each 循环吗?
可以,你可以写一个自己的容器类。如果你想使用 Java 中增强的循环来遍历,你只需要实现 Iterable 接口。如果你实现 Collection 接口,默认就具有该属性
这一点 就跟 python 中 实现 迭代器协议 __iter__ 一个道理
61)有没有可能两个不相等的对象有有相同的 hashcode?
有可能,两个不相等的对象可能会有相同的 hashcode 值,这就是为什么在 hashmap 中会有冲突。相等 hashcode 值的规定只是说如果两个对象相等,必须有相同的hashcode 值,但是没有关于不相等对象的任何规定。
所以我们在 覆写 equals 方法的 同时要 覆写 hashCode 方法,减少 hash 碰撞
65)为什么在重写 equals 方法的时候需要重写 hashCode 方法?(答案)
因为有强制的规范指定需要同时重写 hashcode 与 equal 是方法,许多容器类,如 HashMap、HashSet 都依赖于 hashcode 与 equals 的规定
public static void main(String [] args) {
test();
}
public static void add(Byte b)
{
b = b++;
}
public static void test()
{
Byte a = 127;
Byte b = 127;
add(++a);
System.out.print(a + " ");
add(b);
System.out.print(b + "");
}
// -128 127
一道算法题
public static void main(String [] args) {
/*假设有一个数组 A ,int[] A = { 1 , 3 , -1 ,0 , 2 , 1 , -4 , 2 , 0 ,1 ... N};
原来是需要查出大于0的数组,但是由于传参错误或者其他原因,导致查出0和负数了,
现在要求在不使用新数组和新集合的情况下(即只使用这个A数组,因数组数据比较大,
且只能用一次循环) 实现正数放到数组的前面,小于等于0的数放到数组的末尾*/
int[] arr = { 5,6,7,8,9,10,11,-1,-2,-3,-4};
int base = 0;
int midIndex = arr.length/2;
int midValue = arr[midIndex];
arr[midIndex] = base; // 先将 最中间位置 换位 0
int start = 0, end = arr.length-1;
while(start<end){
System.out.println("start: "+start+" | end: "+end);
System.out.println(Arrays.toString(arr));
if(arr[start] < base) {start++;
if(arr[end] < base){
swap(arr,midIndex,end);
}
end--;
}
else if(arr[start]>base){
swap(arr,start,midIndex);
if(arr[end] < base){
swap(arr,start,end);
}
start++;
end--;
}else{
break;
}
}
System.out.println("********");
System.out.println(Arrays.toString(arr));
if(arr[0] == base){
swap(arr,0,end+1);
}
if(arr[arr.length-1] == base){
swap(arr,start-1,arr.length-1);
}
// 将中间那个值还原回去
arr[start-1] = midValue;
System.out.println(Arrays.toString(arr));
}
线程
package com.ghc.test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class Main {
public static void main(String[] args) throws Exception {
List<Thread> threads = new ArrayList<>();
for (String name : Arrays.asList("Bob", "Alice", "Tom")) {
threads.add(new HelloThread(name));
}
System.out.println("START");
for (Thread t : threads) {
t.start();
//t.join(); //在这里 join 的话,会等待一个 线程完成才会开启下一个 线程
}
for(Thread t: threads){
t.join();
}
System.out.println("END");
}
}
class HelloThread extends Thread {
String name;
public HelloThread(String name) {
this.name = name;
}
@Override
public void run() {
System.out.println("Hello, " + name + "!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
System.out.println("Goodbye, " + name + "!");
}
}
多线程共享数据安全 -> 多次运行后发现一个线程副本修改的数据还未来得及写入主存就被另一个线程读取,就会产生共享数据最后不为 0 的情况
main--> start...
Thread-0--> running...
Thread-1--> running...
main--> end...
ThreadSafe.COUNTER: 10
package com.ghc.test;
import java.util.ArrayList;
import java.util.List;
/**
* @author :Frank Li
* @date :Created in 2019/7/9 14:32
* @description:${description}
* @modified By:
* @version: $version$
*/
public class ThreadSafe {
public static int COUNTER = 0;
public static final int LOOP = 10000;
public static void main(String [] args) throws Exception{
System.out.println(Thread.currentThread().getName()+"--> start...");
Thread t1 = new Thread(new AddThread());
Thread t2 = new SubstractThread();
List<Thread> threadList = new ArrayList<>();
threadList.add(t1);
threadList.add(t2);
for(Thread t:threadList){
t.start();
}
for(Thread t:threadList){
t.join();
}
System.out.println(Thread.currentThread().getName()+"--> end...");
System.out.println("ThreadSafe.COUNTER: "+ThreadSafe.COUNTER);
}
}
class AddThread implements Runnable{
@Override
public void run(){
System.out.println(Thread.currentThread().getName()+"--> running...");
for(int i=0;i<ThreadSafe.LOOP;i++){
ThreadSafe.COUNTER += 1; // 非原子性操作
}
}
}
class SubstractThread extends Thread{
@Override
public void run(){
System.out.println(Thread.currentThread().getName()+"--> running...");
for(int i=0;i<ThreadSafe.LOOP;i++){
ThreadSafe.COUNTER -= 1; // 非原子性操作
}
}
}
对于非原子性操作需要加同步锁
package com.ghc.test;
import java.util.ArrayList;
import java.util.List;
/**
* @author :Frank Li
* @date :Created in 2019/7/9 14:32
* @description:${description}
* @modified By:
* @version: $version$
*/
public class ThreadSafe {
public static int COUNTER = 0;
public static final int LOOP = 10000;
public static void main(String [] args) throws Exception{
System.out.println(Thread.currentThread().getName()+"--> start...");
Thread t1 = new Thread(new AddThread());
Thread t2 = new SubstractThread();
List<Thread> threadList = new ArrayList<>();
threadList.add(t1);
threadList.add(t2);
for(Thread t:threadList){
t.start();
}
for(Thread t:threadList){
t.join();
}
System.out.println(Thread.currentThread().getName()+"--> end...");
System.out.println("ThreadSafe.COUNTER: "+ThreadSafe.COUNTER);
}
}
class AddThread implements Runnable{
@Override
public void run(){
System.out.println(Thread.currentThread().getName()+"--> running...");
for(int i=0;i<ThreadSafe.LOOP;i++){
synchronized (Main.class) {
ThreadSafe.COUNTER += 1; // 非原子性操作 包括 ILOAD IADD ISTORE 三个操作
}
}
}
}
class SubstractThread extends Thread{
@Override
public void run(){
System.out.println(Thread.currentThread().getName()+"--> running...");
for(int i=0;i<ThreadSafe.LOOP;i++){
synchronized (Main.class) {
ThreadSafe.COUNTER -= 1; // 非原子性操作 包括 ILOAD IADD ISTORE 三个操作
}
}
}
}
原子操作
可重入锁
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Counter {
private final Lock lock = new ReentrantLock();
private int value;
public void inc(int m){
if(lock.tryLock()){
// 如果获取到了锁才进入
try{
this.value+=m;
}finally{
lock.unlock();
}
}
}
public void dec(int m){
if(lock.tryLock()){
try{
this.value-=m;
}finally {
lock.unlock();
}
}
}
}
别人的
java-study
java面试知识点总结(持续更新,欢迎star,fork补充)
抽象类和接口的比较
相同点:
都不能被实例化
都包含抽象方法,这些抽象方法用于描述系统能提供哪些服务,而这些服务是由子类来提供实现的
在系统设计上,两者都代表系统的抽象层,当一个系统使用一棵继承树上的类时,应该尽量把引用变量声明为继承树的上层抽象类型,这样可以提高两个系统之间的松耦合
不同点:
在抽象类中可以为部分方法提供默认的实现,从而避免在子类中重复实现它们;但是抽象类不支持多继承。接口不能提供任何方法的实现,但是支持多继承。
接口代表了接口定义者和接口实现者的一种契约;而抽象类和具体类一般而言是一种继承的关系,即两者在概念本质上是不同。
内部类
1.内部类可以很好的实现隐藏,一般的非内部类,是不允许有 private 与protected权限的,但内部类可以
2.内部类拥有外围类的所有元素的访问权限
3.可实现多重继承
静态内部类
静态内部类,定义在类中,任何方法外,用static定义;静态内部类只能访问外部类的静态成员。
生成(new)一个静态内部类不需要外部类成员:这是静态内部类和成员内部类的区别。静态内部类的对象可以直接生成:Outer.Inner in=new Outer.Inner();而不需要通过生成外部类对象来生成。这样实际上使静态内部类成为了一个*类。可以定义私有静态内部类。
静态内部类与非静态的内部类区别:
是否可以创建静态的成员方法与成员变量(静态内部类可以创建静态的成员而非静态的内部类不可以)、对于访问外部类的成员的限制(静态内部类只可以访问外部类中的静态成员变量与成员方法而非静态的内部类即可以访问静态的也可以访问非静态的外部类成员方法与成员变量)。这两个差异是静态内部类与非静态外部类最大的差异,也是静态内部类之所以存在的原因
子类为什么不能重写父类的静态方法
重写"只能适用于实例方法.不能用于静态方法.对于静态方法,只能隐藏(形式上被重写了,但是不符合的多态的特性),“重写”是用来实现多态性的,只有实例方法是可以实现多态,而静态方法无法实现多态
自动装箱(autoboxing)与拆箱(unboxing)
自动装箱是 Java 编译器在基本数据类型和对应的对象包装类型之间做的一个转化。
比如:把 int 转化成 Integer,double 转化成 Double等,反之就是自动拆箱。
Integer a=1;//这就是一个自动装箱,如果没有自动装箱的话,需要这样Integer a=new Integer(1)
int b=a;//这就是一个自动拆箱,如果没有自动拆箱的话,需要这样:int b=a.intValue()
这样就能看出自动装箱和自动拆箱是简化了基本数据类型和相对应对象的转化步骤
Java中的自动装箱与拆箱
Java中为什么要为基本类型提供封装类呢?
一是为了在各种类型间转化,通过各种方法的调用。否则你无法直接通过变量转化。
比如,现在int要转为String
int a=0;
String result=Integer.toString(a);
二是比如我现在要用泛型
List nums;
这里<>需要类。如果你用int。它会报错的
Java 创建对象的几种方式
(1) 用 new 语句创建对象,这是最常见的创建对象的方法
(2) 运用反射手段,调用 java.lang.Class 或者 java.lang.reflect.Constructor 类的 newInstance() 实例方法
(3) 调用对象的 clone() 方法
(4) 运用反序列化手段,调用 java.io.ObjectInputStream 对象的 readObject() 方法
(1)和(2)都会明确的显式的调用构造函数;(3)是在内存上对已有对象的影印,所以不会调用构造函数 (4)是从文件中还原类的对象,也不会调用构造函数。
序列化(Serializable )与反序列化(Deserialize)
对象序列化(Serializable)是指将对象转换为字节序列的过程,而反序列化则是根据字节序列恢复对象的过程。
序列化一般用于以下场景:
1.永久性保存对象,保存对象的字节序列到本地文件中;
2.通过序列化对象在网络中传递对象;
3.通过序列化在进程间传递对象。
只有实现了Serializable和Externalizable接口的类的对象才能被序列化,
java.io.ObjectOutputStream代表对象输出流,它的writeObject(Objectobj)方法可对参数指定的obj对象进行序列化,把得到的字节序列写到一个目标输出流中。
java.io.ObjectInputStream代表对象输入流,它的readObject()方法从一个源输入流中读取字节序列,再把它们反序列化为一个对象,并将其返回。
覆盖 (Override) 和重载 (Overload)
Java 中的方法重载发生在同一个类里面两个或者是多个方法的方法名相同但是参数不同的情况;
方法覆盖是说子类重新定义了父类的方法,方法覆盖必须有相同的方法名,参数列表和返回类型。
内存中的栈(stack)、堆(heap)和静态存储区的用法
通常我们定义一个基本数据类型的变量,一个对象的引用,还有就是函数调用的现场保存都使用内存中的栈空间;而通过new关键字和构造器创建的对象放在堆空间;程序中的字面量(literal)如直接书写的100、“hello”和常量都是放在静态存储区中。栈空间操作最快但是也很小,通常大量的对象都是放在堆空间,整个内存包括硬盘上的虚拟内存都可以被当成堆空间来使用。
String str = new String(“hello”);
上面的语句中 str 放在栈上,用 new 创建出来的字符串对象放在堆上,而“hello”这个字面量放在静态存储区。
强引用、弱引用、软引用、虚引用
强引用:如“Object obj = new Object()”,这类引用是 Java 程序中最普遍的。只要强引用还存在,垃圾收集器就永远不会回收掉被引用的对象。
软引用:它用来描述一些可能还有用,但并非必须的对象。在系统内存不够用时,这类引用关联的对象将被垃圾收集器回收。JDK1.2 之后提供了 SoftReference 类来实现软引用。
弱引用:它也是用来描述非需对象的,但它的强度比软引用更弱些,被弱引用关联的对象只能生存岛下一次垃圾收集发生之前。当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。在 JDK1.2 之后,提供了 WeakReference 类来实现弱引用。
虚引用:最弱的一种引用关系,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用关联的唯一目的是希望能在这个对象被收集器回收时收到一个系统通知。JDK1.2 之后提供了 PhantomReference 类来实现虚引用。
Java 7之基础 - 强引用、弱引用、软引用、虚引用
Java垃圾回收机制与引用类型
内存管理与垃圾回收
Java垃圾回收机制
在C++中,对象所占的内存在程序结束运行之前一直被占用,在明确释放之前不能分配给其它对象;而在Java中,当没有对象引用指向原先分配给某个对象的内存时,该内存便成为垃圾。JVM的一个系统级线程会自动释放该内存块。垃圾收集意味着程序不再需要的对象是"无用信息",这些信息将被丢弃。当一个对象不再被引用的时候,内存回收它占领的空间,以便空间被后来的新对象使用。事实上,除了释放没用的对象,垃圾收集也可以清除内存记录碎片。由于创建对象和垃圾收集器释放丢弃对象所占的内存空间,内存会出现碎片。碎片是分配给对象的内存块之间的空闲内存洞。碎片整理将所占用的堆内存移到堆的一端,JVM将整理出的内存分配给新的对象。
垃圾收集能自动释放内存空间,减轻编程的负担。这使Java虚拟机具有一些优点。首先,它能使编程效率提高。在没有垃圾收集机制的时候,可能要花许多时间来解决一个难懂的存储器问题。在用Java语言编程的时候,靠垃圾收集机制可大大缩短时间。其次是它保护程序的完整性, 垃圾收集是Java语言安全性策略的一个重要部份。垃圾收集的一个潜在的缺点是它的开销影响程序性能。Java虚拟机必须追踪运行程序中有用的对象,而且最终释放没用的对象。这一个过程需要花费处理器的时间。其次垃圾收集算法的不完备性,早先采用的某些垃圾收集算法就不能保证100%收集到所有的废弃内存。当然随着垃圾收集算法的不断改进以及软硬件运行效率的不断提升,这些问题都可以迎刃而解。
一般来说,Java开发人员可以不重视JVM中堆内存的分配和垃圾处理收集,但是,充分理解Java的这一特性可以让我们更有效地利用资源。同时要注意finalize()方法是Java的缺省机制,有时为确保对象资源的明确释放,可以编写自己的finalize方法。(引用自百度)
Java 垃圾收集机制
List,Map,Set
由Collection接口派生的两个接口是List和Set;
Vector非常类似ArrayList,但是Vector是同步的;
Stack继承自Vector,实现一个后进先出的堆栈,push和pop,还有peek方法得到栈顶的元素
Set是一种不包含重复的元素的Collection
Map没有继承Collection接口,Map提供key到value的映射,一个Map中不能包含相同的key,每个key只能映射一个 value
集合大家族
集合类之Vector和ArrayList
1,vector是线程同步的,所以它也是线程安全的,而arraylist是线程异步的,是不安全的。如果不考虑到线程的安全因素,一般用arraylist效率比较高。
2,如果集合中的元素的数目大于目前集合数组的长度时,vector增长率为目前数组长度的100%,而arraylist增长率为目前数组长度的50%.如过在集合中使用数据量比较大的数据,用vector有一定的优势。
3,如果查找一个指定位置的数据,vector和arraylist使用的时间是相同的,都是0(1),这个时候使用vector和arraylist都可以
集合类之Hashtable
添加数据使用put(key, value),取出数据使用get(key),这两个基本操作的时间开销为常数。
Hashtable通过initial capacity和load factor两个参数调整性能。通常缺省的load factor 0.75较好地实现了时间和空间的均衡。增大load factor可以节省空间但相应的查找时间将增大,这会影响像get和put这样的操作。
集合类之HashMap和Hashtable
HashMap Hashtable区别HashMap是Hashtable的轻量级实现(非线程安全的实现),效率上可能高于Hashtable。他们都完成了Map接口。HashMap允许null值作为key和value,而Hashtable不可以。
最大的不同是,Hashtable的方法是Synchronize的,而HashMap不是,在多个线程访问Hashtable时,不需要自己为它的方法实现同步,而HashMap 就必须为之提供外同步(Collections.synchronizedMap)。
迭代HashMap采用快速失败机制(不是迭代完成后才告诉你出错了),而Hashtable不是。迭代器的快速失败机制会抛出一个并发修改异常 (ConcurrentModificationException) ,应该仅用于检测程序错误。
HashMap之快速失败机制
我们知道java.util.HashMap不是线程安全的,因此如果在使用迭代器的过程中有其他线程修改了map,那么将抛出ConcurrentModificationException,这就是所谓fail-fast策略。这一策略在源码中的实现是通过modCount域,modCount顾名思义就是修改次数,对HashMap内容的修改都将增加这个值,那么在迭代器初始化过程中会将这个值赋给迭代器的expectedModCount。在迭代过程中,判断modCount跟expectedModCount是否相等,如果不相等就表示已经有其他线程修改了Map。modCount声明为volatile,保证线程之间修改的可见性。
hashcode的作用
Java中的hashCode方法就是根据一定的规则将与对象相关的信息(比如对象的存储地址,对象的字段等)映射成一个数值,这个数值称作为散列值。
如果集合中已经存在一万条数据或者更多的数据,如果采用equals方法去逐一比较,效率必然是一个问题。此时hashCode方法的作用就体现出来了,当集合要添加新的对象时,先调用这个对象的hashCode方法,得到对应的hashcode值,实际上在HashMap的具体实现中会用一个table保存已经存进去的对象的hashcode值,如果table中没有该hashcode值,它就可以直接存进去,不用再进行任何比较了;如果存在该hashcode值,就调用它的equals方法与新元素进行比较,相同的话就不存了,不相同就散列其它的地址,所以这里存在一个冲突解决的问题,这样一来实际调用equals方法的次数就大大降低了。
hashcode方法的作用
HashCode和equal方法
1、hashCode的存在主要是用于查找的快捷性,如Hashtable,HashMap等,hashCode是用来在散列存储结构中确定对象的存储地址的;
2、如果两个对象相同,就是适用于equals(java.lang.Object) 方法,那么这两个对象的hashCode一定要相同;
3、如果对象的equals方法被重写,那么对象的hashCode也尽量重写,并且产生hashCode使用的对象,一定要和equals方法中使用的一致,否则就会违反上面提到的第2点;
4、两个对象的hashCode相同,并不一定表示两个对象就相同,也就是不一定适用于equals(java.lang.Object)方法,只能够说明这两个对象在散列存储结构中,如Hashtable,他们“存放在同一个篮子里”。
HashCode和equal方法
用户线程(User Thread)与守护线程(Daemon Thread)
守护线程,是指用户程序在运行的时候后台提供的一种通用服务的线程。只要当前JVM实例中尚存在任何一个用户线程没有结束,守护线程就全部工作;只有当最后一个用户线程结束时,守护线程随着 JVM 一同结束工作。 守护线程最典型的应用就是 GC (垃圾回收器)。
JAVA并发编程——守护线程(Daemon Thread)
进程和线程的区别
一个进程对应一个程序的执行,而一个线程则是进程执行过程中的一个单独的执行序列,一个进程可以包含多个线程。线程有时候也被称为轻量级进程。
一个Java虚拟机的实例运行在一个单独的进程中,不同的线程共享Java虚拟机进程所属的堆内存。这也是为什么不同的线程可以访问同一个对象。线程彼此共享堆内存并保有他们自己独自的栈空间。这也是为什么当一个线程调用一个方法时,他的局部变量可以保证线程安全。但堆内存并不是线程安全的,必须通过显示的声明同步来确保线程安全。
排序算法
package com.algorithms;
import java.util.Arrays;
/**
* @author :Frank Li
* @date :Created in 2019/10/21 9:35
* @description:${description}
* @modified By:
* @version: $version$
*/
public class Sorter {
public static void main(String[] args) {
int [] arr = {10,9,8,7,6,5,4,3,2,1};
System.out.println(Arrays.toString(arr)+"\\n");
quickSort(arr, 0, arr.length-1);
System.out.println(Arrays.toString(arr));
}
public static void quickSort(int [] arr, int leftBound, int rightBound){
if(leftBound>=rightBound){return;}
int mid = partition(arr, leftBound, rightBound);
quickSort(arr, leftBound, mid-1);
quickSort(arr, mid+1, rightBound);
}
public static int partition(int[] arr, int leftBound, int rightBound){
int left = leftBound;
int base = rightBound - 1;
int right = rightBound;
while(left < right){
while(arr[left] < arr[base]) {left++;}
while(arr[right] > arr[base]) {right--;}
if(left<right){swap(arr, left, right);}
}
// 换比较的 基准值
if(arr[left]<arr[base]) {swap(arr, left, base);}
return left;
}
public static void swap(int[] arr, int left, int right){
int tmp = arr[left];
arr[left] = arr[right];
arr[right] = tmp;
}
}
interview
2017届春招秋招公司的面试题目
阿里内推(Android开发)
笔试
选择题:
快速排序
二叉树遍历
UML类图
问答题:
1.Java中final,finally,finalize的区别
2.hashmap的特性是什么,和hashtable的区别
3.java线程中sleep和wait方法区别
4.谈谈常用容器类的原理和应用场景
面试
1.一个文件中有100万个整数,由空格分开,在程序中判断用户输入的整数是否在此文件中。说出最优的方法
金山一面(Android开发)
1.arraylist与vector的区别
2.优化view
3.线程安全
4.实现树
5.内存限制
6.枚举
7.安卓性能优化工具
阿里面试(JAVA研发)
一面
1.写出知道的所有单例模式的写法
2.整数的二进制找出有几个1,效率要求最高
3.会场安排问题
4.如何用两个栈模拟一个队列
(前面四道考了快40分钟了)
5.TCP三次握手的过程
6.http了解吗?(这道我直接说没有深入研究web开发)
7.java集合类,哪些线程安全,哪些线程不安全
8.线程安全问题(如何保证线程安全,哪些地方要使用线程安全;lock和synchronized区别,用哪个更好,为什么?)
9.用过volatile吗,说一下?
10.有看过JDK源码吗?(这个我说没有深入了解过)
11.数据库隔离级别哪几种?(这道直接说忘了,可以提示一下吗)
12.其他问题忘了
(没问项目,全问基础题,算法题最重要,答不好估计进不了)
二面
1.详细问了项目的整个架构和一些具体功能,关键技术的实现?(谈了估计快半个钟)
2.TCP三次握手,以及一些具体问题如”为什么要有第三次握手?客户端发给服务器的报文段丢失了怎么办?这时客户端还没收到服务器的确认包,那它会继续发吗?服务器收到之后重传的确认号应该是多少?”等等(两轮都问了TCP,总的来说就是要精通TCP,滑动窗口,确认重传都要很清楚)
3.线程安全问题(同一面)
4.平衡二叉树(说一下原理?开发实践中哪些地方要用到它?)
5.一个字符串中找出给定字符串,要求效率最高?(字符串匹配的KMP算法)
6.如果HashMap的大小超过了负载因子定义的容量,怎么办?
7.说一下hashcode?(自己关联了equals和==回答)
8.知道Unicode编码吗?(这道我直接说没有深入研究,怕被问深)
9.其他细节题,如char a=‘海’;float a=1.1; short a=1;分别问了这几个正确吗?
10.想问一道概率论的题目(我直接跟面试官说没修这门课,广外之前把概率论当选修课了…面试官也说那算了)
11.学习理论和做项目你是怎么看的?
12.问了我的职业规划是什么?
13.考不考研,为什么?
(能过估计是TCP,线程安全和hashcode与equals这几道题我答的比较好,面试官一直在点头)
hr面
1.看到我的实践经历,问我怎么在学习中能抽出时间做这些实践?
2.看到我之前被内推过安卓,问为什么从安卓转做java?(我给了很多原因,面试官都表示不理解。。)
3.说一下职业规划?怎么学习自己这个方向?
4.详细介绍一个自己的项目?
5.自己扩展过的网络通信框架有测试过性能吗,比原框架好在哪里?从几个指标来回答?
6.实习去哪个城市都行吗?
(hr面表现不太好,没有事先准备)
腾讯内推电面(Android开发)
1.一次完整的http访问流程
2.图片内存缓存LRUCache怎么实现
3.App点击桌面图标的启动流程
4.如何进程间通信?
5.看过Binder源码吗,底层怎么实现
6.Android新版本的特性,如何实现的
CVTE校招(Android开发)
一面
(两个面试官轮流问...)
1.项目介绍,画架构图
2.手写代码实现一个栈
3.TCP握手过程
4.说一下内存溢出
5.遇过内存泄漏吗
6.设计模式,说一下抽象工厂
7.线程模型是怎样的(难倒我了,没复习操作系统...)
8.如何进程间通信?
9.AIDL如何实现?
10.说一下事件分发机制(这里还是没理解透)
二面
1.项目介绍,画架构图
2.实习的工作内容
3.手写快速排序
4.一道挺有意思的软件架构设计题,要求设计具有扩展性
5.后面几乎都在自我展现技术了(说了binder机制的内部实现)
三面
1.家庭情况,性格,缺点各种...
2.什么情况下你会拒绝cvte的offer?
滴滴校招(软件研发)
一面(考的很全也真的很难)
1.先做算法题
二叉搜索树与双向链表
输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。
2.TCP三次握手和四次挥手的过程,timewait,closewait状态分别在哪
3.osi分层
4.进程间通信方式
5.分页与分段的区别,各自有什么优缺点
6.HashMap、HashTable区别,concurrentHashmap
7.B树
8.java中软引用,虚引用,弱引用区别
9.classLoader
10.gc垃圾回收,分代回收
11.深拷贝 浅拷贝
12.静态内部类,内部类,匿名内部类
13.反射机制
14.sleep()和wait()区别,在同步方法中哪个可以释放锁?
二面
算法题:
给定文件格式如下
1.1.1.2 1.1.1.200 100
3.2.2.1 3.2.2.144 102
..
每一行代表: 起始IP 结束IP 省份编号
求指定IP的所在省份
int searchProv(char *ip)
可写伪代码 时间10分钟。
(题意都看错,所以被刷了T T)
百度校招(Android开发)
一面
(项目上问了编译器,给一条语句让我画出语法树)
1.说一下final的意义
2.java集合类都问了一遍(基础)
3.如何设计StringBuffer(没答好)
4.死锁的定义,哪4个条件?
5.单例模式
6.手写二分查找,冒泡排序(面试官注重代码细节)
7.activity生命周期,启动模式(问的比较细,singleInstance的activity开启一个新activity,任务栈是怎样状态)
8.handler底层实现?消息队列为空会怎样?(会发生空闲等待,其实就是利用了管道机制)
二面
(面试官也是先问编译器,大公司对这个感兴趣)
1.hashmap的内部实现
2.说一下堆栈的内存结构
3.设计一个栈(不用list,set或map)
4.Asynctask内部实现
5.设计一个线程池?(之前阿里还考过,然而还是不会...)
6.sleep()和wait()使用过程要注意什么?
7.如何设计一个阻塞队列 ?
8.斐波那契数列的实现(手写递归和非递归)
9.字符串第一个出现一次的字符(遍历一遍,时间o(n),用数组int[256]或hashmap存储)
10.什么是满二叉树,下个定义?(我说了当深度为n时,叶子节点数为2^(n-1)的树...)
11.堆排序,如何初建大根堆?
12.又问了Activity启动模式
13.如何实现AIDL?
三面
1.有一个数列有10万个数,另一个数列是1亿个数,找出这两个数列共同拥有的数
2.TCP
3.画HTTP报文
4.短连接和长连接
5.两台服务器不同地方,传输一个文件需要考虑什么因素
6.数据库索引
7.B树,B+树
8.平时怎么学习,学习方法?
9.意向工作地点,工作业务?
携程校招(后台开发)
一面是电话面试,然后通知到公司面试,先写做一份笔试,后面三轮面试
电面
java基础,排序算法之类
现场面
比较难的一道算法题,设计数据结构,手写代码
机票路线,从A地到B地不能直达,要中转1个站,求需要中转1个站的路线占所有路线比例