一、JavaEE
A、基础
//1、接口的幂等性怎么解决
8、接口API幂等性:对外提供接口为了支持幂等调用,接口中必须传两个参数:1、source来源 2、来源方序列号seq,这两个字段在提供方系统做联合唯一索引。当第三方调用时,必须先在系统中查询一下是否已经处理过,并返回相应的结果。如果没有处理过再处理。
1、查询操作:在数据不变的情况下,查询一次和查询多次结果一样。select天然幂等
2、删除操作:删除一个和删除多个结果都是为空,也是幂等性操作。只是删除多个返回多个结果
3、唯一索引:防止新增脏数据。支付宝的资金账户,只允许用户有一个资金账户。给资金账户表中的用户id添加唯一索引,就可以保证用户只有一个资金账户。
4、token机制:防止页面的重复提交。由于网络原因或导致重复点击造成的页面重复提交,为了只提交一次,集群环境:可以采用token+redis(redis是单线程的,实现顺序性)。单JVM环境:采用token+redis或者token+jvm内存。处理流程:1. 数据提交前要向服务的申请token,token放到redis或jvm内存,token设置有效时间;2. 提交后后台校验token,校验成功删除token,生成新的token返回。token特点:要申请,一次有效性,可以限流。注意:redis要用删除操作来判断token,删除成功代表token校验通过,如果用select+delete来校验token,存在并发问题。
5、悲观锁:获取数据时加锁处理。但id字段一定得是主键索引或者唯一索引,不然会锁表。同时悲观锁使用时会伴随事务一起运行,锁定数据时间会比较长。
6、乐观锁:乐观锁在更新数据那一刻上锁,其他时间不上锁,相对于悲观锁效率高。可以采用版本号实现。但数据更新最好用唯一索引或主键更新,这样是行锁。不然会造成表锁情况。
7、分布式锁:分布式系统中唯一索引确定比较困难,所以可以采用分布式锁(通过Redis或者zookeeper实现),在业务系统插入数据或更新数据获取分布式锁,然后做操作,之后再释放锁。这是分布式系统中的思路。(如果某个流程要求不能并发执行,可以在执行之前根据id+后缀的形式获取分布式锁,其他流程获取锁就会失败,保证同一时间只有此线程一个可以执行成功。执行完成后再释放锁)
//2、说说用过哪些集合
集合:MAP + Collection
Map:hashmap、treemap、hashtable、linkedhashmap
Collection:List + set + Queue
List:arraylist、linkedlist、vactor
Set:hashset、linkedhashset、treeset
list:是有序的,可重复的
arrayList:底层是数组,查询快,增删慢,线程不安全
linkedList:底层是链表,查询慢,增删快,线程不安全
vactor:底层是数组,查询快,增删慢,线程安全
set:无序的,不可重复的
hashSet:底层是哈希表,无序的,唯一的。通过hashCode()和equals()来保证唯一性
linkedHashSet:底层是数组+哈希表,有序的,唯一的。通过hashCode()和equals()来保证唯一性,通过链表来保证有序性
treeSet:底层是红黑树,有序的,唯一的。通过自然排序或比较器排序来保证有序性,通过表交返回值是0来确定唯一性
Map:k-v型的键值对
treeMap是有序的,hashMap和hashTable是无序的
hashTable:方法是同步的,线程是安全的,底层所有public修饰的方法都有synchronized修饰,效率较低。hashTable不允许有null值
hashMap:方法是不同步的,线程是不安全的,效率较高。hashMap允许有null值(k和v都允许有null值)
//3、B-tree和B+tree的区别,估算一千万的B+tree树有多高
1、B-tree在非叶子结点和叶子结点都有数据,B+tree只有在叶子结点上有数据,非叶子结点上不存数据,只存索引;
2、B-tree叶子结点之间不存在指针,B+tree叶子结点之间存在指针,有利于在每一个节点上开始遍历查找。
mysql对每一个节点分配的内存大小约为:16K
每一个非叶子结点可以存放大约1170个索引元素
每一个叶子结点可以存放16个索引元素(一个K+一个V 大小为 1K)
一个三阶B+tree大约可以存放 2100万个数据 1170*1170*16=2100万
//4、数组和链表的区别
1、链表是链式的存储结构,数组是顺序的存储结构;
2、链表是用指针来链接元素,数组是将元素按次序存储;
3、链表的插入删除元素比数组简单,因为不需要移动元素,而且较容易实现扩容,但是查找某个元素比较困难;数组寻找某个元素比较简单,但插入删除元素比较复杂。由于数组的最大长度在程序初始化是指定,所以扩容比较麻烦。
//5、静态代码块和构造方法哪个先执行
静态代码块执行优先于构造方法。因为static修饰的会随着类的加载而加载。
//6、类在什么情况下会被初始化
1、创建类的实例时;
2、调用类的成员方法时;
3、访问类的变量或为类的变量赋值时;
4、使用反射方式来强制创建某个类或接口对应的java.lang.class对象时;
5、初始化某个类的子类,这个子类的父类会被初始化;
6、使用java.exe命令运行某个主类,这个主类先被初始化。
注:static修饰的属性和方法在类加载时被初始化。普通的属性和方法在创建类的实例对象时才被初始化。
类在什么时候不被初始化:
1、通过子类饮用父类的静态字段,子类不会初始化;
2、通过数组定义来引用类时,不会被初始化;
3、调用类的常量,类不会被初始化。
//7、hash冲突了怎么解决
1、开放地址法
2、再哈希法:产生冲突时,再计算另一个哈希函数地址,知道不在冲突为止。
3、链地址法:将所有哈希地址相同的记录都放在一个链表中(hashMap底层)
4、建立一个公共溢出区:在表外建立一个存放冲突的区域。
//8、set、map、list区别
1、set、list是collection接口下的子接口集合,而map是和collection同级别的接口集合;
2、set集合元素是无序的,唯一的,list集合元素是有序的,可重复的;
3、set和list集合是单节点集合,map是以键值对存在的集合。
//9、int与Integer的区别
1、int是基本数据类型,Integer是int类型的包装类;
2、int可以直接使用,Integer必须实例化后才能使用;
3、int可以直接存储数值,Integet是一个对象的引用;
4、int默认值是0,Integer默认值是null。
//10、==和equals的区别
1、==比较的是内存地址,equals比较的是内容
2、基本数据类型只有==,没有equals;
3、对于对象:==比较的是内存地址,equals比较的是值。
//11、做过接口开发吗?
//12、多态的应用
1、父类作为方法的行参;
2、父类作为方法的返回值。
//13、重载和重写的区别、动态绑定和静态绑定的区别
静态绑定:编译时刻由编译器完成,编译时刻就能确定调用的是哪段代码
动态绑定:运行时刻完成类型绑定,编译时刻不知道对象的真实类型,执行的是哪段代码,由运行时刻传入的对象类型来动态决定。
重载:通过调用参数的类型和顺序来确认调用哪个涵数,是静态绑定
重写:是子类重新实现父类的相同签名的涵数,从而改变父类的行为,原先调用父类这个方法的对象,会动态绑定到这个子类的新实现,属于动态绑定。
//14、局部变量和静态变量的区别?哪个更容易导致内存溢出?
1、局部变量只有执行到变量定义的语句时才分配内存,静态变量在编译阶段就已经分配内存;
2、局部变量离开{}作用域就释放内存,静态变量只有程序结束才释放;
3、局部变量不初始化,为随机值,静态变量不初始化为默认值;
4、静态变量只能用敞亮初始化。
静态变量更容易内存溢出。因为静态变量随着类的加载而加载随着程序的结束而终止,就造成了没有被引用还不能被垃圾回收,所以更容易内存溢出。
//15、final、finally、finalize()区别
1、final:是修饰符,如果修饰类,此类就不能被继承;如果修饰方法或变量,表示此方法或变量不能被修改;
2、finally:是try{}catch{}finally{}的最后一部分,表示无论发生任何情况,都会被执行;
3、finalize:是object类的一个方法,在垃圾回收时会调用被回收对象的此方法。
//16、如何判断一个文件/目录是否存在
1、WIN32_FIND_DATA 来判断
2、使用PathFileExists来判断
//17、List<Integer>中的Integer类型中的范型如何拿出
Type[] getGenericInterfaces():获得当前类实现的类型接口(参数化类型)
//18、深拷贝 浅拷贝
1、深拷贝:把要复制的对象所引用的对象都复制了一遍;
2、浅拷贝:仅仅复制所考虑的对象,而不复制它所引用的对象
//19、同步代码块和同步方法区别
1、同步方法的锁对象是this类,同步代码块的锁对象是obj类;
2、同步方法容易出现问题,锁住了this类,但this中操作的对象那个并不是需要锁的对象;
3、同步代码块只要找准锁对象就好了,但是效率比较低。
//20、BeanFactory 与 FactoryBean 区别
1、BeanFactory是一个工厂类,用于管理Bean的一个工厂,在spring中,所有的bean都是有BeanFantory(IOC)容器管理的;
2、FactoryBean是一个可以生产或修饰对象生成的工厂bean。他在IOC容器的基础上给bean的实现增加了一个简单的工厂模式和装饰者模式。一般情况下,spring通过反射机制利用<bean>的class对象指定实力类实例化bean,但有些时候,实例化的bean过程很繁琐,按照传统方式,需要在配置<bean>中提供大量的配置信息,此时可以实现FactoeyBean接口来定制实例化bean的逻辑。
FactoryBean是一个接口,挡在IOC容器中的bean实现可FactoryBean后,通过getBean(String BeanName)获取到的bean并不是这个FactoryBean的实力对象,而是这个实例类中getObject方法的返回对象。要想获取FactoryBean的实例对象,就需要getBean(&BeanName)加上&。
//22、十进制如何转二进制
十进制的数除以2取余数,得到的商进行递归,直到商为0。得到的余数倒序的结果即为对应的二进制,如
//23、日志放哪里,用什么工具, 怎么查错误
slf4j工具 日志一般放在系统盘目录下的logging文件目录下
怎么查错误:查看有价值的地方做标记,写入时间线,方便查看哪一步的之前/之后是什么状态,然后找出问题的关键点
//24、binlog日志做什么用的
binlog日志:二进制日志
作用:1、恢复:数据库宕机,使用二进制日志进行Point in time(PIT)恢复;
2、主从复制:实际上通过复制和执行一个二进制文件,来完成主从数据库之间的实时同步;
3、审计:通过binlog日志中的信息,判断是否有对数据库进行注入攻击
binlog日志保存的什么内容?
有三种值
1、STATEMENT:记录的是日志的逻辑sql语句,可以减少磁盘IO,提高性能,但有些情况会导致主从数据库数据不一致
2、ROW格式:记录的是对表中行的更改。也就是这一行中的有哪些字段从什么值变成了什么值。可以为复制和恢复带来可靠性,但会增大日志文件的大小;
3、混合模式:默认情况下使用STATEMENT格式,特殊情况会使用ROW格式(比如用了一些不确定函数)
查看binlog日志 需要使用mysql提供的工具:mysqlbinlog才能查询。
//25、JDk1.8的新特性
1、lambda表达式
2、函数式接口
3、新增了日期类API LocalData、LocalTime、LocalDataTime
4、加入了io
//26、什么是函数式接口,有几种
1、函数式接口:就是只定义了一个没有实现的方法的接口(Object类的public方法除外),注解@FunctionalInterface
常见的函数式接口:
1、Consumer<T> 消费型接口 有参无返回值
2、Supplier<T> 供给型接口 无参有返回值
3、Function<T,R> 函数式接口 有参有返回值
4、Predicate<T> 断言型接口 有参有返回值 返回值是boolean类型
//27、Java中参数传递有几种方式
java中方法参数传递方式是按值传递。
如果参数是基本类型,传递的是基本类型的字面量值的拷贝。
如果参数是引用类型,传递的是该参量所引用的对象在堆中地址值的拷贝
B、List
//1、list的去重的几种实现方式
1、使用 LinkedHashSet 删除 list 中的重复数据,可以保证愿数据顺序
2、利用java8中的stream进行list去重,使用的是stream的distinct()方法返回一个不同数据组成的流,通过对象的equals()方法进行比较
3、利用hashSet不能添加重复数据 去重,不能保证顺序
4、利用list的contains方法循环遍历,重新排序。
5、利用双重for循环,添加判断语句去重。
//2、ArrayList底层
1、ArrayList类主要是继承 AbstractList 类并实现了 List 接口,实现 Cloneable 和 Serializable 接口使得 ArrayList 具有克隆和序列化的功能。
2、ArrayList初始化容量大小为 10;
3、ArrayList扩容:当添加数据容量满了以后,检查默认1.5倍的扩容能不能满足,不能满足就按你的需求扩张,但是最大不能超过最大数组长度 128 -8;确定好以后 开辟一个确定好容量长度的新数组拷贝数据。
4、ArrayList在进行增删方法时,会抛出“并发修改异常”。因为ArrayList在每次修改容器时,内部都会有一个计数器count进行++操作,在最终遍历时,迭代器会将自己保存的计数器count数值与原容器中的count进行比较是否进行了修改。也就是数组在添加、删除时,count都会增长。
(解决:1、使用普通 for 循环遍历,不可使用增强 for 循环 2、使用 listIterator 迭代器代替 Iterator 迭代器)
//3、ArrayList里面插入10万条数据怎么优化
通过ensureCapacity()提前的对集合的底层数组进行扩容,这样能有效的显著提高执行效率
//4、ArrayList和LinkedList的区别
1、ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。
2、对于随机访问get和set,ArrayList绝对优于LinkedList,因为LinkedList要移动指针。
3、对于新增和删除操作add和remove,如果只是单条数据插入或删除,ArrayList比较快,但如果是批量插入或删除,LinedList比较占优势,因为ArrayList要移动数据。
//5、ArrayList初始化大小、如何扩容
1、ArrayList的初始化大小为 10 (jdk1.6),之前是初始化大小为 0;
2、当向ArrayList中添加数据时,如果容量已经满了,则按默认的1.5倍扩容看是否能够达到要求,如果达不到,则按你的要求扩容,但是最大不能超过最大数组长度 INTEGER_MAX_SIZE-8 128-8
//6、ArrayList创建时为啥最好给一个初始值
如果不指定初始值,JDK1.6以后,默认初始容量为10,当添加的数据超过10,就会以1.5倍的容量进行扩容。如果指定了初始值,则当指定的值不满足后才会扩容。
//7、LinkedList是单向链表还是双向链表
LinkedList是双向链表,从而使添加删除耗时比较短。因为没有索引,所以只能循环遍历,而每次循环遍历时,都会判断一下是属于前半段还是后半段,这样每次遍历其实只遍历了链表的一半,而不是全遍历。
C、Set
//1、HashSet怎么实现去重
依赖于hashCode和equals方法。
1、用hashCode方法获取传入元素的哈希值,在集合中查找是否包含哈希值相同的元素。如果有,则继续用equals方法比较他们的地址值及对象内的属性值,如果全为false,则存入,如果有true则不存入。
D、Map
//1、hashmap底层原理
HashMap底层是一个数组结构,数组中的每一项又是一个链表结构。在JDK1.8以后变成了数组+链表+红黑树
新建一个HashMap时,会初始化一个数组,Entry是以数组中的一个个k-v的形式存在,当要插入一个对象时,会根据哈希算法决定其在数组中的位置,再根据equals方法决定它在数组中链表中的位置。当需要取一个对象时,也会根据哈希算法找到他在数组中的位置,再根据equals方法找到它在链表中的位置。
//2、hashmap扩容机制、初始容量
初始容量:新建一个HashMap时,HashMap是空的,没有初始容量。当添加元素时,会默认容量为16.
源码中HashMap有四种构造函数:1、给定初始容量+加载因子的构造方法;
2、给定初始容量+默认的加载因子的构造方法;
3、什么参数都不给,使用默认的初始容量+默认的加载因子的构造方法;
4、传进一个map,使用默认的加载因子的构造方法。
所以新建一个HashMap时,HashMap是空的,因为HashMap使用懒加载机制,只有第一次添加元素时才会进行容量设置。
但是当我们设置初始容量为2的N次方时,会按照我们设置的容量设置,当不是2的N次方时,会按照设置的那个值找到最接近的2的N次方进行设 置。
扩容机制:HashMap的加载因子时0.75,阈值时16*0.75=12.当兼职对的数量大于12时,就会进行扩容操作。扩容以初始容量2倍的形式进行。扩容以后会将原本的数据从新计算元素位置并复制数据。
为什么是2倍?1、HashMap进行扩容计算时,使用的是位运算,更高效;2、因为初始容量是2的N次方,扩容以后也是2的N次方,可以使添加的元素均匀分布在HashMap的数组上,减少了哈希碰撞,避免形成链表的结构,可以提升查询效率。
//3、hashmap安全吗,如何使hashmap线程安全?有哪些集合是安全的
HashMap是非线程安全的。HashMap在处理并发问题时,1、如果此时有多个线程都在put添加元素,正好有两个put的k发生了哈希碰撞,根据HashMap的实现,这两个K会被添加进同一个位置,从而导致其中的一个K被覆盖。2、当多个线程在put添加元素时,都检测到数组需要扩容,就会同时对数组扩容,并且同时重新计算元素的位置并复制数据,但最终只会有一个线程成功操作,其他线程就是操作失败,数据就会被舍弃。
如何使HashMap安全:1、使用HashTable,因为底层方法都是使用synchronized关键字修饰;
2、使用JUC包下的ConcurrentHashMap,使用的是CAS算法;
3、使用synchronized Map,底层使用了synchronized同步关键字来保证Map的线程安全问题。
哪些集合是线程安全的:1、List集合下的vector;
2、Map集合下的HashTable;
3、JUC包下的ConcurrentHashMap;
4、栈。继承与Vector
//4、hashmap的put流程
1、创建HashMap;
2、调用put方法,会先计算key的哈希值:hash = key.hashCode()。
3、调用 tableSizeFor()方法,保证哈希表散列均匀。
4、计算 Nodes[index]的索引:先进行 index = (tab.length - 1) & hash。
5、如果索引位为 null,直接创建新节点,如果不为 null,再判断所因为上是否有元素
6、如果有:则先调用hash()方法判断,再调用 equals()方法进行判断,如果都相同则直接用新的 Value 覆盖旧的;
7、如果不同,再判断第一个节点类型是否为树节点(涉及到:链表转换成树的阈值,默认 8),如果是,则按照红黑树的算法进行存储;如果不是,则按照链表存储;
8、当存储元素过多时,需要进行扩容:默认的负载因子是 0.75,如果实际元素所占容量占分约变为原来的2 倍(newThr = oldThr << 1);
//5、并发map的底层原理
并发Map实际上是JUC包下的ConcurrentHashMap。
JDK1.8前,是分成多个数组,分段加锁,一个数组一个锁。
JDK1.8以后,是一个数组,对每个数组每个元素进行CAS比较加交换。同一个时间,只有一个线程能够成功执行这个CAS,如果这个索引位置是null,则会直接操作,如果已经加锁,说明已经有值,此时测绘基于链表或者红黑树进行处理。通过判断hashcode值及equals方法来判断。
//6、ConcurrentHashMap为什么线程安全?与hashmap的区别
ConcurrentHashMap底层使用synchronized来保证线程安全,在计算size时采用的是CAS比较加交换操作。
与HashMap的区别:
1、HashMap是线程非安全的,ConcurrentHashMap是线程安全的;
2、HashMap本质是数组+链表/红黑树。根据k值取哈希值然后计算数组的下标,如果多个k的下标相同,就用链表串联起来,新添加的在前面。
3、ConcurrentHashMap是在HashMap的基础上,将数据分成多个段(segment),默认没个段长度为16,然后每次操作就对一个段加锁,避免多线程锁的机率,提升并发效率。
//7、map可以存空值吗?它的实现中那个可以存空值?那个不可以存空值?为什么
Map的K和V都可以存空值。
HashMap可以存空值,HashTable不可以存空值,出现空值会报空指针异常。
1、HashMap计算key的哈希值时会调用单独的方法,如果是null,则返回0;HashTable则直接调用HashCode方法,如果key为null,则抛出空指针异常;
2、以上主要原因是由于Hashtable继承自Dictionary,而HashMap继承自AbstractMap;
3、虽然ConcurrentHashMap也继承自AbstractMap,但是其也过滤掉了key或value为null的键值对。
//8、map遍历的方式有几种?哪种的效率高
1、如果只是获取key或者value,推荐使用KeySet或者values方式;
2、如果同时需要获取key或者value,推荐使用entrySet;
3、如果在遍历过程中删除元素则使用Iterator;
4、如果在遍历元素过程中需要增加元素,可以新建一个临时的map存放新增的元素,等遍历完成,再将临时map放到原map中。
//9、hashmap怎么计算哈希值和索引
//10、hashmap和treemap的区别
1、treeMap是有序的,hashMap是无序的;
2、treeMap底层是红黑树,HashMap底层1.8之前数组+链表,1.8以后时数组+链表+红黑树。所以hashMap支持链表操作,treeMap不支持;
3、hashMap有默认初始长度:16及最大长度:2^30,列表转红黑树阈值:8,列表转红黑树最小数组长度:64,而treeMap没有范围限制;
4、hashMap的k和V都可以为null,而treeMap的k不能为null,v可以为null;
5、hashMap实现的接口为AbstractMap,而treeMap实现的多为NavigableMap;
6、hashMap的定位由哈希算法定位,针对哈希冲突的值,采用列表和红黑树的方式存储,而treeMap,通过自定义的key比较器或折磨人的比较算法来进行定位红黑树节点的存储位置。
//11、hashmap和hashtable的区别
1、hashMap的方法不是同步的,hashTable的方法是同步的;
2、hashMap是线程非安全的,hashTable是线程安全的;
3、hashMap效率较高,hashTable效率较低;
4、hashTable底层的公共方法都有synchronized修饰,hashMap没有;
5、hashTable不允许有null值,hashMap中的key和value都允许有null值;
6、hashTable的父类是Dictionary,hashMap的父类是AbstractMap
E、Java面向对象
//1、Java面向对象的思想
面向对象思想就是利用面向对象的特性:封装、继承、多态的特性来不断的创建对象(将对象的属性和行为封装,让二者作为一个整体参与程序执行),然后使用对象,指挥对象做事情。(在已有的对象的情况下,直接使用对象,而不再去考虑对象的内部结构)
封装:隐藏不需要对外公开的属性和方法,以减少对外的耦合(通常将成员变量private,提供对应的getXxx()/setXxx()方法),而对外指通过方法来控制成员变量的操作,提高了代码的安全性,同时把代码用方法进行封装,提高了代码的复用性;
继承:当多个类有共通的属性(成员变量)和行为(成员方法)时,抽取到另一个类中,让多个类取继承这个父类;
多态:通俗来说,就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生出不同的状态。
多态的成员变量,编译和运行时都参考等号的左边;多态的成员方法,编译时参考等号左边,运行时参考等号的右边
F、IO
//1、stream的简单用法
Stream是通过将集合转换为一种叫做“流”的元素序列,通过声明性方式,能够对集合中的每个元素进行一系列的秉性或穿行的流水线操作;
整个流就是一条流水线,将元素放在流上进行一个个处理。
其中数据源就是原始集合,然后将如List<T>的集合类型转换为Stream<T>类型的流,并对流进行一些列的中间操作,比如过滤保留部分元素、对集合进行排序、类型转换等,最后再进行一个终端操作调用 collect(toList()) 方法,将Stream转换为集合类型。JDK1.8有了Stream以后,做这些一系列操作就不需要进行迭代器或者foreach循环遍历,而可以直接声明式的下指令,流会帮你完成这些操作。
常用方法:1、filter(T->boolean) 保留boolean为true的元素
2、distinct()去除重复元素,这个方法是通过equals方法来判断两个元素是否相等
3、sorted() 如果流中的元素的类实现了Comparable接口,那么可以直接调用sorted()方法对元素进行排序。
4、limit(Long n) 返回前那个元素
5、skip(Long n) 去除前n个元素
//2、了解过工作流引擎吗?
工作流引擎:Activiti
工作流:工作流将一套大的业务逻辑分解成业务逻辑段,并统一控制这些业务逻辑段的执行条件,执行顺序以及相互通信。实现业务逻辑的分解和解耦;
Activiti:是一个开源的工作流引擎,可以发布设计好的流程定义,可以通过Api进行流程调度。
工作流进行的基本过程:
在框架外定义流程 -> 部署流程定义 -> 启动流程实例,框架移动到任务1 -> 拾取任务组 -> 办理个人业务 -> 框架移动到任务2 -> 拾取任务 -> 办理个人业务。。。
组任务是多个用户都可以完成的任务,没有组任务直接办理个人任务;有组任务先通过拾取将组任务变成个人任务在进行拾取。
//3、项目中有流的处理吗(字节流、字符流)
字节流:inPutStream、outPutStream FileInputStream、FileOutputStream
字符流:Reader、Writer FileReader、FileWriter
//4、java1.8中如何用stream流进行数据类型转换
从Object角度考虑:
Object可以转换成任何包装类型和基本类型;
Object数组只能转换包装类型数组;
从基本类型和包装类型考虑:
基本类型的数组,只能由Object转换,也就意味着应当看作Object看待;
基本类型的多维数组,可以由Object数组转换,但是基本类型数组的维度必须大于Object数组的维度;
包装类型的数组和多维数组,可以由Object或者Object数组转换,但需要保证包装类型的数组维度不大于object数组的维度。
//5、创建stream的方式
1、值创建流: 可通过 Stream.of() 显示的来创建流,对应流中的参数类型不受限制;
2、数组创建流:可通过 Arrays.stream() 来创建;
3、文件创建流:可通过 Files.lines() 来创建流;
4、函数生成:可以通过 Stream.generate() 或者 Stream.iterate() 来创建流,但是根据函数生成的流没有限制大小,一般需要加上来 limit 控制流的大小
G、通信方式
//1、三次握手、四次挥手
TCP协议,java提供了两个Socket
1、服务端socket
java.net.ServerSocket
创建对应的ServerSocket开启服务器,等待客户端链接
构造方法:Socket accept();监听并且连接,得到一个Socket对象,是一个阻塞方法,始终处于监听状态。返回的是客户端传输过的Socket对象,并使使用的 Socket 和客户端一致。
2、客户端socket
java.net.Socket
创建客户端Socket,并且连接服务器,同时将Socket发送给服务器绑定注册
构造方法:Socket(String host, int port) host是服务器ip地址,port是服务器程序的端口号
成员方法:1、InputStream getInputStream(); 获取Socket对象输入字节流,从服务器获取对应的数据。Read
2、OutputStream getOutputStream();获取Socket对象输出字节流,可以讲数据发送到服务器。Write
3、void close(); 关闭客户端Socket;
4、void shutDownoutput(); 禁止当前Socket发送数据。
3、文件上传操作 客户端程序流程:
1. 创建对应文件的输入字节流操作,这里可以使用缓冲
2. 启动Socket,
3. 获取Socket输出OutputStream对象,发送数据给服务器
4. 边读边发
5. 当文件读取结束,发送完毕,关闭客户端
文件上传操作 服务器程序流程
1. 开启服务端服务,创建ServerSocket对象
2. 明确保存文件的位置,创建对应文件夹的输出缓冲字节流
3. 读取数据,写入文件
4. 关闭服务器
三次握手流程:
1、服务端创建ServerSocket对象,时刻监听客户端消息;
2、客户端启动Socket,询问服务器端Socket状态,是否可以发送数据;
3、服务器端响应,调用OutputStream对象输出信息,并断开连接;
4、客户端调用InputStream接受程序,写入文件,关闭资源,并持续监听客户端;
四次挥手流程:
1、客户端发送完数据,通知服务器端,并询问是否可以关闭连接;
2、服务器端响应已经成功接受程序,可以断开;
3、客户端断开连接,并发送状态给服务器端;
4、服务器端接受状态。
H、设计模式
//1、手写懒汉式和饿汉式,哪一种是线程安全的?
1、/*饿汉式单例模式*/
class Singleton{
//创建私有的构造函数,保证外类不能实例化本类
private Singleton(){}
//自己创建一个类的实例化
private static Singleton singleton = new Singleton();
//创建一个get方法,返回一个实例singleton
public static Singleton getInstance(){
return singleton;
}
}
2、/*懒汉式单例模式*/
class Singleton{
//创建私有的构造函数,保证外类不能实例化本类
private Singleton(){}
//自己创建一个类的实例化
private static Singleton singleton;
//创建一个get方法,返回一个实例singleton
public static Singleton getInstance(){
//判断singleton是否为空,如果为空,则需要实例化
if(singleton == null){
singleton = new Singleton();
}
return singleton;
}
}
3、饿汉式是线程安全的,懒汉式是非线程安全的,可以使用synchronized关键字。
I、排序算法
//1、冒泡排序
public static void ArraySortTest(){
int[] ages = {1,2,4,5,7,8,23,454,442};
System.out.println(Arrays.toString(ages));
for(int i = 0; i < ages.length - 1; i++){
Boolean flag = false;
for(int j = 0; j < ages.length - i - 1; j++){
if(ages[j] > ages[j + 1]){
int tmp = 0;
ages[j] = tmp;
ages[j] = ages[j + 1];
ages[j + 1] = tmp;
flag = true;}
if(!flag){
System.out.println("已经排好序了")
break;}}}
System.out.println(Arrays.toString(ages));}
//2、快速排序
public class QuickSort{
public static void quickSort(int[] arr, int low, int high){
int i,j,tmp,t;
if(low > high){
return;}
i = low; j = high; tmp = arr[low];
while(i < j){
//先看左边,依次往左递减
while(tmp <= arr[j] && i < j){
j--;}
//再看左边,依次往右递减
while(tmp >= arr[i] && i < j){
i++;}
//如果满足条件则交换
if(i < j){
t = arr[j];
arr[j] = arr[i];
arr[i] = t;}}
//最后将与i和j相等位置的元素交换
arr[low] = arr[i];
arr[i] = tmp;
//递归调用左边数组
quickSort(arr, low, j-1);
//递归调用右边数组
quickSort(arr, j+1, high);}
public static void main(String[] args){
int[] arr = {1,4,6,7};
quickSort(arr, 0, arr.length-1);
for(int i = 0; i < arr.length; i++){
System.out.println(arr[i]);}}}
持续更新中。。。