10种排序算法总结

 
‎2012‎年‎11‎月‎28‎日,‏‎10:38:00 | YangJin转到全文

排序算法有很多,所以在特定情景中使用哪一种算法很重要。为了选择合适的算法,可以按照建议的顺序考虑以下标准: (1)执行时间 (2)存储空间 (3)编程工作    对于数据量较小的情形,(1)(2)差别不大,主要考虑(3);而对于数据量大的,(1)为首要。   主要排序法有: 一、冒泡(Bubble)排序——相邻交换 二、选择排序——每次最小/大排在相应的位置 三、插入排序——将下一个插入已排好的序列中 四、壳(Shell)排序——缩小增量 五、归并排序 六、快速排序 七、堆排序 八、拓扑排序 九、锦标赛排序 十、基数排序     一、冒泡(Bubble)排序 ----------------------------------Code 从小到大排序n个数------------------------------------ void BubbleSortArray() {       for(int i=1;i<n;i++)       {         for(int j=0;i<n-i;j++)          {               if(a[j]>a[j+1])//比较交换相邻元素                {                    int temp;                    temp=a[j]; a[j]=a[j+1]; a[j+1]=temp;                }          }       } } -------------------------------------------------Code------------------------------------------------ 效率 O(n²),适用于排序小列表。     二、选择排序 ----------------------------------Code 从小到大排序n个数-------------------------------- void SelectSortArray() {     int min_index;     for(int i=0;i<n-1;i++)     {          min_index=i;          for(int j=i+1;j<n;j++)//每次扫描选择最小项             if(arr[j]<arr[min_index])  min_index=j;          if(min_index!=i)//找到最小项交换,即将这一项移到列表中的正确位置          {              int temp;              temp=arr[i]; arr[i]=arr[min_index]; arr[min_index]=temp; } } } -------------------------------------------------Code----------------------------------------- 效率O(n²),适用于排序小的列表。     三、插入排序 --------------------------------------------Code 从小到大排序n个数------------------------------------- void InsertSortArray() { for(int i=1;i<n;i++)//循环从第二个数组元素开始,因为arr[0]作为最初已排序部分 {     int temp=arr[i];//temp标记为未排序第一个元素     int j=i-1; while (j>=0 && arr[j]>temp)/*将temp与已排序元素从小到大比较,寻找temp应插入的位置*/ {     arr[j+1]=arr[j];     j--; } arr[j+1]=temp; } } ------------------------------Code-------------------------------------------------------------- 最佳效率O(n);最糟效率O(n²)与冒泡、选择相同,适用于排序小列表 若列表基本有序,则插入排序比冒泡、选择更有效率。     四、壳(Shell)排序——缩小增量排序 -------------------------------------Code 从小到大排序n个数------------------------------------- void ShellSortArray() {   for(int incr=3;incr<0;incr--)//增量递减,以增量3,2,1为例 {        for(int L=0;L<(n-1)/incr;L++)//重复分成的每个子列表 {    for(int i=L+incr;i<n;i+=incr)//对每个子列表应用插入排序    {       int temp=arr[i];       int j=i-incr;       while(j>=0&&arr[j]>temp)       {           arr[j+incr]=arr[j];           j-=incr; } arr[j+incr]=temp; } } } } --------------------------------------Code------------------------------------------- 适用于排序小列表。 效率估计O(nlog2^n)~O(n^1.5),取决于增量值的最初大小。建议使用质数作为增量值,因为如果增量值是2的幂,则在下一个通道中会再次比较相同的元素。 壳(Shell)排序改进了插入排序,减少了比较的次数。是不稳定的排序,因为排序过程中元素可能会前后跳跃。     五、归并排序 ----------------------------------------------Code 从小到大排序--------------------------------------- void MergeSort(int low,int high) {    if(low>=high)   return;//每个子列表中剩下一个元素时停止    else int mid=(low+high)/2;/*将列表划分成相等的两个子列表,若有奇数个元素,则在左边子列表大于右侧子列表*/    MergeSort(low,mid);//子列表进一步划分    MergeSort(mid+1,high);    int [] B=new int [high-low+1];//新建一个数组,用于存放归并的元素    for(int i=low,j=mid+1,k=low;i<=mid && j<=high;k++)/*两个子列表进行排序归并,直到两个子列表中的一个结束*/    {        if (arr[i]<=arr[j];) {     B[k]=arr[i];     I++; } else     { B[k]=arr[j]; j++; } } for(   ;j<=high;j++,k++)//如果第二个子列表中仍然有元素,则追加到新列表       B[k]=arr[j];    for(   ;i<=mid;i++,k++)//如果在第一个子列表中仍然有元素,则追加到新列表中       B[k]=arr[i];    for(int z=0;z<high-low+1;z++)//将排序的数组B的 所有元素复制到原始数组arr中       arr[z]=B[z]; } -----------------------------------------------------Code--------------------------------------------------- 效率O(nlogn),归并的最佳、平均和最糟用例效率之间没有差异。 适用于排序大列表,基于分治法。   六、快速排序 ------------------------------------Code-------------------------------------------- /*快速排序的算法思想:选定一个枢纽元素,对待排序序列进行分割,分割之后的序列一个部分小于枢纽元素,一个部分大于枢纽元素,再对这两个分割好的子序列进行上述的过程。*/                  void swap(int a,int b){int t;t =a ;a =b ;b =t ;}         int Partition(int [] arr,int low,int high)         {             int pivot=arr[low];//采用子序列的第一个元素作为枢纽元素             while (low < high)             {                 //从后往前栽后半部分中寻找第一个小于枢纽元素的元素                 while (low < high && arr[high] >= pivot)                 {                     --high;                 }                 //将这个比枢纽元素小的元素交换到前半部分                 swap(arr[low], arr[high]);                 //从前往后在前半部分中寻找第一个大于枢纽元素的元素                 while (low <high &&arr [low ]<=pivot )                 {                     ++low ;                 }                 swap (arr [low ],arr [high ]);//将这个枢纽元素大的元素交换到后半部分             }             return low ;//返回枢纽元素所在的位置         }         void QuickSort(int [] a,int low,int high)         {             if (low <high )             {                 int n=Partition (a ,low ,high );                 QuickSort (a ,low ,n );                 QuickSort (a ,n +1,high );             }         } ----------------------------------------Code------------------------------------- 平均效率O(nlogn),适用于排序大列表。 此算法的总时间取决于枢纽值的位置;选择第一个元素作为枢纽,可能导致O(n²)的最糟用例效率。若数基本有序,效率反而最差。选项中间值作为枢纽,效率是O(nlogn)。 基于分治法。     七、堆排序 最大堆:后者任一非终端节点的关键字均大于或等于它的左、右孩子的关键字,此时位于堆顶的节点的关键字是整个序列中最大的。 思想: (1)令i=l,并令temp= kl ; (2)计算i的左孩子j=2i+1; (3)若j<=n-1,则转(4),否则转(6); (4)比较kj和kj+1,若kj+1>kj,则令j=j+1,否则j不变; (5)比较temp和kj,若kj>temp,则令ki等于kj,并令i=j,j=2i+1,并转(3),否则转(6) (6)令ki等于temp,结束。 -----------------------------------------Code--------------------------- void HeapSort(SeqIAst R)     { //对R[1..n]进行堆排序,不妨用R[0]做暂存单元    int I;    BuildHeap(R); //将R[1-n]建成初始堆for(i=n;i>1;i--) //对当前无序区R[1..i]进行堆排序,共做n-1趟。{      R[0]=R[1]; R[1]=R[i]; R[i]=R[0]; //将堆顶和堆中最后一个记录交换      Heapify(R,1,i-1);  //将R[1..i-1]重新调整为堆,仅有R[1]可能违反堆性质     }    } ---------------------------------------Code--------------------------------------   堆排序的时间,主要由建立初始堆和反复重建堆这两部分的时间开销构成,它们均是通过调用Heapify实现的。       堆排序的最坏时间复杂度为O(nlgn)。堆排序的平均性能较接近于最坏性能。     由于建初始堆所需的比较次数较多,所以堆排序不适宜于记录数较少的文件。     堆排序是就地排序,辅助空间为O(1),     它是不稳定的排序方法。   堆排序与直接插入排序的区别:      直接选择排序中,为了从R[1..n]中选出关键字最小的记录,必须进行n-1次比较,然后在R[2..n]中选出关键字最小的记录,又需要做n-2次比较。事实上,后面的n-2次比较中,有许多比较可能在前面的n-1次比较中已经做过,但由于前一趟排序时未保留这些比较结果,所以后一趟排序时又重复执行了这些比较操作。      堆排序可通过树形结构保存部分比较结果,可减少比较次数。   八、拓扑排序 例 :学生选修课排课先后顺序 拓扑排序:把有向图中各顶点按照它们相互之间的优先关系排列成一个线性序列的过程。 方法: 在有向图中选一个没有前驱的顶点且输出 从图中删除该顶点和所有以它为尾的弧 重复上述两步,直至全部顶点均已输出(拓扑排序成功),或者当图中不存在无前驱的顶点(图中有回路)为止。 ---------------------------------------Code-------------------------------------- void TopologicalSort()/*输出拓扑排序函数。若G无回路,则输出G的顶点的一个拓扑序列并返回OK,否则返回ERROR*/ {       int indegree[M];       int i,k,j;       char n;       int count=0;       Stack thestack;       FindInDegree(G,indegree);//对各顶点求入度indegree[0....num]       InitStack(thestack);//初始化栈       for(i=0;i<G.num;i++)           Console.WriteLine("结点"+G.vertices[i].data+"的入度为"+indegree[i]);       for(i=0;i<G.num;i++)       {            if(indegree[i]==0)               Push(thestack.vertices[i]);       }       Console.Write("拓扑排序输出顺序为:");       while(thestack.Peek()!=null)       {                Pop(thestack.Peek());                j=locatevex(G,n);                if (j==-2)                   {                          Console.WriteLine("发生错误,程序结束。");                          exit();                   }                 Console.Write(G.vertices[j].data);                 count++;                 for(p=G.vertices[j].firstarc;p!=NULL;p=p.nextarc)                 {                      k=p.adjvex;                      if (!(--indegree[k]))                          Push(G.vertices[k]);                 }       }       if (count<G.num)           Cosole.WriteLine("该图有环,出现错误,无法排序。");       else           Console.WriteLine("排序成功。"); } ----------------------------------------Code-------------------------------------- 算法的时间复杂度O(n+e)。     九、锦标赛排序 锦标赛排序的算法思想与体育比赛类似。     首先将n个数据元素两两分组,分别按关键字进行比较,得到n/2个比较的优胜者(关键字小者),作为第一步比较的结果保留下来,     然后对这n/2个数据元素再两两分组,分别按关键字进行比较,…,如此重复,直到选出一个关键字最小的数据元素为止。   --------------------------------Code in C--------------------------------------- #include <stdio.h> #include <stdlib.h> #include <string.h> #include <math.h> #define SIZE 100000 #define MAX 1000000 struct node { long num;//关键字 char str[10]; int lastwin;//最后胜的对手 int killer;//被击败的对手 long times;//比赛次数 }data[SIZE]; long CompareNum=0; long ExchangeNum=0; long Read(char name[])//读取文件a.txt中的数据,并存放在数组data[]中;最后返回数据的个数 { FILE *fp; long i=1; fp=fopen(name,"rw"); fscanf(fp,"%d%s",&data[i].num,data[i].str); while(!feof(fp)) {   i++;   fscanf(fp,"%d%s",&data[i].num,data[i].str);  } return (i-1); } long Create(long num)//创建胜者树,返回冠军(最小数)在数组data[]中的下标 { int i,j1,j2,max,time=1; long min;//记录当前冠军的下标 for(i=1;pow(2,i-1)<num;i++)   ; max=pow(2,i-1);//求叶子结点数目 for(i=1;i<=max;i++)//初始化叶子结点 {   data[i].killer=0;   data[i].lastwin=0;   data[i].times=0;   if(i>num)    data[i].num=MAX; } for(i=1;i<=max;i+=2)//第一轮比赛 {   ++CompareNum;   if(data[i].num <= data[i+1].num)   {    data[i].lastwin = i+1;    data[i+1].killer=i;    ++data[i].times;    ++data[i+1].times;    min=i;   }   else   {    data[i+1].lastwin=i;    data[i].killer=i+1;    ++data[i].times;    ++data[i+1].times;    min=i+1;   } } j1=j2=0;//记录连续的两个未被淘汰的选手的下标 while(time <= (log(max)/log(2)))//进行淘汰赛 {   for(i=1;i<=max;i++)   {    if(data[i].times==time && data[i].killer==0)//找到一名选手    {     j2=i;//默认其为两选手中的后来的     if(j1==0)//如果第一位置是空的,则刚来的选手先来的      j1=j2;     else//否则刚来的选手是后来的,那么选手都已到场比赛开始     {      ++CompareNum;      if(data[j1].num <= data[j2].num)//先来的选手获胜      {       data[j1].lastwin = j2;//最后赢的是j2       data[j2].killer=j1;//j2是被j1淘汰的       ++data[j1].times;       ++data[j2].times;//两选手场次均加1        min=j1;//最小数下标为j1       j1=j2=0;//将j1,j2置0      }      else//同理      {       data[j2].lastwin=j1;       data[j1].killer=j2;       ++data[j1].times;       ++data[j2].times;            min=j2;       j1=j2=0;      }     }    }      }   time++;//轮数加1 } return min;//返回冠军的下标 } void TournamentSort(long num)//锦标赛排序 { long tag=Create(num);//返回最小数下标 FILE *fp1; fp1=fopen("sort.txt","w+");//为写入创建并打开文件sort.txt while(data[tag].num != MAX)//当最小值不是无穷大时 {   printf("%d %s\n",data[tag].num,data[tag].str);//输出数据   fprintf(fp1,"%d %s\n",data[tag].num,data[tag].str);//写入数据   data[tag].num=MAX;//将当前冠军用无穷大替换   tag=Create(num);//返回下一个冠军的下标  } } int main() { int num; char name[10]; printf("Input name of the file:"); gets(name); num=Read(name);//读文件 TournamentSort(num);//锦标赛排序 printf("CompareNum=%d\nExchangeNum=%d\n",CompareNum,ExchangeNum); return 0; } ------------------------------------------Code-------------------------------------     十、基数排序 基数排序又被称为桶排序。与前面介绍的几种排序方法相比较,基数排序和它们有明显的不同。     前面所介绍的排序方法都是建立在对数据元素关键字进行比较的基础上,所以可以称为基于比较的排序;     而基数排序首先将待排序数据元素依次“分配”到不同的桶里,然后再把各桶中的数据元素“收集”到一起。 通过使用对多关键字进行排序的这种“分配”和“收集”的方法,基数排序实现了对多关键字进行排序。 ——————————————————————————————————————— 例:     每张扑克牌有两个“关键字”:花色和面值。其大小顺序为:     花色:§<¨<©<ª     面值:2<3<……<K<A     扑克牌的大小先根据花色比较,花色大的牌比花色小的牌大;花色一样的牌再根据面值比较大小。所以,将扑克牌按从小到大的次序排列,可得到以下序列: §2,…,§A,¨2,…,¨A,©2,…,©A,ª2,…,ªA     这种排序相当于有两个关键字的排序,一般有两种方法实现。     其一:可以先按花色分成四堆(每一堆牌具有相同的花色),然后在每一堆牌里再按面值从小到大的次序排序,最后把已排好序的四堆牌按花色从小到大次序叠放在一起就得到排序的结果。 其二:可以先按面值排序分成十三堆(每一堆牌具有相同的面值),然后将这十三堆牌按面值从小到大的顺序叠放在一起,再把整副牌按顺序根据花色再分成四堆(每一堆牌已按面值从小到大的顺序有序),最后将这四堆牌按花色从小到大合在一起就得到排序的结果。 ——————————————————————————————————————— 实现方法:   最高位优先(Most Significant Digit first)法,简称MSD法:先按k1排序分组,同一组中记录,关键码k1相等,再对各组按k2排序分成子组,之后,对后面的关键码继续这样的排序分组,直到按最次位关键码kd对各子组排序后。再将各组连接起来,便得到一个有序序列。   最低位优先(Least Significant Digit first)法,简称LSD法:先从kd开始排序,再对kd-1进行排序,依次重复,直到对k1排序后便得到一个有序序列。 ---------------------------------Code in C#------------------------------------------   using System;   using System.Collections.Generic;   using System.Linq;   using System.Text;   namespace LearnSort   {   class Program   {   static void Main(string[] args)   {   int[] arr = CreateRandomArray(10);//产生随机数组   Print(arr);//输出数组   RadixSort(ref arr);//排序   Print(arr);//输出排序后的结果   Console.ReadKey();   }   public static void RadixSort(ref int[] arr)   {   int iMaxLength = GetMaxLength(arr);   RadixSort(ref arr, iMaxLength);   }   private static void RadixSort(ref int[] arr, int iMaxLength)   {   List<int> list = new List<int>();//存放每次排序后的元素   List<int>[] listArr = new List<int>[10];//十个桶   char currnetChar;//存放当前的字符比如说某个元素123 中的2   string currentItem;//存放当前的元素比如说某个元素123   for (int i = 0; i < listArr.Length; i++)//给十个桶分配内存初始化。   listArr[i] = new List<int>();   for (int i = 0; i < iMaxLength; i++)//一共执行iMaxLength次,iMaxLength是元素的最大位数。   {   foreach (int number in arr)//分桶   {   currentItem = number.ToString();//将当前元素转化成字符串   try { currnetChar = currentItem[currentItem.Length-i-1]; }//从个位向高位开始分桶   catch { listArr[0].Add(number); continue; }//如果发生异常,则将该数压入listArr[0]。比如说5 是没有十位数的,执行上面的操作肯定会发生越界异常的,这正是期望的行为,我们认为5的十位数是0,所以将它压入listArr[0]的桶里。   switch (currnetChar)//通过currnetChar的值,确定它压人哪个桶中。   {   case '0': listArr[0].Add(number); break;   case '1': listArr[1].Add(number); break;   case '2': listArr[2].Add(number); break;   case '3': listArr[3].Add(number); break;   case '4': listArr[4].Add(number); break;   case '5': listArr[5].Add(number); break;   case '6': listArr[6].Add(number); break;   case '7': listArr[7].Add(number); break;   case '8': listArr[8].Add(number); break;   case '9': listArr[9].Add(number); break;   default: throw new Exception("unknow error");   }   }   for (int j = 0; j < listArr.Length; j++)//将十个桶里的数据重新排列,压入list   foreach (int number in listArr[j].ToArray<int>())   {   list.Add(number);   listArr[j].Clear();//清空每个桶   }   arr = list.ToArray<int>();//arr指向重新排列的元素   //Console.Write("{0} times:",i);   Print(arr);//输出一次排列的结果   list.Clear();//清空list   }   }   //得到最大元素的位数   private static int GetMaxLength(int[] arr)   {   int iMaxNumber = Int32.MinValue;   foreach (int i in arr)//遍历得到最大值   {   if (i > iMaxNumber)   iMaxNumber = i;   }   return iMaxNumber.ToString().Length;//这样获得最大元素的位数是不是有点投机取巧了...   }   //输出数组元素   public static void Print(int[] arr)   {   foreach (int i in arr)   System.Console.Write(i.ToString()+'\t');   System.Console.WriteLine();   }   //产生随机数组。随机数的范围是0到1000。参数iLength指产生多少个随机数   public static int[] CreateRandomArray(int iLength)   {   int[] arr = new int[iLength];   Random random = new Random();   for (int i = 0; i < iLength; i++)   arr[i] = random.Next(0,1001);   return arr;   }   }   } ---------------------------------Code --------------------------------------------- 基数排序法是属于稳定性的排序,其时间复杂度为O (nlog(r)m),其中r为所采取的基数,而m为堆数,在某些时候,基数排序法的效率高于其它的比较性排序法。

本文链接

 

八大排序算法总结

 
‎2012‎年‎11‎月‎28‎日,‏‎10:37:00 | YangJin转到全文

插入排序

 

1.直接插入排序

原理:将数组分为无序区和有序区两个区,然后不断将无序区的第一个元素按大小顺序插入到有序区中去,最终将所有无序区元素都移动到有序区完成排序。

要点:设立哨兵,作为临时存储和判断数组边界之用。

实现:

Void InsertSort(Node L[],int length)

{

Int i,j;//分别为有序区和无序区指针

for(i=1;i<length;i++)//逐步扩大有序区

{

j=i+1;

if(L[j]<L[i])

{

L[0]=L[j];//存储待排序元素

While(L[0]<L[i])//查找在有序区中的插入位置,同时移动元素

{

L[i+1]=L[i];//移动

i--;//查找

}

L[i+1]=L[0];//将元素插入

}

i=j-1;//还原有序区指针

}

}

2.希尔排序

原理:又称增量缩小排序。先将序列按增量划分为元素个数相同的若干组,使用直接插入排序法进行排序,然后不断缩小增量直至为1,最后使用直接插入排序完成排序。

要点:增量的选择以及排序最终以1为增量进行排序结束。

实现:

Void shellSort(Node L[],int d)

{

While(d>=1)//直到增量缩小为1

{

Shell(L,d);

d=d/2;//缩小增量

}

}

Void Shell(Node L[],int d)

{

Int i,j;

For(i=d+1;i<length;i++)

{

if(L[i]<L[i-d])

{

L[0]=L[i];

j=i-d;

While(j>0&&L[j]>L[0])

{

L[j+d]=L[j];//移动

j=j-d;//查找

}

L[j+d]=L[0];

}

}

}

交换排序

1.冒泡排序

原理:将序列划分为无序和有序区,不断通过交换较大元素至无序区尾完成排序。

要点:设计交换判断条件,提前结束以排好序的序列循环。

实现:

Void BubbleSort(Node L[])

{

Int i ,j;

Bool ischanged;//设计跳出条件

For(j=n;j<0;j--)

{

ischanged =false;

For(i=0;i<j;i++)

{

If(L[i]>L[i+1])//如果发现较重元素就向后移动

{

Int temp=L[i];

L[i]=L[i+1];

L[i+1]=temp;

Ischanged =true;

}

}

If(!ischanged)//若没有移动则说明序列已经有序,直接跳出

Break;

}

}

2.快速排序

原理:不断寻找一个序列的中点,然后对中点左右的序列递归的进行排序,直至全部序列排序完成,使用了分治的思想。

要点:递归、分治

实现:

 

选择排序

1.直接选择排序

原理:将序列划分为无序和有序区,寻找无序区中的最小值和无序区的首元素交换,有序区扩大一个,循环最终完成全部排序。

要点:

实现:

Void SelectSort(Node L[])

{

Int i,j,k;//分别为有序区,无序区,无序区最小元素指针

For(i=0;i<length;i++)

{

k=i;

For(j=i+1;j<length;j++)

{

If(L[j]<L[k])

k=j;

}

If(k!=i)//若发现最小元素,则移动到有序区

{

Int temp=L[k];

L[k]=L[i];

L[i]=L[temp];

}

 

}

}

2.堆排序

原理:利用大根堆或小根堆思想,首先建立堆,然后将堆首与堆尾交换,堆尾之后为有序区。

要点:建堆、交换、调整堆

实现:

Void HeapSort(Node L[])

{

BuildingHeap(L);//建堆(大根堆)

For(int i=n;i>0;i--)//交换

{

Int temp=L[i];

L[i]=L[0];

L[0]=temp;

Heapify(L,0,i);//调整堆

}

}

 

Void BuildingHeap(Node L[])

{For(i=length/2 -1;i>0;i--)

Heapify(L,i,length);

}

归并排序

原理:将原序列划分为有序的两个序列,然后利用归并算法进行合并,合并之后即为有序序列。

要点:归并、分治

实现:

Void MergeSort(Node L[],int m,int n)

{

Int k;

If(m<n)

{

K=(m+n)/2;

MergeSort(L,m,k);

MergeSort(L,k+1,n);

Merge(L,m,k,n);

}

}

 

基数排序

原理:将数字按位数划分出n个关键字,每次针对一个关键字进行排序,然后针对排序后的序列进行下一个关键字的排序,循环至所有关键字都使用过则排序完成。

要点:对关键字的选取,元素分配收集。

实现:

Void RadixSort(Node L[],length,maxradix)

{

Int m,n,k,lsp;

k=1;m=1;

Int temp[10][length-1];

Empty(temp);//清空临时空间

While(k<maxradix)//遍历所有关键字

{

For(int i=0;i<length;i++)//分配过程

{

If(L[i]<m)

Temp[0][n]=L[i];

Else

Lsp=(L[i]/m)%10;//确定关键字

Temp[lsp][n]=L[i];

n++;

}

CollectElement(L,Temp);//收集

n=0;

m=m*10;

k++;

}

}

本文链接

 

 

Oracle存储过程学习

 
‎2012‎年‎11‎月‎28‎日,‏‎8:58:00 | YangJin转到全文

存储过程创建语法:

       create or replace procedure 存储过程名(param1 in type,param2 out type)

as

变量1 类型(值范围);

变量2 类型(值范围);

Begin

    Select count(*) into 变量1 from 表A where列名=param1;

    If (判断条件) then

       Select 列名 into 变量2 from 表A where列名=param1;

       Dbms_output。Put_line(‘打印信息’);

    Elsif (判断条件) then

       Dbms_output。Put_line(‘打印信息’);

    Else

       Raise 异常名(NO_DATA_FOUND);

    End if;

Exception

    When others then

       Rollback;

End;

 

注意事项:

1,  存储过程参数不带取值范围,in表示传入,out表示输出

2,  变量带取值范围,后面接分号

3,  在判断语句前最好先用count(*)函数判断是否存在该条操作记录

4,  用select 。。。into。。。给变量赋值

5,  在代码中抛异常用 raise+异常名

 

 

以命名的异常

命名的系统异常                          产生原因

ACCESS_INTO_NULL                   未定义对象

CASE_NOT_FOUND                     CASE 中若未包含相应的 WHEN ,并且没有设置

ELSE 时

COLLECTION_IS_NULL                集合元素未初始化

CURSER_ALREADY_OPEN          游标已经打开

DUP_VAL_ON_INDEX                   唯一索引对应的列上有重复的值

INVALID_CURSOR                 在不合法的游标上进行操作

INVALID_NUMBER                       内嵌的 SQL 语句不能将字符转换为数字

NO_DATA_FOUND                        使用 select into 未返回行,或应用索引表未初始化的

 

TOO_MANY_ROWS                      执行 select into 时,结果集超过一行

ZERO_DIVIDE                              除数为 0

SUBSCRIPT_BEYOND_COUNT     元素下标超过嵌套表或 VARRAY 的最大值

SUBSCRIPT_OUTSIDE_LIMIT       使用嵌套表或 VARRAY 时,将下标指定为负数

VALUE_ERROR                             赋值时,变量长度不足以容纳实际数据

LOGIN_DENIED                           PL/SQL 应用程序连接到 oracle 数据库时,提供了不

正确的用户名或密码

NOT_LOGGED_ON                       PL/SQL 应用程序在没有连接 oralce 数据库的情况下

访问数据

PROGRAM_ERROR                       PL/SQL 内部问题,可能需要重装数据字典& pl./SQL

系统包

ROWTYPE_MISMATCH                宿主游标变量与 PL/SQL 游标变量的返回类型不兼容

SELF_IS_NULL                             使用对象类型时,在 null 对象上调用对象方法

STORAGE_ERROR                        运行 PL/SQL 时,超出内存空间

SYS_INVALID_ID                         无效的 ROWID 字符串

TIMEOUT_ON_RESOURCE         Oracle 在等待资源时超时

本文链接

 

Spring,hibernate,struts的面试笔试题

 
‎2012‎年‎11‎月‎27‎日,‏‎13:47:00 | YangJin转到全文

1 Action是不是线程安全的?如果不是 有什么方式可以保证Action的线程安全?如果是,说明原因 不是

声明局部变量,或者扩展RequestProcessor,让每次都创建一个Action,或者在spring中用scope="prototype"来管理

2.MVC,分析一下struts是如何实现MVC的 m:JavaBean 或结合 EJB 组件或者pojo构成 c:Action 来实现 v:一组 JSP 文件及其标签构成。

3.struts中的几个关键对象的作用(说说几个关键对象的作用) Action:控制器类,ActionForm:表单对象,DynaValidatorForm:动态form,ActonMapping:配置文件中action节点的信息......

4.说说AOP和IOC的概念以及在spring中是如何应用的 AOP:面向方面编程,ioc:依赖注入;声明式事务和编程式事务积极一些通用部分

5.Hibernate有哪几种查询数据的方式 hql查询,sql查询,条件查询

6.load()和get()的区别 hibernate对于load方法认为该数据在数据库中一定存在,可以放心的使用代理来延迟加载,load默认支持延迟加载,在用到对象中的其他属性数 据时才查询数据库,但是万一数据库中不存在该记录,只能抛异常ObjectNotFoundEcception;所说的load方法抛异常是指在使用该对 象的数据时,数据库中不存在该数据时抛异常,而不是在创建这个对象时。由于session中的缓存对于hibernate来说是个相当廉价的资源,所以在 load时会先查一下session缓存看看该id对应的对象是否存在,不存在则创建代理(load时候之查询一级缓存,不存在则创建代理)。get() 现在一级缓存找,没有就去二级缓存找,没有就去数据库找,没有就返回null ;而对于get方法,hibernate一定要获取到真实的数据,否则返回null。

7.谈谈hibernate的延迟加载和openSessionInView 延迟加载要在session范围内,用到的时候再加载;opensessioninview是在web层写了一个

filter来打开和关闭session,这样就表示在一次request过程中session一直开着,保证了延迟

加载在session中的这个前提。

8.spring的事务有几种方式?谈谈spring事务的隔离级别和传播行为。 声明事务和编程事务 隔离级别: - DEFAULT使用数据库默认的隔离级别 - READ_UNCOMMITTED会出现脏读,不可重复读和幻影读问题 - READ_COMMITTED会出现重复读和幻影读 - REPEATABLE_READ会出现幻影读 - SERIALIZABLE最安全,但是代价最大,性能影响极其严重 和传播行: - REQUIRED存在事务就融入该事务,不存在就创建事务 - SUPPORTS存在事务就融入事务,不存在则不创建事务 - MANDATORY存在事务则融入该事务,不存在,抛异常 - REQUIRES_NEW总是创建新事务 - NOT_SUPPORTED存在事务则挂起,一直执行非事务操作 - NEVER总是执行非事务,如果当前存在事务则抛异常 - NESTED嵌入式事务

9.Hibernate中的update()和saveOrUpdate()的区别. 摘自hibernate说明文档: saveOrUpdate()做下面的事: 如果对象已经在本session中持久化了,不做任何事 如果另一个与本session关联的对象拥有相同的持久化标识(identifier),抛出一个异常 如果对象没有持久化标识(identifier)属性,对其调用save() 如果对象的持久标识(identifier)表明其是一个新实例化的对象,对其调用save() 如果对象是附带版本信息的(通过 <version>或 <timestamp>) 并且版本属性的值表明其是一个新实例化的对象,save()它。 否则update() 这个对象

10.Spring对多种ORM框架提供了很好的支持,简单描述在Spring中使用Hibernate的方法,并结合事务管理。 getHiberanteTemplate里面提供了save,update,delete,find等方法。 简单说一个:如果配置了声明式事务,当执行getHibernateTemplate的各种方法的时候,事务会

自动被加载 如果没有配置事务,那么以上操作不会真正的被同步到数据库,除非配置了hibernate的

autocommit=true

8.spring的事务有几种方式?谈谈spring事务的隔离级别和传播行为。 spring事务分两种形式,声明式事务和编程式事务,spring提供了一个事务的接口

PaltformTractionManager接口,针对不同的事务,spring进行了不同的实现,对hibernate事务

的实现HIbernateTractionManager,对JDBC的JdbcTractionManager,

DataSourceTractionManager以及JdoTractionManager。接口platformTractionManager提供了三

个方法,获取事务,提交和回滚的方法。

******************************************************************************

分享面试题二】Spring,hibernate,struts的面试笔试题(含答案) (声明:这里不是为其他商业利益,是为学习讨论使用)

【郑重声明】:单纯接分将被删帖,希望大家有自己的感触 Hibernate工作原理及为什么要用? 原理: 1.读取并解析配置文件 2.读取并解析映射信息,创建SessionFactory 3.打开Sesssion 4.创建事务Transation 5.持久化操作 6.提交事务 7.关闭Session 8.关闭SesstionFactory

为什么要用: 1.    对JDBC访问数据库的代码做了封装,大大简化了数据访问层繁琐的重复性代码。

2.    Hibernate是一个基于JDBC的主流持久化框架,是一个优秀的ORM实现。他很大程度的简化DAO层的编码工作

3.    hibernate使用Java反射机制,而不是字节码增强程序来实现透明性。

4.    hibernate的性能非常好,因为它是个轻量级框架。映射的灵活性很出色。它支持各种关系数据库,从一对一到多对多的各种复杂关系。

2. Hibernate是如何延迟加载? 1. Hibernate2延迟加载实现:a)实体对象 b)集合(Collection)

2. Hibernate3 提供了属性的延迟加载功能

当Hibernate在查询数据的时候,数据并没有存在与内存中,当程序真正对数据的操作时,对象才存在与内存中,就实现了延迟加载,他节省了服务器的内存开销,从而提高了服务器的性能。

3.Hibernate中怎样实现类之间的关系?(如:一对多、多对多的关系)

类与类之间的关系主要体现在表与表之间的关系进行操作,它们都市对对象进行操作,我们程序中把所有的表与类都映射在一起,它们通过配置文件中的many-to-one、one-to-many、many-to-many、

4. 说下Hibernate的缓存机制

1. 内部缓存存在Hibernate中又叫一级缓存,属于应用事物级缓存

2. 二级缓存: a) 应用及缓存 b) 分布式缓存 条件:数据不会被第三方修改、数据大小在可接受范围、数据更新频率低、同一数据被系统频繁使用、非              关键数据 c) 第三方缓存的实现

