收集到的一些Java基础问题

Java数据结构:
线性表(ArrayList)
链表(LinkedList)
栈(Stack)
队列(Queue)
图(Map)
树(Tree)

Collection
set 无序不重复
TreeSet 内部元素进行排序,是不同步的。
HashSet 内部数据结构是哈希表,是不同步的
LinkHashSet

 list	有序可重复
         Vector  内部是数组数据结构,是同步的(线程安全的)。增删查询都很慢。
		 Stack
		 ArrayList 内部是数组数据结构  线程不安全  ----和LinkList用法一样,功能主要是查询
		 LinkedList   内部是双向循环链表数据结构,是不同步的(线程不安全的) ----增删

Map
HashMap 遍历时数据是随机的,允许一条记录的键是NULL ,允许多条记录的值是NULL
HashTable 类型hashmap 是hashmap的线程安全版,支持现成的同步,效率低,键和值都不能为NULL
ConcurrentHashMap线程安全,而且锁分离
LinkHashMap 保存了记录插入的顺序,用Iteraor遍历时先得到的肯定是先插入的,遍历时比
hashmap慢,有HashMap的全部特性
TreeMap 实现的SortMap接口,能够把它保存的记录根据键排序,默认是升序(自然),也可以指定
排序的比较器,用Iterator遍历TreeMap时,得到的记录是排过序的,不允许key空,非同步
TreeMap的实现原理是红黑树实现的

HashMap的实现原理
1 HashMap概述: HashMap是基于哈希表的Map接口的非同步实现。此实现提供所有可选的映射操作,
并允许使用null值和null键。此类不保证映射的顺序,特别是它不保证该顺序恒久不变。
2 HashMap的数据结构: 在java编程语言中,最基本的结构就是两种,一个是数组,另外一个是模拟指针(引用),
所有的数据结构都可以用这两个基本结构来构造的,HashMap也不例外。
HashMap实际上是一个“链表散列”的数据结构,即数组和链表的结合体。

	当我们往Hashmap中put元素时,首先根据key的hashcode重新计算hash值,根绝hash值得到这个元素在数组中的位置(下标),
	如果该数组在该位置上已经存放了其他元素,那么在这个位置上的元素将以链表的形式存放,新加入的放在链头,
	最先加入的放入链尾.如果数组中该位置没有元素,就直接将该元素放到数组的该位置上.

遍历collection
Iterator it = arr.iterator();
while(it.hasNext()){ object o =it.next(); …}

