码农工具包

hutool工具

hutool工具类判断各种类型数据

<!-- https://mvnrepository.com/artifact/cn.hutool/hutool-all -->
<dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.8.8</version>
</dependency>
============================1.判断数组是否为空============================
String[] strArr = new String[]{};// 判断数组不为null,且素组长度不为0
if (ArrayUtil.isNotEmpty(strArr)) {System.out.println("数组不为null,且素组长度不为0");
}// 判断数组为null或数组长度为0
if (ArrayUtil.isEmpty(strArr)) {System.out.println("数组为null或数组长度为0");
}============================2.判断集合是否为空============================
List<String> list = new ArrayList();// 判断集合list是否为空,同时判断list为null,为空集合
if (CollectionUtil.isEmpty(list)) {System.out.println("集合list是否为空,同时判断list为null,为空集合");
}// 判断集合list是否为空,同时判断list不为null,不为空集合
if (CollectionUtil.isNotEmpty(list)) {System.out.println("集合list是否为空,同时判断list不为null,不为空集合");
}if (CollUtil.isNotEmpty(list)) {System.out.println("集合list是否为空,同时判断list不为null,不为空集合");
}============================3.判断字符串是否为空============================
String str = null;System.out.println("判断字符串是否为空:" + StrUtil.isNotBlank(str));
// 判断string不为"null"、""、" "
if (StrUtil.isNotBlank(str)) {}// 判断string为"null"、""、" "
if (StrUtil.isBlank(str)) {}============================4.判断两个字符串是否相等(内容相等)============================
String str1 = null;
String str2 = null;System.out.println("判断两个字符串是否相等:" + ObjectUtil.equals(str1, str2));// 判断两个字符串是否相等,此方法可以避免空指针异常
if (ObjectUtil.equals(str1, str2)) {/*  如果 string1 = null && string1 = null 返回true如果 string1 = null || string1 = null 返回false*/
}

hutool工具类时间字符串转换

============================获取当前时间============================
Date date = DateUtil.date(); //当前时间
Date date2 = DateUtil.date(Calendar.getInstance()); //当前时间
Date date3 = DateUtil.date(System.currentTimeMillis()); //当前时间
String now = DateUtil.now(); //当前时间字符串,格式:yyyy-MM-dd HH:mm:ss
String today= DateUtil.today();  //当前日期字符串,格式:yyyy-MM-dd============================字符串转时间============================
eg1:
String dateStr = "2017-03-01";
Date date = DateUtil.parse(dateStr);
Date date = DateUtil.parse(dateStr, "yyyy-MM-dd");  //自定义日期格式转化eg2:
String dateStr = "2015-08-12";
DateFormat format = new SimpleDateFormat("yyyy-MM-dd"); //自定义日期格式化的格式
Date classDate = format.parse(dateStr);//把字符串转化成指定格式的日期============================时间转字符串============================
Date date = new Date();
String dateStr = DateUtil.format(date, "yyyy-MM-dd"); // 输出为(yyyy-MM-dd)格式的字符串============================格式化日期输出============================
String dateStr = "2017-03-01";
Date date = DateUtil.parse(dateStr);String format = DateUtil.format(date, "yyyy/MM/dd");  //结果 2017/03/01
String formatDate = DateUtil.formatDate(date);  //常用格式的格式化,结果:2017-03-01
String formatDateTime = DateUtil.formatDateTime(date);  //结果:2017-03-01 00:00:00
String formatTime = DateUtil.formatTime(date);  //结果:00:00:00

数据类型互相转换

Convert.toLong(str);
Convert.toInt(num);

开发日常编码

封装树结构

1.封装数据

主表

//value为标识编码,label为前端需要显示的数据
private String label;
private Integer value;
private List<DictData> children;

从表

private String label;
private Integer value;

接口

@GetMapping("/getDictList")
public AjaxResult getDictList(DictType type){List<DictType> list = deptService.selectArchiveTreeList(type);return AjaxResult.success(list);
}@Override
public List<DictType> selectArchiveTreeList(DictType type) {return deptMapper.selectArchiveTreeList(type);
}<resultMap id="dictListMap" type="com.sk.common.core.domain.entity.archivesEntity.DictType"><id column="dict_id" property="value"/><result column="dict_name" property="label"/><collection property="children" ofType="com.sk.common.core.domain.entity.archivesEntity.DictData"><result column="dict_code" property="value"/><result column="dict_label" property="label"/></collection>
</resultMap>
<select id="selectArchiveTreeList" resultMap="dictListMap">SELECTt.dict_id,t.dict_name,d.dict_label,d.dict_codeFROMsys_dict_data AS dLEFT JOIN  sys_dict_type AS t ON t.dict_type = d.dict_typeWHEREt.dict_type = 'sys_archives_type';
</select>

2.前端页面

<el-col :span="4" :xs="24"><div class="head-container"><el-tree:data="basicsOptions":props="defaultProps":expand-on-click-node="false":filter-node-method="filterNode"ref="tree"default-expand-allhighlight-current@node-click="handleNodeClick"/></div>
</el-col>basicsOptions: undefined,
defaultProps: {children: "children",label: "label",value: "value"
},// 筛选节点
filterNode(value, data) {if (!value) return true;return data.label.indexOf(value) !== -1;
},
// 节点单击事件
handleNodeClick(data) {
},
/** 查询部门下拉树结构 */
getBasicsTree() {basicsTreeSelect().then(response => {this.basicsOptions = response.data;});
}


通过时间范围查询数据

前端

<el-form-item label="上传时间"><el-date-pickerv-model="form.queryTime"   <!-- queryTime: '' -->type="daterange"value-format="yyyy-MM-dd" <!--限制日期格式-->range-separator="至"start-placeholder="开始日期"end-placeholder="结束日期"></el-date-picker>
</el-form-item>
<el-form-item><el-button type="primary" @click="query(form)">查询</el-button>
</el-form-item>

后台

@Override
public List<SkArchiveArchives> selectArchivesList(SkArchiveArchives archives) throws ParseException {//判断集合是否为空if (ArrayUtil.isNotEmpty(archives.getQueryTime())) {String[] queryTime = archives.getQueryTime();archives.setStartTime(queryTime[0]);String endTime = queryTime[1];DateFormat format = new SimpleDateFormat("yyyy-MM-dd"); //定义日期格式化的格式Date classDate = format.parse(endTime);//把字符串转化成指定格式的日期Calendar calendar = Calendar.getInstance(); //使用Calendar日历类对日期进行加减calendar.setTime(classDate);calendar.add(Calendar.DAY_OF_MONTH, 1);classDate = calendar.getTime();// 将日期类型转换成指定的字符串类型archives.setEndTime(DateUtil.format(classDate, "yyyy-MM-dd"));
}


java日期类型实现加减天数

String dateStr = "2015-08-12"; //需要加减的字符串型日期DateFormat format = new SimpleDateFormat("yyyy-MM-dd"); //定义日期格式化的格式
Date classDate = format.parse(dateStr);//把字符串转化成指定格式的日期Calendar calendar = Calendar.getInstance(); //使用Calendar日历类对日期进行加减
calendar.setTime(classDate);
calendar.add(Calendar.DAY_OF_MONTH, - 1);//-为昨天,+为明天classDate = calendar.getTime();//获取加减以后的Date类型日期dateSStr = DateUtil.format(classDate,"yyyy-MM-dd"); //得到处理以后的字符串

前端分页

 <!--分页-->
<paginationbackgroundv-show="total>0":total="total":page.sync="form.pageNum":limit.sync="form.pageSize"@pagination="getList"
/>/** 总条数 */
total: 0,form: {pageNum: 1,/** 每页显示10条数据 */pageSize: 10,
},//获取表单数据的方法
getList(){}

JSON

# 从redis取出token(token包含当前登录用户的信息)
JSONObject jsonObject = JSONObject.parseObject(String.valueOf(redisTemplate.opsForValue().get(token)));
String userName = jsonObject.getString("userName");

将对象转为json,目的使对象称为k,v格式

Data data = new Data();
JSONObject dataToJson = (JSONObject) JSONObject.toJSON(data);

注解

lombak相关注解