5. Hibernate的查询方式 Sql、Criteria,object comptosition Hql: 1、 属性查询 2、 参数查询、命名参数查询 3、 关联查询 4、 分页查询 5、 统计函数

6. 如何优化Hibernate? 1.使用双向一对多关联,不使用单向一对多 2.灵活使用单向一对多关联 3.不用一对一,用多对一取代 4.配置对象缓存,不使用集合缓存 5.一对多集合使用Bag,多对多集合使用Set 6. 继承类使用显式多态 7. 表字段要少,表关联不要怕多,有二级缓存撑腰

7. Struts工作机制?为什么要使用Struts? 工作机制: Struts的工作流程: 在web应用启动时就会加载初始化ActionServlet,ActionServlet从 struts-config.xml文件中读取配置信息,把它们存放到各种配置对象 当ActionServlet接收到一个客户请求时,将执行如下流程.     -(1)检索和用户请求匹配的ActionMapping实例,如果不存在,就返回请求路径无效信息;     -(2)如果ActionForm实例不存在,就创建一个ActionForm对象,把客户提交的表单数据保存到ActionForm对象中;     -(3)根据配置信息决定是否需要表单验证.如果需要验证,就调用ActionForm的validate()方法;     -(4)如果ActionForm的validate()方法返回null或返回一个不包含ActionMessage的ActuibErrors对象, 就表示表单验证成功;     -(5)ActionServlet根据ActionMapping所包含的映射信息决定将请求转发给哪个Action,如果相应 的                    Action实例不存在,就先创建这个实例,然后调用Action的execute()方法;     -(6)Action的execute()方法返回一个ActionForward对象,ActionServlet在把客户请求转发给 ActionForward对象指向的JSP组件;     -(7)ActionForward对象指向JSP组件生成动态网页,返回给客户;

