1.调用泛型方法
public static void main(String[] args) {
String[] s1 = {"1", "3", "5"};
test.<String>print(s1);
}
public static <T> void print(T[] data) {
for (int i = 0; i < data.length; i++) {
System.out.println(data[i]);
}
}
使用.<类型>函数方法
的方式调用泛型方法,而此<类型>
是可以省略的。
2.泛型类型的定义
- 定义类为泛型类型,需要将泛型类型放在类的后面 如
public class test<T>
- 定义方法为泛型类型,需要将泛型类型写在方法返回类型的前面。如
public static <T> void print(T[] data)
3.泛型通配符
为了避免上面的问题,我们使用通配符来解决。
通配符的三种形式
- ? 非限制通配符,和
? extends object
一样- ? extends T 上限通配,也就是必须是T的子类型(包含T)
- ? supter T 下限通配,也就是必须是T的父类型(包含T)
4.类型擦除
java
中的泛型使用的是类型擦除的方法实现的。泛型只存在于编译阶段,而在运行阶段是不存在泛型类型的,一旦编译器在编译时期检测类型是安全的,就会将它转换为原始类型。
下面是编译完成后的.class
运行时文件:
也就是说在运行时是没有泛型这一概念的,泛型只在编译期用于检测类型安全。
为什么new E()是错误的?
在java
中,不能直接创建泛型的对象,因为创建对象是在运行时动态创建的,而运行时是没有泛型类型的。
5.泛型的几种常见的限制
- 不能使用
new E()
,也就是不能创建泛型对象- 不能使用
new E[]
,也就是不能创建泛型数组- 静态方法不能使用类的泛型类型作用参数
- 异常类不能是泛型的
6.Collections
1.Collections.nCopies
使用此方法可以创建指定对象的n个副本的不可变list。此方法接收一个对象,并接收一个int count
的参数来指定要创建几个该对象的副本。也就是说,此list
中会有n个该对象的副本,而且此list
是不可变的,也就是不进对该list
进行修改的操作。否则,会报错:
Exception in thread "main" java.lang.UnsupportedOperationException
at java.util.AbstractList.add(AbstractList.java:148)
at java.util.AbstractList.add(AbstractList.java:108)
at generic.test.main(test.java:19)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)
2.Collections.fill
表示用指定元素填充(覆盖)原集合中的所有元素。
List<Integer> mDatas = new ArrayList<>();
mDatas.add(1);
mDatas.add(2);
mDatas.add(3);
Collections.fill(mDatas, 4);
打印结果为4 4 4
3.使用Collections
建立单元素和不可变的集合和map
7.Map类型及作用
常用的Map
类有hashMap
、linkedHashMap
、TreeMap
。
- 如果更新map时不需要保持元素的顺序,就用HashMap
- 如果需要按插入时间或最后访问时间来排序,就用LinkedHashMap
- 如果需要按键来进行排序,就使用TreeMap
TreeMap例子
对一段字符串进行分割,然后统计每个被分割部分的单词出现的次数,并按字母的自然顺序进行排序。
8.算法时间复杂度的计算
在计算算法的时间复杂度时,我们主要看算法时间的增加,主要是由哪个变量引起的,就取此值,写法为O(值)
。比如:
下面是关于指数级的时间复杂度的计算:
各时间复杂度的对比关系:
9.优化的求最大公约数的算法
private static int getMaxGCD(int m, int n) {
int gcd = 1;
if (m % n == 0) {
return n;
}
for (int i = n / 2; i >= 1; i--) {
if (m % i == 0 && n % i == 0) {
gcd = i;
break;
}
}
return gcd;
}
最优化的方法:
private static int getMaxGCD(int m, int n) {
if (m % n == 0) {
return n;
}
return getMaxGCD(n, m % n);
}
10.StringTokenizer的用法
StringTokenizer
的作用主要是根据指定的分割符将字符串分割成n部分,然后通过hasMoreToken
来判断是否还有下一个被分割的部分,nextToken
的方法来获取对应的数据。
StringTokenizer stringTokenizer = new StringTokenizer("www.baidu.com", ".a", true);
while (stringTokenizer.hasMoreTokens()) {
System.out.println("token:" + stringTokenizer.nextToken());
}
上面的参数二,表示按.
或者a
来对字符串进行分割,也就是遇到.
或者a
就进行分割,默认本身不被算作分割的部分。
参数三,表示将本身也算作分割的一部分。
上面的结果为:
token:www
token:.
token:b
token:a
token:idu
token:.
token:com
11.二叉查找树
二叉查找树的特征:对于树中的每一个节点,它的左子树中的节点都是小于当前节点的值的;而它的右子树中的节点都是大于当前节点的值的。
判断是否包含某个元素
在二叉树中添加一个元素
在二叉树中添加一个元素,有两种情况:
- 二叉树中已经存在此元素,会直接丢弃要插入的元素
- 将元素添加到某个叶子节点的下一层,也就是最底层。
从上面的代码示例子,可以得知,当current
为null,也就是while
循环结束后,就是添加元素所要插入的位置了。通过此元素创建新节点,然后与parent
的值进行比较,大于就添加到右边,小于就添加到左边。
中序遍历
递归地先遍历左子树,然后访问当前节点,最后右子树。中序遍历法以递增顺序显示BST
中的所有节点。
后序遍历
首先递归地遍历左子树,然后递归地遍历右子树,最后访问该节点本身。
前序遍历
首先递归访问当前节点,然后递归地访问该节点的左子树,最后递归地访问该节点的右子树。
广度优先遍历
逐层访问树中的节点。首先访问根节点,然后从左往右访问根节点的孩子节点,然后再从左住右的访问根节点的所有孙子节点,以此类推。
广度优先遍历主要是从层级关系出发,一层一层的对树进行遍历。
12.图
1.图的说明
图由顶点和边组成,分为有向图和无向图。
2.图的表示
图一般由两种常用的方式来表示,一种是邻接矩阵(也就是二维数组);另一种是邻接线性表(链表).一般根据图的情况(边的多少),来决定使用哪种表示方式:
- 如果边比较多,一般使用邻接矩阵。
- 如果边比较少,一般使用邻接线性表。
- 由于邻接矩阵会占用大量的存储空间,一般建议使用邻接纯线性表。
3.图的遍历
- 深度优先搜索
- 广度优先搜索
13.多线程
1.新建的线程什么时候会被关闭
当run
方法中的代码都执行完毕后,就会被终止。
2.直接调用run
方法和start
的区别
直接调用run
方法,任务会在当前线程上运行,而不是新创建一个线程。
3.使用Runnable
,而不直接使用Thread
来创建并执行任务的好处
使用Runnable
的好处就是把任务和运行任务进行了解耦,更方便复用。
4.yield
让cpu为其它线程临时让出cpu
执行时间,并且只能让同优先级及以上线程有执行机会。
5.sleep
暂停执行一段时间,以给其它线程运行机会,不会考虑线程的优先级。会抛出interruptedException
异常,如果线程在sleep
状态下被调用interrupt
方法(也就是终止),就会抛出此异常。
在synchronized
代码执行中,不会释放对象锁。
6.sleep和yield的区别
- sleep方法给其它线程运行机会时不会考虑线程的优先级,而
yield
只会给同级别或者更高级别以运行机会。- 线程执行
sleep
方法后会转入阻塞
状态,而调用yield
方法后,会进入就绪状态。sleep
方法声明抛出InterruptedException
异常,而yield
没有声明任何异常sleep
方法比yield
方法有更好的可移植性。(在操作系统调度方面)
7.wait
让线程让出cpu
执行机会,一旦调用了wait
方法后,只能通过notify
或者notifyAll
来唤醒该线程。也会抛出InterruptedException
异常。调用wait
后线程处于挂起状态。
如果某个线程获得了对象锁,在调用了wait
方法后,会释放该对象锁。
同时调用wait
之前,该线程必须要获得线程锁,否则会抛出IllegalMonitorStateException
异常。
8.wait和sleep的区别
wait
只能在同步方法或者同步代码块中被调用,因为需要获取对象锁,否则会抛出IllegalMonitorStateException
异常,而sleep
可以在任何地方被调用。wait
属于object
的方法,而sleep
是Thread
的方法- 在同步代码中执行时,
sleep
不会释放线程锁,而wait
会释放线程锁。- 执行
wait
后,线程进入挂起状态,只能通过notify
或者notifyAll
才能被唤醒,而sleep
被调用后,会进入阻塞状态,睡眠时间一过,就会被自动唤醒。
9.join
join
的作用是:使一个线程等待另一个线程的结果后再开始执行。
10.setPriority
使用该方法可以设置线程的优先级。
11:线程池
Executor
是java
中的一个线程池类。一般我们通过静态方法来创建线程池。也可以通过ThreadPoolExecutor
类来创建一个自定义的线程池。
- Executors.newFixedThreadPool(int i)
创建固定线程数量的线程池,处于线程池中的线程不会被关闭,除非调用interrupt
方法将其终止。
2.Executors.newCachedThreadPool()
此线程池为缓存可伸缩的非常灵活的线程池。当有大量任务需要执行时,该线程池会为任务创建新的线程,可创建线程的数量取决于JVM
可创建的最大线程大小。
而当任务执行完毕后,线程处于空闲状态后60s
,会将此线程进行回收。
3.Executors.newSingleThreadExecutor()
创建一个单线程的线程池,这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。
4.Executors.newScheduledThreadPool()
创建一个大小无限的线程池,此线程池支持定时或周期性执行任务的需求。
//执行线程池大小
ScheduledExecutorService service = Executors.newScheduledThreadPool(10);
long initialDelay1 = 1;
long period1 = 1;
// 从现在开始1秒钟之后,每隔1秒钟执行一次job1
service.scheduleAtFixedRate(
new ScheduledExecutorTest("job1"), initialDelay1,
period1, TimeUnit.SECONDS);
5.Executor.shutdown和shutdown的区别
shutdown调用后,不可以再submit新的task,已经submit的将继续执行。
shutdownNow试图停止当前正执行的task,并返回尚未执行的task的list
6.不带缓冲区的生产者/消费者的示例
1.depositTask代码(存钱的任务)
public class DepositTask implements Runnable {
private Acount mAcount;
public DepositTask(Acount acount) {
this.mAcount = acount;
}
@Override
public void run() {
while (true) {
try {
mAcount.deposit((int) (Math.random() * 10 + 1));
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
2.WithDrawTask(取钱的任务)
public class WithDrawTask implements Runnable {
private Acount mAcount;
public WithDrawTask(Acount acount) {
this.mAcount = acount;
}
@Override
public void run() {
mAcount.withDraw((int) (Math.random() * 10 + 1));
}
}
3.Accout(帐户)
public class Acount {
private int balance;
private static Lock lock = new ReentrantLock();
private static Condition mCondition = lock.newCondition();
public void deposit(int amount) {
lock.lock();
try {
balance += amount;
System.out.println("deposit:" + balance);
mCondition.signalAll();
} catch (Exception e) {
} finally {
lock.unlock();
}
}
public void withDraw(int amount) {
lock.lock();
try {
while (amount > balance) {
System.out.println("withDraw: wait.....");
mCondition.await();
}
balance -= amount;
System.out.println("withdraw:" + amount + "======balance:" + balance);
} catch (Exception e) {
} finally {
lock.unlock();
}
}
}
4.测试类
public class test {
private static Acount acount = new Acount();
public static void main(String[] args) {
ExecutorService service = Executors.newFixedThreadPool(2);
service.execute(new DepositTask(acount));
service.execute(new WithDrawTask(acount));
service.shutdown();
}
}
12。 concurrent包
1.BlockingQueue
BlockingQueue
(阻塞队列):是一个依赖生产者/消费者模型来设计的,主要通过put
(添加)和take
(取出)来模拟生产者和消费者。事实上,使用其它方法添加和删除时,当队列为空或者已满的情况下,是会抛出异常的。
ArrayBlockingQueue(数组阻塞队列)
此队列是一个FIFO的有界队列,有界是说明它的大小是有限的,必须给其设置一个值。
DelayQueue(延迟队列)
此队列主要是延迟处理队列中的元素,可以为中的元素设置一个过期时间,当使用take
去取数据时,如果过了过期时间,可以马上取出数据,如果还没到过期时间,则等待到达过期时间后才能取出该数据。
在使用此队列时,需要添加的元素实现Delayd
接口。
class DelayedElement implements Delayed {
private final long delay;
private final long expire;
public DelayedElement(long delay) {
this.delay = delay;
expire = Calendar.getInstance().getTimeInMillis() + delay;
}
@Override
public long getDelay(TimeUnit unit) {
return expire - Calendar.getInstance().getTimeInMillis();
}
@Override
public int compareTo(Delayed o) {
return (int) (this.delay - o.getDelay(TimeUnit.MILLISECONDS));
}
@Override
public String toString() {
return "DelayedElement is " + delay + "\n" + Calendar.getInstance().getTime().toString() + "\n elapsed time is " + getDelay(TimeUnit.DAYS);
}
}
这样子看起来,好像这个对象并没有做什么其它的操作,只是在判断当前对象是否超时,而没有具体的东西。你可以在构造方法中加入具体的业务对象来与此延迟对象来进行绑定。
LinkedBlockingQueue(链式阻塞队列)
LinkedBlockingQueue
是一个FIFO链式结构的队列,可以指定队列的最大值,默认为Integer.maxValue()
.
PriorityBlockingQueue(优先级阻塞队列)
PriorityBlockingQueue(优先级队列)
是一个无界的并发队列。添加到队列中的元素必须实现Comparable
接口(让其进行排序)
SynchronousQueue(同步队列)
SynchronousQueue(同步队列)
:队列中只能有一个元素,当存在一个元素后,添加操作会进入阻塞,此时的操作应该是取出此单个元素,取出元素后,取出的操作就会被阻塞,而等待添加新的元素,依此循环。
blockingDeque(阻塞双端队列接口)
blockingDeque(阻塞双端队列)
:表示可以从任意一端插入数据或者取出数据。当队列满时,插入操作会进入阻塞,而当队列为空时,取出数据会进入阻塞状态。通过addFirst/addLast
以及removeFirst/removeLast
.此接口的唯一实现类为LinkedBlockingDeque
LinkedBlockingDeque(链式阻塞双端队列)
由链表组成的双端可读写的阻塞式队列。
2.信号量
信号量可以用来访问共享资源的线程数,线程必须从信号量中获取许可,访问完资源后,这个线程需要把许可返回给信号量。