在现公司三年了,Android项目写的越来越少了,最近效率比较高,决定重新拾起Android。一个好的结束意味着一个更好的开始。本篇文章是Android面试中的java部分,结合自身情况来写的,部分题目答案简单。
另外两篇:Android部分 、 Android框架部分正在整理中
byte short int long char float double boolean
大数据精准类型:bigInterger bigDecimal ------compareTo方法来比较大小
##扩展——保留两位小数(常用两种)
#DecimalFormat
public static String format(double value) {
DecimalFormat df = new DecimalFormat("0.00");
df.setRoundingMode(RoundingMode.HALF_UP);//当前舍入模式8种---向“最接近的”整数舍入
//FLOOR:向远离零的方向舍入。
DOWN:向接近零的方向舍入。
return df.format(value);
}
#NumberFormat
public static String format(double value) {
NumberFormat nf = NumberFormat.getNumberInstance();
nf.setMaximumFractionDigits(2);
/*
* setMinimumFractionDigits设置成2
*
* 如果不这么做,那么当value的值是100.00的时候返回100
*
* 而不是100.00
*/
nf.setMinimumFractionDigits(2);
nf.setRoundingMode(RoundingMode.HALF_UP);
/*
* 如果想输出的格式用逗号隔开,可以设置成true
*/
nf.setGroupingUsed(false);
return nf.format(value);
}
Pattern pattern =Pattern.complie(String regex)
String regex=pattern.pattern();得到正则表达式
Matcher matcher=pattern.matcher( String imput);
Pattern pattern=matcher.pattern();返回创建mathcer的pattern实例;
matcher.matcher();完全匹配
matcher.find();局部匹配
String.Trim()方法就是把字符串两端的空格字符给删去
public class Singleton {
private static Singleton INSTANCE;
private Singleton (){}
public static Singleton getSingleton() {
INSTANCE = new Singleton();
return INSTANCE;
}
}
public class Singleton {
private static Singleton INSTANCE = new Singleton();
private Singleton (){}
public static Singleton getSingleton() {
return INSTANCE;
}
}
public class Singleton {
private **volatile** static Singleton INSTANCE;
private Singleton (){}
public static Singleton getSingleton() {
if (INSTANCE == null) {
synchronized (Singleton.class) {
if (INSTANCE == null) {
INSTANCE = new Singleton();
}
}
}
return INSTANCE;
}
}
public class BeanContainer {
public static BeanContainer getInstance() {
return ContainerHolder.HOLDER.instance;
}
private enum ContainerHolder {
HOLDER;
private BeanContainer instance;
ContainerHolder() {//在构造中实例化
instance = new BeanContainer();
}
}
}
public class SingleTon{
private SingleTon(){}
public static SingleTon getInstance(){
return SingleTonHoler.INSTANCE;
}
private static class SingleTonHoler{
private static SingleTon INSTANCE = new SingleTon();
}
}
要理解静态内部类方式,首先要理解类加载机制。(面试中高级很有可能问到)
虚拟机把Class文件加载到内存,然后进行校验,解析和初始化,最终形成java类型,这就是虚拟机的类加载机制。加载,验证,准备,解析、初始化这5个阶段的顺序是确定的,类的加载过程,必须按照这种顺序开始。这些阶段通常是相互交叉和混合进行的。解析阶段在某些情况下,可以在初始化阶段之后再开始—为了支持java语言的运行时绑定(动态绑定,多态的原理)。
在Java虚拟机规范中,没有强制约束什么时候要开始加载,但是,却严格规定了几种情况必须进行初始化(加载,验证,准备则需要在初始化之前开始):
1. 遇到 new、getstatic、putstatic、或者invokestatic 这4条字节码指令,如果没有类没有进行过初始化,则触发初始化
2. 使用java.lang.reflect包的方法,进行反射调用的时候,如果没有初始化,则先触发初始化
3. 初始化一个类时候,如果发现父类没有初始化,则先触发父类的初始化
我们仅说与本期主题相关的初始化阶段:
类初始化阶段是类加载过程的最后阶段。在这个阶段,java虚拟机才真正开始执行类定义中的java程序代码。在编译的时候,编译器会自动收集类中的所有静态变量(类变量)和静态语句块(static{}块)中的语句合并产生的,编译器收集的顺序是根据语句在java代码中的顺序决定的。收集完成之后,会编译成java类的
static{} 方法,java虚拟机则会保证一个类的static{}
方法在多线程或者单线程环境中正确的执行,并且只执行一次。在执行的过程中,便完成了类变量的初始化。如果我们的java类中,没有显式声明static{}块,如果类中有静态变量,编译器会默认给我们生成一个static{}方法。对于静态变量来说,虚拟机会保证在子类的static{}方法执行之前,父类的static{}方法已经执行完毕(即如果父类没有加载则先加载父类)。由于父类的static{}方法先执行,也就意味着父类的静态变量要优先于子类的静态变量赋值操作。
对于实例变量来说,在实例化对象时,JVM会在堆中为对象分配足够的空间,然后将空间清零(即所有类型赋默认值,引用类型为null)。JVM会收集类中的复制语句放于构造函数中执行,如果没有显式声明构造函数则会默认生成一个构造函数。子类默认生成的构造函数第一行默认为super();即如果父类有无参的构造方法,子类会先调用父类的构造方法再调用本身的构造方法。因为它继承父类成员的使用,必须先初始化这些成员。如果父类没有无参的构造方法则子类继承会报错,需要子类通过super显式调用父类的有参构造方法。如果类中显式定义一个或多个构造方法,则不再生成默认构造方法。
对于静态变量,上面的描述还不太准确。类初始化阶段,JVM保证同一个类的static{}方法只被执行一次,这是静态内部类单例模式的核心。JVM靠类的全限定类名以及加载它的类加载器来唯一确定一个类。(这个很重要,经常会有这方面的坑!比如反序列化时,被序列化的对象使用java默认的类加载器加载,而使用了反序列化的一方使用的框架(如springBoot就有自己的类加载器)强制使用自己的类加载器加载某个类,则会因为JVM判定不是一个类而报ClassNotFoundException!)
所以修正一下的说法便是,静态内部类单例模式的核心原理为对于一个类,JVM在仅用一个类加载器加载它时,静态变量的赋值在全局只会执行一次!
使用静态内部类的优点是:因为外部类对内部类的引用属于被动引用,不属于前面提到的三种必须进行初始化的情况,所以加载类本身并不需要同时加载内部类。在需要实例化该类是才触发内部类的加载以及本类的实例化,做到了延时加载(懒加载),节约内存。同时因为JVM会保证一个类的()方法(初始化方法)执行时的线程安全,从而保证了实例在全局的唯一性。
有些人–为什么使用内部类而不是直接使用静态变量,我觉着有两个原因(求指正,第二条并不是很确定,后续会写代码测试):
1. 使用内部类可以延时加载。如果直接使用静态变量,因为加载子类等其它原因对实例进行了初始化,而此时并不需要该类的实例,造成了资源的浪费。
2. 原类因为带有业务含义,在使用上会有各种可能,比如使用了特定的类加载器进行加载,这样就对单例造成了破坏。
说完了优点我们再来说说缺点,那就是内部类的传参不是很灵活,需要将参数定义为final。当然我们也可以将其写入final的Object数组或者在内部类定义一个接受参数的init()方法来接收参数,但总的来说传参确实不方便。
原文地址:点击进入原文
1.在堆内存开辟内存空间。
2.在堆内存中实例化对象里面的各个参数。
3.把对象指向堆内存空间。
1.用来区分当前对象
2.构造方法中调用本身其它构造
3.用来返回类的引用
static 方法是类方法,先于任何的实例(对象)存在。即static 方法再类加载时就已经存在了,但是对象是在创建时才在内存中生成。
static方法就是没有this的方法
在方法中定义使用的this关键字,它的值是当前对象的引用。也就是说你只能用它来调用属于当前对象的方法或者使用this处理方法中成员变量和局部变量重名的情况。而且更为重要的是this和super都无法出现在static修饰的方法中,static修饰的方法是属于类的,该方法的调用者可能是一个类,而不是对象,如果使用的是类来调用而不是对象,则this就无法指向合适的对象,所以static修饰的方法中不能使用this
修饰类——当用final去修饰一个类的时候,表示这个类不能被继承。
a.被final修饰的类,final类中的成员变量可以根据自己的实际需要设计final。
b.final类中的成员方法都会被隐式的指定为final方法
修饰方法——被final修饰的方法不能被重写
修饰成员变量
a.必须初始化值
b.被final修饰的成员变量赋值有两种方式 1.直接赋值 2.全部在构造方法中附初始值
c.如果修饰的成员变量是一个引用类型,则是说这个引用的地址的值不能修改,但是这个引用所指向的对象里面的内容还是可以改变的
注意:
a.一个类的private 方法会被隐式的被指定为final方法
b.如果父类中有final修饰的方法,那么子类不能去重写
finalize() 实在java.lang.Object里定义的,也就是说没一个对象都有这么一个方法。这个方法在gc启动,该对象被回收的时候被调用。
使用finalize 还需要注意一个事,调用super.finalize();
一个对象的finalize()方法只会被调用一次,而且finalize被调用后,该对象又不需要被回收了,然后到了真正要被回收的时候,因为前面调用过一次,所以不会再被调用,产生问题。所以使用的时候一定要注意
object定义—— protected void finalize()//定义为子类可见
执行时机不可预知
当一个对象变得不可触及时,垃圾回收器某个时期会回收此对象。当回收对象之前会调用finalize方法,这就类似于人类临终之前要做的事:写遗言。因为GC是不确定的(这和JVM有关)。所以finalize方法的执行具有不可预知性。
finalize 忽略异常——即finalize代码中若出现异常,异常会被忽略
finalize使用
什么时候使用?一般来说,finalize被作为第二种安全网来使用,如FileInputStream类,
当对象回收时,有可能资源为释放,所以这里第二次来确认(那也总比不释放强吧,虽然具体释放时机未定)
protected void finalize() throws IOException {
if (fd != null) {
if (fd != fd.in) {
close();
}
}
}
注意:某些用到finalize的地方,你必须像如下所示,显式调用回收链。
protected void finalize() throws IOException {
try{
...
}finally{
super.finalize();
}
}
建议:尽量不要使用finalize,除非以它作为安全网,或是为了终结非关键的原生资源。
int a1= {1,2,3} ; int a2 ; a2= a1 如果改变a2的值a1也会变,其实真正做的只是复制了一个引用,操作的是同一个对象。
(1)关键字的作用:保证了变量的可见性(visibility)和防止指令重排序。被volatile关键字修饰的变量,如果值发生了变更,其他线程立马可见,避免出现脏读的现象。防止指令重排:在基于偏序关系的Happens-Before内存模型中,指令重排技术大大提高了程序执行效率
原理:获取JIT(即时Java编译器,把字节码解释为机器语言发送给处理器)的汇编代码,发现volatile多加了lock addl指令,这个操作相当于一个内存屏障,使得lock指令后的指令不能重排序到内存屏障前的位置。这也是为什么JDK1.5以后可以使用双锁检测实现单例模式。
lock前缀的另一层意义是使得本线程工作内存中的volatile变量值立即写入到主内存中,并且使得其他线程共享的该volatile变量无效化,这样其他线程必须重新从主内存中读取变量值。
性能:读操作与普通变量无差别,写操作会慢一些,大多情况比锁消耗低。
(2)为什么会出现脏读?
Java内存模型规定所有的变量都是存在主存当中,每个线程都有自己的工作内存。线程对变量的所有操作都必须在工作内存中进行,而不能直接对主存进行操作。并且每个线程不能访问其他线程的工作内存。变量的值何时从线程的工作内存写回主存,无法确定。
(3)happens-before规则的理解与勘误
在网上查volatile关键字相关信息时,多篇博客提到了happens-before原则,个人对此原则的理解是:当操作该volatile变量时,所有前序对该变量的操作都已完成(如不存在已变更,但未写回主存的情况),所有后续对该变量的操作,都未开始。仅此而已。
Collection 接口的接口 对象的集合(单列集合)
1.Synchronized有很好的的扩展性和兼容功能,可以转换所有的List子类安全
2.使用SynchroizedList的时候,进行遍历时要手动进行同步处理Synchronized类并没有都是用synchronized同步代码块(如listIterator),所以遍历需要手动同步。
3.synchronizedList 可以锁定指定的对象,用同步代码块,vector用的是同步方法。
1.同步方法使用synchronized修饰方法,在调用该方法前,需要获得内置锁(java每个对象都有一个内置锁),否则就处于阻塞状态
代码如: public synchronized void save(){//内容}
2.同步代码块使用synchronized(object){}进行修饰,在调用该代码块时,需要获得内置锁,否则就处于阻塞状态
代码如:
synchronized(object){
//内容
}
背景:
1.7和1.8主要在处理哈希冲突和扩容问题上区别比较大。
jdk 1.7扩容的条件是 数组长度大于阈值且存在哈希冲突,由此我们可以想象,默认长度为16的情况下,数组最多可以存27个元素后才扩容,原因是在一个下标存储12个元素后(阈值为12),在剩下的15个下标各存一个元素,最多就可存27个元素,当然这种是很偶然的情况。不过也可以看到 jdk1.7 中,这个阈值的作用并不是特别的大,并不是超过阈值就一定会扩容。
1.8中,数组有两种情况会发生扩容,一种是超过阈值,一种是链表转为红黑树且数组元素小于64时,由此在jdk1.8中,默认长度为16情况下,要么元素一直放在同一下标,数组长度为9时就会扩容,要么超过阈值12时才会扩容。
总结:
其实可以看出JDK1.8版本的ConcurrentHashMap的数据结构已经接近HashMap,相对而言,ConcurrentHashMap只是增加了同步的操作来控制并发,从JDK1.7版本的ReentrantLock(重进入锁)+Segment+HashEntry,到JDK1.8版本中synchronized+CAS+HashEntry+红黑树。
1.数据结构:取消了Segment分段锁的数据结构,取而代之的是数组+链表+红黑树的结构。
2.保证线程安全机制:JDK1.7采用segment的分段锁机制实现线程安全,其中segment继承自ReentrantLock。JDK1.8采用CAS(Compare and Swap),即比较并替换+Synchronized保证线程安全。
3.保存key,value及key的hash值的数据结构。其中value和next都用volatile修饰,保证并发的可见性
4.锁的粒度:原来是对需要进行数据操作的Segment加锁,现调整为对每个数组元素加锁(Node)。
5.链表转化为红黑树:定位结点的hash算法简化会带来弊端,Hash冲突加剧,因此在链表节点数量大于8时,会将链表转化为红黑树进行存储。
6.查询时间复杂度:从原来的遍历链表O(n),变成遍历红黑树O(logN)。
HashMap和Hashtable都实现了Map接口,但决定用哪一个之前先要弄清楚它们之间的分别。主要的区别有:线程安全性,同步(synchronization),以及速度。
HashMap几乎可以等价于Hashtable,除了HashMap是非synchronized的,并可以接受null(HashMap可以接受为null的键值(key)和值(value),而Hashtable则不行)。
HashMap是非synchronized,而Hashtable是synchronized,这意味着Hashtable是线程安全的,多个线程可以共享一个Hashtable;而如果没有正确的同步的话,多个线程是不能共享HashMap的。Java 5提供了ConcurrentHashMap,它是HashTable的替代,比HashTable的扩展性更好。
另一个区别是HashMap的迭代器(Iterator)是fail-fast迭代器,而Hashtable的enumerator [i’nju:mə,reitə]迭代器不是fail-fast的。所以当有其它线程改变了HashMap的结构(增加或者移除元素),将会抛出ConcurrentModificationException,但迭代器本身的remove()方法移除元素则不会抛出ConcurrentModificationException异常。但这并不是一个一定发生的行为,要看JVM。这条同样也是Enumeration和Iterator的区别。
由于Hashtable是线程安全的也是synchronized,所以在单线程环境下它比HashMap要慢。如果你不需要同步,只需要单一线程,那么使用HashMap性能要好过Hashtable。
HashMap不能保证随着时间的推移Map中的元素次序是不变的。
HashMap实现了Map接口 HashSet实现了Set接口
HashMap储存键值对 HashSet仅仅存储对象
HashMap使用put()方法将元素放入map中 HashSet使用add()方法将元素放入set中
HashMap中使用键对象来计算hashcode值 HashSet使用成员对象来计算hashcode值,对于两个对象来说hashcode可能相同,所以equals()方法用来判断对象的性,如果两个对象不相等同的话,那么返回false
HashMap比较快,因为是使用唯一的键来获取对象 HashSet较HashMap来说比较慢
(1) 传统java.io包:对文件进行了抽象、通过输入流输出流进行IO 同步、阻塞 代码简单、直观
(2) java.net包: 网络通信同样是IO行为 同步、阻塞 网络API:Socket、ServerSocket、HttpURLConnection
(3) java.nio包:Java1.4中引入了NIO框架 同步、非阻塞 提供了 Channel、Selector、Buffer 等新的抽象
(4) java7的NIO2:引入了异步非阻塞IO方式 异步 IO 操作基于事件和回调机制—应用操作直接返回,而不会阻塞,当后台处理完成后,操作系统会通知相应线程进行后续工作。
分散和聚集: 利用Scatter/Gather委托操作系统完成数据分散和聚集的工作
文件锁定功能:
网络异步IO: 非阻塞IO、IO多路复用(解决服务端多线程时的线程占用问题)
服务端多线程并发处理任务,即使使用线程池,高并发处理依然会因为上下文切换,导致性能问题。NIO是利用单线程轮询事件的机制,高效的去选择来请求连接的Channel仅提供服务。
(1)字节流(基类是InputStream/OutputStream)、字符流(reader/writer)
(2)输入流、输出流
字符流是输出到缓冲区的。只有在调用close()方法关闭缓冲区时,信息才输出。要想字符流在未关闭时输出信息,则需要手动调用flush()方法
IO 工具类都实现了 Closeable 接口,因为需要进行资源的释放,cleaner 或 finalize 机制作为资源释放的最后把关,也是必要的。
File、RandomAccessFile、InputStream、OutputStream、Reader、Writer
RandomAccessFile随机文件操作,一个独立的类,直接继承至Object.功能丰富,可以从文件的任意位置进行存取(输入输出)操作。
点击查看参考链接,更多IO详细
run没有开辟新的栈空间,没有新线程,都是主线程在执行
start开辟了新的栈空间,在新的栈空间启动run()方法
sleep 线程进入被阻塞的状态
yeild 线程转入暂停执行的状态
1、用标记,当终止线程时,会执行完run方法
2、stop()方法,不建议使用,会执行不到特定的代码
3、interrupt(),只能中断正在休眠的线程,通过抛异常的方法中断线程的终止。
InputStream inputStream=System.in;
int m=inputStream.read();
myThread2.interrupt();//通过外界输入打断
DOM:内存消耗大 但是便于遍历.打开文档,将其转化为节点树,然后在其用循环的方式,遍历节点,一一查找.
SAX:速度快,战内存少.但是文件结构信息会丢失,采用的是流的处理方式.从起始标签开始一一往下逐个查找.起始标签与结尾标签作为标记来将一组数据存入一个集合中,像水流一样一直到最尾,然后最后返回集合,集合中就存下了所有的数据(这也应该就是所谓的流处理方式吧).
PULL:是Android内置,推荐的一种,相对来说有点类似SAX,也是从上往下,但是它还是以文档开始与结尾为条件,在其中间进行查找处理,然后分为不同标签开始逐一查找.
线程池 ThreadPoolExcutor -----FixedThreadPool SingleThreadPool CachedThreadPool ScheduledThreadPool
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime,TimeUnit unit,BlockingQueue workQueue,ThreadFactory threadFactory)
ThreadPoolExecutor有两个方法可以供我们执行,分别是submit()和execute(),我们先来看看这两个方法到底有什么差异。
在不需要线程执行返回结果值时,我们使用execute 方法。 而当我们需要返回值时,则使用submit方法,他会返回一个Future对象。Future不仅仅可以获得一个结果,他还可以被取消,我们可以通过调用future的cancel()方法,取消一个Future的执行。 比如我们加入了一个线程,但是在这过程中我们又想中断它,则可通过sumbit 来实现。
1.shutDown() 关闭线程池,不影响已经提交的任务
2.shutDownNow() 关闭线程池,并尝试去终止正在执行的线程
3.allowCoreThreadTimeOut(boolean value) 允许核心线程闲置超时时被回收
4.submit 一般情况下我们使用execute来提交任务,但是有时候可能也会用到submit,使用submit的好处是submit有返回值。
5.beforeExecute() - 任务执行前执行的方法
6.afterExecute() -任务执行结束后执行的方法
7.terminated() -线程池关闭后执行的方法
Runtime.getRuntime().availableProcessors();获取CPU的数量。
tcp/Ip协议栈是一系列网络协议的总和,是构成网络通信的核心骨架,它定义了电子设备如何连接因特网,以及数据如何在它们之间进行传输。
协议之间的通信最终都是要转换为0和1的电信号,通过物理介质进行传输的,因此物理介质是网络通信的基石。
TCP三次握手过程
主机A通过向主机B 发送一个含有同步序列号标志位的数据段(SYN)给主机B ,向主机B 请求建立连接,通过这个数据段,主机A告诉主机B 两件事:我想要和你通信;你可以用哪个序列号作为起始数据段来回应我。
主机B 收到主机A的请求后,用一个带有确认应答(ACK)和同步序列号(SYN)标志位的数据段响应主机A,也告诉主机A两件事:我已经收到你的请求了,你可以传输数据了;你要用哪个序列号作为起始数据段来回应我。
主机A收到这个数据段后,再发送一个确认应答,确认已收到主机B 的数据段:“我已收到回复,我现在要开始传输实际数据了”。
这样3次握手就完成了,主机A和主机B 就可以传输数据了。
TCP四次挥手过程
当主机A完成数据传输后,将控制位FIN置1,提出停止TCP连接的请求。
主机B收到FIN后对其作出响应,确认这一方向上的TCP连接将关闭,将ACK置1。
由B 端再提出反方向的关闭请求,将FIN置1。
主机A对主机B的请求进行确认,将ACK置1,双方向的关闭结束。
首先,在代码被编译器编译后生成的二进制字节流(.class)文件;
然后,JVM把Class文件加载到内存,并进行验证、准备、解析、初始化;
最后,能够形成被JVM直接使用的Java类型的过程。
加载①. 通过类的全限定名来获取类的二进制字节流。
②. 将字节流中所有代表的静态存储结构转化为【方法区】的运行时数据结构。
③. 在内存Java堆中生成一个代表这个类的Java.lang.Class对象,作为方法区中这个类的各种数据的访问入口。
连接 连接阶段负责将类的二进制数据合并入JRE(Java运行时环境)中
1)验证:确保被加载的类符合JVM规范和安全。验证阶段是非常重要的,但不是必须的,它对程序运行期没有影响,可以关闭以缩短虚拟机类加载的时间。
2)准备 静态变量在方法区分配内存 静态变量在分配内存后,附上初始值。为类变量分配内存并设置类变量的初始值阶段,即在方法区中分配这些变量所使用的内存空间。
static final int a = 10; // 该静态常量a 会在【准备阶段】直接将10赋值。static int b = 11;// 该静态变量b 在【准备阶段】只会赋值初始值0,等到了【初始化】阶段会将真正的11赋值给静态变量b。
3)解析 把类中的符号引用转换为直接引用。
解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程,解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符7类符号引用进行。
4)初始化 初始化,为类的静态变量赋予正确的初始值。初始化阶段是执行类构造器()方法。
有且只有主动引用才会触发类初始化的过程
JVM初始化步骤
① 如果这个类还没有被加载和连接,则程序先加载并连接该类。(其实就是执行上面的类加载、连接两步骤)
② 如果的直接父类还没有被初始化,则先初始化其直接父类。
③ 如果这个类中有初始化语句,则系统会一次执行这些初始化语句。
类的初始化时机有且只有主动引用时才会触发类的初始化。
主动引用:创建类的实例。即通过new的方式,new一个对象;
调用类的静态变量(非final修饰的常量) 和静态方法。;
过反射对类进行调用。;
初始化某个类的子类,则父类也会被初始化;
Java虚拟机启动时,指定的main方法所在的类,需要被提前初始化。;
被动引用:
当访问一个类的静态变量时(该静态变量是父类所持有),只有真正声明这个变量的类才会初始化。;
通过数据定义引用类,不会触发类的初始化。;
final 常量不会触发类的初始化,因为编译阶段就存储在常量池中;
5)使用
类的正常使用。
6)卸载
类的卸载需要根据【该类对象不再被引用+GC回收 】来判断何时被卸载。
①由Java虚拟机自带的类加载器所加载的类,在虚拟机的生命周期中,始终不会被卸载。因为一直被引用着。
②由用户自定义的类加载器加载的类是可以被卸载
类加载器并不需要等到某个类被“首次主动使用”时才加载它,JVM规范允许类加载器在预料某个类将要被使用时就预先加载。
如果预先加载的过程中遇到了.class文件缺失或者存在错误,类加载器不会马上报告错误;类加载器必须在程序【首次主动使用】该类时才报告错误(LinkageError错误)。
把类加载阶段中的"通过一个类的全限定名来获取描述此类的二进制字节流"这个动作放到Java虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的类。实现这个动作的代码模块称为“类加载器”。
比较两个类是否“相等“,只有在这两个类是由同一个类加载器加载的前提下才由意义;否则,即使两个类来源于同一个Class文件,被同一个虚拟机加载,只要加载他们的类加载器不同,那么这两个类就必定不相等。
类加载器可以分为:
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。(防止反射—应用的权限进行校验,保护用户的隐私目的)
Class类 代表类的实体,在运行的Java应用程序中表示类和接口
Field类 代表类的成员变量(成员变量也称为类的属性)
Method类 代表类的方法
Constructor类 代表类的构造方法
序列化是将对象状态转换为可保持或传输的格式的过程。
–创建一个名为stonestory的数据库
create database stonestory
–创建一个名为roles的表
create table roles
(
roleId int primary key identity(1,1), --id号设为主键,从1开始递增量为1
roleName varchar(15), --名字 varchar(可变)
roleAge int, --年龄
roleSex char(2), --性别
)
–删除表(把表的结构本身和数据都删掉)
drop table roles
–插入语句
insert into roles values(‘林黛玉’,18,‘女’)
–查询语句
select * from roles --查询表中全部内容
select * from roles where roleAge<20 --查询表中年龄小于20的人物信息
select * from roles where roleAge between 18 and 28 --查询表中年龄在18到28之间的人物信息
select top 1 * from roles where roleId > 2 --查询表中id号大于2的人物信息(只显示第一条)
----查询id号除第一个的前两个人物信息(即第2,3位置的人物信息)
Select top 2 * from roles where roleId not in(select top 1 roleId from roles)
–更新语句(修改)
update roles set roleAge=19,roleSex=“男” where roleName=‘薛宝钗’ --将名为薛宝钗的人的年龄改为19
delete from roles --删除表中全部数据
delete from roles where roleName=‘贾政’ --删除表中名为贾政的人
public class BubbleSoerOpt2 {
public static void main(String[] args) {
int[] list = {6,4,7,5,1,3,2};
int len = list.length-1;
int temp = 0; // 开辟一个临时空间, 存放交换的中间值
int tempPostion = 0; // 记录最后一次交换的位置 第二次优化
// 要遍历的次数
for (int i = 0; i < list.length-1; i++) {
int flag = 1; //设置一个标志位 第一次优化
//依次的比较相邻两个数的大小,遍历一次后,把数组中第i小的数放在第i个位置上
for (int j = 0; j < len; j++) {
// 比较相邻的元素,如果前面的数小于后面的数,交换
if (list[j] < list[j+1]) {
temp = list[j+1];
list[j+1] = list[j];
list[j] = temp;
flag = 0; //发生交换,标志位置0
tempPostion = j; //记录交换的位置
}
}
len = tempPostion; //把最后一次交换的位置给len,来缩减内循环的次
if (flag == 1) {//如果没有交换过元素,则已经有序
return;
}
}
}
}
public class SelectSort {//基本选择排序
public static void selectSort(int[] arr){
for (int i = 0; i arr[j]){
int temp=arr[i];
arr[i]=arr[j];
arr[j]=temp;
}
}
}
}
//选择排序的优化
public static void selectSort2(int[] arr){
for(int i = 0;iarr[j]){//如果最小元素大于比较的值,则将num值重新赋值,index赋值新的下标
num=arr[j];
index = j;
}
}
if(index!=i){//如果index是i值,说明i下标的值是最小值,如果不是则说明不是,则交换
int temp =arr[index];
arr[index]=arr[i];
arr[i]=temp;
}
}
}
//其实选择排序优化的本质就是减少了每次两元素相比之后相互交换的操作,而是直接记录下标用取得的元素去比较,直到找到最小的元素,然后再进行交换。
public static void main(String[] args) {
int[] arr={5,3,2,1,5,6,7};
// selectSort(arr);
selectSort2(arr);
for (int i:arr
) {
System.out.println(i);
}
}
}
挖坑法
public class QuickSort {
public static void quickSort(int[] arr, int startIndex, int endIndex) {
// 递归结束条件:startIndex大等于endIndex的时候
if (startIndex >= endIndex) {
return;
}
// 得到基准元素位置
int pivotIndex = partition(arr, startIndex, endIndex);
// 用分治法递归数列的两部分
quickSort(arr, startIndex, pivotIndex - 1);
quickSort(arr, pivotIndex + 1, endIndex);
}
private static int partition(int[] arr, int startIndex, int endIndex) {
// 取第一个位置的元素作为基准元素
int pivot = arr[startIndex];
int left = startIndex;
int right = endIndex;
// 坑的位置,初始等于pivot的位置
int index = startIndex;
//大循环在左右指针重合或者交错时结束
while ( right >= left ){
//right指针从右向左进行比较
while ( right >= left ) {
if (arr[right] < pivot) {
arr[left] = arr[right];
index = right;
left++;
break;
}
right--;
}
//left指针从左向右进行比较
while ( right >= left ) {
if (arr[left] > pivot) {
arr[right] = arr[left];
index = left;
right--;
break;
}
left++;
}
}
arr[index] = pivot;
return index;
}
public static void main(String[] args) {
int[] arr = new int[] {4,7,6,5,3,2,8,1};
quickSort(arr, 0, arr.length-1);
System.out.println(Arrays.toString(arr));
}
}
指针交换法 和挖坑法相比,指针交换法在partition方法中进行的元素交换次数更少。
public class QuickSort {
public static void quickSort(int[] arr, int startIndex, int endIndex) {
// 递归结束条件:startIndex大等于endIndex的时候
if (startIndex >= endIndex) {
return;
}
// 得到基准元素位置
int pivotIndex = partition(arr, startIndex, endIndex);
// 根据基准元素,分成两部分递归排序
quickSort(arr, startIndex, pivotIndex - 1);
quickSort(arr, pivotIndex + 1, endIndex);
}
private static int partition(int[] arr, int startIndex, int endIndex) {
// 取第一个位置的元素作为基准元素
int pivot = arr[startIndex];
int left = startIndex;
int right = endIndex;
while( left != right) {
//控制right指针比较并左移
while(left pivot){
right--;
}
//控制right指针比较并右移
while( left
二分查找
1.二分查找又称折半查找,它是一种效率较高的查找方法。
2.二分查找要求:(1)必须采用顺序存储结构 (2).必须按关键字大小有序排列
3.原理:将数组分为三部分,依次是中值(所谓的中值就是数组中间位置的那个值)前,中值,中值后
将要查找的值和数组的中值进行比较,若小于中值则在中值前 面找,若大于中值则在中值后面找,
等于中值时直接返回。然后依次是一个递归过程,将前半部分或者后半部分继续分解为三部分。
4.实现:
二分查找的实现用递归和循环两种方式
public class _00BinarySearch {
public static void main(String[] args) {
int[] arr = {6, 12, 33, 87, 90, 97, 108, 561};
System.out.println("循环查找:" + binarySearch(arr, 87));
System.out.println("递归查找:" + binSearch(arr, 0, arr.length - 1, 87));
}
//循环实现二分查找算法arr 已排好序的数组x 需要查找的数-1 无法查到数据
public static int binarySearch(int[] srcArray, int des) {
//定义初始最小、最大索引
int low = 0;
int high = srcArray.length - 1;
//确保不会出现重复查找,越界
while (low <= high) {
//计算出中间索引值
int middle = (high + low) >>> 1;//防止溢出
if (des == srcArray[middle]) {
return middle;
//判断下限
} else if (des < srcArray[middle]) {
high = middle - 1;
//判断上限
} else {
low = middle + 1;
}
}
//若没有,则返回-1
return -1;
}
/**
* 二分查找递归实现。
* @param srcArray 有序数组
* @param start 数组低地址下标
* @param end 数组高地址下标
* @param key 查找元素
* @return 查找元素不存在返回-1
*/
public static int binSearch(int srcArray[], int start, int end, int key) {
// int mid = (end - start) / 2 + start;
int mid = (end - start) >>> 1;
if (srcArray[mid] == key) {
return mid;
}
if (start >= end) {
return -1;
} else if (key > srcArray[mid]) {
return binSearch(srcArray, mid + 1, end, key);
} else if (key < srcArray[mid]) {
return binSearch(srcArray, start, mid - 1, key);
}
return -1;
}
}