为什么要用: JSP、Servlet、JavaBean技术的出现给我们构建强大的企业应用系统提供了可能。但用这些技术构建的系统非常的繁乱,所以在此之上,我们需要一个规则、一个把这些技术组织起来的规则,这就是框架,Struts便应运而生。

基于Struts开发的应用由3类组件构成:控制器组件、模型组件、视图组件

8. Struts的validate框架是如何验证的? 在struts配置文件中配置具体的错误提示,再在FormBean中的validate()方法具体调用。

9. 说下Struts的设计模式 MVC模式: web应用程序启动时就会加载并初始化ActionServler。用户提交表单时,一个配置好的ActionForm对象被创建,并被填入表单相应的数 据,ActionServler根据Struts-config.xml文件配置好的设置决定是否需要表单验证,如果需要就调用ActionForm的 Validate()验证后选择将请求发送到哪个Action,如果Action不存在,ActionServlet会先创建这个对象,然后调用 Action的execute()方法。Execute()从ActionForm对象中获取数据,完成业务逻辑,返回一个ActionForward对 象,ActionServlet再把客户请求转发给ActionForward对象指定的jsp组件,ActionForward对象指定的jsp生成动 态的网页,返回给客户。

10. spring工作机制及为什么要用? 1.spring mvc请所有的请求都提交给DispatcherServlet,它会委托应用系统的其他模块负责负责对请求进行真正的处理工作。 2.DispatcherServlet查询一个或多个HandlerMapping,找到处理请求的Controller. 3.DispatcherServlet请请求提交到目标Controller 4.Controller进行业务逻辑处理后,会返回一个ModelAndView 5.Dispathcher查询一个或多个ViewResolver视图解析器,找到ModelAndView对象指定的视图对象 6.视图对象负责渲染返回给客户端。