//生成所有字段的getter、toString()、hashCode()、equals()、所有非final字段的setter、构造器,相当于设置了 @Getter @Setter @RequiredArgsConstructor @ToString @EqualsAndHashCode
@Data@Accessors(chain = true) //链式赋值
@AllArgsConstructor
@NoArgsConstructor
public class Student {private String name;private int score;
}
public class Main {public static void main(String[] args) {Student student = new Student().setName("yolo").setScore(98);//链式赋值String s = JSON.toJSONString(student);System.out.println(s);}
}

JSON相关注解、依赖

<!--以下两个依赖完成对象与json的相互转换-->
<dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.3</version>
</dependency>
<dependency><groupId>com.colobu</groupId><artifactId>fastjson-jaxrs-json-provider</artifactId><version>0.3.1</version>
</dependency>
public class Person implements Serializable {//ordinal:自定义字段的输出顺序  name:给字段起别名 serialize = false:表示不参与序列化@JSONField(ordinal = 1)private Integer eid;@JSONField(ordinal = 2,name = "user")private String username;@JSONField(serialize = false)private String sex;@JSONField(ordinal = 4)private Integer age;
}

表示不是数据库字段、时间格式化

@TableField(exist = false):表示该属性不为数据库表字段@TableField(exist = true):表示该属性为数据库表字段。/**
* date
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date date;
/**
* localDate
*/
@JsonFormat(pattern = "yyyy-MM-dd")
@DateTimeFormat(pattern = "yyyy-MM-dd")
private LocalDate localDate;
/**
* localDateTime
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime localDateTime;
/**
* localTime
*/
@JsonFormat(pattern = "HH:mm:ss")
private LocalTime localTime;

打包部署

1、上传jar包到服务器响应的文件目录下

2、停掉服务

ps -ef|grep sk-blo
kill -9 1111

3、备份当前jar包

mv 原jar包名 新名

4、修改上传的jar包名称

5、开启服务

nohup java -jar -Xms2g -Xmn1g java.jar >java.out 2>&1 &
java -jar ./java..jar

6、查看日志

tail -f -n 200 java.out

报错

杀进程

1、解决端口占用问题,首先查看端口的启动情况

win+R 输入cmd打开DOS命令框。输入:netstat -ano | findstr 80 其中80是我服务的端口号。

2、接下来我们就来杀死它

根据PID杀死任务: taskkill /F /PID “18560”

Git

idea查看git地址

查看git地址:git remote -v

git拉取代码提示Authentication failed for []

git拉取代码的时候提示Authentication failed for []

解决办法,用管理员身份打开git命令行,执行 git config --global credential.helper store重新clone的时候会提示让输入用户名,然后弹出框让输入密码,就可以了

跑项目时报错


Cannot resolve com.sun:tools:1.8解决方法

引入阿里的**druid **启动器,druid-spring-boot-starter-1.2.6.pom

<!--数据库连接池-->
<dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>1.2.6</version>
</dependency>

启动器应用的druid,druid-1.2.6.pom

<dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.2.6</version>
</dependency>

重点是里面的profiles,会根据系统选择。这里只选择windows的

<profile><id>jconsole</id><activation><!--<activeByDefault>true</activeByDefault>--><file><exists>${env.JAVA_HOME}/lib/jconsole.jar</exists></file></activation><properties><toolsjar>${env.JAVA_HOME}/lib/tools.jar</toolsjar><jconsolejar>${env.JAVA_HOME}/lib/jconsole.jar</jconsolejar>
</properties>
...
</profile>

根据JAVA_HOME获取jar包,所以要看一下环境配置是否正确。

最后解决办法,原因是因为maven版本3.8.2有问题,没有搜索到jar包,换成3.6.3的就解决了

报的警告,但是过了,程序员无视警告就好了


java: You aren’t using a compiler supported by lombok, so lombok will not work and has been disab…

报错问题

java: You`aren't using a compiler supported by lombok, so lombok will not work and has been disabled.Your processor is: com.sun.proxy.$Proxy26Lombok supports: sun/apple javac 1.6, ECJ

解决

方法一

在以下位置加上该配置

-Djps.track.ap.dependencies=false

方法二

更新一下版本到以下版本

<!--Lombok-->
<dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.14</version><scope>provided</scope>
</dependency>

org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): com.kimi.service.edu.mapper.EduCourseMapper.getPublishCourseInfo

因为maven默认加载机制,它只会把src-main-java文件夹中的java类型文件进行加载,其它类型文件不会加载。

在target文件夹中也没有加载xml文件夹中的xml文件

解决方式:

1.将xml文件复制到target文件夹中的mapper文件夹里面去;

2.将xml文件写到resources文件下;

3.通过配置文件进行配置,让maven自动加载xml文件:

pom.xml文件中添加如下配置:

<build>
<resources><resource><directory>src/main/java</directory><includes><include>**/*.xml</include></includes><filtering>false</filtering></resource>
</resources>
</build>

application.properties文件中添加如下配置:

# 配置mapper xml文件的路径
mybatis-plus.mapper-locations=classpath:com/kimi/service/edu/mapper/xml/*xml

com.mongodb.MongoSocketOpenException: Exception opening socket

23:29:11.057 [cluster-ClusterId{value='6318b8c44a60ed674eff44ac', description='null'}-localhost:27017] INFO  o.m.driver.cluster - [info,76] - Exception in monitor thread while connecting to server localhost:27017
com.mongodb.MongoSocketOpenException: Exception opening socketat com.mongodb.internal.connection.SocketStream.open(SocketStream.java:70)at com.mongodb.internal.connection.InternalStreamConnection.open(InternalStreamConnection.java:143)at com.mongodb.internal.connection.DefaultServerMonitor$ServerMonitorRunnable.lookupServerDescription(DefaultServerMonitor.java:188)at com.mongodb.internal.connection.DefaultServerMonitor$ServerMonitorRunnable.run(DefaultServerMonitor.java:144)at java.lang.Thread.run(Thread.java:745)
Caused by: java.net.ConnectException: Connection refused: connectat java.net.DualStackPlainSocketImpl.waitForConnect(Native Method)at java.net.DualStackPlainSocketImpl.socketConnect(DualStackPlainSocketImpl.java:85)at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:350)at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:206)at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:188)at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:172)at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392)at java.net.Socket.connect(Socket.java:589)at com.mongodb.internal.connection.SocketStreamHelper.initialize(SocketStreamHelper.java:107)at com.mongodb.internal.connection.SocketStream.initializeSocket(SocketStream.java:79)at com.mongodb.internal.connection.SocketStream.open(SocketStream.java:65)... 4 common frames omitted

解决方法

springboot自动配置了支持mongodb。在启动springboot时会自动实例化一个mongo实例,需要禁用自动配置 ,增加@SpringBootApplication(exclude = MongoAutoConfiguration.class)这个注解即可

Java基础


八大基本类型

JDK1.8新特性

JDK1.7和JDK1.8的区别

Java8 是 Java发布以来改动最大的一个版本
添加了函数式编程、Stream、全新的日期处理类
函数式编程新加了一些概念:Lambda表达式、函数式接口、函数引用、默认方法、Optional类等
Stream中提供了一些流式处理集合的方法,并提供了一些归约、划分等类的方法
日期中添加了ZoneDateTime、DataFormat等线程安全的方法类

1.JDK1.8接口中除了定义抽象方法以外,还可以定义static和default方法, static和default方法有方法体;

2.JDK1.8中switch语句支持String;

扩展:

基本类型有:byte,short,int,char

包装类型有:Byte,Short,Integer,Character,String,enum

switch实际上只支持int类型,使用其他的类型时是通过转化支持的:

1、基本类型byte char short ,原因:这些基本数字类型可自动向上转为int, 实际还是用的int。

2、包装类型Byte,Short,Character,Integer ,原因:java的自动拆箱机制 可将这些对象自动转为基本类型

3、String 类型 原因:实际switch比较的string.hashCode值,它是一个int类型

4、enum类型 原因 :实际比较的是enum的ordinal值(表示枚举值的顺序),它也是一个int类型

3.Lamdba表达式

4.函数式接口

5.日期中添加了ZoneDateTime、DataFormat等线程安全的方法类

因为JDK1.8新特性比较多,只记得这几个!


final, finally, finalize 的区别

final

final修饰变量时,如果是基本类型的变量,在声明时必须给定初始值,则其数值一旦被初始化后就不能被修改。如果是引用类型的变量,则初始化之后便不能再让其指向另外一个对象,但是它指向对象的内容可以被改变。

final修饰 的方法不能被重写,但是可以重载。

final修饰的类称为终类,该类不能被继承,final不能和abstract一起使用。

finally

finally用在try/catch/finally语句中,表示这段代码最终会被执行,通常被用来释放资源、关闭资源等操作。但是有时候finally语句不会执行,比如当程序执行try语句时,所有线程被结束,finally语句就不会执行。

try{System.out.println("没有异常时执行");System.exit(0);//正常退出   System.exit(0); 会结束整个虚拟机,也就是所有线程// System.exit(-1);
}catch (Exception e){System.out.println("发生异常时执行");
}finally {System.out.println("最终会被执行");
}

延伸:try块中的内容是在无异常时执行到结束。catch块中的内容,是在try块内容发生catch所声明的异常时,

跳转到catch块中执行。

finalize

finalize是Object类中的一个方法,在GC执行的时候会被调用回收对象的finalize()方法,可以覆盖此方法来实现对其它资源的回收,例如关闭文件等。需要注意的是,一旦GC准备好释放对象的占用空间,将首先调用其finalize()方法,并且在下一次GC回收动作发生时,才会真正回收对象占用的内存。

由于finalize()方法存在很多问题,JDK1.9之后已经弃用了该方法。


栈、堆、方法区

栈:基本数据类型、堆中对象的引用、局部变量

堆:对象、数组、静态变量、字符串常量池

方法区:类信息、运行时常量池


多态

父类引用指向子类对象,同一类的对象收到相同消息时,会得到不同的结果。而这个消息是不可预测的。多态,顾名思义,就是多种状态,也就是多种结果。

反射

运行时拿到类的字节码对象(.class),得到类中的属性和方法。

这种动态获取的信息以及动态调用对象方法的功能称为java语言的反射机制。

项目中Spring框架IOC就使用了反射机制。


notify和notifyAll的区别

notify()和notifyAll()都是用来唤醒调用wait()方法进入等待锁资源队列的线程,区别在于:

notify():唤醒正在等待此对象监视器的单个线程。如果有多个线程在等待,则选择其中一个随机唤醒(由调度器决定),唤醒的线程享有公平竞争资源的权利。

notifyAll():唤醒正在等待此对象监视器的所有线程,唤醒的所有线程公平竞争。

sleep和wait的区别

1.sleep是线程类Tread的静态方法,wait是Object类的方法;

2.sleep是使线程休眠,不会释放对象锁;wait是使线程等待,释放锁;

sleep让出的是cpu,如果此时代码是加锁的,那么即使让出了CPU,其他线程也无法运行,因为没有得到锁;
wait是让自己暂时等待,放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象发出notify方法(或notifyAll)
后本线程才进入对象锁定池准备获得对象锁进入运行状态。

3.调用sleep使线程进入阻塞状态;调用 wait使线程进入就绪状态。

悲观锁和乐观锁

悲观锁:
当多事务/多线程并发执行时,事务总是悲观的认为,在自己访问数据期间,其他事务一定会并发执行,此时会产生线程安全问题,
所以为了保证线程安全,这个事务在访问数据时,立即给数据加锁,从而保证线程安全.
特点:可以保证线程安全,但是并发执行效率低下
synchronized、排它锁都是悲观锁的应用乐观锁:
在多线程/多事务并发执行中,某个事务总是乐观的认为,在自己执行期间,没有其他事务与之并发,认为不会产生线程安全问题,所以不会给数据加锁;但是确实存在其他事务与之并发执行的情况,确实存在线程安全问题,为了保证线程安全,通过版本号机制或CAS来保证线程安全.
CAS:compare and swap  比较并交换

线程篇

线程创建的四种方式,什么地方使用了线程池

创建线程的方式:1.继承Thread类

​ 2.实现Runnable接口

​ 3.实现callable接口

Callable和Runnable的区别:1.Callable重写的是call()方法,Runnable重写的是run()方法2.实现Callable接口有返回值,实现Runnable接口没有返回值

​ 4.线程池创建线程

平时使用那种线程池:

提高一下插入表的性能优化,因为是两张表,先插旧的表,紧接着插新的表,一万多条数据就有点慢了

后面就想到了线程池ThreadPoolExecutor,而用的是Spring Boot项目,

可以用Spring提供的对ThreadPoolExecutor封装的线程池ThreadPoolTaskExecutor,直接使用注解启用

使用步骤

先创建一个线程池的配置,让Spring Boot加载,用来定义如何创建一个ThreadPoolTaskExecutor,要使用@Configuration和@EnableAsync**(/ əˈsɪŋk /)**这两个注解,表示这是个配置类,并且是线程池的配置类;

创建一个Service接口,是异步线程的接口;

实现类;

将Service层的服务异步化,在executeAsync()方法上增加注解@Async(“asyncServiceExecutor”),asyncServiceExecutor方法是前面ExecutorConfig.java中的方法名,表明executeAsync方法进入的线程池是asyncServiceExecutor方法创建的。

在Controller里或者是哪里通过注解@Autowired注入这个Service

线程池

为什么要使用线程池

创建线程和销毁线程的花销是比较大的,这些时间有可能比处理业务的时间还要长。这样频繁的创建线程和销毁线程,再加上业务工作线程,消耗系统资源的时间,可能导致系统资源不足。使用线程池可以把创建和销毁的线程的过程去掉。

线程池有什么作用

1、提高效率 创建好一定数量的线程放在池中,等需要使用的时候就从池中拿一个,这要比需要的时候创建一个线程对象要快的多。
2、方便管理 可以编写线程池管理代码对池中的线程同一进行管理,比如说启动时有该程序创建100个线程,每当有请求的时候,就 分配一个线程去工作,如果刚好并发有101个请求,那多出的这一个请求可以排队等候,避免因无休止的创建线程导致系统崩溃。

**线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式更加明确线程池的运行规则,规避资源耗尽的风险。 **

四种常见线程池

1.newFixedThreadPool

创建一个固定大小线程池,可控制线程最大并发数,超出的线程会在队列中等待。

固定大小的线程池,可以指定线程池的大小,该线程池corePoolSize和maximumPoolSize相等,阻塞队列使用的是LinkedBlockingQueue,大小为整数最大值。

该线程池中的线程数量始终不变,当有新任务提交时,线程池中有空闲线程则会立即执行,如果没有,则会暂存到阻塞队列。对于固定大小的线程池,不存在线程数量的变化。同时使用无界的LinkedBlockingQueue来存放执行的任务。当任务提交十分频繁的时候,LinkedBlockingQueue迅速增大,存在着耗尽系统资源的问题。而且在线程池空闲时,即线程池中没有可运行任务时,它也不会释放工作线程,还会占用一定的系统资源,需要shutdown。

2.newSingleThreadExecutor

创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行

单个线程线程池,只有一个线程的线程池,阻塞队列使用的是LinkedBlockingQueue,若有多余的任务提交到线程池中,则会被暂存到阻塞队列,待空闲时再去执行。按照先入先出的顺序执行任务。

3.newCachedThreadPool

创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。

缓存线程池,缓存的线程默认存活60秒。执行效率最快,线程的核心池corePoolSize大小为0,核心池最大为Integer.MAX_VALUE,阻塞队列使用的是SynchronousQueue。是一个直接提交的阻塞队列, 他总会迫使线程池增加新的线程去执行新的任务。在没有任务执行时,当线程的空闲时间超过keepAliveTime(60秒),则工作线程将会终止被回收,当提交新任务时,如果没有空闲线程,则创建新线程执行任务,会导致一定的系统开销。如果同时又大量任务被提交,而且任务执行的时间不是特别快,那么线程池便会新增出等量的线程池处理任务,这很可能会很快耗尽系统的资源。

4.newScheduledThreadPool(/ ˈskedʒuːld )

创建一个定长线程池,支持定时及周期性任务执行

定时线程池,该线程池可用于周期性地去执行任务,通常用于周期性的同步数据。

scheduleAtFixedRate:是以固定的频率去执行任务,周期是指每次执行任务成功执行之间的间隔。

schedultWithFixedDelay:是以固定的延时去执行任务,延时是指上一次执行成功之后和下一次开始执行的之前的时间。

线程池七大参数

public ThreadPoolExecutor(int corePoolSize, //线程池中常驻核心线程数int maximumPoolSize, //线程池能够容纳同时执行的最大线程数,此值必须大于1//多余空闲线程的存活时间。当前线程池数量超过corePoolSize时,当空闲时间达到keepAliveTime时,多余空闲线程会被销毁直到剩下corePoolSize为止。long keepAliveTime, TimeUnit unit, //keepAliveTime的单位BlockingQueue<Runnable> workQueue,//:里面放了被提交但是尚未执行的任务ThreadFactory threadFactory, //表示线程池中工作线程的线程工厂,用于创建线程//拒绝策略,当队列满了并且工作线程大于等于线程池的最大线程数(maximumPoolSize)时,对任务的拒绝方式。RejectedExecutionHandler handler)

synchronized(同步锁)

synchronized可以修饰静态方法成员函数,同时还可以直接定义代码块

但是归根结底它上锁的资源只有两类:一个是对象,一个是

Synchronized如何避免死锁

Synchronized不要嵌套定义;

synchronized底层实现原理

synchronized具有四个特性:原子性,可见性,有序性,可重入性(可重入锁)

synchronized是可重入锁,每次锁对象会有一个计数器记录线程获取几次锁,在执行完同步代码块时,计数器的数量会-1,直到计数器的数量为0,就释放这个锁。

注意!面试时经常会问比较synchronized和volatile(可见性),它们俩特性上最大的区别就在于原子性,volatile不具备原子性。

volatile只能作用于变量,保证了操作可见性和有序性,不保证原子性。


多线程怎么解决线程安全问题

1.用局部变量去取代实例变量和静态变量。
2.如果是实例变量,则可以创建多个实例。
3.用Synchronized加锁。

Java多线程之间的通讯和协作

生产者-消费者模型

可以通过中断和共享变量的方式实现线程间的通讯和协作;比如说最经典的生产者-消费者模型:当队列满时,生产者需要等待队列有空间才能继续往里面放入商品,而在等待的期间内,生产者必须释放临界资源(即队列)的占用权。因为生产者如果不释放对临界资源的占用权,那么消费者就无法消费队列中的商品,就不会让队列有空间,那么生产者就会一直无限等待下去。因此,一般情况下,当队列满时,会让生产者交出对临界资源的占用权,并进入挂起状态。然后等待消费者消费了商品,消费者通知生产者队列空间有了。同样地,当队列空时,消费者也必须等待,等待生产者通知消费者队列中有商品了。这种互相通信的过程就是先线程间的协作。Java中线程通信协作的最常见三种方式:
1.Syncrhoized加锁线程的Object类中的wait()/notify()/notifyAll();wait:等待让作用在仓库上的线程处于等待状态,会释放锁notify: 唤醒
可以唤醒仓库上等待的线程wait和notify方法是object方法,sleep是Thread方法2.ReentrantLock类加锁的线程的Condition类的await()/signal()/signalAll()
线程间直接的数据交换:
通过管道进行线程间通信:1)字节流、2)字符流3.volatile(可见性)

代码解析

public class No4 {public static void main(String[] args) {List list = new ArrayList<>();Thread t1 = new Thread(new Producer(list));Thread t2 = new Thread(new Consumer(list));t1.setName("生产者");t2.setName("消费者");t1.start();t2.start();}
}class Producer implements Runnable {List list;public Producer(List list) {this.list = list;}@Overridepublic void run() {while (true) {synchronized (list) {if (list.size() > 0) {try {list.wait();Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}}//说明仓库为空,可以生产Object producer = new Object();list.add(·);System.out.println(Thread.currentThread().getName() + "===>" + producer);list.notify();}}}
}class Consumer implements Runnable {List list;public Consumer(List list) {this.list = list;}@Overridepublic void run() {while (true) {synchronized (list) {if (list.size() == 0) {try {list.wait();Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}}//说明仓库里可以被消费Object remove = list.remove(0);System.out.println(Thread.currentThread().getName() + "===>" + remove);list.notify();}}}
}

线程三大特性

/*** 线程有三个特性:可见性(volatile)、原子性、安全性*/static volatile boolean a = true;//volatile保证多个线程之间可见性,必须至少有两个线程一写一读public static void main(String[] args) throws InterruptedException {new Thread(new Runnable() {@Overridepublic void run() {while (a) {//                            try {//                                Thread.sleep(1000);
//                            } catch (InterruptedException e) {//                                e.printStackTrace();
//                            }}}}).start();Thread.sleep(1000);a = false; //写入System.out.println(a); //读取}
}

    //static int a = 0;static AtomicInteger a = new AtomicInteger(0);//AtomicInteger(/ əˈtɒmɪk /):原子性public static void main(String[] args) throws InterruptedException {for (int i = 0; i < 20000; i++) {new Thread(new Runnable() {@Overridepublic void run() {//                    synchronized (Demo06.class){//                        a++;
//                    }a.getAndAdd(1);}}).start();}Thread.sleep(1000);System.out.println(a);}

线程的5种状态

1、新建状态(New):新创建了一个线程对象。

2、就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于“可运行线程池”中,变得可运行,只等待获取CPU的使用权即在就绪状态的进程除CPU之外,其它的运行所需资源都已全部获得。

**3、运行状态(Running):**就绪状态的线程获取了CPU,执行程序代码。

**4、阻塞状态(Blocked):**阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。

阻塞的情况分三种:

(1)、等待阻塞:运行的线程执行wait()方法,该线程会释放占用的所有资源,JVM会把该线程放入“**等待池”**中。进入这个状态后,是不能自动唤醒的,必须依靠其他线程调用notify()或notifyAll()方法才能被唤醒,

(2)、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入**“锁池”**中。

(3)、其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。

**5、死亡状态(Dead):**线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

start()和run()的区别

new一个Thread,线程进入新建状态,调用start()方法,使线程进入就绪状态,当得到cpu时间片后自动调用run(),run()执行结束后才能继续执行下一个run(),所有run()并没有多线程的体现。

start()
用 start方法来启动线程,是真正实现了多线程, 通过调用Thread类的start()方法来启动一个线程,这时此线程处于就绪(可运行)状态,并没有运行,一旦得到cpu时间片,就开始执行run()方法。但要注意的是,此时无需等待run()方法执行完毕,即可继续执行下面的代码。所以run()方法并没有实现多线程。

run()
run()方法只是类的一个普通方法而已,如果直接调用Run方法,程序中依然只有主线程这一个线程,其程序执行路径还是只有一条,还是要顺序执行,还是要等待run方法体执行完毕后才可继续执行下面的代码。

区别:
1、线程中的start()方法和run()方法的主要区别在于,当程序调用start()方法,将会创建一个新线程去执行run()方法中的代码。但是如果直接调用run()方法的话,会直接在当前线程中执行run()中的代码,注意,这里不会创建新线程。这样run()就像一个普通方法一样。

2、另外当一个线程启动之后,不能重复调用start(),否则会报IllegalStateException异常。但是可以重复调用run()方法。

总结起来就是run()就是一个普通的方法,而start()会创建一个新线程去执行run()的代码。

还有:
1、start方法用来启动相应的线程;

2、run方法只是thread的一个普通方法,在主线程里执行;

3、需要并行处理的代码放在run方法中,start方法启动线程后自动调用run方法;

4、run方法必去是public的访问权限,返回类型为void。

如果直接调用线程类的run()方法,这会被当做一个普通的函数调用,程序中仍然只有主线程这一个线程,也就是说,start()方法能够异步地调用run()方法,但是直接调用run()方法却是同步的,因此也就无法达到多线程的目的。

只有通过调用线程类的start()方法才能真正达到多线程的目的。


过滤器和拦截器的区别?

1.拦截器(Interceptor)是基于Java的反射机制(属于面向切面编程(AOP)的一种运用),而过滤器(Filter)是基于函数回调。
2.过滤器依赖于servlet容器,而拦截器不依赖于servlet容器。
3.过滤器可以对几乎所有的请求起作用,拦截器只能对action请求起作用。
4.过滤器不能获取IOC容器中的各个bean,而拦截器可以,在拦截器里注入一个service,可以调用业务逻辑。

过滤器(Filter):

它依赖于servlet容器,它可以对几乎所有请求进行过滤。使用过滤器的目的,是用来做一些过滤操作,获取我们想要获取的数据,

比如:在Javaweb中,对传入的request、response提前过滤掉一些信息,或者提前设置一些参数,然后再传入servlet或controller进行业务逻辑操作。

通常用的场景是:在过滤器中修改字符编码(CharacterEncodingFilter)、在过滤器中修改HttpServletRequest的一些参数(XSSFilter[自定义过滤器]),如:过滤低俗文字、危险字符等。

拦截器(Interceptor):

它依赖于web框架,在SpringMVC中就是依赖于SpringMVC框架。在实现上,基于Java反射机制。

属于面向切面编程(AOP)的一种运用,就是在servlet或一个方法前,调用一个方法,或者在方法后,调用一个方法,比如动态代理就是拦截器的简单实现,在调用方法前打印出字符串(或者做其它业务逻辑的操作),也可以在调用方法后打印出字符串,甚至在抛出异常的时候做业务逻辑操作。

由于拦截器是基于web框架的调用,因此可以使用Spring的依赖注入(DI)进行一些业务操作,同时一个拦截器实例在一个controller生命周期之内可以多次调用。拦截器可以对静态资源的请求进行拦截处理。

两者的本质区别:

拦截器(Interceptor)是基于Java的反射机制,而过滤器(Filter)是基于函数回调。

从灵活性上说拦截器功能更强大些,Filter能做的事情,都能做,而且可以在请求前,请求后执行,比较灵活。

Filter主要是针对URL地址做一个编码的事情、过滤掉没用的参数、安全校验(比较泛的,比如登录不登录之类),太细的话,还是建议用interceptor。不过还是根据不同情况选择合适的。


迭代器

Iterable接口称为迭代器,主要用于遍历实现Collection接口的集合中的元素。

特点是更加安全,因为它可以确保在当前遍历的集合元素被修改的时候会抛出

ConcurrentModificationException异常

Iterator怎么使用,有什么特点:

1.java.lang.Iterable接口被java.util.Collection接口继承,java.util.Collection接口的iterator()方法返回一个Iterator对象;

2.next()方法获得集合的下一个元素;

3.hasNext()检查集合是否还存在下一个元素;

4.remove()方法将迭代器返回的元素删除。


集合篇

Java集合框架继承图


那些集合是线程安全的

  • Vector:就比ArrayList多了个同步化机制(线程安全)
  • Stack:栈,也是线程安全,继承与Vector
  • HashTable:就比HashMap多了个线程安全
  • ConcurrentHashMap:是一种高效但是线程安全的集合

综合面试题

1.List、Set、Queue、Map四者的区别

  • List、Set、Queue继承自Collection
  • List存储的元素是有序的、可重复的
  • Set存储的元素是无序的、不可重复的
  • Queue按照特定的排队规则来排序,存储的元素是有序的、可重复的
  • Map使用Key-value来进行存储数据,key不可重复、value是无序的、可重复的

2.集合的底层数据结构

List

  • 实际上有两种List:一种是基本的ArrayList,其优点在于随机访问元素,另一种是LinkedList,它并不是为快速随机访问设计的,而是快速的插入或删除。

    • ArrayList:底层由**Object[]**数组实现。允许对元素进行快速随机访问,但是向List中间插入与移除元素的速度很慢。

    • LinkedList :底层是链表结构。对顺序访问进行了优化,向List中间插入与删除的开销并不大。随机访问则相对较慢。还具有下列方 法:addFirst(), addLast(), getFirst(), getLast(), removeFirst() 和 removeLast(),

      这些方法 (没有在任何接口或基类中定义过)使得LinkedList可以当作堆栈、队列和双向队列使用。

Set

  • 简单回答:set集合底层就是一个Map集合,区别是Set是一个单列数据,也就是一个一个的数据,Map是双列数据,就是以键值对的形式存储数据。

  • 复杂回答:Set集合的底层就是数组+分支的结构,简称Hash表(散列表)。

  • 往Set集合中添加元素的算法:

    根据对象的地址算出一个hasCode值,如果底层数组中没有对应的hashCode值,则找一个数组空间放进去,如果集合中有了对应的hashCode值对象,则进一步判断两个对象是否为equlas,如果不为equlas,则新加的对象以链表的形式挂在对应的hashCode的值元素下,如果两个对象equlas,则认为对象重复了,就不添加。所以往set集合中添加的元素会

    重写hashCode方法和equlas方法。

    • HashSet(无序、唯一):底层采用HasMap来保存元素。为快速查找设计的Set。存入HashSet的对象必须定义hashCode()。

    • TreeSet(有序、唯一): 底层基于TreeMap实现,TreeMap底层是红黑树。保存次序的Set, 底层为树结构。使用它可以从Set中提取有序的序列,TreeSet集合中存放的对象必须实现了comparable接口,因为红黑树要求TreeSet集合元素

      必须按照顺序存放。

Queue

  • ArrayDeque:Object[]数组+双指针
  • PriorityQueue:Object[]数组来实现二叉堆

Map

  • HashMap jdk1.8之前由数组+链表组成,数组是HashMap的主体,而链表主要是为了解决哈希冲突。jdk1.8之后引入了红黑树。当链表长度大于等于8且数组长度大于等于64,链表为转换为红黑树。如果只满足一个条件,那么会优先选择数组扩容。
  • TreeSet底层基于TreeMap实现
  • TreeMap:红黑树(自平衡二叉排序树)
  • HashTable:数组+链表组成,数组是HashTable的主体,链表则是主要为了解决哈希冲突而存在的。

3.如何选取集合结构

  • 如果我们需要根据键值来获取元素值,就可以选用Map接口下的集合。需要排序就选用TreeMap,不需要排序就选用HashMap,需要保证线程安全就选用ConcurrentHashMap(Concurrent:/ kənˈkʌrənt /);
  • 如果只需要保存元素值,就选择实现Conllection接口的集合。需要保证元素唯一时选择Set接口下的集合,比如TreeSet或者是HashSet,不需要就选用List接口下的集合,比如ArrayList或者LinkedList

4.为什么要使用集合

当需要保存一组数据类型相同的数据的时候,由于数据的类型是多种多样的,所以我们使用了集合。集合提高了数据存储的灵活性,还可以保存具有映射关系的数据。

List

1、List常用方法

.add():添加元素           .remove(index):通过下标删除元素      .remove(Object o):删除指定的一个元素
.contains(Object o):是否包含某个元素                    .set(index,element):将index位置的元素替换为element
.add(index,element):将element元素放到index位置,原来的元素后移一位
.indexOf(Object o):显示集合中元素第一次出现的下标              .lastIndexOf(Object o):显示集合中元素最后一次出现的下标
.size():得到集合中元素的个数       .subList(fromIndex,toIndex):截取集合中的元素生成一个新集合(含头不含尾)
.equals():判断两个集合是否相等 .isEmpty():判断集合是否为空      .toString():集合转为字符串//将多个对象同时添加到一个集合中
Article article1 = new Article();
Article article2 = new Article();
Article article3 = new Article();
ArrayList<Article> list = new ArrayList<>();
Collections.addAll(list,article1,article2,article3);// 迭代器
.iterator():返回Iterator集合对象
Iterator<String> iterator = person.iterator();
while (iterator.hasNext()){System.out.println(iterator.next());
}.toArray():集合转为数组
String[] phones = (String[]) phone.toArray();
System.out.println("将集合转换为数组:"+phones);

2.ArrayList与LinkedList的区别?

(1)是否保证线程安全: ArrayList和LinkedList都是不同步的、线程不安全的。

(2)底层实现: ArrayList底层使用Object[]数组实现,LinkedList底层使用双向链表实现。(jdk1.6之前是双向循环链表,1.6之后取消了循环)。

(3)是否支持快速随机访问: ArrayList底层使用数组实现,支持快速访问。而LinkedList底层是链表实现,不支持快速访问。

(4)空间内存占用:ArrayList的空间浪费主要体现在list列表结尾会预留一定的容量空间,而LinkedList的空间花费则体现在它的每一个元素存储都需要比ArrayList消耗更多的空间(因为节点要放置前驱和后继)。

3.System.arraycopy()方法和Arrays.copyOf()方法的区别

通过查看源码我们可以知道,Arrays.copyOf()方法内部调用了System.arraycopy()方法。

arraycopy()方法需要目标数组,将原数组拷贝到自定义的数组中或者是原数组,并且可以选择拷贝的起点以及放入新的数组中的位置。

copyOf()时系统自动在内部新建一个数组,并返回该数组。

拓展:

ArrayList实现了RandomAccess接口,只是作为一个标识,说明ArrayList支持快速随机访问功能。

4.ensureCapacity()方法的作用

ensureCapacity()方法并不是在ArrayList内部调用的,而是提供给用户来使用的,在向ArrayList里面添加大量元素之前最好先使用ensureCapacity方法,以减少增量重新分配的次数,提高效率。

5.ArrayList扩容机制

  • 先创建的ArrayList的容量为0
  • 第一次调用add()方法扩容成10
  • 当容量不够需要扩容时按照1.5倍的速度进行扩容

6.ArrayList如何做到并发修改,而不出现并发修改异常?

为解决此问题呢,java引入了一个可以保证读和写都是线程安全的集合(读写分离集合):CopyOnWriteArrayList

所以解决方案就是:

// private static ArrayList<String> list = new ArrayList<>();// 使用读写分离集合替换掉原来的ArrayListprivate static CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String>();static {list.add("Jack");list.add("Amy");list.add("Lucy");}

7.ArrayList和Vector的区别?

Set

1.comparable 和 Comparator 的区别

  • comparable接口实际上是出自java.lang包中的一个CompareTo(Object obj)方法用来排序;
  • comparator实际上是出自java.util包中有一个compare(Object obj1,Object obj2)方法来排序。

当我们需要对一个集合进行自定义排序后,我们就要重写CompareTo或者是compare方法。

2.无序性和不可重复性的含义

(1)无序性?无序性不等于随机性,无序性是指存储的数据在底层数组中并非按照数组索引的顺序添加,而是根据数据的哈希值决定的。

(2)不可重复性?不可重复性是指添加的元素按照equals判断时,返回false,需要同时重写equals方法和hashCode方法。

3.比较HashSet、LinkedListHashSet、TreeSet的异同

  • HashSet、LinkedListHashSet、TreeSet三者都是Set集合的实现类,都能保证元素唯一,并且都不是线程安全的。
  • HashSet、LinkedListHashSet、TreeSet的主要区别在于底层数据结构不同。HashSet底层数据结构式HashMap(哈希表);LinkedListHashSet的底层数据结构是链表和哈希表。TreeSet的底层数据结构是红黑树,元素是有序的。
  • HashSet用于不需要保证元素插入和去除顺序的场景;LinkedListHashSet用于保证元素的插入和取出满足FIFO的场景;TreeSet用于支持元素自定义排序规则的场景。

Queue

1.Queue与Deque的区别

Queue是单端队列,只能从一端插入元素,另一端删除元素,遵循FIFO的规则。

Queue根据容量问题而导致操作失败后的处理方式不同。一种抛出异常,一种返回null。

Deque是双端队列,在队列的两端均可以插入或者删除元素。

实际上,Deque还提供了其他的方法,可以当作栈来使用。

2.ArrayDeque和LinkedList的区别

ArrayDeque和LinkedList都实现了Deque接口,两者都可以当作队列来使用。

  • ArrayDeque是基于可变容量的数组和双指针来实现的,而LinkedList是通过链表来实现的
  • ArrayDeque不支持Null数据,而LinkedLisr可以使用Null数据
  • ArrayDeque是在jdk1.6引入的,而LinkedList早在jdk1.2都已存在
  • ArrayDeque插入时可能存在扩容过程,不过插入的操作时间复杂度仍为O(1),虽然LinkedList不需要扩容,但是每次插入数据时都需要重新申请空间,性能相比更差一点

从性能上来说,ArrayDeque来实现队列要比LinkedList要好一点,另外,ArrayDeque也可以直接用作栈。

3.简单说一下PriorityQueue

PriorityQueue是在jdk1.5被引入的,与Queue的区别在于元素出队顺序是与优先级相关的,即总是优先级高的元素出队列。

  • PriorityQueue利用了二叉堆的数据结构来实现,底层使用可变长的数组来存储数据
  • PriorityQueue通过堆元素的上浮和下沉,实现了在(logn)的时间复杂度内插入元素和删除堆顶元素
  • PriorityQueue是非线程安全的,而且不支持存储NULL对象
  • PriorityQueue默认是小顶堆,但是可以接收一个comparator作为构造参数,可以自定义元素优先级的先后

Map

1.HashMap和Hashtable的区别

  • 线程是否安全:HashMap是非线程安全的,而Hashtable是线程安全的。Hashtable内部的方法基本都经过synchronized修饰。

  • 效率:因为线程安全的问题,HashMap要比Hashtable效率高,而且Hashtable已经快要淘汰了。HashMap线程不安全,HashTable线程安全,要使用线程安全的map集合可以使用ConcurrentHashMap;

  • 初始容量和每次扩充容量的大小:

    • 创建时如果不指定初始容量,Hashtable默认初始容量为11,每次扩充之后,边缘原来的 2n+1 ;而HashMap默认初始化大小为16,每次扩容会变为原来的2倍。
  • Hashmap是允许key和value为null值的,用containsValue和containsKey方法判断是否包含对应键值对;

    ​ HashTable键值对都不能为空,否则包空指针异常。

  • 底层数据结构:JDK1.8之后HashMap在解决哈希冲突时,当链表长度大于8且数组长度大于64,链表就会转化为红黑树,若有一个条件不满足,那么会优先选择数组扩容。而Hashtable没有这样的机制。

2.HashMap和HashSet的区别

HashSet底层就是基于HashMap实现的。

HashMap实现了Map接口,而HashSet实现了Set接口。

HashMap用于存储键值对,而HashSet用于存储对象。

HashMap不允许有重复的键,可以允许有重复的值。HashSet不允许有重复元素。

HashMap允许有一个键为空,多个值为空,HashSet允许有一个空值。

HashMap中使用put()将元素加入map中,而HashSet使用add()将元素放入set中。

HashMap比较快,因为其使用唯一的键来获取对象。

3.HashMap和TreeMap的区别

HashMap和TreeMap都继承于AbstractMap,但是TreeMap还实现了NavigableMap接口和SortedMap接口。

  • 实现Navigable接口让TreeMap对集合内元素有了搜索的功能;
  • 实现SortedMap接口让TreeMap有了对集合中的元素根据键排序的能力。

相比来说,TreeMap多了对集合中元素根据键排序的功能和对集合元素进行搜索的能力。

4.HashSet如何检查重复?

当把对象加入到HashSet中时,HashSet会先计算对象的hashCode值来判断对象加入的下标位置,同时也会与其他的对象的hashCode进行比较,如果没有相同的,就直接插入数据;如果有相同的,就进一步使用equals来进行比较对象是否相同,如果相同,就不会加入成功。5.HashCode与equals的相关规定

(1)如果两个对象相等,则hashCode也一定是相等的

(2)两个对象相等,对两个进行equals也会返回true

(3)两个对象有相同的hashCode,他们也不一定是相等的

(4)hashCode和equals方法都必须被同时覆盖

6.HashMap的长度为什么是2的n次方?

为了能让HashMap存取高效,尽量减少哈希碰撞,尽量把数据均匀分配。

7.HashMap多线程下操作导致死循环问题

主要原因是并发下的Rehash会造成元素之间形成一个循环链表。多线程下HashMap会存在数据丢失的问题,并发环境下推荐使用ConcurrentHashMap、

8.ConcurrentHashMap 和 Hashtable 的区别

ConcurrentHashmap和Hashtable的区别主要体现在实现线程安全的方式上不同

  • 底层数据结构: jdk1.7的ConcurrentHashMap底层采用分段的数组+链表实现,jdk1.8采用的数据结构跟HashMap1.8的结构是一样的,数树。Hashtable和jdk1.8之前的HashMap的底层数据结构类似,都是采用数组+链表的形式, 数组是hashMap的主体,链表则是为了解决哈希冲突。
  • 实现线程安全的方法:①在1.7的时候,ConcurrentHashMap(分段锁)对整个桶数组进行了分割分段(Segment),到1.8直接使用数组+链表+红黑树的实现。

9.ConcurrentHasMap底层如何实现分布式锁

将同一个容器的划分为好多Segmet(段),段数就是并发度,某个线程执行某些操作只会获取一个段的锁,

而不会影响其他线程获取其他锁的段。

扩展: ConcurrentHashMap把整个容器分为许多个Segment(段),段数就是并发度,每个Segment类似于一个Hashtable,管理一个HashEntry数组,每个HashEntry是一个链表头,或者红黑树的根,其实Segment的数据结构类似于HashMap。

10.map常用方法

.put(Object key,Object value):将指定key-value添加或修改当前map对象中
.putAll(Map m):将集合m中所有的key-value添加到当前map中
.remove(Object key):删除指定key的key-value对,并返回当前key对应的value
.clear():清空当前map中的所有数据(clear方法只是把元素清除掉,但在堆上开辟的map还在)
.isEmpty():判断集合是否为空(底部是用size==0判断的)
.get(Object key):通过key获取value
.containsKey(Object key)/.containsValue(Object value)是否包含key/value
.size():得到集合键值对个数
.keySet():获取Map中所有的key,返回一个Set集合
.values():获取map中所有的value,返回一个Conllection

HashMap

1.put数据时key与已有数据的HashCode相等时会怎么样?

会产生哈希碰撞。

如果Hash码相同,则会通过equals方法进行比较key是否相同:

如果key值相同,则使用新的value代替旧的value;

如果key值不相同,则会在该节点的链表结构上新增一个节点(如果链表长度>=8并且数组节点数>=64 ,链表结构就会转化成红黑树)。

2.什么时候会产生hash碰撞?如何解决哈希碰撞?

只要通过hash函数计算所得到的两个元素的hash值相同就会产生hash碰撞。

HashMap在jdk8之前采用链表解决哈希碰撞,jdk8之后采用链表+红黑树解决哈希碰撞。

3.如果hashCode相同,那么如何存储key-value对?

当hashCode值相同时,就会进一步使用equals方法进行比较key是否相同。

如果key相同,那么新值覆盖旧值;

如果key不相同,则将新的key-value添加到HashMap中。

4.HashMap如何进行扩容?

当通过put方法不断进行数据添加时,如果元素个数超过了当前阈值就会进行扩容,默认扩容大小是原来的2倍,扩容之后会将原来的数据复制到新的数组中。

5.如果确定了要存储的元素个数n,设置多少的初始容量可以减少扩容导致的性能损失?

应该设置初始容量为 n/0.75 + 1 取整即可减少resize导致的性能损失。

Collections工具类

常用方法:

  • 排序
  • 查找,替换
  • 同步控制(不推荐)

排序操作

void reverse(List list)//反转
void shuffle(List list)//随机排序
void sort(List list)//按自然排序的升序排序
void sort(List list, Comparator c)//定制排序,由Comparator控制排序逻辑
void swap(List list, int i , int j)//交换两个索引位置的元素
void rotate(List list, int distance)//旋转。当distance为正数时,将list后distance个元素整体移到前面。当distance为负数时,将 list的前distance个元素整体移到后面
123456

查找,替换

int binarySearch(List list, Object key)//对List进行二分查找,返回索引,注意List必须是有序的
int max(Collection coll)//根据元素的自然顺序,返回最大的元素。 类比int min(Collection coll)
int max(Collection coll, Comparator c)//根据定制排序,返回最大元素,排序规则由Comparatator类控制。类比int min(Collection coll, Comparator c)
void fill(List list, Object obj)//用指定的元素代替指定list中的所有元素
int frequency(Collection c, Object o)//统计元素出现次数
int indexOfSubList(List list, List target)//统计target在list中第一次出现的索引,找不到则返回-1,类比int lastIndexOfSubList(List source, list target)
boolean replaceAll(List list, Object oldVal, Object newVal)//用新元素替换旧元素
1234567

集合注意事项

集合判空

集合判断是否为空,使用isEmpty()的方法,而不是size==0的方式

因为isEmpty()方法的可读性好,而且时间复杂度为O(1)

集合转化Map

使用Java.util.stream.Collectors类的toMap()方法转化为map集合时,要注释当value为null时会抛出NPE异常。

集合遍历

不要在foreach里面进行元素的remove/add操作,remove请使用Iterator方式,如果并发操作,需要对Iterator对象加锁。

fail-fast 机制 :多个线程对 fail-fast 集合进行修改的时候,可能会抛出ConcurrentModificationException

集合去重

可以利用Set元素唯一的特性,进行集合去重,避免使用List的contains进行遍历去重或者判断包含操作

集合转数组

使用集合转数组的方法,必须使用集合的toArray(T[] array),传入的类型完全一致,长度为0的空数组。

toArray(T[] array) 方法的参数是一个泛型数组,如果 toArray 方法中没有传递任何参数的话返回的是 Object类 型数组。

数组转集合

使用工具类Arrays.asList()把数组转化成集合时,不能使用其修改集合相关的方法,它的add/remove/clear方法会抛出UnsupportedOperationException异常。


集合知识补充

一, 先来看几个变量、常量、静态变量、静态常量:
1)链表与红黑树互相转化的阀值:
 //将链表转化为红黑树的阀值static final int TREEIFY_THRESHOLD = 8;//将红黑树转回为链表的阀值static final int UNTREEIFY_THRESHOLD = 6;//将链表转为红黑树的最小entry数组的长度为 64//当链表的长度 >= 8,且entry数组的长度>= 64时,才会将链表转为红黑树。static final int MIN_TREEIFY_CAPACITY = 64;
2) Map的默认初始化容量以及容量极限:
//HashMap默认的初始容量大小--16,容量必须是2的幂
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16//HashMap的容量极限,为2的30次幂;
static final int MAXIMUM_CAPACITY = 1 << 30;
3) 默认负载因子、实际元素数量:
//负载因子的默认大小,
//元素的数量/容量得到的实际负载数值与负载因子进行对比,来决定容量的大小以及是否扩容;
static final float DEFAULT_LOAD_FACTOR = 0.75f;//HashMap的实际元素数量
transient int size;
4)table——Entry数组;
//Node是Map.Entry接口的实现类,可以将这个table理解为是一个entry数组;
//每一个Node即entry,本质都是一个单向链表
transient Node<K,V>[] table;
5)Map已经修改的次数、下一次扩容的大小、存储负载因子的常量:
//HashMap已在结构上修改的次数 结构修改是指更改 HashMap 中的映射数量或以其他方式修改其内部结构(例如,重新散列)的那些
transient int modCount;//下一次HashMap扩容的阀值大小,如果尚未扩容,则该字段保存初始entry数组的容量,或用零表示
int threshold;//存储负载因子的常量,初始化的时候将默认的负载因子赋值给它;
final float loadFactor;

5.5)什么是Map已经修改的次数?

​ modCount用于记录HashMap的修改次数,在HashMap的put(),get(),remove(),Interator()等方法中,都使用了该属性。

​ 由于HashMap不是线程安全的,所以在迭代的时候,会将modCount赋值到迭代器的expectedModCount属性中,然后进行迭代;

​ 如果在迭代的过程中HashMap被其他线程修改了,modCount的数值就会发生变化,这个时候expectedModCount和ModCount不相等,迭代器就会抛出ConcurrentModificationException()并发修改异常

二,链表转红黑树

为什么要将链表转为红黑树:

当这个链表过长了,查找这个链表上的元素的时候自然会变慢,所以链表如果过长了会影响到HashMap的性能,

所以后来在Java8中,当链表过长时,会将该链表自动转为红黑树,红黑树是一个自平衡二叉树,能够优化查找的性能。

什么时候将链表转换为红黑树

①,当链表的长度 >= 8,并且entry数组的长度>= 64时,才会将链表转为红黑树。

②,当链表的长度 >= 8,但是entry数组的长度< 64时,不转为红黑树,而是调用resize方法进行扩容;

什么时候将红黑树退化回为链表:

在resize()方法扩容的时候,在将原Node数组迁移到扩容后的新Node数组的时候,如果该Node元素是一个红黑树,则对其进行拆分、然后才迁移到新的Node数组中,如果拆分之后的子树的数量小于等于6了,则将该子树转回为链表结构;


如何通过不改变引用的指向来修改内容

String str = new String("abc")在不改变引用指向的前提下输出“abcd”

public class No1 {public static void main(String[] args) throws Exception{String str = new String("abc");.....System.out.println(str); //abcd
}

只要通过String源码可以看到,它是通过参数赋值给value属性,value为一个char[]数组,

即可通过反射来获取value字段,将字段值直接修改为abcd即可:

public class No1 {public static void main(String[] args) throws Exception{String str = new String("abc");//通过反射拿到str里面的value字段Field value = str.getClass().getDeclaredField("value");//修改权限为truevalue.setAccessible(true);//将abc直接修改为abcd并转换为字符数组value.set(str,"abcd".toCharArray());System.out.println(str);}

代码解析:

setAccessible()方法不属于Field,它属于AccessibleObject类,
Field通过extends AccessibleObject来获得setAccessible()方法;getDeclaredField("value"):
java.lang.Class类的getDeclaredField()方法用于获取此类的指定字段。该方法以Field对象的形式返回此类的指定字段。setAccessible(true):
该方式是用来设置获取权限的。
如果 accessible 标志被设置为true,那么反射对象在使用的时候,不会去检查Java语言权限控制(private之类的);
如果设置为false,反射对象在使用的时候,会检查Java语言权限控制。
需要注意的是,设置为true会引起安全隐患。

String底层源码解析:

public String(String original) {this.value = original.value;this.hash = original.hash;}
// 调用String类的构造方法,通过源码可以看到,它是把传进去的值赋值给value属性public final class Stringimplements java.io.Serializable, Comparable<java.lang.String>, CharSequenc/** The value is used for character storage. */private final char value[];......
// value属性为一个char数组

冒泡排序、选择排序、插入排序

冒泡排序演示:

public class No6 {public static void main(String[] args) {int[] ary = new int[100000];for (int i = 0; i < ary.length; i++) {int ran = (int) (Math.random()*200000);ary[i] = ran;}
9long t1 = System.currentTimeMillis();/*** 冒泡外层:每一轮都是从第一个元素开始比较,所以起始下标永远都是0* 外层:比较轮数* 内层:元素下标*///冒泡排序 耗时:21327msfor (int i = 0; i < ary.length; i++) {for (int j = 0; j < ary.length-1-i; j++) {if (ary[j]<ary[j+1]){int tmp = ary[j];ary[j]=ary[j+1];ary[j+1]=tmp;}}}long t2 = System.currentTimeMillis();System.out.println("耗时:"+(t2-t1)+"ms");}
}

选择排序演示:

public class No6 {public static void main(String[] args) {int[] ary = new int[100000];for (int i = 0; i < ary.length; i++) {int ran = (int) (Math.random()*200000);ary[i] = ran;}long t1 = System.currentTimeMillis();/*** 选择排序:* 选择排序(Selection sort)是一种简单直观的排序算法。* 它的工作原理是:* 第一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,* 然后再从剩余的未排序元素中寻找到最小(大)元素,然后放到已排序的序列的末尾。* 以此类推,直到全部待排序的数据元素的个数为零。选择排序是不稳定的排序方法。**///选择排序 耗时:6139ms//外层:i表示对该位置求最小值for (int i = 0; i < ary.length; i++) {int min = 1;//内层:j表示对i后的每个元素进行遍历,判断其与min指向元素的大小for (int j = i+1; j < ary.length; j++) {if (ary[j]<ary[min]){min = j;}}//将min指向的元素和i位置的元素互换if (min!=i){int tmp = ary[i];ary[i] = ary[min];ary[min] = tmp;}}long t2 = System.currentTimeMillis();System.out.println("耗时:"+(t2-t1)+"ms");}
}

插入排序演示

public class No6 {public static void main(String[] args) {int[] ary = new int[100000];for (int i = 0; i < ary.length; i++) {int ran = (int) (Math.random()*200000);ary[i] = ran;}long t1 = System.currentTimeMillis();/*** 插入排序:* 将序列分为排好序和未排序部分,对未排序部分元素进行遍历,* 将遍历到的元素插入到排好序元素的适当位置,通过比较和交换来实现*///插入排序 耗时:2149msfor (int i = 1; i < ary.length; i++) {for (int j = i; j>0 && ary[j]<ary[j-1]; j--) {int tmp = ary[j];ary[j] = ary[j-1];ary[j-1] = tmp;}}long t2 = System.currentTimeMillis();System.out.println("耗时:"+(t2-t1)+"ms");}
}

Java中八种数据结构

  • 哈希表(Hash)
  • 队列(Queue)
  • 树(Tree)
  • 堆(Heap)
  • 数组(Array)
  • 栈(Stock)
  • 链表(Linked List)
  • 图(Graph)

哈希表(Hash)

哈希表也叫散列表,是一种可以通过关键码值(Key-Value)直接访问的数据结构,可以实现快速查询、插入、删除。

数组类型的数据结构在插入和删除时时间复杂度高;链表类型的数据结构在查询时时间复杂度高;而哈希表结合了数组与链表的优势。

在jdk8中,Java中经典的HashMap,以数组+链表+红黑树构成。

哈希函数在哈希表中起着关键作用,能够将任意长度的输入转为定长的输出(哈希值)。通过哈希函数,能够快速地对数据元素进行定位。

哈希值并不是具有唯一性,在某些情况下Hash值会冲突,HashMap在Hash冲突时,会将元素在数组的位置上添加为链表元素结点,当链表长度大于8时,链表会转换为红黑树。

队列(Queue)

类比水管,两端放开,一端入水,一端出水。

队列是一种特殊的线性表,它只允许在表的前端进行删除操作,而在表的后端进行插入操作。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cPzOtJJo-1667037486895)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20220802085955709.png)]

树(Tree)

树是一种非线性结构,由n(n>0)个有限结点组成有层次关系的集合。

术语:

二叉树:每个结点最多含有2个子树。

完全二叉树:除了最外层的结点,其他各层结点都达到最大数。

满二叉树

国内定义:除最后一层无任何子节点外,每一层上的所有结点都有两个子结点的二叉树。
国外定义:如果一棵二叉树的结点要么是叶子结点,要么它有两个子结点,这样的树就是满二叉树。
二叉查找树

任意结点的左子树不为空,左子树所有结点的值均小于根结点的值。
任意结点的右子树不为空,右子树所有结点的值均大于根节点的值。
任意结点的左右子树也是一颗二叉查找树。
平衡二叉树:也称AVL树,当且仅当任何结点的两棵子树的高度差不大于1的二叉树。Java中HashMap的红黑树就是平衡二叉树!!!

B树:一种对读写优化的自平衡二叉树,在数据库的索引中常见的BTREE就是自平衡二叉树。

B+树:B+树是应文件系统所需而产生的B树的变形树。

所有的非终端结点可以看成是索引部分,结点中仅含有其子树根结点中最大(或最小)关键字
所有的叶子结点中包含了全部关键字的信息,及指向含有这些关键字记录的指针,且叶子结点本身依关键字的大小自小而大的顺序链接。
有m个子树的中间节点包含有m个元素(B树中是k-1个元素),每个元素不保存数据,只用来索引。
Java8中HashMap的红黑树
实质上就是平衡二叉树,通过颜色约束二叉树的平衡:
1)每个节点都只能是红色或者黑色

2)根节点是黑色

3)每个叶节点(NIL 节点,空节点)是黑色的。

4)如果一个节点是红色的,则它两个子节点都是黑色的。也就是说在一条路径上不能出现相邻的两个红色节点。

5)从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。

堆(Heap)

堆可以被看成一个树的数组对象,具有如下特点:

堆是一颗完全二叉树。
最大堆/大根堆:某个结点的值不大于父结点的值。
最小堆/小根堆:某个结点的值不小于父结点的值。

数组(Array)

数组是一种线性表的数据结构,连续的空间存储相同类型的数据。

  • 优点:

按照索引查询元素的速度很快;

按照索引遍历数组也很方便。

  • 缺点:

数组的大小在创建后就确定了,无法扩容;

数组只能存储一种类型的数据;

添加、删除元素的操作很耗时间,因为要移动其他元素。

栈(Stock)

栈可以类比为水桶,只有一端能够进出,遵循的先进后出的规则。

栈先进的元素进入栈底,读元素的时候从栈顶取元素。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-erQRTUNV-1667037486897)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20220802085901651.png)]

链表(Linked List)

链表是一种线性表的链式存储方式,链表的内存是不连续的,前一个元素存储地址的下一个地址中存储的不一定是下一个元素。链表通过一个指向下一个元素地址的引用将链表中的元素串起来。

单向链表:单向链表是最简单的链表形式。我们将链表中最基本的数据称为节点(node),每一个节点包含了数据块和指向下一个节点的指针。
双向链表:顾名思义,双向链表就是有两个方向的链表。同单向链表不同,在双向链表中每一个节点不仅存储指向下一个节点的指针,而且存储指向前一个节点的指针。通过这种方式,能够通过在O(1)时间内通过目的节点直接找到前驱节点,但是同时会增加大量的指针存储空间。
循环链表:循环链表与双向链表相似,不同的地方在于:在链表的尾部增加一个指向头结点的指针,头结点也增加一个指向尾节点的指针,以及第一个节点指向头节点的指针,从而更方便索引链表元素。

图(Graph)

一个图就是一些顶点的集合,这些顶点通过一系列边结对(连接)。顶点用圆圈表示,边就是这些圆圈之间的连线。顶点之间通过边连接。

节点之间的关系是任意的,图中任意两个数据元素之间都有可能相关。


设计模式

设计模式有哪六大原则?

  • 单一职责原则
  • 开放封闭原则
  • 里氏替换原则
  • 依赖倒置原则
  • 迪米特原则
  • 接口隔离原则

设计模式的三大类

总体来说设计模式分为三大类:

创建型模式(5种):工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。

结构型模式(7种):适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。

行为型模式(11种):策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。

其实还有两类:并发型模式和线程池模式。用一个图片来整体描述一下:

根据作用范围来分

根据模式是主要用于类上还是主要用于对象上来分,这种方式可分为类模式和对象模式两种。

类模式:用于处理类与子类之间的关系,这些关系通过继承来建立,是静态的,在编译时刻便确定下来了。工厂方法、(类)适配器、模板方法、解释器属于该模式。
对象模式:用于处理对象之间的关系,这些关系可以通过组合或聚合来实现,在运行时刻是可以变化的,更具动态性。

范围\目的 创建型模式 结构型模式 行为型模式
类模式 工厂方法 (类)适配器 模板方法、解释器
对象模式 单例 原型 抽象工厂 建造者 代理 (对象)适配器 桥接 装饰 外观 享元 组合 策略 命令 职责链 状态 观察者 中介者 迭代器 访问者 备忘录

单例模式

单例模式? 懒汉模式? 饿汉模式? 它们之间的安全问题

单例模式:1.单例类只有一个实例;

​ 2.单例类必须自己创建自己的实例;

​ 3.单例类必须给所有其它对象提供这一实例。

单例模式的实现:1.创建一个私有的构造函数(防止其他类直接new此对象)

​ 2.创建一个私有的属性对象

​ 3.创建一个公共的对外暴露得单例对象

懒汉式 :非线程安全,懒汉比较懒只有当调用getInstance的时候,才会去初始化这个单例。

if判断以及其内存执行代码是非原子性的。其次,new Singleton()无法保证执行的顺序性。

public class SingletonLH {/***是否 Lazy 初始化:是*是否多线程安全:否*实现难度:易*描述:这种方式是最基本的实现方式,这种实现最大的问题就是不支持多线程。
*因为没有加锁 synchronized,所以严格意义上它并不算单例模式。*这种方式 lazy loading 很明显,不要求线程安全,在多线程不能正常工作。*/private static SingletonLH instance;private SingletonLH (){}public static SingletonLH getInstance() {if (instance == null) {instance = new SingletonLH();}return instance;
}
}

饿汉式:线程安全,饿汉就是类一旦加载,就把单例初始化完成,保证getInstance的时候,单例是已经存在的了。

public class SingletonEH {/***是否 Lazy 初始化:否*是否多线程安全:是*实现难度:易*描述:这种方式比较常用,但容易产生垃圾对象。*优点:没有加锁,执行效率会提高。*缺点:类加载时就初始化,浪费内存。*它基于 classloder 机制避免了多线程的同步问题,* 不过,instance 在类装载时就实例化,虽然导致类装载的原因有很多种,* 在单例模式中大多数都是调用 getInstance 方法,* 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,* 这时候初始化 instance 显然没有达到 lazy loading 的效果。*/private static SingletonEH instance = new SingletonEH();private SingletonEH (){}public static SingletonEH getInstance() {System.out.println("instance:"+instance);System.out.println("加载饿汉式....");return instance;}
}

保证懒加载的线程安全

我们首先想到的就是使用synchronized关键字。synchronized加载getInstace()函数上确实保证了线程的安全。但是,如果要经常的调用getInstance()方法,不管有没有初始化实例,都会唤醒和阻塞线程。为了避免线程的上下文切换消耗大量时间,如果对象已经实例化了,我们没有必要再使用synchronized加锁,直接返回对象。

public class Singleton {private static Singleton instance = null;private Singleton() {};public static synchronized Singleton getInstance() {if (instance == null) {instance = new Singleton();}return instance;}
}

我们把sychronized加在if(instance==null)判断语句里面,保证instance未实例化的时候才加锁

public class Singleton {private static Singleton instance = null;private Singleton() {};public static synchronized Singleton getInstance() {if (instance == null) {synchronized (Singleton.class) {if (instance == null) {instance = new Singleton();}}}return instance;}
}

new一个对象的代码是无法保证顺序性的,因此,我们需要使用另一个关键字volatile保证对象实例化过程的顺序性。

public class Singleton {private static volatile Singleton instance = null;private Singleton() {};public static synchronized Singleton getInstance() {if (instance == null) {synchronized (instance) {if (instance == null) {instance = new Singleton();}}}return instance;}
}

到此,我们就保证了懒加载的线程安全。

工厂方法模式

1、工厂方法模式

创建对象的过程不再由当前类实例化,而是由工厂类完成,在工厂类中只需要告知对象类型即可。工厂模式中必须依赖接口

简单工厂模式

简单工厂模式的工厂类一般是使用静态方法,通过接收的参数的不同来返回不同的对象实例。不修改代码的话,是无法扩展的。

以生产“电脑”为例,电脑有办公的功能,可以生产一体机或笔记本

代码与静态工厂一样

2.静态工厂模式

//电脑接口
public interface Computer {//电脑办公public void work();
}
//笔记本
public class PersonComputer implements  Computer{@Overridepublic void work() {System.out.println("这是笔记本电脑,正在办公");}
}
//一体机
public class WorkComputer implements  Computer{@Overridepublic void work() {System.out.println("这是一体机正在办公");}
}
//用于生产电脑的工厂 (这个工厂既可以生产台式机也可以生产笔记本)
public class ComputerFactory {/*** 根据不同的类型 生产不同的产品* @param type* @return*/public Computer produce(String type){Computer computer =null;if(type.equals("personComputer")){computer = new PersonComputer();}else if(type.equals("workComputer")){computer = new WorkComputer();}else{System.out.println("不能生产");}return computer;}
//静态工厂方法模式
public class ComputerFactory2 {/***  静态工厂方法* @param type* @return*/public static Computer produce(String type){// 定义一个接口的引用    通过接口new 一个实现类的对象// 提高扩展性Computer computer=null;if(type.equals("workComputer")){computer = new WorkComputer();}else if(type.equals("personComputer")){computer = new PersonComputer();}else{System.out.println("不能创建对象");}return computer;}
}
//测试类
public class Test1 {public static void main(String[] args) {// 通过工厂类创建对象ComputerFactory factory = new ComputerFactory();// 要对象 找工厂Computer computer1 = factory.produce("workComputer");computer1.work();// 创建笔记本Computer computer2 = factory.produce("personComputer");computer2.work();Computer computer3 = ComputerFactory2.produce("workComputer");computer3.work();}
}

3.3工厂方法模式

工厂方法是针对每一种产品提供一个工厂类。通过不同的工厂实例来创建不同的产品实例。在同一等级结构中,支持增加任意产品。

例如:

//汽车接口
public interface Car {public void  showInfo();
}
public class AudiCar implements Car {@Overridepublic void showInfo() {System.out.println("这是一台奥迪汽车。。");}
}
public class BMWCar implements Car {@Overridepublic void showInfo() {System.out.println("这是一台宝马汽车。");}
}
/**
生产汽车的工厂接口
**/
public interface CarFactory {public Car produce();
}
public class AudiCarFactory implements  CarFactory {@Overridepublic Car produce() {return  new AudiCar();// 这里AudiCar是Car的实现类}
}
public class BMWCarFactory implements CarFactory {@Overridepublic Car produce() {return new BMWCar();// 因为BWMCar是Car的实现类}
}
public class Test1 {public static void main(String[] args) {//先创建 汽车工厂CarFactory bmwFactory = new BMWCarFactory();// 这个工厂生产的汽车就是 宝马Car bmw = bmwFactory.produce();bmw.showInfo();//这个模式对于同一级别的产品,可扩展性高//可以扩展不同品牌的汽车,此时不需要修改代码,只需要增加代码即可// 创建一个新的品牌汽车  大众汽车CarFactory dazhongFactory = new DazhongCarFactory();Car car = dazhongFactory.produce();car.showInfo();}}

抽象工厂模式

对于在工厂方法的基础上,对同一个品牌的产品有不同的分类,并对分类产品创建的过程 ,一个汽车产品 会分为不同的种类(迷你汽车 ,SUV汽车 )

/*** 迷你汽车接口*/
public interface MiniCar {public void showInfo();
}
/*** SUV汽车接口*/
public interface SUVCar {public void showInfo();}
public class AudiMiniCar implements  MiniCar {@Overridepublic void showInfo() {System.out.println("这是奥迪迷你汽车 ");}
}
public class BMWMiniCar implements  MiniCar {@Overridepublic void showInfo() {System.out.println("这是宝马Cooper迷你汽车");}
}
public class AudiSUVCar implements  SUVCar {@Overridepublic void showInfo() {System.out.println("这是一辆 奥迪SUV汽车");}
}
public class BMWSUVCar implements  SUVCar {@Overridepublic void showInfo() {System.out.println("这宝马的SUV系列");}
}
public interface CarFactory {//生成不同型号的汽车 ,两条产品线public MiniCar produceMiniCar();public SUVCar produceSUVCar();
}
public class AudiCarFactory implements  CarFactory {@Overridepublic MiniCar produceMiniCar() {return new AudiMiniCar();}@Overridepublic SUVCar produceSUVCar() {return new AudiSUVCar();}
}
public class BMWCarFactory implements  CarFactory {// 生成迷你汽车的方法,返回MiniCar@Overridepublic MiniCar produceMiniCar() {return new BMWMiniCar();}//生成SUV汽车的方法, 返回SUVCar@Overridepublic SUVCar produceSUVCar() {return new BMWSUVCar();}
}
/*** 测试类*/
public class Test1 {public static void main(String[] args) {//创建宝马迷你汽车  找工厂CarFactory factory = new BMWCarFactory();MiniCar car = factory.produceMiniCar();car.showInfo();}
}

总结

对于简单工厂,工厂方法模式和抽象工厂的区别和用途

★工厂模式中,重要的是工厂类,而不是产品类。产品类可以是多种形式,多层继承或者是单个类都是可以的。但要明确的,工厂模式的接口只会返回一种类型的实例,这是在设计产品类的时候需要注意的,最好是有父类或者共同实现的接口。

★使用工厂模式,返回的实例一定是工厂创建的,而不是从其他对象中获取的。

★工厂模式返回的实例可以不是新创建的,返回由工厂创建好的实例也是可以的。

区别

1、对于简单工厂,用于生产同一结构中的任意产品,对于新增产品不适用。

2、对于工厂方法,在简单工厂的基础上,生产同一等级结构中笃定产品,可以支持新增产品。

3、抽象工厂,用于生产不同种类(品牌)的相同类型(迷你,SUV),对于新增品牌可以,不支持新增类型


JVM

JVM构成

  • 类加载器: 负责加载类到内存中
  • 运行时数据区: 储存数据 对象,方法等等

  • 执行引擎: 负责解释执行字节码,GC操作等
  • 本地库接口: 融合其他语言为Java所用
JVM运行内存如何划分?
  • 堆内存
  • 栈内存
  • 程序计数器
  • 方法区

什么是JVM调优

简单理解,JVM调优主要就是为了解决系统运行时慢、卡顿、OOM、死锁等问题。

其实上面所说的问题存在很多方面的原因,比如网络波动导致响应时间慢、数据库查询慢、死锁等,今天我们主要分析JVM层面的,而JVM调优,主要是为了减少Full GC问题,也就是针对堆内存进行优化。

jvm调优涉及到两个很重要的概念:吞吐量和响应时间。jvm调优主要是针对他们进行调整优化,达到一个理想的目标,根据业务确定目标是吞吐量优先还是响应时间优先。

吞吐量:用户代码执行时间/(用户代码执行时间+GC执行时间)。指系统在单位时间内处理请求的数量。对于并发系统,通常需要用吞吐 量作为性能指标。
响应时间:整个接口的响应时间(用户代码执行时间+GC执行时间),stw时间越短,响应时间越短。指系统对请求作出响应的时间。对 于单用户的系统,响应时间可以很好地度量系统的性能。

Stop-the-World,简称STW
指的是GC事件发生过程中,会产生应用程序的停顿。停顿产生时整个应用程序线程都会被暂停,没有任何响应,
有点像卡死的感觉,这个停顿称为STW。

一、调优步骤

​ 调优的前提是熟悉业务场景,先判断出当前业务场景是吞吐量优先还是响应时间优先。调优需要建立在监控之上,由压力测试来判断是否达到业务要求和性能要求。

简单描述就是牺牲响应时间来满足内存要求,或者是增加内存来满足响应时间

调优的步骤大致可以分为:

1.熟悉业务场景,了解当前业务系统的要求,是吞吐量优先还是响应时间优先;

2.选择合适的垃圾回收器组合,如果是吞吐量优先,则选择ps+po组合;如果是响应时间优先,在1.8以后选择G1,在1.8之前选择ParNew+CMS组合;

在注重吞吐量或者处理器资源较为稀缺的场合,都可以优先考虑Parallel Scavenge加ParallelOld收集器这个组合

Exception in thread “main” java.lang.OutOfMemoryError: Java heap space`这个异常吧,没错,

它就是大名鼎鼎的OOM(堆内存溢出)

一、CMS定义和特点
CMS收集器是一种以获取最短停顿时间为目标的收集器。它的优点是并发收集和低停顿。
二、CMS 基于哪种GC算法实现的
CMS主要是基于标记清除算法实现的
三、标记清除算法详解
算法主要是分为 标记 和 清除 两个阶段实现,在标记阶段 从 GC root出发通过可达性分析将需要被清除的对象标记起来,在清除阶段全部进行清除,这种算法有两个比较显著的一个缺点,一个是效率问题,一个是空间问题,标记-清除算法效率其实并不理想,这与它需要事先将需要清除的对象标记起来有关系,再一个就是空间的问题,该算法在清理的过程中会产生许多的空间碎片,这对内存是一种不小的浪费。为了解决空间碎片的问题 jvm引入了标记-整理算法,这将在之后的文章中介绍。
四、CMS 回收的四个阶段
(1)、初始标记
根据可达性分析从 GC roots 出发标记待清理对象的过程
(2)、并发标记
并发 从 GC roots tracing 的过程
(3)、重新标记
并发表基过程中由于 程序运行而导致标记产生变动的那一部分对象的重新标记
(4)、并发清除
清除标记对象的过程

3.规划内存需求,只能进行大致的规划。

4.CPU选择,在预算之内性能越高越好;

5.根据实际情况设置升级年龄,最大年龄为15;

为了避免这种朝生夕死的对象进入老年代,我们可以加大一下年轻代的容量和减少对象进入老年代的年龄阈值。

6.设定日志参数:-Xloggc:/path/name-gc-%t.log -XX:+UseGCLogFileRotation -XX:NumberOfGCLogs=5 -XX:GCLogFileSize=20M -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCCauses

-XX:+UseGCLogFileRotation:GC文件循环使用

-XX:NumberOfGCLogs=5:使用5个GC文件

-XX:GCLogFileSize=20M:每个GC文件的大小

上面这三个参数放在一起代表的含义是:5个GC文件循环使用,每个GC文件20M,总共使用100M存储日志文件,当5个GC文件都使用完毕以后,覆盖第一个GC日志文件,生成新的GC文件。

二、CPU使用率飙高问题

​ 当cpu经常飙升到100%的使用率,那么证明有线程长时间占用系统资源不进行释放,需要定位到具体是哪个线程在占用,定位问题的步骤如下(linux系统):

​ 1.使用top命令查看当前服务器中所有进程(jps命令可以查看当前服务器运行java进程),找到当前cpu使用率最高的进程,获取到对应的pid;

2.然后使用top -Hp pid,查看该进程中的各个线程信息的cpu使用,找到占用cpu高的线程pid

3.使用jstack pid打印它的线程信息,需要注意的是,通过jstack命令打印的线程号和通过top -Hp打印的线程号进制不一样,需要进行转换才能进行匹配,jstack中的线程号为16进制,而top -Hp打印的是10进制。

使用jastack命令分析线程信息的时候需要关注线程对应的运行状态:

runnable代表当前线程正在运行,waiting代表当前线程正在等待,该状态需要进行特殊关注wait fot 后面的线程号,因为如果当前处于

waiting状态的程序长时间处于等待状态,那么就需要知道它在等待哪个线程结束,也就是wait for后面的线程号(jdk版本不同,单词可能不一样,总之就是在日志中找到它等待的线程号)然后根据线程号找到对应的线程,去查看当前线程有什么问题。


三、内存标高问题

​ 内存飙高一般都是堆中对象无法回收造成,因为java中的对象大部分存储在堆内存中。

其实也就是常见的oom问题(Out Of Memory)。

指令
1.jinfo pid,可以查看当前进行虚拟机的相关信息列举出来,如下图
2.jstat -gc pid ms,多长毫秒打印一次gc信息,打印信息如下,里面包含gc测试,年轻代/老年带gc信息等:
3.jmap -histo pid  head -20,查找当前进程堆中的对象信息,加上管道符后面的信息以后,代表查询对象数量最多的20个:

jmap -dump:format=b,file=xxx pid,可以生成堆信息的文件,但是这个命令不建议在生产环境使用,因为当内存较大时,执行该命令会占用大量系统资源,甚至造成卡顿。建议在项目启动时添加下面的命令,在发生oom时自动生成堆信息文件:-XX:+HeapDumpOnOutOfMemory。如果需要在线上进行堆信息分析,如果当前服务存在多个节点,可以下线一个节点,生成堆信息,或者使用第三方工具,阿里的arthas。

四、jvm调优常用参数

​ 通用GC参数
​ -Xmn:年轻代大小 -Xms:堆初始大小 -Xmx:堆最大大小 -Xss:栈大小

