数据结构
数组
数组是将元素在内存中连续存放,由于每个元素占用内存相同,可以通过下标迅速访问数组中任何元素。如果应用需要快速访问数据,很少或不插入和删除元素,就应该用数组,** 数组作用就是申请内存**,redis和memcache等内存数据库内部其实就是申请了个很大的内存。
- 无序数组可以提供快速的插入,但查找和删除较慢。
- 有序数组可以使用二分查找。二分查找需要的时间与数组中数据项的个数的对数成正比。
链表
链表与数组在底层实现上恰好相反,链表中的元素在内存中不是顺序存储的,而是通过存在元素中的指针联系到一起。 单链表: Node结构: ①指向下个节点的“指针”(node 对象) ②节点数据。 ③对于尾节点next 指向null
双向链表:不只是有一个first,还要有一个last,主要使用场景就是可以倒序遍历。
单向链表简单结构:
//http://blog.csdn.net/tayanxunhua/article/details/11100097
public class LinkList<T> {
// 节点信息
class Node<T> {
protected Node<T> next;
protected T data;
public void display() {
System.out.println(data);
}
public Node(T data) {
this.data = data;
}
}
private Node<T> header;
public LinkList() {
this.header = null;
}
public void addFirst(T data) { // 插入到头节点中
Node<T> node = new Node<T>(data); // 创建新的节点
node.next = header; // 新节点的 next --> header, header 变为下一个节点
this.header = node; // header --> node , node 变为头节点
//header.display();
}
public void displayAllNode() {
Node<T> current = header;
while (current != null) {
current.display();
current = current.next; // 当前节点指向下一个节点
}
}
public static void main(String[] args) {
LinkList<Integer> link1 = new LinkList<Integer>();
link1.addFirst(1);
link1.addFirst(2);
link1.addFirst(3);
link1.displayAllNode();
}
}
树
是由n(n>=1)个节点组成的具有层次结构的集合。因为样子像一颗树倒挂而得名。
二叉树(每个节点的子树最多有两个):
深度为K的二叉树至多有2^k-1个节点 完全二叉树:除最后一层最后未满的二叉树 满二叉树:除最后一层全为叶子节点外,所有节点均为两个子节点。
public class BinTree {
// TreeNode 节点结构
class TreeNode{
protected int value;
protected TreeNode leftChild;
protected TreeNode rightChild;
public TreeNode(int value) {
this.value = value;
}
}
private TreeNode root;
public BinTree(int data) {
TreeNode treeNode = new TreeNode(data);
this.root = treeNode;
}
public static void main(String[] args) {
BinTree tree1 = new BinTree(1);
tree1.init();
tree1.display(tree1.root);
}
//前序遍历
private void display(TreeNode root) {
// TODO Auto-generated method stub
System.out.println(root.value);
if( root.leftChild != null){
display(root.leftChild);
}
if( root.rightChild != null){
display(root.rightChild);
}
}
/*
* 1
* 2 3
* 4 5
*/
private void init() {
TreeNode ltree1 = new TreeNode(2);
TreeNode ltree2 = new TreeNode(4);
TreeNode rtree1 = new TreeNode(3);
TreeNode rtree2 = new TreeNode(5);
this.root.leftChild = ltree1;
this.root.rightChild = rtree1;
ltree1.leftChild = ltree2;
ltree1.rightChild = rtree2;
}
}
递归
递归是一种方法调用自己的编程技术。通常可以把一个大型的复杂问题转化为一个与原问题相似的规模较小的问题来求解。
优点:代码简洁、清晰,并且容易验证正确性。 缺点:它的运行需要较多次数的函数调用,如果调用层次比较深,需要增加额外的堆栈处理,可能出现堆栈溢出的情况。
一般设计三部分: 边界条件 递归前进段: 边界条件不满足时,递归前进。 递归返回段: 边界条件满足时,递归返回。
很多地方都体现递归的思想,比如二叉树遍历。 主要的两个思想: 分而治之 递归
算法
算法的时间复杂度
算法的时间复杂度是一个函数,它定量描述了该算法的运行时间。复杂度越大,算法执行效率越低。
计算方法:
一般简单的计算方法是: 看看有几重循环,一重时间复杂度为O(n),二重则为O(n^2),以此类推,如果有二分则为O(logn),如果是一个for循环套一个二分,那么时间复杂度为O(nlogn)
查找算法基础
顺序查找:无序存储结构最方便的查找方法,时间复杂度为O(n); 折半查找法:在按顺序存储的数据结构中,每次取出中间位置的数据与目标数据比较。比如:二分法查找。 时间复杂度为O(logN)。
基础排序算法
- 选择排序:首先从要排序的数组中选出最大(最小)的与第一个位置交换,然后再从剩下的数组中选择(最大)最小与第二个位置进行交换,直至到最后一个元素。
- 插入排序:基本思想是将数插入到一个<u>有序</u>的队列中,第一次把第一个数当作有序队列将第二个数插入,第二次将前两个数当作有序队列,插入第三个数,直至最后一个元素插入完毕。
- 冒泡排序:让大数往下沉,小数往上走,如果<u>相邻</u>的两个元素不符合排序顺序就交换。每一趟可以让一个元素到达最终位置。
- 快速排序: 基本思想: 1. 选择一个基准,将要排序的数组分成两组,比基准数都小的一组,比基准数都大的一组。 2.调用自身对左边的一组进行排序。 3.调用自身对右边的一组进行排序。
其实这也是递归的思想。基本的递归的快速排序算法的代码相当简单。另一个是基准值的位置:怎么才能把这个基准值移动到正确位置上来呢?
只要交换基准值和右边子数组的最左端的数据项就可以了。
- 归并排序: 基本思想是将两个(或两个以上)有序表合并成一个新的有序表。即把一个无序的表分割成若干个有序的子序列,每个子序列是有序的。然后再把有序子序列合并为整体有序序列。
java内存特性之内存管理
1.显示内存管理和自动内存管理
显示内存管理(C/C++) 内存管理是程序开发者的职责 显示管理常见问题:①野指针②内存泄露。
2.自动内存管理(java/c#/一些脚本语言)
垃圾回收器程序自动管理。 优点:增加了程序的可靠性,减小了内存泄露和野指针的情况,提高了开发人员的效率。 缺点:GC时间无法控制;逻辑上的内存泄露依然会存在;系统需要花费时间进行内存回收,增加了软件负担。
java内存模型
Young(年轻代) 1.分为Eden区和两个Survivor区(大小一致,有些地方称为from区和to区) 2.新的对象实例总是先放到Eden区,Survior区作为Eden区和Tenure区的缓冲,可以向Tenure区转移对象
Tenured(终身代) 存放生命周期长久的实例对象,但是对象依然也会被回收掉
Perm(永久代) 主要存放加载的Class类级对象如class本身,method,field等等。 java8永久代去掉了,增加了一个元空间Metaspace。
常见的GC算法
GC机制一般做两个事情:发现无用信息对象;回收被无用对象占用的内存空间,使该空间可被程序再次使用。 GC算法:
-
引用计数法:使用引用计数器来区分存活对象和不再使用对象。引用计数器为0时即满足被回收的时机。客观来说判定效率很高,但是无法解决对象循环引用的问题。
-
标记-清除算法(Mark-Sweep):使用了根集的概念。从根基开始扫描,识别出哪些对象可达,哪些对象不可达,不可达的即可以被回收。在扫描识别过程中,基于tracing算法的垃圾收集也称为标记和清除(mark-and-sweep)垃圾收集器。最大的问题是回收后的空间不连续,对于内存分配,不连续的内存空间的工作效率要低于连续的空间。</br>
-
copying算法:为了解决内存空间碎片的问题,复制算法开始时把堆分成两块。程序从空闲面对象分配空间,当对象满了,基于coping算法的垃圾 收集就从根集中扫描活动对象,并将每个 活动对象复制到空闲面。一般内存中新生代的From区和To区就作为两个这两个对象块。从Survior区经过GC存活的对象可能移动到另一个Survivor区或者移动到老年代当中.</br>
-
adaptive算法:特性的情况下,不同的GC算法会有不同效率。基于Adaptive算法的垃圾收集器就是监控当前堆的使用情况,并将选择适当算法的垃圾收集器。
四中引用类型:
- Strong Reference(强引用) 会尽可能长时间的存活于JVM内,只有当没有任何对象指向它时GC执行后才会被回收。 eg:StrongReference sr = new StrongReference(new A());(存储) A a = (A)sr.get(); (取出)
- WeakReference & WeakHashMap(弱引用) WeakReference:当所引用的对象不再有强引用时,weakreference会被GC回收,作用是引用一个对象,但是并不阻止该对象被回收。 弱引用最常见的用处在于解决强引用所带来的对象之间的存活时间上的耦合关系。 WeakHashMap:使用WeakReference作为key,一旦没有指向key的强引用,GC会自动删除相关的value
- SoftReference 与WeakReference的区别是尽可能最长时间保留引用直到JVM内存不足才会被回收,这一特性使得SoftRefrence非常适合于设计缓存。
- PhantomReference(虚引用) 与其他所有引用都不同,它并不会决定对象的声明周期。虚引用主要用来跟踪对象被垃圾回收器回收的活动。与软引用和若引用的一个区别在于必须与引用队列联合使用。垃圾回收器回收时,如果发现它还有虚引用,就会在回收对象之前把这个虚引用加入到与之关联的引用队列中。 参看:Java之美[从菜鸟到高手演变]之JVM内存管理及垃圾回收