为什么用: {AOP 让开发人员可以创建非行为性的关注点,称为横切关注点,并将它们插入到应用程序代码中。使用 AOP 后,公共服务 (比 如日志、持久性、事务等)就可以分解成方面并应用到域对象上,同时不会增加域对象的对象模型的复杂性。     IOC 允许创建一个可以构造对象的应用环境,然后向这些对象传递它们的协作对象。正如单词 倒置 所表明的,IOC 就像反 过来的 JNDI。没有使用一堆抽象工厂、服务定位器、单元素(singleton)和直接构造(straight construction),每一个对象都是用其协作对象构造的。因此是由容器管理协作对象(collaborator)。 Spring即使一个AOP框架,也是一IOC容器。 Spring 最好的地方是它有助于您替换对象。有了 Spring,只要用 JavaBean 属性和配置文件加入依赖性(协作对象)。然后可以很容易地在需要时替换具有类似接口的协作对象。}

Struts,Spring,Hibernate优缺点

Struts跟Tomcat、Turbine等诸 多Apache项目一样,是开源软件,这是它的一大优点。使开发者能更深入的了解其内部实现机制。 Struts开放源码框架的创建是为了使开发者在构建基于Java Servlet和JavaServer Pages(JSP)技术的Web应用时更加容易。Struts框架为开放者提供了一个统一的标准框架,通过使用Struts作为基础,开发者能够更专注 于应用程序的商业逻辑。Struts框架本身是使用Java Servlet和JavaServer Pages技术的一种Model-View-Controller(MVC)实现.
具体来讲, Struts的优点有: 1. 实现MVC模式,结构清晰,使开发者只关注业务逻辑的实现. 2. 有丰富的tag可以用 ,Struts的标记库(Taglib),如能灵活动用,则能大大提高开发效率。另外,就目前国内的JSP开发者而言,除了使用JSP自带的常用标记外,很少开发自己的标记,或许Struts是一个很好的起点。 3. 页面导航.页面导航将是今后的一个发展方向,事实上,这样做,使系统的脉络更加清晰。通过一个配置文件,即可把握整个系统各部分之间的联系,这对于后期的维护有着莫大的好处。尤其是当另一批开发者接手这个项目时,这种优势体现得更加明显。 4. 提供Exception处理机制 . 5. 数据库链接池管理 6. 支持I18N 缺点: 一、转到展示层时,需要配置forward,每一次转到展示层,相信大多数都是直接转到jsp,而涉及到转向, 需要配置forward,如果有十个展示层的jsp,需要配置十次struts,而且还不包括有时候目录、文件变更,需要重新修改forward,注意, 每次修改配置之后,要求重新部署整个项目,而tomcate这样的服务器,还必须重新启动服务器,如果业务变更复杂频繁的系统,这样的操作简单不可想象。 现在就是这样,几十上百个人同时在线使用我们的系统,大家可以想象一下,我的烦恼有多大。 二、 Struts 的Action必需是thread-safe方式,它仅仅允许一个实例去处理所有的请求。所以action用到的所有的资源都必需统一同步,这个就引起了线程安全的问题。 三、 测试不方便. Struts的每个Action都同Web层耦合在一起,这样它的测试依赖于Web容器,单元测试也很难实现。不过有一个Junit的扩展工具Struts TestCase可以实现它的单元测试。 四、 类型的转换. Struts的FormBean把所有的数据都作为String类型,它可以使用工具Commons-Beanutils进行类型转化。但它的转化都是在Class级别,而且转化的类型是不可配置的。类型转化时的错误信息返回给用户也是非常困难的。 五、 对Servlet的依赖性过强. Struts处理Action时必需要依赖ServletRequest 和ServletResponse,所有它摆脱不了Servlet容器。 六、前端表达式语言方面.Struts集成了JSTL,所以它主要使用JSTL的表达式语言来获取数据。可是JSTL的表达式语言在Collection和索引属性方面处理显得很弱。 七、 对Action执行的控制困难. Struts创建一个Action,如果想控制它的执行顺序将会非常困难。甚至你要重新去写Servlet来实现你的这个功能需求。 八、 对Action 执行前和后的处理. Struts处理Action的时候是基于class的hierarchies,很难在action处理前和后进行操作。 九、 对事件支持不够. 在struts中,实际是一个表单Form对应一个Action类(或DispatchAction),换一句话说:在Struts中实际是一个表单只能 对应一个事件,struts这种事件方式称为application event,application event和component event相比是一种粗粒度的事件。
Struts重要的表单对象ActionForm是一种对象,它代表了一种应用,这个对象中至少包含几个字段, 这些字段是Jsp页面表单中的input字段,因为一个表单对应一个事件,所以,当我们需要将事件粒度细化到表单中这些字段时,也就是说,一个字段对应一 个事件时,单纯使用Struts就不太可能,当然通过结合JavaScript也是可以转弯实现的。
2.Hibernate Hibernate是一个开放源代码的对象关系映射框架,它对JDBC进行了非常轻量级的对象封装,使得Java程序员可以随心所欲的使用对象编程思维来操纵数据库。 Hibernate可以应用在任何使用JDBC的场合,既可以在Java的客户端程序实用,也可以在Servlet/JSP的Web应用中使用,最具革命意义的是,Hibernate可以在应用EJB的J2EE架构中取代CMP,完成数据持久化的重任。 大多数开发机构经常采取创建各自独立的数据持久层。一旦底层的数据结构发生改变,那么修改应用的其余部分使之适应这种改变的代价将是十分巨大的。 Hibernate适时的填补了这一空白,它为Java应用提供了一个易用的、高效率的对象关系映射框架。hibernate是个轻量级的持久性框架,功 能却非常丰富。 优点: a.Hibernate 使用 Java 反射机制而不是字节码增强程序来实现透明性。 b.Hibernate 的性能非常好,因为它是个轻量级框架。映射的灵活性很出色。 c.它支持各种关系数据库,从一对一到多对多的各种复杂关系。 缺点:它限制您所使用的对象模型。(例如,一个持久性类不能映射到多个表)其独有的界面和可怜的市场份额也让人不安,尽管如此,Hibernate 还是以其强大的发展动力减轻了这些风险。其他的开源持久性框架也有一些,不过都没有 Hibernate 这样有市场冲击力。 上面回贴情绪有点激动,希望谅解,我不是因为有人批评Hibernate而感到不快,而是因为帖子里面的观点实在让我觉得荒谬。不管觉得 Hibernate好也吧,不好也吧,我唯一觉得遗憾的是,在中文论坛里面找不到一个对Hibernate的真正高水平的评价。在TSS上有一个关于 Hibernate的hot thread,跟了几百贴,其中包括Hibernate作者Gavin和LiDO JDO的CTO,对于JDO和Hibernate有过一些激烈的争论,我曾经耐心的看了一遍,仍然没有发现针对Hibernate真正有力的攻击,那些所 谓的攻击无非针对Hibernate没有一个GUI的配置工具,没有商业公司支持,没有标准化等等这些站不住脚的理由。 补充几点我的意见: 一、Hibernate是JDBC的轻量级的对象封装,它是一个独立的对象持久层框架,和App Server,和EJB没有什么必然的联系。Hibernate可以用在任何JDBC可以使用的场合,例如Java应用程序的数据库访问代码,DAO接口 的实现类,甚至可以是BMP里面的访问数据库的代码。从这个意义上来说,Hibernate和EB不是一个范畴的东西,也不存在非此即彼的关系。 二、Hibernate是一个和JDBC密切关联的框架,所以Hibernate的兼容性和JDBC驱动,和数据库都有一定的关系,但是和使用它的Java程序,和App Server没有任何关系,也不存在兼容性问题。 三、Hibernate不能用来直接和Entity Bean做对比,只有放在整个J2EE项目的框架中才能比较。并且即使是放在软件整体框架中来看,Hibernate也是做为JDBC的替代者出现的,而 不是Entity Bean的替代者出现的,让我再列一次我已经列n次的框架结构: 传统的架构: 1) Session Bean <-> Entity Bean <-> DB 为了解决性能障碍的替代架构: 2) Session Bean <-> DAO <-> JDBC <-> DB 使用Hibernate来提高上面架构的开发效率的架构: 3) Session Bean <-> DAO <-> Hibernate <-> DB 就上面3个架构来分析: 1、内存消耗:采用JDBC的架构2无疑是最省内存的,Hibernate的架构3次之,EB的架构1最差。 2、运行效率:如果JDBC的代码写的非常优化,那么JDBC架构运行效率最高,但是实际项目中,这一点几乎做不到,这需要程序员非常精通JDBC,运用 Batch语句,调整PreapredStatement的Batch Size和Fetch Size等参数,以及在必要的情况下采用结果集cache等等。而一般情况下程序员是做不到这一点的。因此Hibernate架构表现出最快的运行效率。 EB的架构效率会差的很远。 3、开发效率:在有JBuilder的支持下以及简单的项目,EB架构开发效率最高,JDBC次之,Hibernate最差。但是在大的项目,特别是持久层关系映射很复杂的情况下,Hibernate效率高的惊人,JDBC次之,而EB架构很可能会失败。 4、分布式,安全检查,集群,负载均衡的支持 由于有SB做为Facade,3个架构没有区别。 四、EB和Hibernate学习难度在哪里? EB的难度在哪里?不在复杂的XML配置文件上,而在于EB运用稍微不慎,就有严重的性能障碍。所以难在你需要学习很多EJB设计模式来避开性能问题,需 要学习App Server和EB的配置来优化EB的运行效率。做EB的开发工作,程序员的大部分精力都被放到了EB的性能问题上了,反而没有更多的精力关注本身就主要 投入精力去考虑的对象持久层的设计上来。 Hibernate难在哪里?不在Hibernate本身的复杂,实际上Hibernate非常的简单,难在Hibernate太灵活了。 当你用EB来实现持久层的时候,你会发现EB实在是太笨拙了,笨拙到你根本没有什么可以选择的余地,所以你根本就不用花费精力去设计方案,去平衡方案的好坏,去费脑筋考虑选择哪个方案,因为只有唯一的方案摆在你面前,你只能这么做,没得选择。 Hibernate相反,它太灵活了,相同的问题,你至少可以设计出十几种方案来解决,所以特别的犯难,究竟用这个,还是用那个呢?这些方案之间到底有什 么区别呢?他们的运行原理有什么不同?运行效率哪个比较好?光是主键生成,就有七八种方案供你选择,你为难不为难?集合属性可以用Set,可以用 List,还可以用Bag,到底哪个效率高,你为难不为难?查询可以用iterator,可以用list,哪个好,有什么区别?你为难不为难?复合主键你 可以直接在hbm里面配置,也可以自定义CustomerType,哪种比较好些?你为难不为难?对于一个表,你可以选择单一映射一个对象,也可以映射成 父子对象,还可以映射成两个1:1的对象,在什么情况下用哪种方案比较好,你为难不为难? 这个列表可以一直开列下去,直到你不想再看下去为止。当你面前摆着无数的眼花缭乱的方案的时候,你会觉得幸福呢?还是悲哀呢?如果你是一个负责的程序员, 那么你一定会仔细研究每种方案的区别,每种方案的效率,每种方案的适用场合,你会觉得你已经陷入进去拔不出来了。如果是用EB,你第一秒种就已经做出了决 定,根本没得选择,比如说集合属性,你只能用Collection,如果是Hibernate,你会在Bag,List和Set之间来回犹豫不决,甚至搞 不清楚的话,程序都没有办法写。
3. Spring 它是一个开源的项目,而且目前非常活跃;它基于IoC(Inversion of Control,反向控制)和AOP的构架多层j2ee系统的框架,但它不强迫你必须在每一层中必须使用Spring,因为它模块化的很好,允许你根据自 己的需要选择使用它的某一个模块;它实现了很优雅的MVC,对不同的数据访问技术提供了统一的接口,采用IoC使得可以很容易的实现bean的装配,提供 了简洁的AOP并据此实现Transcation Managment,等等 优点: a. Spring能有效地组织你的中间层对象,不管你是否选择使用了EJB。如果你仅仅使用了Struts或其他为J2EE的 API特制的framework,Spring致力于解决剩下的问题。 b. Spring能消除在许多工程中常见的对Singleton的过多使用。根据我的经验,这是一个很大的问题,它降低了系统的可测试性和面向对象的程度。 c. 通过一种在不同应用程序和项目间一致的方法来处理配置文件,Spring能消除各种各样自定义格式的属性文件的需要。曾经对某个类要寻找的是哪个魔法般的 属性项或系统属性感到不解,为此不得不去读Javadoc甚至源编码?有了Spring,你仅仅需要看看类的JavaBean属性。Inversion of Control的使用(在下面讨论)帮助完成了这种简化。 d.通过把对接口编程而不是对类编程的代价几乎减少到没有,Spring能够促进养成好的编程习惯。 e. Spring被设计为让使用它创建的应用尽可能少的依赖于他的APIs。在Spring应用中的大多数业务对象没有依赖于Spring。 f. 使用Spring构建的应用程序易于单元测试。 g.Spring能使EJB的使用成为一个实现选择,而不是应用架构的必然选择。你能选择用POJOs或local EJBs来实现业务接口,却不会影响调用代码。 h. Spring帮助你解决许多问题而无需使用EJB。Spring能提供一种EJB的替换物,它们适用于许多web应用。例如,Spring能使用AOP提供声明性事务管理而不通过EJB容器,如果你仅仅需要与单个数据库打交道,甚至不需要一个JTA实现。 i. Spring为数据存取提供了一个一致的框架,不论是使用的是JDBC还是O/R mapping产品(如Hibernate)。 Spring确实使你能通过最简单可行的解决办法来解决你的问题。而这是有有很大价值的。 缺点: 使用人数不多、jsp中要写很多代码、控制器过于灵活,缺少一个公用控制器。