    -XX:+UseTlab:使用tlab,默认打开,涉及到对象分配问题-XX:+PrintTlab:打印tlab使用情况-XX:+TlabSize:设置Tlab大小-XX:+DisabledExplictGC:java代码中的System.gc()不再生效,防止代码中误写,导致频繁触动GC,默认不起用。-XX:+PrintGC(+PrintGCDetails/+PrintGCTimeStamps)打印GC信息(打印GC详细信息/打印GC执行时间)-XX:+PrintHeapAtGC打印GC时的堆信息-XX:+PrintGCApplicationConcurrentTime 打印应用程序的时间-XX:+PrintGCApplicationStopedTime 打印应用程序暂停时间-XX:+PrintReferenceGC 打印回收多少种引用类型的引用-verboss:class 类加载详细过程-XX:+PrintVMOptions 打印JVM运行参数-XX:+PrintFlagsFinal(+PrintFlagsInitial)  -version  grep 查找想要了解的命令,很重要-X:loggc:/opt/gc/log/path  输出gc信息到文件-XX:MaxTenuringThreshold  设置gc升到年龄,最大值为15parallel常用参数-XX:PreTenureSizeThreshold 多大的对象判定为大对象,直接晋升老年代-XX:+ParallelGCThreads 用于并发垃圾回收的线程-XX:+UseAdaptiveSizePolicy 自动选择各区比例CMS常用参数-XX:+UseConcMarkSweepGC 使用CMS垃圾回收器-XX:parallelCMSThreads CMS线程数量-XX:CMSInitiatingOccupancyFraction 占用多少比例的老年代时开始CMS回收,默认值68%,如果频繁发生serial old,适当调小该比例,降低FGC频率-XX:+UseCMSCompactAtFullCollection 进行压缩整理-XX:CMSFullGCBeforeCompaction 多少次FGC以后进行压缩整理-XX:+CMSClassUnloadingEnabled 回收永久代-XX:+CMSInitiatingPermOccupancyFraction 达到什么比例时进行永久代回收GCTimeTatio 设置GC时间占用程序运行时间的百分比,该参数只能是尽量达到该百分比,不是肯定达到-XX:MaxGCPauseMills GCt停顿时间,该参数也是尽量达到,而不是肯定达到G1常用参数-XX:+UseG1 使用G1垃圾回收器-XX:MaxGCPauseMills GCt停顿时间,该参数也是尽量达到,G1会调整yong区的块数来达到这个值-XX:+G1HeapRegionSize 分区大小,范围为1M~32M,必须是2的n次幂,size越大,GC回收间隔越大,但是GC所用时间越长G1NewSizePercent 新生代所占最小比例,默认5%G1MaxNewSizePercent 新生代所占最大比例,默认60%GCTimeRatio GC时间比例,此值为建议值,G1会调整堆大小来尽量达到这个值ConcGCThreads GC线程数量InitiatingHeapOccupancyPercent 启动G1的堆空间占用比例

五、JVM的作用

首先通过编译器把 Java 代码转换成字节码,类加载器(ClassLoader)再把字节码加载到内存中,将其放在

运行时数据区(Runtime data area)的方法区内,而字节码文件只是 JVM 的一套指令集规范,并不能直接交给底层操作系统去执行,因此需要特定的命令解析器执行引擎(Execution Engine),将字节码翻译成底层系统指令,再交由 CPU 去执行,而这个过程中需要调用其他语言的本地库接口(Native Interface)来实现整个程序的功能。

GC的垃圾回收算法?

垃圾回收只涉及到堆内存。

新生代使用复制算法:分配同等大小的内存空间,标记被GC Root引用的对象,将引用的对象连续的复制到新的内存空间,

​ 清除原来的内存空间。

老年代使用:
标记清除法:标记没有被GC Root引用的对象,清除被标记位置的内存。
标记整理法:标记没有被GC Root引用的对象。


双亲委派

当某个类加载器需要加载某个.class文件时,它首先把这个任务委托给他的上级类加载器,
一层层的向上传递,如果上级的类加载器都没有加载,自己才会去加载这个类。

向上委派,向下查找


Spring

Spring、SpringBoot、SpringMVC的区别

spring是一个开源的轻量级的java开发框架,主要负责创建对象并管理对象,它的核心功能是IOC和AOP;

SpringBoot是基于Spring的一套快速开发整合包,类似于脚手架,可以快速搭建一个项目;

SpringMVC是基于Spring实现了servlet规范的MVC框架,用于Java Web开发;

Spring是什么,怎么理解它的aop,ioc

spring是一个开源的轻量级的java开发框架,主要负责创建对象并管理对象;
IOC:控制反转,Spring 的 IOC 的实现原理就是工厂模式加反射机制。把一个类放到spring容器去管理,对象的创建,初始化,销毁的工作都交给spring容器去做。由spring容器控制对象的生命周期。
AOP:面向切面编程,把系统分为核心关注点与横切关注点。核心主要是处理业务逻辑,横切主要是权限验证,日志,事务处理。

Spring AOP是基于代理实现的,默认标准的JDK 动态代理,这使得任何接口(或者接口的集合)可以被代理。
Spring AOP 也使用 CGLIB 代理。如果业务对象没有实现任何接口那么默认使用CGLIB

**JDK动态代理:**利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。

**CGlib动态代理:**利用ASM(开源的Java字节码编辑库,操作字节码)开源包,将处理对象类的class文件加载进来,通过修改其字节码生成子类来处理

SpringBean的作用域

