<< 乘 >>除
Collections.shuffle(array);
小写转大写(相差32) c-'0'
基本类型:
32位 | 64位 | |
char | 1 | 1 |
char* | 4 | 8 |
short int | 2 | 2 |
unsigned int | 4 | 4 |
float | 4 | 4 |
double | 8 | 8 |
long | 4 | 8 |
long long | 8 | 8 |
unsigned long | 4 | 8 |
int Integer自动装箱(-128-127):https://blog.csdn.net/to_myslef/article/details/79523055
将int的变量转换成Integer对象,这个过程叫做装箱
反之将Integer对象转换成int类型值,这个过程叫做拆箱
因为这里的装箱和拆箱是自动进行的非人为转换,所以就称作为自动装箱和拆箱
是java早年设计缺陷。基础类型是数据,不是对象,也不是Object的子类。
把一个基本类型包装成一个类,一个是可以使这个类型具有很多可以调用的方法。二个是Java向面像对象语言的靠近。其实Java还不算是很纯的面向对象的语言。真正的面向对象,是没有基本数据类型的。它只有一种类型,就是对象。三个是在泛型中,基本类型是不可以做泛型参数的。如:List list = new ArrayList ();这是不合法的。你只能这个样写List list = new ArrayList ();也就是要用int型的包装类类型来解决基本类型不可以做泛型参数的问题 。
一名话,包装类有更多的方法和用途, 而这是基本类型没有的!!!
参考:java装箱和拆箱的意义_java的自动拆箱和装箱是每个程序员都要知道的_太空X基地的博客-程序员宅基地 - 程序员宅基地
计算机存储单位换算:
16位机,32位机表示比特(bit)
- bit:计算机中表示数据的最小单位
- Byte:计算机处理数据的单位
1Byte = 8bit
GB 与 Gb不同 ==> GByte Gbit
public protected default private范围:
类内部 | 本包 | 子类 | 外部包 | |
public | √ | √ | √ | √ |
protected | √ | √ | √ | × |
default | √ | √ | × | × |
private | √ | × | × | × |
Collection:
List | ArrayList、Vector、LinkedList |
Map | HashMap、HashTable、LinkedHashMap、TreeMap、WeakHashMap 其中:LinkedHashMap accessOrder为 true: 访问为时间顺序 / false:插入顺序排序 |
Set | HashSet、LinkedHashSet、TreeSet 是基于Map的:map(e,PERSENT); //PERSENT为静态Object对象 |
Stack | 基于Vector |
Queue | ArrayBlockingQueue、LinkedBlockingQueue、PriorityBlockingQueue、DelayQueue、SynchronousQueue |
Deque | 基于Queue ArrayDeque(基于Vector)、LinkedList、LinkedBlockingDeque |
List补充:
- ArrayList 底层为数组,扩容时扩1.5倍
扩容:底层Object数组默认长度为10。扩容时新建一个数组的拷贝,修改原数组指向新数组
- Vector 底层为数组,扩容时扩2倍
- LinkedList 非线程安全
LinkedHashMap有序:
LinkedHashMap继承自HashMap,具有高效性,同时在HashMap的基础上,又在内部增加了一个双向链表,用以存放元素的顺序,可以按元素插入顺序或元素最近访问顺序(LRU)排列,简单地说:
LinkedHashMap=散列表+循环双向链表
TreeMap有序:
TreeMap 实现了NavigableMap接口,意味着它支持一系列的导航方法。比如返回有序的key集合
TreeMap提供了四个构造方法,实现了方法的重载。无参构造方法中比较器的值为null,采用自然排序的方法,如果指定了比较器则称之为定制排序.
- 自然排序:TreeMap的所有key必须实现Comparable接口,所有的key都是同一个类的对象
- 定制排序:创建TreeMap对象传入了一个Comparator对象,该对象负责对TreeMap中所有的key进行排序,采用定制排序不要求Map的key实现Comparable接口。
Map补充:
- HashMap hash数组默认16
- HashTable hash数组默认11
解决冲突的方法:
- 开方地址法
- 再hash法
- 链地址法
HashMap实现原理:
位桶 + 链表 + 红黑树(当阈值超过8时,转换为红黑树)
HashMap存储结构:
transient Entry[] table:
/**实际存储的key-value键值对的个数*/
transient int size;
/**阈值,当table == {}时,该值为初始容量(初始容量默认为16);当table被填充了,也就是为table分配内存空间后,
threshold一般为 capacity*loadFactory。HashMap在进行扩容时需要参考threshold,后面会详细谈到*/
int threshold;
/**负载因子,代表了table的填充度有多少,默认是0.75
加载因子存在的原因,还是因为减缓哈希冲突,如果初始桶为16,等到满16个元素才扩容,某些桶里可能就有不止一个元素了。
所以加载因子默认为0.75,也就是说大小为16的HashMap,到了第13个元素,就会扩容成32。
*/
final float loadFactor;
/**HashMap被改变的次数,由于HashMap非线程安全,在对HashMap进行迭代时,
如果期间其他线程的参与导致HashMap的结构发生变化了(比如put,remove等操作),
需要抛出异常ConcurrentModificationException*/
transient int modCount;
staic class Entry<K,V> implements Map.Entry<K,V>{
V values; //concurrent HashMap: volatile V values;
Entry<K,V> next; //concurrent HashMap:final HashEntry<K,V> next;
final int hash;
}
Hash原理:使用key、hashcode找到bucket位置,获取存储的Entry对象
- index 获取: index = (n-1) $ hash
- hash方法: return (h=key.hashcode()^(h>>>16))
- hashcode(): return key==null?0:key.hashcode() ^ value.hashcode()
- get方法:
get(Object key){
for(Entry<k,v> e = table[indexFor(hash,table.length)];e!=null;e=e.next){
Object k;
if(e.hash == hash && ((k=e.key)==key)||key.equals(k)) return e.value;
return null;
} }
Hash攻击:hashCode相同,将HashMap变成了SingleLinkedList(Java8使用TreeMap提高性能)
HashMap死循环:多个线程同时put,同时触发rehash操作,会导致出现循环节点,从而在get的时候,产生死循环(与table扩容有关)
https://www.51cto.com/article/699495.html
什么时候扩容:通过HashMap源码可以看到是在put操作时,即向容器中添加元素时,判断当前容器中元素的个数是否达到阈值(负载因子*容量)的时候,就要自动扩容了。
扩容(resize):当表中75%被占用时重新计算容量;而这个扩容是计算出所需容器的大小之后重新定义一个新的容器,将原来容器中的元素放入其中。
https://www.jianshu.com/p/ee0de4c99f87
Java集合之一—HashMap_深入浅出学JAVA-CSDN博客_hashmap
BlockingQueue不熟悉的操作:
抛异常 | 返回特殊值 | 执行阻塞 | |
插入 | add | offer | put |
取出 | remove | poll | take |
检验 | element | peek |
并发数据结构
并发List | Vector、Collections.synchronized(List list) CopyOnWriteArrayList: 当对象进行写操作时,复制该对象;若进行读操作,则直接返回结果 (add方法比Vecor弱) 高并发读选CopyOnWriteArrayList,写频繁用Vector |
并发Set | CopyOnWriteArraySet(基于CopyOnWriteArrayList) |
并发Map | HashTable、synchronizedMap()、ConcurrentHashMap() |
并发Queue | ConcurrentLinkedQueue、BlockingQueue |
并发Deque (双端队列) | LinkedBlockingDeque |
主要关注点在于:拷贝的对象里是否有通过“组合”的方式关联的对象
- 浅拷贝:会复制这个对象,但原对象的“对象”是共享的,即原对象和拷贝对象共享
- 深拷贝:会复制这个对象 + 对象的对象
程序优化手段:
缓冲:控制流速
实例:BufferedWriter、BufferedOutputStream
优点:改善I/O性能,提高显示效果
缓存:存结果
实例:
- HashMap ---> WeakHashMap
- EHCache ---> Hibernate
- OSCache ---> OpenSymphony
- JBossCache ---> JBoss
池:对象池化。线程池、数据库连接池
Java:Jakarta Commons Pool组件
ThreadPool{
List<PThread> idleThreads;
public synchronized static ThreadPool getInstance(){...}
}
负载均衡:
Java Terracotta分布式缓存框架
引用类型:
- 强引用:只要引用存在,永远不会回收
- 软引用:在系统内存紧张的情况下,软引用会被回收(SoftReference)
- 弱引用:在下一次垃圾回收时回收(WeakRefence)
- 虚引用:主要用于跟踪对象回收,不能单独使用,与引用队列联合使用(RefenceQueue:如PhantomQueue 虚引用队列)(PhantomRefence)
String类的hashcode方法:
hash = S[0]*pow(31,n-1)+S[1]*pow(31,n-2)+...+S[n-1];
红黑树:
- 根永远是黑色
- 叶节点是空节点
- 红色节点的两个子节点是黑色的(黑色不一定)
- 每个路径包含相同数量的黑色节点
- 节点要么是红色,要么是黑色
冲突解决方法:1. 改变节点颜色 2. 执行旋转操作
旋转操作不会影响旋转结点的父结点,父结点以上的结构还是保持不变的
左旋只影响旋转结点和其右子树的结构,把右子树的结点往左子树挪了
右旋只影响旋转结点和其左子树的结构,把左子树的结点往右子树挪了
ps:黑色节点要么是根,要么是两个孩子,单个孩子不可能是黑
插入操作:一查找插入的位置;二插入后自平衡
插入时要解决:红红冲突
插入情景1:红黑树为空树:直接把插入结点作为根结点就行,插入的节点的颜色转换成黑色
插入情景2:插入节点已经存在:把I设为当前结点的颜色,更新当前结点的值为插入结点的值
插入情景3:插入节点的父节点为黑节点:直接插入
插入情景4:插入节点的父节点为红节点:该父结点不可能为根结点,所以插入结点总是存在祖父结点
插入情景4.1:叔叔结点存在并且为红结点:
- 将P和S设置为黑色
- 将PP设置为红色
- 把PP设置为当前插入结点
如果PP的父结点是黑色,那么无需再做任何处理;但如果PP的父结点是红色,根据性质4,此时红黑树已不平衡了,所以还需要把PP当作新的插入结点,继续做插入操作自平衡处理,直到平衡为止
试想下PP刚好为根结点时,那么根据性质2,我们必须把PP重新设为黑色,那么树的红黑结构变为:黑黑红。换句话说,从根结点到叶子结点的路径中,黑色结点增加了。这也是唯一一种会增加红黑树黑色结点层数的插入情景
插入情景4.2:叔叔结点不存在或为黑结点
插入情景4.2.1: 插入结点的父亲结点是祖父结点的左子结点
- 将P设为黑色
- 将PP设为红色
- 对PP进行右旋
插入情景4.2.2:插入结点是其父结点的右子结点
这种情景显然可以转换为情景4.2.1
- 对P进行左旋
- 把P设置为插入结点,得到情景4.2.1
- 进行情景4.2.1的处理
插入情景4.3:叔叔结点不存在或为黑结点,并且插入结点的父亲结点是祖父结点的右子结点
插入情景4.3.1:插入结点是其父结点的右子结点
- 将P设为黑色
- 将PP设为红色
- 对PP进行左旋
插入情景4.3.2:插入结点是其父结点的左子结点
- 对P进行右旋
- 把P设置为插入结点,得到情景4.3.1
- 进行情景4.3.1的处理
总结:
情景 | 操作 |
空 | 直接把插入点当根节点即可 |
插入节点已存在 | |
插入节点的父节点是黑节点 | 直接插入 |
插入节点的父节点是红节点 | |
(1)叔叔节点存在,且为红节点 | 改颜色: 1)父节点、叔叔节点改为黑色 2)祖父节点改为红色 并把祖父节点当成插入节点,继续红黑平衡 |
(2)叔叔节点不存在,或为黑节点 | |
插入结点的父亲结点是祖父结点的左子结点,插入左侧 | 改颜色、右旋转 1)父节点改为黑色、祖父节点改为红色 2)祖父节点右旋 |
插入结点的父亲结点是祖父结点的左子结点,插入右侧 | 左旋转、改颜色、右旋转 1)父亲节点左旋(变更为上一种情况) 2)父亲节点变为插入节点 进行上面的处理... |
插入结点的父亲结点是祖父结点的右子结点,插入左侧 | 改颜色、左旋转 1)父亲节点改为黑色、祖父节点改为红色 2)祖父节点左旋 |
插入结点的父亲结点是祖父结点的右子结点,插入右侧 | 右旋转、改颜色、左旋转 1)父亲节点右旋转 2)父亲节点变为插入节点 进行上面的处理.... |
红黑树与跳表:性能差别不大,更新数据时,跳表更新部分少,且在并发下跳表性能更好
B-:多路搜索树(非二叉)
B+:B-树的变体,多路搜索树。只有到叶子节点才命中
B*:在非根非叶子节点再加入指针链表
AVL:平衡二叉查找树
- 平衡因子:bf(x) = h(x-right) - h(x-left);
- 平衡因子 1、0、-1的节点都被认为是平衡的
LL型、RR型:(1)旋转(2)变换根节点
LR型、RL型:(1)转化LL,RR (2)再进行LL,RR转换
红黑树与AVL树的差别:
AVL是严格的平衡树,因此在增加或者删除节点的时候,根据不同情况,旋转的次数比红黑树要多;
红黑是弱平衡的,用非严格的平衡来换取增删节点时候旋转次数的降低;
所以简单说,搜索的次数远远大于插入和删除,那么选择AVL树,如果搜索,插入删除次数几乎差不多,应该选择红黑树。
聚合 & 组合:
聚合是关联关系的一种特例,他体现的是整体与部分、拥有的关系,即has-a的关系,例如:
Family ---> Child
组合也是关联关系的一种特例,他体现的是一种contains-a的关系,这种关系比聚合更强,也称为强聚合。但此时整体与部分是不可分的,整体的生命周期结束也就意味着部分的生命周期结束,例如:
Person ---> Brain
Java内部类:静态内部类、非静态内部类、局部内部类、匿名内部类
为什么匿名内部类必须用final修饰?
内部类编译后class文件与外部不同,仅仅保留了外部引用;外部参数传入内部类时,内部类并不是直接调用,而是对参数备份,保持内部参数与外部参数一致,用final让引用不改变,否则,内外不一致。保证了即使外部生命周期结束,内部依然可用
不可变类:
不可变对象一旦创建之后就不可更改,不可变类自身线程安全;任何修改都会创建一个新的对象
- 成员变量使用private
- 不提供setter方法
- getter方法中,不直接返回对象本身,而是克隆对象
接口 & 抽象类
相同点
(1)都不能被实例化
(2)接口的实现类或抽象类的子类都只有实现了接口或抽象类中的方法后才能实例化
不同点
(1)接口只有定义,不能有方法的实现,java 1.8中可以定义default方法体,而抽象类可以有定义与实现,方法可在抽象类中实现。
(2)实现接口的关键字为implements,继承抽象类的关键字为extends。一个类可以实现多个接口,但一个类只能继承一个抽象类。所以,使用接口可以间接地实现多重继承。
(3)接口强调特定功能的实现,而抽象类强调所属关系。
(4)接口成员变量默认为public static final,必须赋初值,不能被修改;其所有的成员方法都是public、abstract的。抽象类中成员变量默认default,可在子类中被重新定义,也可被重新赋值;抽象方法被abstract修饰,不能被private、static、synchronized和native等修饰,必须以分号结尾,不带花括号。
String为不可变类主要是因为:public final class String extends Object
String 不可变原因:
- 字符串常量池需要
- hash唯一,可放入缓存
- 当参数时,保证使用安全
subString潜在内存泄漏:
内部调用了String(int offset, int count, char value[]){......},很长的字符串,截取一个短小的字符串,如果短小字符串没有被回收,大的字符串也没有被回收。
消除方法: String str = new(s.substring(...))
StringTokenizer的使用:
StringTokenizer st = new StringTokenizer(";");
st.hasMoreTokens();
st.nextToken();
String中isEmpty(),null以及""的区别:
- isEmpty():分配了内存空间,值为空(值=空)
- null:未分配内存空间,值为无(值不存在)
- "":分配了内存空间,值为空字符串(值=空字符串)
Enumeration接口和Iterator接口的区别?
- Enumeration的速度是Iterator的两倍,也使用更少的内存
- Iterator更加安全,允许从集合中移除元素(Iterator的remove方法)
Java反射机制底层:
- ClassforName
- 方法反射
- 方法调用
ClassforName:
- findLoadedClass(String)
- 父加载器loadClass方法
- findClass()
- ClassLoader.defineclass将字节数组转化为class实例
- Class.newInstance 创建
方法反射:
- getDeclaredMethod
- SearchMethods
- privateGetDeclaredMethods 找到class中声明方法的列表
- getRefectionFactory().copyMehod返回
方法调用:
- MethodAccessor通过RefactionFactory类中的newMethodAccessor创建一个MethodAccessor接口对象
- NativeMethodAccessorImpl,DelegatingMehodAccessorImpl 中的invoke方法
- MehodAccessorGenerator().generateMehod返回
有关序列化:
父类序列化时,子类自动实现序列化,无需显示实现Serializable接口。对象引用到其他对象,跟着序列化
SerialVersionId:提供运行效率,序列化时会计算(hashCode值)
Java如何实现跨平台的?Java虚拟机负责将.class字节码文件翻译成特定平台下的机器码
为什么Java有反射而C++没有?
Java运行时拥有类的一切信息,而C++通过RTT运行时识别类型信息不完整
面向对象五大原则:
- 单一职责原则:一个类,最好只做一件事,只有一个引起它的变化
- 开放封闭原则:软件实体应该是可扩展的,而不可修改的
- 里氏替换原则:子类可以替换父类
- 依赖倒置原则:抽象不依赖于具体,具体依赖于抽象
- 接口隔离原则:使用多个小的专门的接口,而不要使用一个大的总接口
异常 & 错误:
- 运行时异常:除数为0,数组越界
- 被检查异常:
- Error:一般与虚拟机有关(系统崩溃、JVM错误、内存空间不足、方法调用栈溢出等)
this逃逸:
对象还没有构造成功,this引用被发布出去,线程中看到该对象状态是没有初始化完的状态。取得对象线程并不一定会等待对象完结后才使用
发生条件:1. 在构造函数中创建内部类 2. 在构造函数中把这个内部类发布出去
避免:避免这两个条件同时出现(private修饰、getInstance方法)
Timsort思想:(待确认???)
合并 + 插入排序
排序单位是一个个块分区,优化了merge最坏情况O(n2),复杂度小于nlongn,最坏nlongn
- 分区:对严格反序做分区,并反转。如果分区过小,用后面元素补足
- 合并:(优化合并排序)
- 小于某值,直接二分插入排序
- 二分插入合并
- 如果两个run长度加起来比前一个长,则中间位置和较短的合并
- 若2个run长度加起来比前面短,合并
蓄水池问题:一共有N个数据,且N个数据未知。把前K个元素放在水库中,对之后的第i个元素,以k/i的概率替换掉某一个元素。
原型模式:
- 简化对象创建过程
- 性能优于new一个对象
- Class.forName可重复创建相似对象
MyObject object = (MyObject)Class.forName("...").newInstance()
Class.forName 与 ClassLoader.loadClass 的区别
- Class.forName将.class文件加载,执行static块 ClassLoader.loadClass将.class文件加载,不执行static块,只有newInstance才执行static块
- Class.forName(xx.xx)等同于Class.forName(xx.xx,true,CALLClass.class.getClassLoader()) 可以控制是否初始化类
- ClassLoader.loadClass 实际调用了ClassLoader.load(className,false),loadClass加载时是没有初始化的
HashCode算法:
- Object:对象经过处理后的内存地址
- String:只要字符串内容相同,哈希码相同
- Integer包装类:返回对象中包含的整数值
- int,char基础类,如需存储将自动装箱
OOM异常 OutOfMemoryError
除了程序计数器外,虚拟机内存的其他几个运行时区域都有OOM异常的可能
- Java Heap溢出(不断创建对象)-->检查虚拟机的参数-Xmx
- 运行时常量池溢出:如果向常量池中添加内容,使用String.intern()这个native方法
- 方法区溢出
- 虚拟机栈和本地方法栈溢出:
- 线程请求的栈深度大于虚拟机最大深度 StackOverFlowError
- 在扩展栈时无法申请到足够的内存空间 OutOfMemoryError
Java8特性:
- Lambda表达式
- 函数式接口
- 接口方法(父类方法优先于接口)
- Base64编码引入标准包中
- 日期?
- 并行数组
- HashMap的变化
- Stream更强大?
- jjs接收一些JavaScript源码参数
- 类依赖分析器jdeps
Java动态代理
- 通过实现InvocationHandler接口创建自己的调用处理器
- 通过为Proxy类指定ClassLoader对象和一组interface创建动态代理类
- 通过反射机制获得动态代理类的构造参数
- 通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数传入
Interface proxy =
(Interface)Proxy.newProxyInstance(classloader,new Class[]{Interface.class},new Invocation HandlerImpl...);
- 动态代理主要使用了newProxyInstance
- ClassLoader loader:指定当前目标对象使用类加载器,写法固定
- Class<?>[] interfaces:目标对象实现的接口的类型,写法固定
- InvocationHandler:事件处理接口,需传入一个实现类,一般直接使用匿名内部类
User proxy = (User) Proxy.newProxyInstance(
user.getClass().getClassLoader(), //classloader
user.getClass().getInterfaces(), //interfaces
new InvocationHandler() {//handler
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// TODO Auto-generated method stub
return method.invoke(proxy, args);
}
});
CGLib代理:https://github.com/vicotorz/Vcode/blob/master/src/Proxy_CGLib/ProxyFactory.java
算法:
- 动态规划:从开始推到终
- 贪心算法:从终推到开始
- 回溯法:深度优先(所有解)
- 分支限界法:广度优先(一个解)
Bloom Filter:查找一个元素是不是在集合中
原理:用K个Hash函数计算数字的K个位数,在数组中相应位置置为1
查询过程中,检查K个位中的值,若有位数不为1,则一定不存在
同步/异步(相当于通知) 阻塞/非阻塞(相当于等待)
- 同步阻塞(BIO):调用完函数之后,什么也不做了,原地一动不动等待结果
- 同步非阻塞(NIO):调用完函数之后,什么也不做了,时不时看看结果是否输出
- 异步阻塞:调用完函数之后,什么也不做了,等着结果找过来
- 异步非阻塞(AIO):调用完函数之后,去做别的,等着结果找过来
- Java BIO : 同步并阻塞 适用于连接数目比较小且固定的架构
- Java NIO : 同步非阻塞 适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器
- Java AIO(NIO.2) : 异步非阻塞 连接数目多且连接比较长(重操作)的架构
【NIO详解】Channel、Selector与Pipe_白夜行-CSDN博客_channel selector
Java NIO: 相比面向流I/O NIO面向缓存
- 为所有原始类提供缓存支持
- 使用Java.nio.charset.Charset作为字符集编码解决方案
- 增加通道Channel对象
- 支持锁和内存映射文件的文件访问接口
- 基于Selector异步网络I/O
核心:Buffer、Channel、Selector
- Buffer:
类型:ByteBuffer、CharBuffer、DoubleBuffer、FloatBuffer、IntBuffer、LongBuffer、ShortBuffer
MappredBuffer:Java大文件直接映射到内存,比较大可以分段映射(force()、load()、isloaded())
DirectBuffer:直接访问系统内部类,直接分配在物理内存,不占用堆空间。读写比Buffer快,但创建和销毁慢
Buffer使用步骤:
- 分配空间
- 写入到Buffer
- 调用flip方法
- 从Buffer读取到数据
- 调用clear方法
- flip:读写转换
- allocate:创建
- warp:从数组中创建
- rewind:将position置零,清除标志位,为Buffer提取有效数据
- clear:重写Buffer准备
- compact,mark:标记
- duplicate:复制缓冲区,完全一样的Buffer,但position和limit不同
- slice:创建子缓存,缓冲分区
2. Channel:
类型:FileChannel、DatagramChannel、SocketChannel、ServerSocketChannel
- SocketChannel.open();
- SocketChannel.write(buffer);
- ScoketChannel.close();
3. Selector:运行单线程处理多个Channel
Selector管理多个I/O
创建一个Selector实例,并将channel注册监控的信道上,调用Selector方法,阻塞等待
channel.register(selector,就绪事件(Connect、Accept、Read、Write))
selector.open() 主要完成建立pipe,并把pipe的wakeupSourceFd放入pollArray中,注册过程中把channel文件描述符放在pollArray中
select对应内核sys_select调用,有事件发生,select会将临时结果写入到用户空间并返回(轮询pollArray中FD)
Linux 中用epoll不断轮询,当事件发生,回调函数把发生事件存储在就绪事件链表,写到用户空间
1. 使用 FileInputStream fin = new FileInputStream(new File(...));
2. Channel FileChannel fc = fin.getChannel();
3. 创建Buffer ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
4. channel(Buffer) fc.read(byteBuffer); fc.write(byteBuffer);
5. 关闭channel fc.close();
6. Buffer关闭 byteBuffer.flip();
NIO补充功能:
- Scatter / Gatter:分散多个buffer,多个buffer写入一个Channel
- transferForm / transferTo:数据直接在内核空间移动
toChannel.transforForm(fromChannel,position,count);
fromChannel.transforTo...
3. Pipe:两个线程之间的单向连接
Thread A ------> sink channel ------> source channel ------>Thread B
sinkChannel = Pipe.sink(); //写入sink通道
sourceChannel = Pipe.source(); //从source通道读
4. DatagramChannel:用于接收UDP数据包
- NIO:非阻塞IO Buffer / Channel / Selector
- NIO2:引入四个异步 Channel,异步阻塞IO (WatchService watch类)
selector 无阻塞io,阻塞唤醒通过:
channel有事件发生
selector.select(timeout)超时
selector.wakeup()主动唤醒
Wakeup() 用于唤醒阻塞在select方法上的线程
在开始建立的pipe的sink端写入一个字节,source文件描述符处于就绪状态
poll方法返回,select方法返回
NIO优化数据访问的方式:
- FileChannel.transferForm / FileChannel.transferTo 数据直接在内核空间移动
- FileChannel.map 适合大文件只读操作
将文件按照一定大小映射到内存区域,程序访问内存区域将直接操作这个文件数据,省去了内核空间到用户空间复制的过程的损耗
DSL语言:Html,shell,make,ant,maven,rpm,dpkg,awk,正则表达式,dc计算机语言
1.为什么等待和通知是在 Object 类而不是 Thread 中声明的?
- wait和notify不仅仅是普通方法或同步工具,更重要的他们是java中两个线程之间的通信机制
- 每个对象都可以上锁
- 在java中为了进入代码临界区,线程需要锁定并等待锁定
- java是基于Hoare的监视器的思想:在Java中,所有对象都有一个监视器。在 Java 中,所有在另一个线程的执行中侵入的操作都被弃用了(例如 stop 方法)
2.为什么Java中不支持多重继承?
- 可能产生钻石型继承问题产生歧义
- 多重继承使设计复杂化并在转换,构造函数链接等过程中产生问题
3.为什么Java不支持运算符重载?
- 简单和清晰
- 避免编译错误
- JVM复杂性:复杂的 JVM 可能导致 JVM 更慢,并为保证在 Java 中运算符行为的确定性从而减少了优化代码的机会
- 让开发工具处理更容易
4.为什么 String 在 Java 中是不可变的?
- 字符串池中的字符改变会影响其他引用的对象
- 字符串已被广泛引用到许多java类的参数
- 线程安全,避免了java中的同步问题
- 允许string缓存其哈希码,不会在每次调用String的hashcode方法时重新计算,在HashMao中作为键时非常快
- 类加载机制使用
5.为什么 char 数组比 Java 中的 String 更适合存储密码?
- String会存在字符串池中,会在内存中持续很长时间,构成安全威胁
- 存在日志文件或打印纯文本的风险
如果Serializable包含一个不可序列化的成员,会发生什么?:任何序列化尝试都会因NotSerializableException失败,可以通过trancient或static变量解决
什么是序列化?:序列化是把对象改成可以存到磁盘或通过网络发送到其他运行中的 Java 虚拟机的二进制格式的过程, 并可以通过反序列化恢复对象状态
- 可序列化接口 & 可外部接口 :Externalizable 给我们提供 writeExternal() 和 readExternal() 方法,让我们灵活控制Java序列化机制,不依赖Java默认序列化。正确实现Externalizable接口可以显著提高程序性能
- serialVersionUID:SerialVerionUID 用于对象的版本控制,不指定 serialVersionUID的后果是,当你添加或修改类中的任何字段时, 则已序列化类将无法恢复, 因为为新类和旧序列化对象生成的 serialVersionUID 将有所不同。Java 序列化过程依赖于正确的序列化对象恢复状态的, ,并在序列化对象序列版本不匹配的情况下引发 java.io.InvalidClassException 无效类异常
假设新类的超级类实现可序列化接口, 如何避免新类被序列化?新类实现writeObject()和readObject()方法,并从该方法引发NotSerializableException异常
为什么Java中 wait 方法需要在 synchronized 的方法中调用?
- 抛异常:Java 会抛出 IllegalMonitorStateException
- 保证通知不丢失:Javac 中 wait 和 notify 方法之间的任何潜在竞争条件都会干扰正常执行(由于竞态条件,我们可能会丢失通知,如果我们使用缓冲区或只使用一个元素,生产线程将永远等待,你的程序将挂起)
能用Java覆盖静态方法吗?如果我在子类中创建相同的方法是编译时错误?
不能在Java中覆盖静态方法,但在子类中声明一个完全相同的方法不是编译时错误,这称为隐藏在Java中的方法
一些设计思考:
秒杀系统:
- 前端:页面静态化、禁止重复提交、用户限流、图片服务器和应用服务器分离
- 后端:限制uuid,消息队列缓存
高并发 | Kafka削峰填谷 |
超卖 | 查数据时判断是不是查询时候的version,如果是,扣减库存 |
恶意请求 | Ngnix + 负载均衡 |
连接暴露 | 秒杀连接加盐 |
数据库 | Redis库存与预热 |
摇一摇功能:用户坐标geohash传入到redis,并设置过期时间
手机扫二维码登录功能:
- index.html ====== GetQrCodeServlet(生成uuid唯一标识,生成二维码)
- index展示二维码
- 手机扫码后发送验证+ uuid到Server
- index调用LongConnectionCheckServlet进行长时间轮询,参数是uuid;拿到uuid后检查loginUsermap是否为空
设计开发连接池:
- 编写class实现DataSource接口
- 在class的构造器一次性创建n个连接,保存在LinkedList中
- 实现getConnection,从LinkedList中返回一个连接
- 提供将连接放回连接池的方法
定时器设计:如何快速实现一个定时器?_语言 & 开发_云加社区_InfoQ精选文章
1. 基于 Redis ZSet 实现
(执行时间,实行任务),定时查询执行时间最小的元素,如果拿出来当前时间>执行时间,就开始执行
2. 采用某些自带延时选项的队列实现
3. 基于 Timing-Wheel 时间轮算法实现
时间轮(TimeWheel)。一个时间轮是一个环形结构,可以想象成时钟,分为很多格子,一个格子代表一段时间(越短 Timer 精度越高),并用一个 List 保存在该格子上到期的所有任务,同时一个指针随着时间流逝一格一格转动,并执行对应 List 中所有到期的任务。任务通过取模决定应该放入哪个格子