本文链接

 

hibernate怎么使用存储过程

 
‎2012‎年‎11‎月‎27‎日,‏‎13:41:00 | YangJin转到全文

hibernate怎么使用存储过程

 

 

Hibernate3.x调用存储过程
摘要:本文以详尽的实例展示了hibernate3.x中调用存储过程各步骤,从建立测试表、存储过程的建立、工程的建立以及类的编写和测试一步一步引导用户学习hibernate3.x中调用存储过程的方法. 如果底层数据库(eg. Oracle、mysql、sqlserver)等支持存储过程,可通过存储过程执行批量删除、更新等操作。本文以实例说明在hibernate3.x中如何调用存储过程。   说明:本例hibernate所用版本为3.0,mysql所用版本为5.0,所用数据库驱动为mysql-connector-java-5.0.4-bin.jar。 一.             建表与初始化数据 在mysql的test数据库中建立一张新表:tbl_user,建表语句如下: DROP TABLE IF EXISTS `user`; CREATE TABLE `tbl_user` (   `userid` varchar(50) NOT NULL,   `name` varchar(50) default '',   `blog` varchar(50) default '',   PRIMARY KEY (`userid`) ) ENGINE=InnoDB DEFAULT CHARSET=gb2312; 建表成功后,在该表中插入如下4条初始数据,对应的sql语句如下: INSERT INTO `tbl_user` (`userid`,`name`,`blog`) VALUES ('ant', '蚂蚁', 'http://www.blogjava.net/qixiangnj'); INSERT INTO `tbl_user` (`userid`,`name`,`blog`) VALUES ('beansoft', 'bean', 'http://www.blogjava.net/beansoft'); INSERT INTO `tbl_user` (`userid`,`name`,`blog`) VALUES ('sterning', '似水流年', 'http://www.blogjava.net/sterning'); INSERT INTO `tbl_user` (`userid`,`name`,`blog`) VALUES ('tom', 'tom' , 'http://www.blogjava.net/tom'); 二.             建立存储过程 为测试hibernate3.x中存储过程的调用,我们在user表中建立getUserList、createUser、updateUser和deleteUser这四个存储过程,在mysql中建立存储过程的语句如下: 1. 获得用户信息列表的存储过程--getUserList DROP PROCEDURE IF EXISTS `getUserList`; CREATE PROCEDURE `getUserList`() begin      select * from tbl_user; end; 2. 通过传入的参数创建用户的存储过程--createUser DROP PROCEDURE IF EXISTS `createUser`; CREATE PROCEDURE `createUser`(IN userid varchar(50), IN name varchar(50), IN blog varchar(50)) begin     insert into tbl_user values(userid, name, blog); end; 3. 通过传入的参数更新用户信息的存储过程--updateUser DROP PROCEDURE IF EXISTS `updateUser`; CREATE PROCEDURE `updateUser`(IN nameValue varchar(50), IN blogValue varchar(50), IN useidValue varchar(50)) begin     update tbl_user set name = nameValue, blog = blogValue where userid = useridValue; end; 4. 删除用户信息的存储过程--deleteUser DROP PROCEDURE IF EXISTS `deleteUser`; CREATE PROCEDURE `deleteUser`(IN useridValue int(11)) begin     delete from tbl_user where userid = useridValue; end; 三.             编程前准备工作 1.    建立工程 在进入代码编写前,建立新的java工程proc, 目录结构如下: proc    -lib     -bin     -src       -com         -amigo           -proc            -model 2.    引入所需包   将hibernate3.0的包以及其相关包放入编译路径中,另注意:还需将mysql的数据库驱动jar包mysql-connector-java-5.0.4-bin.jar放入编译路径中。 四.             编码与测试 在准备工作完成后,进入编码与测试阶段,本例演示了在hibernate3.0中调用mysql的存储过程的方法。 1、hibernate的配置文件 在hibernate的配置文件中包含数据库的连接信息,以及加入OR mapping的xml格式的映射文件,该文件如下(部分内容略): ……         <property name="connection.url">jdbc:mysql://localhost:3306/test</property>         <property name="connection.username">root</property>         <property name="connection.password">root</property>         <property name="connection.driver_class">com.mysql.jdbc.Driver</property>         <property name="dialect">org.hibernate.dialect.MySQLDialect</property>         <property name="show_sql">true</property>         <mapping resource="com/amigo/proc/model/User.hbm.xml"/>     …… 2、OR Mapping文件 产生的OR Mapping文件有User.java以及其对应的hibernate映射文件User.hbm.xml。其中User.java的内容如下: package com.amigo.proc.model; /** *//** * 用户信息对象 */ public class User implements java.io.Serializable {     private static final long serialVersionUID = 1L;     /** *//** 用户id*/     private String userid;     /** *//** 用户姓名*/     private String name;     /** *//** 用户blog*/     private String blog; //省略get/set方法 } User.hbm.xml文件的内容如下: <?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping package="com.amigo.proc.model">     <class name="User" table="tbl_user">         <id name="userid" column="userid">             <generator class="assigned"/>         </id>         <property name="name" column="name" type="string" />         <property name="blog" column="blog" type="string" />     </class>         <sql-query name="getUserList" callable="true">         <return alias="user" class="User">             <return-property name="userid" column="userid"/>            <return-property name="name" column="name"/>            <return-property name="blog" column="blog" />         </return>         {call getUserList()}     </sql-query> </hibernate-mapping> 在该文件中需注意<sql-query…></sql-query>中的这段代码,调用的存储过程在其中定义,并定义了调用存储过程后将记录组装成User对象,同时对记录的字段与对象的属性进行相关映射。 3.    管理hibernate的session以及事务的类HibernateSessionFactory 该类包括打开session等方法,主要用于管理hibernate的session和事务。该类的内容如下(部分内容略): package com.amigo.proc; import java.io.ByteArrayOutputStream; import java.io.OutputStreamWriter; import org.hibernate.HibernateException; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.Transaction; import org.hibernate.cfg.Configuration; /** *//** * Hibernate相关控制 */ public class HibernateSessionFactory {     /** *//** Hibernate配置文件 */     private static String CONFIG_FILE_LOCATION = "/hibernate.cfg.xml";     /** *//** 存储一个单独的session实例 */     private static final ThreadLocal threadLocal = new ThreadLocal();     /** *//** Hibernate配置相关的一个实例 */     private static Configuration configuration = null;     /** *//** Hibernate SessionFactory的一个实例 */     private static SessionFactory sessionFactory;     /** *//** Hibernate的字符编码集*/     private static String charSet;     /** *//** 若Hibernate未设置字符编码集时,采用的字符编码集*/     private static final String encoding = (new OutputStreamWriter(             new ByteArrayOutputStream())).getEncoding();     /** *//**      * 默认构造函数      */     public HibernateSessionFactory() {     }     /** *//**      * 打开Hibernate的数据库连接      */     public static final synchronized void open() {         if (sessionFactory != null)             return;         try {             sessionFactory = getConfiguration().buildSessionFactory();             charSet = configuration.getProperty("hibernate.connection.charSet");             if (charSet == null)                 charSet = encoding;            return;         } catch (Throwable throwable) {             throw new ExceptionInInitializerError(throwable);         }     }     /** *//**      * 配置Hibernate数据库,并将其打开      */     private static synchronized void configure() throws HibernateException {         if (sessionFactory == null) {             if (configuration == null) {                 getConfiguration().configure(CONFIG_FILE_LOCATION);            }             open();         }     }     /** *//**      * 获得配置实例      */     public static synchronized final Configuration getConfiguration() {         if (configuration == null) {            configuration = new Configuration();         }         return configuration;     }     /** *//**      * 功能说明:获得SessionFactory      */     public static final SessionFactory getSessionFactory() {         return sessionFactory;     }     /** *//**      * 功能说明:获得session      */     public static final Session getSession() throws HibernateException {         configure();         Session session = null;         if (threadLocal.get() == null) {             session = getSessionFactory().openSession();             threadLocal.set(session);         } else {             try {                 session = (Session)threadLocal.get();             } catch(Exception ex) {                 session = getSessionFactory().openSession();                 threadLocal.set(session);             }         }         return session;     }     //其余方法略 } 4. hibernate调用存储过程的测试类 本类是该例的核心类,在本类中,以实例清楚地说明了在hibernate中如何调用存储过程,例示了hibernate调用查询、更新、插入和删除这四类存储过程的方法,该类的内容如下: package com.amigo.proc; import java.sql.CallableStatement; import java.sql.Connection; import java.sql.PreparedStatement; import java.util.List; import com.amigo.proc.model.User; import org.hibernate.Session; import org.hibernate.Transaction; /** *//** * hibernate调用存储过程 * @author Amigo Xie(xiexingxing1121@126.com) * @since 2007/04/30 */ public class ProcTest {     /** *//**      * @param args      */     public static void main(String[] args) throws Exception {         ProcTest proc = new ProcTest();         Session session = HibernateSessionFactory.getSession();         proc.testProcQuery(session);         proc.testProcUpdate(session);         System.out.println("update successfully");                 proc.testProcInsert(session);         System.out.println("insert successfully");                 proc.testProcDelete(session);         System.out.println("delete successfully");         session.close();     }         /** *//**      * 测试实现查询的存储过程      * @throws Exception      */     private void testProcQuery(Session session) throws Exception {         //查询用户列表         List list = session.getNamedQuery("getUserList").list();         for (int i = 0; i < list.size(); i++) {             User user = (User) list.get(i);                System.out.print("序号: " + (i+1));             System.out.print(", userid: " + user.getUserid());             System.out.print(", name: " + user.getName());             System.out.println(", blog: " + user.getBlog());         }     }         /** *//**      * 测试实现更新的存储过程      * @throws Exception      */     private void testProcUpdate(Session session) throws Exception {         //更新用户信息         Transaction tx = session.beginTransaction();         Connection con = session.connection();         String procedure = "{call updateUser(?, ?, ?)}";         CallableStatement cstmt = con.prepareCall(procedure);         cstmt.setString(1, "陈xx");         cstmt.setString(2, "http://www.");         cstmt.setString(3, "sterning");         cstmt.executeUpdate();         tx.commit();     }     /** *//**      * 测试实现插入的存储过程      * @throws Exception      */     private void testProcInsert(Session session) throws Exception {         //创建用户信息         session.beginTransaction();         PreparedStatement st = session.connection().prepareStatement("{call createUser(?, ?, ?)}");         st.setString(1, "amigo");         st.setString(2, "阿蜜果");         st.setString(3, "http://www.wblogjava.net/amigoxie");         st.execute();         session.getTransaction().commit();     }         /** *//**      * 测试实现删除的存储过程      * @throws Exception      */     private void testProcDelete(Session session) throws Exception {         //删除用户信息         session.beginTransaction();         PreparedStatement st = session.connection().prepareStatement("{call deleteUser(?)}");         st.setString(1, "amigo");         st.execute();         session.getTransaction().commit();     } }   在本类中,调用查询类存储过程时,调用session.getNamedQuery("…")方法来获得User.hbm.xml中配置的查询存储过程。在其余的存储过程调用的测试中,首先通过hibernate的session获得connection,然后调用connection对象的相应方法来实现存储过程的调用。 该类的执行结果如下: Hibernate: {call getUserList()} 序号: 1, userid: ant, name: 蚂蚁, blog: 序号: 2, userid: beansoft, name: bean, blog: 序号: 3, userid: sterning, name: 似水流年, blog: 序号: 4, userid: tom, name: tom, blog: update successfully insert successfully delete successfully 五.总结   本例提出了在hibernate3中调用mysql的存储过程的实现方案,从本例可以看出,hibernate提供了在*.hbm.xml中配置调用存储过程,并通过向用户提供session.getNamedQuery(“…”)方法来调用配置的调用查询相关的存储过程的方法,另外,hibernate还提供了取得sql的connection的方法,从而能够通过connection中存储过程调用相关的方法来实现存储过程的调用。