  • singleton:默认作用域,单例bean,每个容器只有一个bean的实例

  • prototype:每一个bean请求创建一个实例

  • request:为每一个request请求创建一个实例,在请求完成以后,bean会消失并被垃圾回收器回收

  • session:与request范围类似,同一个session会话共享一个实例,不同会话使用不同实例

  • global-session:全局作用域,所有会话共享一个实例。如果想要声明让所有会话共享存储变量的话,

    那么这全局变量需要存储在global-session中


SpringBean的生命周期

1.Spring启动,查找并加载需要被Spring管理的Bean,进行Bean的实例化;

2.Bean实例化后对将Bean的应用和值注入到Bean的属性中;

3.如果Bean实现了BeanNameAware接口的话,Spring将Bean的ID传递给setBeaName()方法;

4.如果Bean实现了BeanFactoryAware接口的话,Spring将调用setBeanFactory()方法,将BeanFactory容器实例传入;

5.如果Bean实现了ApplicationContextAware接口的话,Spring将调用Bean的setApplicationContext()方法,将bean所在应用上下文引用传入进来;

6.如果Bean实现了BeanPostProcessor接口,Spring就将调用他们的postProcessBeforelization()方法;

7.如果Bean实现了InitializingBean接口,Spring将调用他们的afterPropertiesSet()方法。如果Bean使用init-method声明了初始化方法,该方法也会被调用;

8.如果Bean实现了BeanPostProcessor接口,Spring就将调用他们的postProcessAfterlnitialization()方法;

9.此时,Bean已经准备就绪,可以被开发者使用了,它们会一直驻留在应用上下文中,知道应用上下文被销毁;

10.如果bean实现了DisposableBean接口,Spring将调用它的destory()接口方法,同样 如果Bean使用destory-methos声明销毁方法,该方法也会被调用。


Spring三大核心组件

core:Core 组件作为 Spring 的核心组件,他其中包含了很多的关键类,其中一个重要组成部分就是定义了资源的访问方式

作用:访问资源,资源的加载

context:spring运行环境和IOC容器

作用:为Spring提供运行环境,Context是作为Spring的IOC容器。

bean:创建和定义bean

作用:1.Bean的创建 2.Bean的定义


Spring常用的注入方式

1.setter注入:创建applicationContext.xml文件,手动配置Bean对象,property为Bean对象赋值。
2.构造器注入:更改applicationContext.xml文件中的property为construct-arg
3.注解注入(属性注入):使用注解方式的属性注入Bean。加@Component注解,将其标记为组件,并使用@Value注解为各属性赋值;


@Resource和@Autowired