遍历Map
keySet的效率没有entrySet效率高
Iterator it = map.entrySet().iterator();
while(it.hasNext()){
Entry e =(Entry) it.next();
System.out.println(“键”+e.getKey () + “的值为” + e.getValue());
集合排序
Collections.sort();

1、你用过HashMap吗?什么是HashMap?你为什么用到它?

     用过,HashMap是基于哈希表的Map接口的非同步实现,它允许null键和null值,且HashMap依托于它的数据结构的设计,存储效率特别高,这是我用它的原因

2、你知道HashMap的工作原理吗?你知道HashMap的get()方法的工作原理吗?

    上面两个问题属于同一答案的问题
	HashMap是基于hash算法实现的,通过put(key,value)存储对象到HashMap中,也可以通过get(key)从HashMap中获取对象。当我们使用put的时候,
 	首先HashMap会对key的hashCode()的值进行hash计算,根据hash值得到这个元素在数组中的位置,将元素存储在该位置的链表上。
	当我们使用get的时候,首先HashMap会对key的hashCode()的值进行hash计算,根据hash值得到这个元素在数组中的位置,将元素从该位置上的链表中取出

3、当两个对象的hashcode相同会发生什么?

hashcode相同,说明两个对象HashMap数组的同一位置上,接着HashMap会遍历链表中的每个元素,通过key的equals方法来判断是否为同一个key,
如果是同一个key,则新的value会覆盖旧的value,并且返回旧的value。如果不是同一个key,则存储在该位置上的链表的链头

4、如果两个键的hashcode相同,你如何获取值对象?

遍历HashMap链表中的每个元素,并对每个key进行hash计算,最后通过get方法获取其对应的值对象

5、如果HashMap的大小超过了负载因子(load factor)定义的容量,怎么办?

负载因子默认是0.75,HashMap超过了负载因子定义的容量,也就是说超过了(HashMap的大小*负载因子)这个值,那么HashMap将会创建为原来HashMap大小两倍的数组大小,作为自己新的容量,这个过程叫resize或者rehash

6、你了解重新调整HashMap大小存在什么问题吗?

当多线程的情况下,可能产生条件竞争。当重新调整HashMap大小的时候,确实存在条件竞争,如果两个线程都发现HashMap需要重新调整大小了,
它们会同时试着调整大小。在调整大小的过程中,存储在链表中的元素的次序会反过来,因为移动到新的数组位置的时候,HashMap并不会将元素放在LinkedList的尾部,
而是放在头部,这是为了避免尾部遍历(tail traversing)。如果条件竞争发生了,那么就死循环了

7、我们可以使用自定义的对象作为键吗?

可以,只要它遵守了equals()和hashCode()方法的定义规则,并且当对象插入到Map中之后将不会再改变了。如果这个自定义对象时不可变的,
那么它已经满足了作为键的条件,因为当它创建之后就已经不能改变了

ConcurrentHashMap的并发度是什么?
ConcurrentHashMap的并发度就是segment的大小,默认为16,
这意味着最多同时可以有16条线程操作ConcurrentHashMap,
这也是ConcurrentHashMap对Hashtable的最大优势

你有哪些多线程开发良好的实践?
给线程命名
最小化同步范围
优先使用volatile
尽可能使用更高层次的并发工具而非wait和notify()来实现线程通信,如BlockingQueue,Semeaphore
优先使用并发容器而非同步容器.
考虑使用线程池

	当一个变量被定义为volatile之后,就可以保证此变量对所有线程的可见性,
	即当一个线程修改了此变量的值的时候,变量新的值对于其他线程来说是可以立即得知的。   
    volatile 不能提供必须的原子特性
     使用场景,只有一个线程写,其它的线程都在读
   
   
   
		   
	poll() 和 remove() 都是从队列中取出一个元素,
	但是 poll() 在获取元素失败的时候会返回空,但是 remove() 失败的时候会抛出异常。	   

DateFormat 的所有实现,包括 SimpleDateFormat 都不是线程安全的,因此你不应该在多线程序中使用,

serializable 接口是一个序列化 Java 类的接口,以便于它们可以在网络上传输或者可以将它们的状态保存在磁盘上,
是 JVM 内嵌的默认序列化方式,成本高、脆弱而且不安全。
Externalizable 允许你控制整个序列化过程,指定特定的二进制格式,增加安全机制。

简单的解释一下垃圾回收
Java 垃圾回收机制最基本的做法是分代回收。内存中的区域被划分成不同的世代,对象根据其存活的时间被保存在对应世代的区域中
。一般的实现是划分成3个世代:年轻、年老和永久。内存的分配是发生在年轻世代中的。当一个对象存活时间足够长的时候,
它就会被复制到年老世代中。对于不同的世代可以使用不同的垃圾回收算法。
进行世代划分的出发点是对应用中对象存活时间进行研究之后得出的统计规律。一般来说,一个应用中的大部分对象的存活时间都很短。
比如局部变量的存活时间就只在方法的执行过程中。基于这一点,对于年轻世代的垃圾回收算法就可以很有针对性.
如何确定一个对象是否可以被回收?
引用计数算法:判断对象的引用数量 任何引用计数为0的对象实例可以被当作垃圾收集

1. 标记——清除算法   
        1、标记所有需要回收的对象2、标记完成后,清除被标记的对象。
   
2. 标记——复制算法  
   
        标记——复制存储算法通过采用双区域交替使用这种方式解决了标记——清除算法中效率低下的问题。
		1.存活的对象将被复制到另外一块区域。2.原先被使用的区域被重置,转为空闲区
3. 标记——整理算法  
   
      标记-复制算法在对象存活率较高的情况下就要进行较多的复制操作,更重要的是该算法浪费一般的内存空间,为了解决该问题,
	  出现了标记——整理算法:
	  其标记的过程和“标记-清除”算法一样,而整理的过程则是让所有存活的对象都想另外一端移动,然后直接清理掉端边界以外的内存。
   
4.分代收集算法
       	
     新生代,大多都会死去《《《《《   标记——复制算法   
     老年代:  标记-清除  标记-复制
   
5.增量收集算法
      以上所述的算法,都存在一个缺点:在进行垃圾回首时需要暂停当前应用的执行,也就是这时候的垃圾回收线程不能和应用线程同时运行
      这也是增量收集算法的目标,即在不中断应用线程的状态下垃圾回收线程也能进行垃圾回收。
  • private 和Public的区别
    public: Java语言中访问限制最宽的修饰符,一般称之为“公共的”。被其修饰的类、属性以及方法不
         仅可以跨类访问,而且允许跨包(package)访问。
    private: Java语言中对访问权限限制的最窄的修饰符,一般称之为“私有的”。被其修饰的类、属性以
         及方法只能被该类的对象访问,其子类不能访问,更不能允许跨包访问。
    protect: 介于public 和 private 之间的一种访问修饰符,一般称之为“保护形”。被其修饰的类、
         属性以及方法只能被类本身的方法及子类访问,即使子类在不同的包中也可以访问。
    default:即不加任何访问修饰符,通常称为“默认访问模式“。该模式下,只允许在同一个包中进行访
         问。
    static 常见的可以用于工具类的工具方法中等,譬如:Math类中的绝大多数方法都是静态方法,他们扮演了工具方法的作用。public static常用的是为该类提供对外暴露即可以被类名直接调用的静态常量。

  • 微信发红包机制:
    在随机产生红包的时候,我们知道随机一个红包后,剩余的红包个数,这时我们又知道红包的最大值、最小值,那么我们就知道了剩余金额需要在一个范围内,因此也就知道了这个产生的随机红包的最值,所以每次产生下一个红包只需要一次随机即可。举个简单的例子,加入现在有一个5分4个的红包,每个红包要求至少1分,最大20000分,那么在分出第一个红包后,剩余3个红包的钱数要在1 * 3 = 3(分) 和 20000 * 3 = 60000(分)之间,因此也就是说,分配第一个红包的随机金额要在5 - 60000 = -59995(分)和 5 - 3 = 2(分)之间,又由于我们对红包的金额要求在1~20000之间,因此确定第一次随机金额的范围是[1分,2分]。这种随机产生红包的方案要远远优于第三步中提供的方案,在此再次感谢子冬同学提供的优化方案。我们只需要将下述方法替换源代码中的方法即可实现红包随机优化。

  • 我的理解是:一个最小值和一个最大值,为了保证最大值不是超级大,可以自己手动设置最大值为平均值的N倍
    然后随机在最大值和最小值之间产生红包,但是
    我们要在分配红包之前要判断金额是否合法,同样要在每个人暂定随机金额后也要判断剩余的金额是否合法。
    不合法,我们就重新产生分配方案,在重新产生分配方案的时候,我们需要确定一个事情,是产生的红包过大还是过小,如果红包过大,下次就随机一个小值到本次红包金额的一个红包,如果红包金额过小,我们就产生一个红包金额到大值的一个红包。 最后一个红包就是剩下的

什么是负载均衡?

为什么要用NGINX
Nginx可以做http服务器、虚拟主机、负载均衡。
所以在低并发的情况下,用户可以直接访问tomcat服务器,然后tomcat服务器返回消息给用户。比如,我们上传图片:
为了解决并发,可以使用负载均衡:也就是我们多增加几个tomcat服务器。当用户访问的时候,请求可以提交到空闲的tomcat服务器上。
NGINX可以配置多个server
根据地址转发到域名下面
同时一个项目可以配置多个tomcat,开不同的端口设置权重,Nginx根据proxy_pass来访问的路径来确定访问的server,
根据权重自动分配选择的哪一个。权重大的处理的概率高。

thrift服务
我的理解是:1-自己写一个thift文件 2.执行thrift程序在gen-java里面找到service文件放到Java目录下
3.写一个接口实现service,然后在其中写自己的代码,其实真实的情况是大数据那边会把算法封装到一个maven项目里面
这个项目会向外部暴露两个接口,你只需要在刚才的接口里面调用就好了
4.写一个服务端,定义端口号等一些内容
5.自己本地写一个客户端测试。但是真实的情况是thrift是跨语言的,你不知道会是什么调用,所以你不用管,你只需要写号服务端,给定参数格式,会有客户端来调用的

你可能感兴趣的:(Java)