本文链接

 

 

Java中调用存储过程

 
‎2012‎年‎11‎月‎27‎日,‏‎13:34:00 | YangJin转到全文

Java中调用存储过程 

 

 

 
 

JDBC中的java.sql.CallableStatement对象为所有的 DBMS提供了一种以标准形式调用已储存过程的方法。已储存过程储存在数据库中。对已储存过程的调用是CallableStatement对象所含的内容。这种调用是用一种换码语法来写的,有两种形式:一种形式带结果参,另一种形式不带结果参数。结果参数是一种输出(OUT)参数,是已储存过程的返回值。两种形式都可带有数量可变的输入(IN参数)、输出(OUT 参数)或输入和输出(INOUT参数)的参数。问号将用作参数的占位符。   在 JDBC中调用已储存过程的语法如下所示。注意,方括号表示其间的内容是可选项;方括号本身并不是语法的组成部份。 
{call 过程名[(?, ?, ...)]}
返回结果参数的过程的语法为:
{? = call 过程名[(?, ?, ...)]}
不带参数的已储存过程的语法类似:
{call 过程名}
  通常,创建 CallableStatement 对象的人应当知道所用的 DBMS是支持已储存过程的,并且知道这些过程都是些什么。然而,如果需要检查,多种DatabaseMetaData方法都可以提供这样的信息。例如,如果 DBMS支持已储存过程的调用,则supportsStoredProcedures 方法将返回true,而getProcedures方法将返回对已储存过程的描述。CallableStatement 继承 Statement的方法(它们用于处理一般的 SQL 语句),还继承了 PreparedStatement的方法(它们用于处理 IN 参)。
  CallableStatement 中定义的所有方法都用于处理 OUT 参数或 INOUT参数的输出部分:注册 OUT 参数的 JDBC 类型(一般 SQL类型)、从这些参数中检索结果,或者检查所返回的值是否为 JDBCNULL。
  1、创建 CallableStatement 对象
  CallableStatement 对象是用 Connection 方法 prepareCall创建的。下例创建 CallableStatement 的实例, 其中含有对已储存过程getTestData 调用。该过程有两个变量,但不含结果参数:
      CallableStatement cstmt = con.prepareCall("{call getTestData(?,?)}");
  其中?占位符为IN、OUT还是INOUT参数,取决于已储存过程getTestData。
  2、IN和OUT参数
  将IN参数传给 CallableStatement 对象是通过 setXXX方法完成的。该方法继承自PreparedStatement。所传入参数的类型决定了所用的setXXX方法(例如,用setFloat 来传入 float 值等)。
  如果已储存过程返回 OUT 参数,则在执行 CallableStatement对象以前必须先注册每个 OUT 参数的 JDBC 类型(这是必需的,因为某些DBMS 要求 JDBC 类型)。注册 JDBC 类型是用 registerOutParameter方法来完成的。语句执行完后,CallableStatement 的 getXXX方法将取回参数值。正确的 getXXX 方法是为各参数所注册的 JDBC类型所对应的 Java 类型。换言之, registerOutParameter 使用的是 JDBC类型(因此它与数据库返回的 JDBC 类型匹配),而 getXXX 将之转换为Java 类型。
  作为示例,下述代码先注册 OUT 参数,执行由 cstmt所调用的已储存过程,然后检索在 OUT 参数中返回的值。方法 getByte从第一个 OUT 参数中取出一个 Java 字节,而 getBigDecimal 从第二个OUT 参数中取出一个 BigDecimal 对象(小数点后面带三位数):

 

 

CallableStatement cstmt = con.prepareCall("{call getTestData(?,?)}"); cstmt.registerOutParameter(1, java.sql.Types.TINYINT); cstmt.registerOutParameter(2, java.sql.Types.DECIMAL, 3); cstmt.executeQuery(); byte x = cstmt.getByte(1); java.math.BigDecimal n = cstmt.getBigDecimal(2, 3);
CallableStatement 与 ResultSet 不同,它不提供用增量方式检索大 OUT值的特殊机制。
3、INOUT参数
  既支持输入又接受输出的参数(INOUT 参数)除了调用registerOutParameter 方法外,还要求调用适当的 setXXX方法(该方法是从 PreparedStatement 继承来的)。setXXX方法将参数值设置为输入参数,而 registerOutParameter 方法将它的 JDBC类型注册为输出参数。setXXX 方法提供一个 Java值,而驱动程序先把这个值转换为 JDBC 值,然后将它送到数据库中。这种IN 值的 JDBC 类型和提供给 registerOutParameter 方法的 JDBC类型应该相同。然后,要检索输出值,就要用对应的 getXXX方法。例如,Java 类型为byte 的参数应该使用方法 setByte来赋输入值。应该给registerOutParameter 提供类型为 TINYINT 的 JDBC类型,同时应使用 getByte 来检索输出值。
  下例假设有一个已储存过程 reviseTotal,其唯一参数是 INOUT参数。方法setByte 把此参数设为 25,驱动程序将把它作为 JDBC TINYINT类型送到数据库中。接着,registerOutParameter 将该参数注册为 JDBCTINYINT。执行完该已储存过程后,将返回一个新的 JDBC TINYINT 值。方法getByte 将把这个新值作为 Java byte 类型检索。
CallableStatement cstmt = con.prepareCall("{callreviseTotal(?)}"); cstmt.setByte(1, 25); cstmt.registerOutParameter(1, java.sql.Types.TINYINT); cstmt.executeUpdate(); byte x = cstmt.getByte(1);
4、先检索结果,再检索 OUT 参数
  由于某些 DBMS的限制,为了实现最大的可移植性,建议先检索由执行CallableStatement对象所产生的结果,然后再用 CallableStatement.getXXX 方法来检索 OUT参数。如果 CallableStatement 对象返回多个 ResultSet 对象(通过调用execute 方法),在检索 OUT参数前应先检索所有的结果。这种情况下,为确保对所有的结果都进行了访问,必须对Statement 方法 getResultSet、getUpdateCount 和getMoreResults进行调用,直到不再有结果为止。
  检索完所有的结果后,就可用 CallableStatement.getXXX 方法来检索OUT 参数中的值。
5、检索作为OUT参数的NULL值
  返回到 OUT 参数中的值可能会是JDBC NULL。当出现这种情形时,将对JDBC NULL 值进行转换以使 getXXX 方法所返回的值为 null、0 或false,这取决于getXXX 方法类型。对于 ResultSet对象,要知道0或false是否源于JDBCNULL的唯一方法,是用方法wasNull进行检测。如果getXXX 方法读取的最后一个值是 JDBC NULL,则该方法返回true,否则返回 flase。

本文链接

 

 

在Java 7里如何对文件进行操作

 
‎2012‎年‎11‎月‎26‎日,‏‎21:34:00 | YangJin转到全文

导读:本文是从《Manipulating Files in Java 7》这篇文章翻译而来。 文章内容如下:

下面的代码片段是由经过验证的程序修改而来。观察这些代码片段你会发现,跟以前的版本相比,在Java7里,文件相关的操作变得简单的多了。通过使用新的Files类里提供的各种方法,你可以只用一行代码就能完成下列的文件操作:

  • 创建文件
  • 删除文件
  • 复制文件
  • 文件移动/改名

这篇文件是以你对Java7里提供的新的Path类很熟悉为前提,如果你不熟悉这个类,这里就简单说一句,Path是文件系统里对位置的一个逻辑概念,例如c:\ 和../foobar.txt都是Path。

创建和删除文件

下面的代码片段向你展示的是用 Files.createFile (Path target) 方法创建文件的基本用法。

  1. Path target = Paths.get ("D:\\Backup\\MyStuff.txt"); Path file = Files.createFile (target); 