  • @Resource和@Autowired都可以用来装配bean,都可以用于字段或setter方法。
  • @Autowired默认按类型装配,默认情况下必须要求依赖对象必须存在,如果要允许null值,可以设置它的required属性为false。
  • @Resource默认按名称装配,当找不到与名称匹配的bean时才按照类型进行装配。名称可以通过name属性指定,如果没有指定name属性,当注解写在字段上时,默认取字段名,当注解写在setter方法上时,默认取属性名进行装配。
    注意:如果name属性一旦指定,就只会按照名称进行装配。

@Autowire和@Qualifier配合使用效果和@Resource一样:

@Autowired(required = false) @Qualifier("example")
private Example example;@Resource(name = "example")
private Example example;
  • @Resource装配顺序
  1. 如果同时指定name和type,则从容器中查找唯一匹配的bean装配,找不到则抛出异常
  2. 如果指定name属性,则从容器中查找名称匹配的bean装配,找不到则抛出异常
  3. 如果指定type属性,则从容器中查找类型唯一匹配的bean装配,找不到或者找到多个抛出异常
  4. 如果都不指定,则自动按照byName方式装配,如果没有匹配,则回退一个原始类型进行匹配,如果匹配则自动装配

简要对比表格

注解对比 @Resource @Autowire
注解来源 JDK Spring
装配方式 优先按名称 优先按类型
属性 name、type required

Spring框架中使用了那些设计模式

(1)工厂模式:Spring使用工厂模式,通过BeanFactory和ApplicationContext来创建对象

(2)单例模式:Bean默认为单例模式

(3)代理模式:Spring的AOP功能用到了JDK的动态代理和CGLIB字节码生成技术

(4)策略模式:例如Resource的实现类,针对不同的资源文件,实现了不同方式的资源获取策略

(5)模板方法:可以将相同部分的代码放在父类中,而将不同的代码放入不同的子类中,用来解决代码重复的问题。

​ 比如RestTemplate, JmsTemplate, JpaTemplate

(6)适配器模式:Spring AOP的增强或通知(Advice)使用到了适配器模式,Spring MVC中也是用到了适配器模式适配Controller

(7)观察者模式:Spring事件驱动模型就是观察者模式的一个经典应用。

(8)桥接模式:可以根据客户的需求能够动态切换不同的数据源。比如我们的项目需要连接多个数据库,

​ 客户在每次访问中根据需要会去访问不同的数据库


Spring事务

**声明式事务(常用):**建立在AOP之上的。其本质是通过AOP功能,对方法前后进行拦截,将事务处理的功能编织到拦截的方法中,也就是在目标方法开始之前启动一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。

编程式事务:控制的细粒度更高,我们能够精确的控制事务的边界,事务的开始和结束完全取决于我们的需求,但这种方式存在一个致命的缺点,那就是事务规则与业务代码耦合度高,难以维护,因此我们很少使用这种方式对事务进行管理。


Spring事务4个特性(ACID):

**原子性(Atomicity):**一个事务是一个不可分割的工作单位,事务要么全部成功、要么全部失败;

**一致性(Consistency)

Java工作笔记/Java面试题/Java八股文/Java常用API相关推荐

  1. Java工作笔记-注解的进一步理解

    目录 基本概念 代码与实例 基本概念 注解:说明程序,给计算机看的. 注释:给程序员看的. 作用分类: 编写文档:通过代码里标识的注解生成文档javadoc(生成doc文档) 代码分析:通过代码里标识 ...

  2. Java工作笔记-Java函数参传值传引用问题

    目录 基本概念 代码与实例 源码打包下载 基本概念 最近的系统中,Java程序和C++程序混着,很多程序都是用Java搞的,不得不稍微研究下Java的细节,其中很关键的一点,就是如何在Java中传引用 ...

  3. C++|Java工作笔记-google protobuf基本使用

    目录 前言 protoc生成 Java相关 C++相关 前言 这里主要是生成序列号,在我所做的项目中,一般是把数据序列化后扔到消息总线上,消费者读取了,自行解析. 个人感觉这种方式比Json和XML都 ...

  4. Java工作笔记-使用Maven创建多模块项目

    目录 前言 演示 前言 在某些项目中会用到多模块,一般情况下都使用Maven进行操作,然后手动写porn.xml,这样就算是spring boot也是手动写的,并没有使用官方的初始化项目工具. 演示 ...

  5. java工作笔记018---java中BigDecimal小数位数的四舍五入等操作