很多时候,出于安全的原因,你可能希望在创建的文件上设置一下属性,例如:是否可读/可写/写执行。这些属性依赖于文件系统的种类,你需要使用跟文件系统相应的权限辅助类来完成这种操作。例如,PosixFilePermission和PosixFilePermissions为POSIX文件系统设计的。下面的是在POSIX文件系统上的文件设置读写权限的用法。

  1. Path target = Paths.get ("D:\\Backup\\MyStuff.txt"); Set<PosixFilePermission> perms  =  
  2. PosixFilePermissions.fromString ("rw-rw-rw-"); FileAttribute<Set<PosixFilePermission>> attr  =  
  3. PosixFilePermissions.asFileAttribute (perms); Files.createFile (target, attr); 

这个java.nio.file.attribute包里提供了很多关于FilePermission的类。

警告当创建一个带有权限属性的文件时,请注意包含这个文件的文件夹是否有权限的强制约束。例如,你会发现,由于这些限制,尽管你给创建的文件指定了rw-rw-rw权限,实际创建的结果却是rw-r–r–。

删除文件更简单,使用Files.delete (Path)这个方法。

  1. Path target = Paths.get ("D:\\Backup\\MyStuff.txt"); Files.delete (target);

拷贝和移动文件

下面的代码向你展示的是使用Files.copy (Path source, Path target)方法做文件拷贝的基本用法。

  1. Path source = Paths.get ("C:\\My Documents\\Stuff.txt"); 
  2. Path target = Paths.get ("D:\\Backup\\MyStuff.txt"); Files.copy (source, target); 

经常的,在拷贝文件的过程中你可能希望指定一些操作设置。在Java7里,你可以通过使用StandardCopyOption enum来设置这些属性。下面看一个例子。

  1. import static java.nio.file.StandardCopyOption.*; 
  2. Path source = Paths.get ("C:\\My Documents\\Stuff.txt"); 
  3. Path target = Paths.get ("D:\\Backup\\MyStuff.txt"); 
  4. Files.copy (source, target, REPLACE_EXISTING); 

拷贝操作时可以使用的属性还包括COPY_ATTRIBUTES (保留文件属性) 和ATOMIC_MOVE (确保移动事务操作的成功,否则进行回滚)。

移动文件的操作跟拷贝很相似,使用Files.move (Path source, Path target)方法。

同样,你也可以指定移动操作的属性,使用Files.move (Path source, Path target, CopyOptions...) 方法里的参数来设置。

  1. import static java.nio.file.StandardCopyOption.*; 
  2. Path source = Paths.get ("C:\\My Documents\\Stuff.txt"); 
  3. Path target = Paths.get ("D:\\Backup\\MyStuff.txt"); Files.move (source, target, REPLACE_EXISTING,  
  4. COPY_ATTRIBUTES); 

可以看出,新的用于文件操作的NIO.2 API 非常便于使用。

本文链接

 

浅谈Java中的几种随机数

 
‎2012‎年‎11‎月‎26‎日,‏‎20:52:00 | YangJin转到全文

众所周知,随机数是任何一种编程语言最基本的特征之一。而生成随机数的基本方式也是相同的:产生一个0到1之间的随机数。看似简单,但有时我们也会忽略了一些有趣的功能。

我们从书本上学到什么?

最明显的,也是直观的方式,在Java中生成随机数只要简单的调用:

  1. java.lang.Math.random()

在所有其他语言中,生成随机数就像是使用Math工具类,如abs, pow, floor, sqrt和其他数学函数。大多数人通过书籍、教程和课程来了解这个类。一个简单的例子:从0.0到1.0之间可以生成一个双精度浮点数。那么通过上面的信息,开发人员要产生0.0和10.0之间的双精度浮点数会这样来写:

  1. Math.random() * 10

而产生0和10之间的整数,则会写成:

  1. Math.round(Math.random() * 10)

进阶

通过阅读Math.random()的源码,或者干脆利用IDE的自动完成功能,开发人员可以很容易发现,java.lang.Math.random()使用一个内部的随机生成对象 - 一个很强大的对象可以灵活的随机产生:布尔值、所有数字类型,甚至是高斯分布。例如:

  1. new java.util.Random().nextInt(10)

它有一个缺点,就是它是一个对象。它的方法必须是通过一个实例来调用,这意味着必须先调用它的构造函数。如果在内存充足的情况下,像上面的表达式是可以接受的;但内存不足时,就会带来问题。

一个简单的解决方案,可以避免每次需要生成一个随机数时创建一个新实例,那就是使用一个静态类。猜你可能想到了java.lang.Math,很好,我们就是改良java.lang.Math的初始化。虽然这个工程量低,但你也要做一些简单的单元测试来确保其不会出错。

假设程序需要生成一个随机数来存储,问题就又来了。比如有时需要操作或保护种子(seed),一个内部数用来存储状态和计算下一个随机数。在这些特殊情况下,共用随机生成对象是不合适的。

并发

在Java EE多线程应用程序的环境中,随机生成实例对象仍然可以被存储在类或其他实现类,作为一个静态属性。幸运的是,java.util.Random是线程安全的,所以不存在多个线程调用会破坏种子(seed)的风险。

另一个值得考虑的是多线程java.lang.ThreadLocal的实例。偷懒的做法是通过Java本身API实现单一实例,当然你也可以确保每一个线程都有自己的一个实例对象。

虽然Java没有提供一个很好的方法来管理java.util.Random的单一实例。但是,期待已久的Java 7提供了一种新的方式来产生随机数:

  1. java.util.concurrent.ThreadLocalRandom.current().nextInt(10)

这个新的API综合了其他两种方法的优点:单一实例/静态访问,就像Math.random()一样灵活。ThreadLocalRandom也比其他任何处理高并发的方法要更快。

经验

Chris Marasti-Georg 指出:

  1. Math.round(Math.random() * 10)

使分布不平衡,例如:0.0 - 0.499999将四舍五入为0,而0.5至1.499999将四舍五入为1。那么如何使用旧式语法来实现正确的均衡分布,如下:

  1. Math.floor(Math.random() * 11)

幸运的是,如果我们使用java.util.Random或java.util.concurrent.ThreadLocalRandom就不用担心上述问题了。

Java实战项目里面介绍了一些不正确使用java.util.Random API的危害。这个教训告诉我们不要使用:

  1. Math.abs(rnd.nextInt())%n

而使用:

  1. rnd.nextInt(n)

本文链接

 

Java堆内存的10个要点

 
‎2012‎年‎11‎月‎26‎日,‏‎20:52:00 | YangJin转到全文

我刚开始学习Java编程时,可不知道什么是堆内存或堆空间(heap space),甚至根本不管对象创建时都放在哪里去了。正式了写一些程序后,经常会遇到java.lang.outOfMemoryError等错误,我才开始关注堆内存。

对大多数程序员都经历过这样的过程,因为学习一种语言是非常容易来的,但是学习基础是非常难的,因为没有什么特定的流程让你学习编程的每个基础,使你发觉编程的秘诀。

对于程序员来说,知道堆空间,设置堆空间,处理堆空间的outOfMemoryError错误,分析heap dump是非常重要的。这个关于Java堆的教程是给我刚开始学编程的兄弟看的。如果你知道这个基础知识或者知道底层发生了什么,当然可能帮助不是那么大。除非你知道了对象被创建在堆中,否则你不会意识到OutOfMemoryError是发生在堆空间中的。我尽可能的将我所知道的所有关于堆的知识都写下来了,也希望你们能够尽可能多的贡献和分享你的知识,以便可以让其他人也受益。

Java中的堆空间是什么?

当Java程序开始运行时,JVM会从操作系统获取一些内存。JVM使用这些内存,这些内存的一部分就是堆内存。堆内存通常在存储地址的底层,向上排列。当一个对象通过new关键字或通过其他方式创建后,对象从堆中获得内存。当对象不再使用了,被当做垃圾回收掉后,这些内存又重新回到堆内存中。要学习垃圾回收,请阅读”Java中垃圾回收的工作原理”。

如何增加Java堆空间

在大多数32位机、Sun的JVM上,Java的堆空间默认的大小为128MB,但也有例外,例如在32未Solaris操作系统(SPARC平台版本)上,默认的最大堆空间和起始堆空间大小为 -Xms=3670K 和 -Xmx=64M。对于64位操作系统,一般堆空间大小增加约30%。但你使用Java 1.5的throughput垃圾回收器,默认最大的堆大小为物理内存的四分之一,而起始堆大小为物理内存的十六分之一。要想知道默认的堆大小的方法,可以用默认的设置参数打开一个程序,使用JConsole(JDK 1.5之后都支持)来查看,在VM Summary页面可以看到最大的堆大小。

用这种方法你可以根据你的程序的需要来改变堆内存大小,我强烈建议采用这种方法而不是默认值。如果你的程序很大,有很多对象需要被创建的话,你可以用-Xms and -Xmx这两个参数来改变堆内存的大小。Xms表示起始的堆内存大小,Xmx表示最大的堆内存的大小。另外有一个参数 -Xmn,它表示new generation(后面会提到)的大小。有一件事你需要注意,你不能任意改变堆内存的大小,你只能在启动JVM时设定它。

堆和垃圾回收

我们知道对象创建在堆内存中,垃圾回收这样一个进程,它将已死对象清除出堆空间,并将这些内存再还给堆。为了给垃圾回收器使用,堆主要分成三个区域,分别叫作New Generation,Old Generation或叫Tenured Generation,以及Perm space。New Generation是用来存放新建的对象的空间,在对象新建的时候被使用。如果长时间还使用的话,它们会被垃圾回收器移动到Old Generation(或叫Tenured Generation)。Perm space是JVM存放Meta数据的地方,例如类,方法,字符串池和类级别的详细信息。你可以查看“Java中垃圾回收的工作原理”来获得更多关于堆和垃圾回收的信息。

java

Java堆中的OutOfMemoryError错误

当JVM启动时,使用了-Xms 参数设置的对内存。当程序继续进行,创建更多对象,JVM开始扩大堆内存以容纳更多对象。JVM也会使用垃圾回收器来回收内存。当快达到-Xmx设置的最大堆内存时,如果没有更多的内存可被分配给新对象的话,JVM就会抛出java.lang.outofmemoryerror,你的程序就会当掉。在抛出 OutOfMemoryError之前,JVM会尝试着用垃圾回收器来释放足够的空间,但是发现仍旧没有足够的空间时,就会抛出这个错误。为了解决这个问题,你需要清楚你的程序对象的信息,例如,你创建了哪些对象,哪些对象占用了多少空间等等。你可以使用profiler或者堆分析器来处理 OutOfMemoryError错误。”java.lang.OutOfMemoryError: Java heap space”表示堆没有足够的空间了,不能继续扩大了。”java.lang.OutOfMemoryError: PermGen space”表示permanent generation已经装满了,你的程序不能再装在类或者再分配一个字符串了。

Java Heap dump

Heap dump是在某一时间对Java堆内存的快照。它对于分析堆内存或处理内存泄露和Java.lang.outofmemoryerror错误是非常有用的。在JDK中有一些工具可以帮你获取heap dump,也有一些堆分析工具来帮你分析heap dump。你可以用“jmap”来获取heap dump,它帮你创建heap dump文件,然后,你可以用“jhat”(堆分析工具)来分析这些heap dump。

Java堆内存(heap memory)的十个要点:

1. Java堆内存是操作系统分配给JVM的内存的一部分。

2. 当我们创建对象时,它们存储在Java堆内存中。

3. 为了便于垃圾回收,Java堆空间分成三个区域,分别叫作New Generation, Old Generation或叫作Tenured Generation,还有Perm Space。

4. 你可以通过用JVM的命令行选项 -Xms, -Xmx, -Xmn来调整Java堆空间的大小。不要忘了在大小后面加上”M”或者”G”来表示单位。举个例子,你可以用 -Xmx256m来设置堆内存最大的大小为256MB。

5. 你可以用JConsole或者 Runtime.maxMemory(), Runtime.totalMemory(), Runtime.freeMemory()来查看Java中堆内存的大小。

6. 你可以使用命令“jmap”来获得heap dump,用“jhat”来分析heap dump。

7. Java堆空间不同于栈空间,栈空间是用来储存调用栈和局部变量的。

8. Java垃圾回收器是用来将死掉的对象(不再使用的对象)所占用的内存回收回来,再释放到Java堆空间中。

9. 当你遇到java.lang.outOfMemoryError时,不要紧张,有时候仅仅增加堆空间就可以了,但如果经常出现的话,就要看看Java程序中是不是存在内存泄露了。

10. 请使用Profiler和Heap dump分析工具来查看Java堆空间,可以查看给每个对象分配了多少内存。

本文链接

 

Java序列化的作用

 
‎2012‎年‎11‎月‎26‎日,‏‎20:51:00 | YangJin转到全文

最近在阅读Core J2EE Patterns 的时候发现例子里用于在各个层次里进行传输的TO(Data Transfer Object)都实现了java.io.Serializable接口,看到这些偶突然感到茅塞顿开~困扰了很久的关于Serializable的疑问渐渐解开了,查找相关资料并总结如下:

  序列化是什么:

  序列化就是将一个对象的状态(各个属性量)保存起来,然后在适当的时候再获得。

  序列化分为两大部分:序列化和反序列化。序列化是这个过程的第一部分,将数据分解成字节流,以便存储在文件中或在网络上传输。反序列化就是打开字节流并重构对象。对象序列化不仅要将基本数据类型转换成字节表示,有时还要恢复数据。恢复数据要求有恢复数据的对象实例

  序列化的什么特点:

  如果某个类能够被序列化,其子类也可以被序列化。声明为static和transient类型的成员数据不能被序列化。因为static代表类的状态, transient代表对象的临时数据。

  什么时候使用序列化:

  一:对象序列化可以实现分布式对象。主要应用例如:RMI要利用对象序列化运行远程主机上的服务,就像在本地机上运行对象时一样。

  二:java对象序列化不仅保留一个对象的数据,而且递归保存对象引用的每个对象的数据。可以将整个对象层次写入字节流中,可以保存在文件中或在网络连接上传递。利用对象序列化可以进行对象的"深复制",即复制对象本身及引用的对象本身。序列化一个对象可能得到整个对象序列。

  ======================

  可以看看接口java.io.serializable的中文解释:

  Serializable

  public interface Serializable

  类通过实现 java.io.Serializable 接口以启用其序列化功能。未实现此接口的类将无法使其任何状态序列化或反序列化。可序列化类的所有子类型本身都是可序列化的。序列化接口没有方法或字段,仅用于标识可序列化的语义。

  要允许不可序列化类的子类型序列化,可以假定该子类型负责保存和还原超类型的公用 (public)、受保护的 (protected) 和(如果可访问)包 (package) 字段的状态。仅在子类型扩展的类有一个可访问的无参数构造方法来初始化该类的状态时,才可以假定子类型有此责任。如果不是这种情况,则声明一个类为可序列化类是错误的。该错误将在运行时检测到。

  在反序列化过程中,将使用该类的公用或受保护的无参数构造方法初始化不可序列化类的字段。可序列化的子类必须能够访问无参数的构造方法。可序列化子类的字段将从该流中还原。

  当遍历一个图形时,可能会遇到不支持可序列化接口的对象。在此情况下,将抛出 NotSerializableException,并将标识不可序列化对象的类。

  在序列化和反序列化过程中需要特殊处理的类必须使用下列准确签名来实现特殊方法:

  private void writeObject(java.io.ObjectOutputStream out)

  throws IOException

  private void readObject(java.io.ObjectInputStream in)

  throws IOException, ClassNotFoundException;

  writeObject 方法负责写入特定类的对象的状态,以便相应的 readObject 方法可以还原它。通过调用 out.defaultWriteObject 可以调用保存 Object 的字段的默认机制。该方法本身不需要涉及属于其超类或子类的状态。状态是通过使用 writeObject 方法或使用 DataOutput 支持的用于基本数据类型的方法将各个字段写入 ObjectOutputStream 来保存的。

  readObject 方法负责从流中读取并还原类字段。它可以调用 in.defaultReadObject 来调用默认机制,以还原对象的非静态和非瞬态字段。defaultReadObject 方法使用流中的信息来分配流中通过当前对象中相应命名字段保存的对象的字段。这用于处理类发展后需要添加新字段的情形。该方法本身不需要涉及属于其超类或子类的状态。状态是通过使用 writeObject 方法或使用 DataOutput 支持的用于基本数据类型的方法将各个字段写入 ObjectOutputStream 来保存的。

  将对象写入流时需要指定要使用的替代对象的可序列化类,应使用准确的签名来实现此特殊方法:

  ANY-ACCESS-MODIFIER Object writeReplace() throws ObjectStreamException;

  此 writeReplace 方法将由序列化调用,前提是如果此方法存在,而且它可以通过被序列化对象的类中定义的一个方法访问。因此,该方法可以拥有私有 (private)、受保护的 (protected) 和包私有 (package-private) 访问。子类对此方法的访问遵循 java 访问规则。

  在从流中读取类的一个实例时需要指定替代的类应使用的准确签名来实现此特殊方法。

  ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException;

  此 readResolve 方法遵循与 writeReplace 相同的调用规则和访问规则。

  序列化运行时使用一个称为 serialVersionUID 的版本号与每个可序列化类相关联,该序列号在反序列化过程中用于验证序列化对象的发送者和接收者是否为该对象加载了与序列化兼容的类。如果接收者加载的该对象的类的 serialVersionUID 与对应的发送者的类的版本号不同,则反序列化将会导致 InvalidClassException。可序列化类可以通过声明名为 "serialVersionUID" 的字段(该字段必须是静态 (static)、最终 (final) 的 long 型字段)显式声明其自己的 serialVersionUID:

  ANY-ACCESS-MODIFIER static final long serialVersionUID = 42L;

  如果可序列化类未显式声明 serialVersionUID,则序列化运行时将基于该类的各个方面计算该类的默认 serialVersionUID 值,如“Java(TM) 对象序列化规范”中所述。不过,强烈建议 所有可序列化类都显式声明 serialVersionUID 值,原因计算默认的 serialVersionUID 对类的详细信息具有较高的敏感性,根据编译器实现的不同可能千差万别,这样在反序列化过程中可能会导致意外的 InvalidClassException。因此,为保证 serialVersionUID 值跨不同 java 编译器实现的一致性,序列化类必须声明一个明确的 serialVersionUID 值。还强烈建议使用 private 修改器显示声明 serialVersionUID(如果可能),原因是这种声明仅应用于立即声明类 -- serialVersionUID 字段作为继承成员没有用处。

  java.io.Serializable引发的问题——什么是序列化?在什么情况下将类序列化?

  序列化就是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化。可以对流化后的对象进行读写操作,也可将流化后的对象传输于网络之间。序列化是为了解决在对对象流进行读写操作时所引发的问题。序列化的实现:将需要被序列化的类实现Serializable接口,该接口没有需要实现的方法,implements Serializable只是为了标注该对象是可被序列化的,然后使用一个输出流(如:FileOutputStream)来构造一个ObjectOutputStream(对象流)对象,接着,使用ObjectOutputStream对象的writeObject(Object obj)方法就可以将参数为obj的对象写出(即保存其状态),要恢复的话则用输入流。

  序列化:序列化是将对象转换为容易传输的格式的过程。例如,可以序列化一个对象,然后使用 HTTP 通过 Internet 在客户端和服务器之间传输该对象。在另一端,反序列化将从该流重新构造对象。

  是对象永久化的一种机制。

  确切的说应该是对象的序列化,一般程序在运行时,产生对象,这些对象随着程序的停止运行而消失,但如果我们想把某些对象(因为是对象,所以有各自不同的特性)保存下来,在程序终止运行后,这些对象仍然存在,可以在程序再次运行时读取这些对象的值,或者在其他程序中利用这些保存下来的对象。这种情况下就要用到对象的序列化。

  只有序列化的对象才可以存储在存储设备上。为了对象的序列化而需要继承的接口也只是一个象征性的接口而已,也就是说继承这个接口说明这个对象可以被序列化了,没有其他的目的。之所以需要对象序列化,是因为有时候对象需要在网络上传输,传输的时候需要这种序列化处理,从服务器硬盘上把序列化的对象取出,然后通过网络传到客户端,再由客户端把序列化的对象读入内存,执行相应的处理。

  对象序列化是java的一个特征,通过该特征可以将对象写作一组字节码,当在其他位置读到这些字节码时,可以依此创建一个新的对象,而且新对象的状态与原对象完全相同。为了实现对象序列化,要求必须能够访问类的私有变量,从而保证对象状态能够正确的得以保存和恢复。相应的,对象序列化API能够在对象重建时,将这些值还原给私有的数据成员。这是对java语言访问权限的挑战。通常用在服务器客户端的对象交换上面,另外就是在本机的存储。

  对象序列化的最主要的用处就是在传递,和保存对象(object)的时候,保证对象的完整性和可传递性。譬如通过网络传输,或者把一个对象保存成一个文件的时候,要实现序列化接口 。

  *

  Quote:

  比较java.io.Externalizable和java.io.Serializable

  [URL]http://www.zdnet.com.cn/developer/code/story/0,3800066897,39304080,00.htm[/URL]

  即使你没有用过对象序列化(serialization),你可能也知道它。但你是否知道 Java 还支持另外一种形式的对象持久化,外部化(externalization)?

  下面是序列化和外部化在代码级的关联方式:

  public interface Serializable {}

  public interface Externalizable extends Serializable {

  void readExternal(ObjectInput in);

  void writeExternal(ObjectOutput out);

  }

  序列化和外部化的主要区别

  外部化和序列化是实现同一目标的两种不同方法。下面让我们分析一下序列化和外部化之间的主要区别。

  通过Serializable接口对对象序列化的支持是内建于核心 API 的,但是java.io.Externalizable的所有实现者必须提供读取和写出的实现。Java 已经具有了对序列化的内建支持,也就是说只要制作自己的类java.io.Serializable,Java 就会试图存储和重组你的对象。如果使用外部化,你就可以选择完全由自己完成读取和写出的工作,Java 对外部化所提供的唯一支持是接口:

  voidreadExternal(ObjectInput in)

  void writeExternal(ObjectOutput out)

  现在如何实现readExternal() 和writeExternal() 就完全看你自己了。

  序列化会自动存储必要的信息,用以反序列化被存储的实例,而外部化则只保存被存储的类的标识。当你通过java.io.Serializable接口序列化一个对象时,有关类的信息,比如它的属性和这些属性的类型,都与实例数据一起被存储起来。在选择走Externalizable这条路时,Java 只存储有关每个被存储类型的非常少的信息。

  每个接口的优点和缺点

  Serializable接口

  · 优点:内建支持

  · 优点:易于实现

  · 缺点:占用空间过大

  · 缺点:由于额外的开销导致速度变比较慢

  Externalizable接口

  · 优点:开销较少(程序员决定存储什么)

  · 优点:可能的速度提升

  · 缺点:虚拟机不提供任何帮助,也就是说所有的工作都落到了开发人员的肩上。

  在两者之间如何选择要根据应用程序的需求来定。Serializable通常是最简单的解决方案,但是它可能会导致出现不可接受的性能问题或空间问题;在出现这些问题的情况下,Externalizable可能是一条可行之路。

  要记住一点,如果一个类是可外部化的(Externalizable),那么Externalizable方法将被用于序列化类的实例,即使这个类型提供了Serializable方法:

  private void writeObject()

  private void readObject()

本文链接

 
 
显示
10 / 10
10
 
10
 
排序方式:
 

posted on 2012-12-02 01:47  月亮之城  阅读(748)  评论(0编辑  收藏  举报