    技术交流QQ群[JAVA,C++,Python,.NET,BigData,AI]:170933152 这个电视购物项目大量用到了BigDecimal,有钱内存大,哈哈 一.简介 Java在java.m ...

  6. java中级程序员面试题_中级Java程序员常见面试题汇总

    下面是一些中级Java程序员常见面试题汇总,你可以用它来好好准备面试. 什么是线程? 线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位.程序员可以通过它进行多处理器 ...

  7. java软件网络工程师面试题_170道Java工程师面试题,你敢挑战吗?

    1.面向对象的特征有哪些方面? 2.访问修饰符public,private,protected,以及不写(默认)时的区别? 3.String 是最基本的数据类型吗? 4.float f=3.4;是否正 ...

  8. 【Java书笔记】:《深入理解Java虚拟机:JVM高级特性与最佳实践(第3版)》第2部分-自动内存管理,第3部分-虚拟机执行子系统,第5部分-高效并发

    作者:周志明 整理者GitHub:https://github.com/starjuly/UnderstandingTheJVM 第2部分-自动内存管理 第2章 Java内存区域与内存溢出异常 2.2 ...

  9. 【Java学习笔记之二十九】Java中的equals和==的用法及区别

    Java中的"equals"和"=="的用法及区别 在初学Java时,可能会经常碰到下面的代码: 1 String str1 = new String(&quo ...

  10. 【Java学习笔记一】初识计算机和Java语言

    目录 (一)计算机的体系结构(常识) 什么是计算机 计算机的基本概念 常见的主要硬件 主要硬件的详解 CPU的概述 内存的概述 硬盘的概述 科普小知识 输入输出设备 常见的主要软件 计算机的体系结构 ...

最新文章

  1. canvas.width和canvas.style.width区别以及应用
  2. 简单的XML和JSON数据的处理
  3. 4次迭代!10w行级别数据的Excel导入优化记录
  4. php生成格式 word文档,php使用phpword生成word文档
  5. 评教数据的存储和显示问题
  6. password textbox setup
  7. Java集合对象详解
  8. 树莓派是什么 树莓派能做什么 树莓派的功能用途
  9. Google Java Style Guide
  10. 网页提示https“证书错误:导航已阻止”,无法跳转解决办法
  11. cutoff shader
  12. 树莓派3B+安装系统(Raspbian)以及配置环境
  13. 有奖体验 CODING 产品,iPad Pro、HHKB 键盘等超级礼包等你来!
  14. colorFormat颜色转换插件
  15. FPGA通信第二篇--UDP
  16. 2013c语言二级等级考试试题,计算机等级考试二级c语言考试试题
  17. LeetCode-完成旅途的最少时间
  18. 计算机病毒手动查杀,电脑中毒了怎么办 如何手动彻底查杀病毒【解决方法】...
  19. 白话数字签名(3)——Web程序中的数字签名【转】
  20. 计算机信息管理专业个人简历,计算机信息管理专业个人简历个人技能范文

热门文章

  1. vsphere5.0环境中win2000虚机无法安装vmtools解决办法。
  2. 成功人士必备“十商”,一张思维导图让你清晰认识自己
  3. 为什么使用消息中间件
  4. 初中毕业学计算机有啥科目,学好哪些科目最容易成为学霸?初中生学好这几门功课很重要...
  5. 基于STM32、FreeRTOS低功耗设计思路和原理
  6. RTOS环境下低功耗设计思路
  7. 停车场系统无法连接服务器,手动挡停车场 联机版无法连接服务器是什么原因...
  8. 10设置精美的免费网站后台管理系统模板
  9. 温度传感器DS18B20 ISIS仿真
  10. java swf pdf_SWFTools pdf2swf 参数详解 及中文乱码问题